summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app
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 /node_modules/@jet-app
init commit
Diffstat (limited to 'node_modules/@jet-app')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/app-event-page-intent.js14
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/arcade-grouping-page-intent.js10
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/bundle-page-intent.js16
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/category-tabs-intent.js8
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/charts-hub-page-intent.js13
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/charts-page-intent.js13
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/developer-page-intent.js19
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/editorial/editorial-page-intent.js37
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/editorial/editorial-shelf-collection-page-intent.js14
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/eula-page-intent.js19
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/grouping-page-intent.js33
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/product-page-intent.js17
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/room-page-intent.js16
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/routable-arcade-see-all-page-intent.js11
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/routable-article-page-intent.js13
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/routable-intent.js4
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/routable-today-page-intent.js10
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/search-results-page-intent.js19
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/search/search-landing-page-intent.js14
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/intents/see-all-page-intent.js13
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/accessibility.js37
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/actions/actions.js525
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/actions/base-action.js15
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/actions/flow-action.js26
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/ad-incidents.js26
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/annotation.js50
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/app-promotions.js186
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/arcade-upsell.js18
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/arcade.js240
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/article-page.js18
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/base.js596
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/categories.js20
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/dynamic-generic-page.js15
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/generic-page.js47
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/hero-carousel.js37
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/metrics/advert-action-metrics.js21
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/metrics/metrics.js101
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/page-facets.js88
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/page-refresh-policy.js17
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/preview-platform.js25
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/privacy.js75
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/product-media-gallery-page.js12
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/product-page-shelf-ids.js45
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/product.js58
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/reviews.js115
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/search/guided-search-actions.js31
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/search/guided-search.js48
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/search/search-categories.js96
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/search/search.js283
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/shelf-based-product.js25
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/shelves.js709
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/today-page.js300
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/top-charts.js63
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/uber.js19
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/models/web-renderable-page.js4
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/typings/constants.js21
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/api/util.js16
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/accessibility/accessibility-common.js70
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/account/account-links-regex-parser.js35
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/ads/ad-common.js433
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/ads/ad-incident-recorder.js116
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/ads/ad-stitcher.js73
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js286
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-stitch.js98
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-events/app-events-common.js83
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event-detail.js131
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event.js243
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotion.js120
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-common.js385
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-shelf.js120
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer-detail.js106
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer.js189
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item-detail.js147
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item.js206
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-common.js441
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-games-facets.js245
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-request.js78
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-routing.js5
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-upsell.js257
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/arcade/breakouts-common.js272
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/arcade/render-arcade-see-all-games-page.js227
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/builders/additional-page-requirement-util.js36
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/builders/pagination.js51
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/builders/routing.js321
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping-utils.js42
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping.js627
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/categories.js149
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/charts/charts-hub.js39
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/charts/charts-page-model.js156
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/charts/charts-page-url.js12
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/constants/video-constants.js34
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/content/age-ratings.js105
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/content/artwork/artwork.js71
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/content/artwork/legacy-artwork.js33
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/content/attributes.js328
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/content/content.js2820
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/content/device-family.js91
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/content/flow-preview.js485
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/content/game-controller.js77
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/content/product-capabilities.js322
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/content/sad.js93
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/developer/developer-common.js611
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/developer/developer-request.js115
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-action-util.js58
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-data-util.js173
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-media-util.js511
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-component-media-util.js76
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-controller-util.js209
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-editorial-copy-util.js247
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-hero-util.js88
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-intent-controller-utils.js5
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-lockup-utils.js91
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-media-api-utils.js39
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-overlay-content-util.js20
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-arcade-lockup-builder.js17
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-arcade-see-all-games-shelf-builder.js71
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-chart-shelf-builder.js106
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-breakout-large-collection-shelf-builder.js94
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-brick-collection-shelf-builder.js146
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-collection-shelf-common.js248
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-hero-collection-shelf-builder.js137
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-lockup-collection-shelf-builder.js193
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-collection-shelf-builder.js70
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-utils.js128
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-text-only-collection-shelf-builder.js54
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-text-with-artwork-collection-shelf-builder.js66
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/index.js113
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-engagement-shelf-builder.js4
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-game-center-shelf-builder.js4
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-header-shelf-builder.js157
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-image-shelf-builder.js4
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-marker-shelf-builder/editorial-page-quick-links-marker-shelf-builder.js71
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-marker-shelf-builder/index.js20
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-recommendations-shelf-builder.js77
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-tag-shelf-builder.js4
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-text-shelf-builder.js19
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-upsell-shelf-builder.js31
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-video-clip-shelf-builder.js4
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/index.js83
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-metrics.js51
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-token.js120
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-types.js139
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-shelf-collection-page-utils.js120
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/filtering.js446
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-common.js319
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-page-url.js8
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-request.js107
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-types.js128
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/hero/hero-carousel-overlay-common.js263
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/hero/hero-common.js90
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/render-grouping-page.js246
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-batching.js27
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/arcade-download-pack-shelf-controller.js295
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-app-event-shelf-controller.js260
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-arcade-footer-shelf-controller.js160
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-brick-shelf-controller.js313
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-category-shelf-controller.js207
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-card-shelf-controller.js229
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-story-card-shelf-controller.js143
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-activity-feed-shelf-controller.js223
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-continue-playing-shelf-controller.js332
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-popular-with-your-friends-shelf-controller.js314
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-reengagement-shelf-controller.js254
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-suggested-friends-shelf-controller.js279
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-hero-carousel-shelf-controller.js182
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-horizontal-card-shelf-controller.js122
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-large-breakout-shelf-controller.js193
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-link-shelf-controller.js169
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller-common.js608
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller.js104
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-personalized-lockup-shelf-controller.js368
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-ribbon-bar-shelf-controller.js269
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller-common.js992
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller.js395
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-small-breakout-shelf-controller.js168
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tag-brick-shelf-controller.js246
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tags-header-shelf-controller.js92
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/linking/external-deep-link.js73
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/linking/os-update-links.js42
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/locale.js36
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/lockups/ad-lockups.js439
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/lockups/editorial-context.js25
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/lockups/lockups.js1920
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/builder.js257
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/event-linter.js563
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/buy.js331
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/clicks.js458
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/constants.js11
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/impressions.js419
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/legacy-metrics-identifier-fields-opt-out.js18
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/location.js188
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/media.js34
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/misc.js46
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/models.js671
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/page.js482
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-focus-impressions.js57
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-result-impressions.js56
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search/search-shelves.js98
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/util.js407
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js370
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/offers/external-purchases.js65
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/offers/offer-formatting.js231
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/offers/offers.js1735
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-impression-demotion.js73
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-personalization-processing.js370
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-personalization.js134
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-common.js190
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-today.js329
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/placeholders/placeholders.js229
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/preview-platform.js87
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-footer-shelf.js87
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-header-shelf.js427
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-suppression.js34
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-types-shelf.js322
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/badges-common.js123
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/badges.js153
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/category-badge.js29
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/chart-badge.js37
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/content-rating-badge.js76
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/controller-badge.js178
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/developer-badge.js56
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/editors-choice-badge.js55
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/friends-playing-badge.js23
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/language-badge.js27
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/multiplayer-badge.js63
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/rating-badge.js72
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/size-badge.js25
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/storefront-content-rating-badge.js75
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/banner.js460
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/bundle-page-common.js19
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/intent-controller-routing.js81
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-common.js604
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-shelf-metrics.js82
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-util.js65
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-variants.js499
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page.js225
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/reviews.js1077
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-section-mapping.js40
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-ios-arcade.js215
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-ios.js247
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-mac-arcade.js62
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-mac.js70
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-tvos-arcade.js50
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-tvos.js50
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-visionos-arcade.js54
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-visionos.js50
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-watch.js28
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-web-arcade.js15
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-web.js15
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering.js117
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/shelf-based-product-page.js1059
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/accessibility-shelves.js544
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/achievements-shelf.js82
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/action-links-shelf.js136
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/annotations.js310
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/category-annotation.js33
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/compatibility-annotation.js169
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/content-rating-annotation.js368
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/controller-annotation.js68
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/copyright-annotation.js18
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/external-browser-annotation.js54
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/external-purchases-annotation.js39
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/game-license-annotation.js73
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/high-motion-annotation.js53
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/languages-annotation.js61
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/location-annotation.js18
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/product-capabilities-annotation.js25
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/seller-annotation.js117
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/size-annotation.js61
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/spatial-controller-annotation.js60
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/storefront-content-rating-annotation.js53
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/top-in-app-purchases-annotation.js142
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/version-annotation.js41
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/bundle-children-shelf.js52
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/bundle-parents-shelf.js46
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/capabilities-shelf.js41
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/description-shelf.js144
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/editorial-quote-shelf.js36
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/editors-choice-shelf.js63
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/featured-in-shelf.js53
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/friends-playing-shelf.js177
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/in-app-purchases-shelf.js198
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/links-shelf.js269
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/more-by-developer-shelf.js192
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/product-media-shelf.js244
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/ribbon-shelf.js40
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/shelf-based-reviews-shelves.js544
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/similar-items-shelf.js253
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/small-story-card-shelf.js137
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/text-links-shelf.js59
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/textcard-shelf.js226
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/version-history-shelves.js149
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/video-shelf.js38
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/refresh/page-refresh-controller.js123
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/room/room-common.js139
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/room/room-page.js105
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/room/room-request.js48
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/content/search-categories.js334
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/content/search-content-common.js31
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/content/search-lockup-collection.js171
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/content/search-results.js723
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/content/search-shelves.js53
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/custom-creative.js71
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search-metrics.js132
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search.js105
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js84
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js836
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/category-metadata-ribbon-item.js38
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/chart-metadata-ribbon-item.js75
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/developer-metadata-ribbon-item.js37
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/divider-metadata-ribbon-item.js8
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/editors-choice-metadata-ribbon-item.js21
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/game-controller-metadata-ribbon-item.js36
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon-item-factory.js24
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon.js27
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/ranked-secondary-category-metadata-ribbon-item.js22
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/search-tags-ribbon.js72
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/secondary-short-categories-metadata-ribbon-item.js27
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/short-category-metadata-ribbon-item.js36
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/star-rating-metadata-ribbon-item.js20
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/tag-metadata-ribbon-item.js18
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-ads-odml.js87
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js1047
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-common.js59
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-facets.js146
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-landing-page-utils.js386
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-page-url.js5
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-results-fetching.js496
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-results-learn-more-notice.js46
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js248
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-spell-correction.js98
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search-token.js57
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/search.js1024
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/shelves/search-history-shelf.js148
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/sponsored-search-fetching.js122
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/search/web-search-action.js23
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/sharing.js428
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/article-request.js86
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/article.js1572
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-motion-16x9.js34
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-static-16x9.js49
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/day-card.js51
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/event-card.js51
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/general-card.js51
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/index.js85
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-motion-21x9.js116
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-static-21x9.js92
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/media-card.js51
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video-4x3.js22
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video.js25
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-motion-16x9.js34
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-static-16x9.js49
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-motion-16x9.js34
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-static-16x9.js49
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-motion-16x9.js146
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-static-16x9.js107
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/artwork/today-artwork-util.js267
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-ad-card-builder.js232
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-app-event-card-builder.js112
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-base-card-builder.js244
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-branded-card-builder.js58
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-full-bleed-image-card-builder.js37
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-in-app-purchase-card-builder.js62
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-list-card-builder.js71
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-river-card-builder.js62
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-short-image-card-builder.js42
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-single-app-card-builder.js81
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-video-card-builder.js33
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/onboarding-cards.js23
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/routable-article-page-url-utils.js32
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/today-card-overlay-util.js111
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/today-card-util.js785
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/today-controller-util.js534
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/today-horizontal-card-util.js352
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js1047
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/today-types.js118
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/top-charts-common.js212
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/top-charts/top-charts-page.js161
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/util/app-tags-util.js25
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js183
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/util/lottery.js62
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/util/page-common.js25
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/web-navigation/flow-action-presentation.js79
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/web-navigation/inject-web-navigation.js32
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/web-navigation/landing-page-links-by-platform.js62
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-landing-page-intent-controllers.js155
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-landing-page-utils.js139
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-selection.js115
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/web-navigation/search-results-platform-selection.js28
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/web-navigation/web-navigation.js28
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/app-events/app-event-page-intent-controller.js42
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/arcade/arcade-grouping-page-intent-controller.js71
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/arcade/routable-arcade-see-all-page-controller.js42
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/developer/developer-page-intent-controller.js28
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/editorial-pages/editorial-page-intent-controller.js55
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/editorial-pages/editorial-shelf-collection-page-intent-controller.js35
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/grouping/grouping-page-intent-controller.js58
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/product-page/bundle-page-intent-controller.js61
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/product-page/eula-page-intent-controller.js38
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/product-page/product-page-intent-controller.js63
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/product-page/see-all-intent-controller.js39
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/room/room-page-intent-controller.js29
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/search/search-landing-page-intent-controller.js34
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/search/search-results-controller.js152
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/today/routable-article-page-intent-controller.js53
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/today/routable-today-page-intent-controller.js21
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/top-charts/charts-hub-page-intent-controller.js39
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/top-charts/charts-page-intent-controller.js25
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/controllers/web-navigation/category-tabs-intent-controller.js62
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/amp-localization/amp-localization.js371
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/active-intent.js121
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/locale/locale-from-bag.js41
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/locale/locale.js3
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/seo.js3
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/app-store-experiments.js7
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/experiment-area-id.js10
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/experiment-cache.js63
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/product-page-experiments.js21
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/search-results-experiments.js10
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/today-ad-experiments.js15
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/json-parsing/derived-data.js37
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/json-parsing/server-data.js464
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/associations.js17
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/attributes.js149
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js631
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/data-structure.js84
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/network.js304
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/platform-attributes.js143
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/relationships.js43
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/url-builder.js381
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/media/util.js185
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/metrics/buy-parameters.js112
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/metrics/cookies.js27
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/metrics/metrics-identifiers-cache.js245
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/network/http.js42
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/network/network.js123
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/network/url-constants.js452
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/network/urls.js382
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/routing/routing-components.js318
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/runtime/action-provider.js13
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/runtime/app-store-intent-dispatcher.js62
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/runtime/app-store-object-graph.js380
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/runtime/runtime.js52
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/util/array-util.js32
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/util/color-util.js231
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/util/constants.js26
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/util/date-util.js127
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/util/errors.js13
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/util/math-util.js32
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/util/objects.js33
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/util/promise-util.js215
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/util/string-util.js127
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/util/validation-util.js30
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/apple-silicon.js13
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/bag.js1126
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/cached-bag.js82
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/client-ordering.js43
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/client.js204
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/console.js18
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/host.js64
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/localization.js437
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/properties.js74
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/storage.js22
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/wrapper.js6
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/gameservicesui/src/foundation/media-api/requests/recommendation-request-types.js49
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/gameservicesui/src/foundation/object-graph-types.js20
466 files changed, 76151 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/app-event-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/app-event-page-intent.js
new file mode 100644
index 0000000..1913f2a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/app-event-page-intent.js
@@ -0,0 +1,14 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+const APP_EVENT_PAGE_INTENT_KIND = "AppEventPageIntent";
+export function makeAppEventPageIntent(opts) {
+ const { platform, ...rest } = opts;
+ return {
+ ...rest,
+ $kind: APP_EVENT_PAGE_INTENT_KIND,
+ platform: normalizePreviewPlaform(platform),
+ };
+}
+export function isAppEventPageIntent(intent) {
+ return (intent === null || intent === void 0 ? void 0 : intent.$kind) === APP_EVENT_PAGE_INTENT_KIND;
+}
+//# sourceMappingURL=app-event-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/arcade-grouping-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/arcade-grouping-page-intent.js
new file mode 100644
index 0000000..a24df96
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/arcade-grouping-page-intent.js
@@ -0,0 +1,10 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+export function makeArcadeGroupingPageIntent(opts) {
+ const { platform, ...rest } = opts;
+ return {
+ ...rest,
+ platform: normalizePreviewPlaform(platform),
+ $kind: "ArcadeGroupingPageIntent",
+ };
+}
+//# sourceMappingURL=arcade-grouping-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/bundle-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/bundle-page-intent.js
new file mode 100644
index 0000000..b6bed12
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/bundle-page-intent.js
@@ -0,0 +1,16 @@
+import { normalizeAdamID } from "../util";
+import { normalizePreviewPlaform } from "../models/preview-platform";
+export function makeBundlePageIntent(opts) {
+ const { id, platform, lic, ...rest } = opts;
+ return {
+ $kind: "BundlePageIntent",
+ id: normalizeAdamID(id),
+ platform: normalizePreviewPlaform(platform),
+ lic,
+ ...rest,
+ };
+}
+export function isBundlePageIntent(intent) {
+ return (intent === null || intent === void 0 ? void 0 : intent.$kind) === "BundlePageIntent";
+}
+//# sourceMappingURL=bundle-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/category-tabs-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/category-tabs-intent.js
new file mode 100644
index 0000000..0b2ec9e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/category-tabs-intent.js
@@ -0,0 +1,8 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+export function makeCategoryTabsIntent(opts) {
+ return {
+ $kind: "CategoryTabsIntent",
+ platform: normalizePreviewPlaform(opts.platform),
+ };
+}
+//# sourceMappingURL=category-tabs-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/charts-hub-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/charts-hub-page-intent.js
new file mode 100644
index 0000000..bb964f0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/charts-hub-page-intent.js
@@ -0,0 +1,13 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+/**
+ * Create a {@linkcode ChartsHubPageIntent}
+ */
+export function makeChartsHubPageIntent(opts) {
+ const { platform, ...rest } = opts;
+ return {
+ ...rest,
+ platform: normalizePreviewPlaform(platform),
+ $kind: "ChartsHubPageIntent",
+ };
+}
+//# sourceMappingURL=charts-hub-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/charts-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/charts-page-intent.js
new file mode 100644
index 0000000..e900b06
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/charts-page-intent.js
@@ -0,0 +1,13 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+const CHARTS_PAGE_INTENT_KIND = "ChartsPageIntent";
+export function makeChartsPageIntent({ platform, ...rest }) {
+ return {
+ ...rest,
+ platform: normalizePreviewPlaform(platform),
+ $kind: CHARTS_PAGE_INTENT_KIND,
+ };
+}
+export function isChartsPageIntent(intent) {
+ return (intent === null || intent === void 0 ? void 0 : intent.$kind) === CHARTS_PAGE_INTENT_KIND;
+}
+//# sourceMappingURL=charts-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/developer-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/developer-page-intent.js
new file mode 100644
index 0000000..a9633b6
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/developer-page-intent.js
@@ -0,0 +1,19 @@
+import { normalizeAdamID } from "../util";
+const DEVELOPER_PAGE_INTENT_KIND = "DeveloperPageIntent";
+/**
+ * Determines if {@linkcode intent} is a {@linkcode DeveloperPageIntent}
+ */
+export function isDeveloperPageIntent(intent) {
+ return intent.$kind === DEVELOPER_PAGE_INTENT_KIND;
+}
+/**
+ * Creates a {@linkcode DeveloperPageIntent}
+ */
+export function makeDeveloperPageIntent({ id, ...rest }) {
+ return {
+ ...rest,
+ id: normalizeAdamID(id),
+ $kind: DEVELOPER_PAGE_INTENT_KIND,
+ };
+}
+//# sourceMappingURL=developer-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/editorial/editorial-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/editorial/editorial-page-intent.js
new file mode 100644
index 0000000..353a168
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/editorial/editorial-page-intent.js
@@ -0,0 +1,37 @@
+import { normalizePreviewPlaform } from "../../models/preview-platform";
+export function isEditorialPageIntent(intent) {
+ return (intent === null || intent === void 0 ? void 0 : intent.$kind) === "EditorialPageIntent";
+}
+export function isEditorialPageIntentByID(intent) {
+ return isEditorialPageIntent(intent) && "id" in intent;
+}
+export function isEditorialPageIntentByName(intent) {
+ return isEditorialPageIntent(intent) && "name" in intent;
+}
+/**
+ * Creates an {@link EditorialPageIntent} that fetches an Editorial Page by "name"
+ *
+ * @param options the properties of the created {@link EditorialPageIntentByName}
+ */
+export function makeEditorialPageIntentByName(options) {
+ const { platform, ...rest } = options;
+ return {
+ ...rest,
+ $kind: "EditorialPageIntent",
+ platform: normalizePreviewPlaform(platform),
+ };
+}
+/**
+ * Creates an {@link EditorialPageIntent} that fetches an Editorial Page by "id"
+ *
+ * @param options the properties of the created {@linkcode EditorialPageIntentById}
+ */
+export function makeEditorialPageIntentByID(options) {
+ const { platform, ...rest } = options;
+ return {
+ $kind: "EditorialPageIntent",
+ platform: normalizePreviewPlaform(platform),
+ ...rest,
+ };
+}
+//# sourceMappingURL=editorial-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/editorial/editorial-shelf-collection-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/editorial/editorial-shelf-collection-page-intent.js
new file mode 100644
index 0000000..0e9b9af
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/editorial/editorial-shelf-collection-page-intent.js
@@ -0,0 +1,14 @@
+import { normalizePreviewPlaform } from "../../models/preview-platform";
+const EDITORIAL_SHELF_COLLECTION_PAGE_INTENT_KIND = "EditorialShelfCollectionPageIntent";
+export function isEditorialShelfCollectionPageIntent(intent) {
+ return intent.$kind === EDITORIAL_SHELF_COLLECTION_PAGE_INTENT_KIND;
+}
+export function makeEditorialShelfCollectionPageIntent(opts) {
+ const { platform, ...rest } = opts;
+ return {
+ ...rest,
+ $kind: EDITORIAL_SHELF_COLLECTION_PAGE_INTENT_KIND,
+ platform: normalizePreviewPlaform(platform),
+ };
+}
+//# sourceMappingURL=editorial-shelf-collection-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/eula-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/eula-page-intent.js
new file mode 100644
index 0000000..f040ff9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/eula-page-intent.js
@@ -0,0 +1,19 @@
+const EULA_PAGE_INTENT_KIND = "EulaPageIntent";
+/**
+ * Determines if {@linkcode intent} is a {@linkcode EulaPageIntent}
+ */
+export function isEulaPageIntent(intent) {
+ return intent.$kind === EULA_PAGE_INTENT_KIND;
+}
+/**
+ * Creates a {@linkcode EulaPageIntent}
+ */
+export function makeEulaPageIntent({ resourceId, resourceType, ...rest }) {
+ return {
+ ...rest,
+ resourceId,
+ resourceType,
+ $kind: EULA_PAGE_INTENT_KIND,
+ };
+}
+//# sourceMappingURL=eula-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/grouping-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/grouping-page-intent.js
new file mode 100644
index 0000000..5d381a0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/grouping-page-intent.js
@@ -0,0 +1,33 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+import { normalizeAdamID } from "../util";
+export function isGroupingPageIntent(intent) {
+ return intent.$kind === "GroupingPageIntent";
+}
+/**
+ * Creates a {@link GroupingPageIntent} "by name"
+ *
+ * @param options the properties of the created {@link GroupingPageIntent}
+ */
+export function makeGroupingPageIntentByName(options) {
+ const { platform, ...rest } = options;
+ return {
+ ...rest,
+ $kind: "GroupingPageIntent",
+ platform: normalizePreviewPlaform(platform),
+ };
+}
+/**
+ * Creates a {@link GroupingPageIntent} "by ID"
+ *
+ * @param options the properties of the created {@link GroupingPageIntent}
+ */
+export function makeGroupingPageIntentByID(options) {
+ const { platform, id, ...rest } = options;
+ return {
+ ...rest,
+ $kind: "GroupingPageIntent",
+ id: normalizeAdamID(id),
+ platform: normalizePreviewPlaform(platform),
+ };
+}
+//# sourceMappingURL=grouping-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/product-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/product-page-intent.js
new file mode 100644
index 0000000..d8100ea
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/product-page-intent.js
@@ -0,0 +1,17 @@
+import { normalizeAdamID } from "../util";
+import { normalizePreviewPlaform } from "../models/preview-platform";
+export function makeProductPageIntent(opts) {
+ const { id, platform, ppid, lic, ...rest } = opts;
+ return {
+ ...rest,
+ $kind: "ProductPageIntent",
+ id: normalizeAdamID(id),
+ platform: normalizePreviewPlaform(platform),
+ ppid,
+ lic,
+ };
+}
+export function isProductPageIntent(intent) {
+ return (intent === null || intent === void 0 ? void 0 : intent.$kind) === "ProductPageIntent";
+}
+//# sourceMappingURL=product-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/room-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/room-page-intent.js
new file mode 100644
index 0000000..f5ee4bf
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/room-page-intent.js
@@ -0,0 +1,16 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+import { normalizeAdamID } from "../util";
+const ROOM_PAGE_INTENT_KIND = "RoomPageIntent";
+export function makeRoomPageIntent(opts) {
+ const { platform, id, ...rest } = opts;
+ return {
+ ...rest,
+ platform: normalizePreviewPlaform(platform),
+ id: normalizeAdamID(id),
+ $kind: ROOM_PAGE_INTENT_KIND,
+ };
+}
+export function isRoomPageIntent(intent) {
+ return (intent === null || intent === void 0 ? void 0 : intent.$kind) === ROOM_PAGE_INTENT_KIND;
+}
+//# sourceMappingURL=room-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-arcade-see-all-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-arcade-see-all-page-intent.js
new file mode 100644
index 0000000..0a09491
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-arcade-see-all-page-intent.js
@@ -0,0 +1,11 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+const ROUTABLE_ARCADE_SEE_ALL_PAGE_INTENT_KIND = "RoutableArcadeSeeAllPageIntent";
+export function makeRoutableArcadeSeeAllPageIntent(opts) {
+ const { platform, ...rest } = opts;
+ return {
+ ...rest,
+ platform: normalizePreviewPlaform(platform),
+ $kind: "RoutableArcadeSeeAllPageIntent",
+ };
+}
+//# sourceMappingURL=routable-arcade-see-all-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-article-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-article-page-intent.js
new file mode 100644
index 0000000..1f41b32
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-article-page-intent.js
@@ -0,0 +1,13 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+import { normalizeAdamID } from "../util";
+const ROUTABLE_ARTICLE_PAGE_INTENT_KIND = "RoutableArticlePageIntent";
+export function makeRoutableArticlePageIntent(opts) {
+ const { id, platform, ...rest } = opts;
+ return {
+ ...rest,
+ id: normalizeAdamID(id),
+ platform: normalizePreviewPlaform(platform),
+ $kind: ROUTABLE_ARTICLE_PAGE_INTENT_KIND,
+ };
+}
+//# sourceMappingURL=routable-article-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-intent.js
new file mode 100644
index 0000000..3769762
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-intent.js
@@ -0,0 +1,4 @@
+export function isRoutableIntent(intent) {
+ return "storefront" in intent && "language" in intent;
+}
+//# sourceMappingURL=routable-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-today-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-today-page-intent.js
new file mode 100644
index 0000000..0a5c109
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/routable-today-page-intent.js
@@ -0,0 +1,10 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+export function makeRoutableTodayPageIntent(opts) {
+ const { platform, ...rest } = opts;
+ return {
+ ...rest,
+ platform: normalizePreviewPlaform(platform),
+ $kind: "RoutableTodayPageIntent",
+ };
+}
+//# sourceMappingURL=routable-today-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/search-results-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/search-results-page-intent.js
new file mode 100644
index 0000000..94edcf7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/search-results-page-intent.js
@@ -0,0 +1,19 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+export function makeSearchResultsPageIntent(opts) {
+ const { platform, ...rest } = opts;
+ return {
+ ...rest,
+ platform: normalizePreviewPlaform(platform),
+ $kind: "SearchResultsPageIntent",
+ };
+}
+export function makeSearchResultsPageIntentFromURLParams(opts) {
+ return makeSearchResultsPageIntent({
+ ...opts,
+ origin: "userText",
+ });
+}
+export function isSearchResultsPageIntent(intent) {
+ return intent.$kind === "SearchResultsPageIntent";
+}
+//# sourceMappingURL=search-results-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/search/search-landing-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/search/search-landing-page-intent.js
new file mode 100644
index 0000000..74b6374
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/search/search-landing-page-intent.js
@@ -0,0 +1,14 @@
+import { normalizePreviewPlaform } from "../../models/preview-platform";
+const SEARCH_LANDING_PAGE_INTENT_KIND = "SearchLandingPageIntent";
+export function makeSearchLandingPageIntent(opts) {
+ const { platform, ...rest } = opts;
+ return {
+ ...rest,
+ platform: normalizePreviewPlaform(platform),
+ $kind: SEARCH_LANDING_PAGE_INTENT_KIND,
+ };
+}
+export function isSearchLandingPageIntent(intent) {
+ return (intent === null || intent === void 0 ? void 0 : intent.$kind) === SEARCH_LANDING_PAGE_INTENT_KIND;
+}
+//# sourceMappingURL=search-landing-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/intents/see-all-page-intent.js b/node_modules/@jet-app/app-store/tmp/src/api/intents/see-all-page-intent.js
new file mode 100644
index 0000000..2fb8203
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/intents/see-all-page-intent.js
@@ -0,0 +1,13 @@
+import { normalizePreviewPlaform } from "../models/preview-platform";
+import { normalizeAdamID } from "../util";
+export const SEE_ALL_TYPES = ["reviews", "customers-also-bought-apps", "developer-other-apps"];
+export function makeSeeAllPageIntent(opts) {
+ return {
+ ...opts,
+ "$kind": "SeeAllPageIntent",
+ "id": normalizeAdamID(opts.id),
+ "platform": normalizePreviewPlaform(opts.platform),
+ "see-all": opts["see-all"],
+ };
+}
+//# sourceMappingURL=see-all-page-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/accessibility.js b/node_modules/@jet-app/app-store/tmp/src/api/models/accessibility.js
new file mode 100644
index 0000000..5af649e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/accessibility.js
@@ -0,0 +1,37 @@
+import * as models from "./index";
+/**
+ * @public
+ * A model that represents an accessibility feature of a product.
+ */
+export class AccessibilityFeature extends models.Model {
+ constructor(title, description, artwork) {
+ super();
+ this.title = title;
+ this.description = description;
+ this.artwork = artwork;
+ }
+}
+/**
+ * @public
+ * A model that represents a set of accessibility features of a product.
+ */
+export class AccessibilityFeatures extends models.ViewModel {
+ constructor(title, artwork, features) {
+ super();
+ this.title = title;
+ this.artwork = artwork;
+ this.features = features;
+ }
+}
+/**
+ * @public
+ * A model that represents a paragraph of linkable text, displayed in the accessibility section.
+ */
+export class AccessibilityParagraph extends models.ViewModel {
+ constructor(text, actions) {
+ super();
+ this.text = text;
+ this.actions = actions;
+ }
+}
+//# sourceMappingURL=accessibility.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/actions/actions.js b/node_modules/@jet-app/app-store/tmp/src/api/models/actions/actions.js
new file mode 100644
index 0000000..9316414
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/actions/actions.js
@@ -0,0 +1,525 @@
+/**
+ * Created by km on 2/13/17.
+ */
+import { isSome } from "@jet/environment/types/optional";
+import * as base from "../base";
+import { Action } from "./base-action";
+import { ActionMetrics } from "../metrics/metrics";
+/** @public */
+export class TabChangeAction extends Action {
+ constructor(navigationTab) {
+ super("TabChangeAction");
+ this.navigationTab = navigationTab;
+ this.actions = [];
+ this.popToRoot = false;
+ }
+}
+/** @public */
+export class TabBadgeAction extends Action {
+ constructor(navigationTab, text) {
+ super("TabBadgeAction");
+ this.navigationTab = navigationTab;
+ this.text = text;
+ }
+}
+/** @public */
+export class ExternalUrlAction extends Action {
+ constructor(url, isSensitive = true, actionMetrics = new ActionMetrics()) {
+ super("ExternalUrlAction", actionMetrics);
+ this.url = url;
+ this.isSensitive = isSensitive;
+ }
+}
+/** @public */
+export class CompoundAction extends Action {
+ constructor(actions) {
+ super("CompoundAction");
+ const sanitizedActions = [];
+ for (const action of actions) {
+ if (isSome(action.actionMetrics)) {
+ this.actionMetrics.addManyMetricsData(action.actionMetrics.data);
+ Object.assign(this.actionMetrics.custom, action.actionMetrics.custom);
+ action.actionMetrics.clearAll();
+ }
+ sanitizedActions.push(action);
+ }
+ this.actions = sanitizedActions;
+ }
+}
+/** @public */
+export class OfferAction extends Action {
+ constructor(title, adamId, purchaseConfiguration, parentAdamId) {
+ super("OfferAction");
+ this.title = title;
+ this.adamId = adamId;
+ this.parentAdamId = parentAdamId;
+ this.purchaseConfiguration = purchaseConfiguration;
+ if (purchaseConfiguration) {
+ this.bundleId = purchaseConfiguration.bundleId;
+ this.lineItem = purchaseConfiguration.lineItem;
+ }
+ this.includeBetaApps = false;
+ }
+}
+/** @public */
+export class OfferConfirmationAction extends Action {
+ constructor(buyAction, confirmationInitiationAction) {
+ super("OfferConfirmationAction");
+ this.buyAction = buyAction;
+ this.confirmationInitiationAction = confirmationInitiationAction;
+ }
+}
+/** @public */
+export class OfferAlertAction extends Action {
+ constructor() {
+ super("OfferAlertAction");
+ this.title = null;
+ this.message = null;
+ this.footerMessage = null;
+ this.isCancelable = true;
+ this.shouldCheckForAvailableDiskSpace = false;
+ this.checkRestrictionsForContentRating = null;
+ this.remoteControllerRequirement = "NO_BADGE";
+ this.spatialControllerRequirement = null;
+ this.shouldCheckForGameController = false;
+ this.shouldIncludeActiveAccountInFooterMessage = false;
+ this.shouldPromptForConfirmation = false;
+ this.completionAction = null;
+ }
+}
+/** @public */
+export class CancelPreorderAction extends Action {
+ constructor(preorderAdamId, isArcade) {
+ super("CancelPreorderAction");
+ this.preorderAdamId = preorderAdamId;
+ this.isArcade = isArcade;
+ }
+}
+/** @public */
+export class InAppPurchaseAction extends Action {
+ constructor(productIdentifier, appAdamId, appBundleId, installRequiredAction, minimumShortVersionSupportingInAppPurchaseFlow) {
+ super("InAppPurchaseAction");
+ this.productIdentifier = productIdentifier;
+ this.appAdamId = appAdamId;
+ this.appBundleId = appBundleId;
+ this.installRequiredAction = installRequiredAction;
+ this.minimumShortVersionSupportingInAppPurchaseFlow = minimumShortVersionSupportingInAppPurchaseFlow;
+ }
+}
+/** @public */
+export class OfferStateAction extends Action {
+ constructor(adamId, defaultAction) {
+ super("OfferStateAction");
+ this.title = defaultAction.title;
+ this.adamId = adamId;
+ this.defaultAction = defaultAction;
+ this.includeBetaApps = false;
+ }
+}
+/** @public */
+export class OpenAppAction extends Action {
+ constructor(adamId, destination = "app") {
+ super("OpenAppAction");
+ this.adamId = adamId;
+ this.destination = destination;
+ }
+}
+/** @public */
+export class OpenGamesUIAction extends Action {
+ constructor(target) {
+ super("OpenGamesUIAction");
+ this.target = target;
+ }
+}
+/** @public
+ * Purchase action that contains multiple offers for a set of Arcade games.
+ * It is used for "Get all" button in Arcade Download/Starter Pack feature
+ * to make a single purchase via App Store Daemon.
+ * The action implementation doesn't execute its `offerActions`
+ * but uses them as data objects, extracting required parameters.
+ * */
+export class ArcadePackOfferAction extends Action {
+ constructor(offerActions) {
+ super("ArcadePackOfferAction");
+ this.offerActions = offerActions;
+ }
+}
+/** @public
+ * An action representing the "selection" of an app, from the offer button.
+ * The intention is to communicate this app was selected to another process, not actually
+ * purchase/download/etc.
+ * Currently used for the browser selection flow.
+ */
+export class SelectAppAction extends Action {
+ constructor(adamId) {
+ super("SelectAppAction");
+ this.adamId = adamId;
+ }
+}
+/** @public */
+export class HttpAction extends Action {
+ constructor(url) {
+ super("HttpAction");
+ this.url = url;
+ this.method = "GET";
+ this.headers = {};
+ this.body = null;
+ this.isStoreRequest = false;
+ this.needsAuthentication = false;
+ this.needsMediaToken = false;
+ this.retryCount = 0;
+ this.disableCache = false;
+ this.successAction = null;
+ this.failureAction = null;
+ }
+}
+/** @public */
+export class HttpTemplateParameter {
+ constructor(key, target, inputType, title) {
+ this.key = key;
+ this.target = target;
+ this.inputType = inputType;
+ this.title = title;
+ this.isRequired = true;
+ this.maximumLength = null;
+ }
+}
+/** @public */
+export class HttpTemplateAction extends Action {
+ constructor(url) {
+ super("HttpTemplateAction");
+ this.message = null;
+ this.url = url;
+ this.method = "GET";
+ this.headers = {};
+ this.body = null;
+ this.bodyDictionary = {};
+ this.parameters = [];
+ this.isStoreRequest = false;
+ this.needsAuthentication = false;
+ this.needsMediaToken = false;
+ this.retryCount = 0;
+ this.disableCache = false;
+ this.successAction = null;
+ this.failureAction = null;
+ }
+}
+/** @public */
+export class RateAction extends HttpTemplateAction {
+}
+export class ShowSettingsAction extends Action {
+ constructor() {
+ super("ShowSettingsAction");
+ }
+}
+/** @public */
+export class WriteReviewAction extends Action {
+ constructor(adamId, url) {
+ super("WriteReviewAction");
+ this.adamId = adamId;
+ this.url = url;
+ }
+}
+/** @public */
+export class PageTabChangeAction extends Action {
+ constructor(selectedTabId, title, actionClass) {
+ super(actionClass !== null && actionClass !== void 0 ? actionClass : "PageTabChangeAction");
+ this.selectedTabId = selectedTabId;
+ this.title = title;
+ }
+}
+/** @public */
+export class SearchPageSegmentChangeAction extends PageTabChangeAction {
+ constructor(selectedTabId, selectedTabTitle, switchToOtherSegmentText) {
+ super(selectedTabId, "SearchPageSegmentChangeAction");
+ this.switchToOtherSegmentText = switchToOtherSegmentText;
+ this.title = selectedTabTitle;
+ }
+}
+/** @public */
+export class GameCenterDashboardAction extends Action {
+ constructor() {
+ super("GameCenterDashboardAction");
+ }
+}
+/** @public */
+export class SheetAction extends Action {
+ constructor(actions) {
+ super("SheetAction");
+ this.actions = actions;
+ this.isCancelable = false;
+ this.cancelTitle = null;
+ this.message = null;
+ this.style = "default";
+ this.isCustom = false;
+ this.destructiveActionIndex = null;
+ }
+}
+/** @public */
+export class AlertAction extends Action {
+ constructor(style) {
+ super("AlertAction");
+ this.style = style;
+ this.title = null;
+ this.message = null;
+ this.isCancelable = false;
+ this.cancelTitle = null;
+ this.cancelAction = null;
+ this.buttonActions = [];
+ this.buttonTitles = [];
+ this.destructiveActionIndex = null;
+ this.imageName = null;
+ }
+}
+/** @public */
+export class ScrollingAlertAction extends Action {
+ constructor(title, message) {
+ super("ScrollingAlertAction");
+ this.title = title;
+ this.message = message;
+ }
+}
+/**
+ * @public
+ * Describes a unique activity as used in a Share Sheet.
+ */
+export class ShareSheetActivity extends base.ViewModel {
+ constructor(activityType, action) {
+ super();
+ this.activityType = activityType;
+ this.action = action;
+ }
+}
+/** @public */
+export class ShareSheetAction extends Action {
+ constructor(data, activities, style = "expanded") {
+ super("ShareSheetAction");
+ this.data = data;
+ this.activities = activities;
+ this.shareSheetStyle = style;
+ }
+}
+/** @public */
+export class ReportConcernAction extends Action {
+ constructor(reasons) {
+ super("ReportConcernAction");
+ this.reasons = reasons;
+ }
+}
+/** @public */
+export class ReviewSummaryReportConcernAction extends Action {
+ constructor(concerns, title, explanation, sendAction) {
+ super("ReviewSummaryReportConcernAction");
+ this.concerns = concerns;
+ this.title = title;
+ this.explanation = explanation;
+ this.sendAction = sendAction;
+ }
+}
+/** @public */
+export class ProductPageScrollAction extends Action {
+ constructor(section, clicksOnScroll) {
+ super("ProductPageScrollAction");
+ this.section = section;
+ this.clicksOnScroll = clicksOnScroll;
+ }
+}
+/** @public */
+export class ArcadeAction extends Action {
+ constructor(productIdentifier, appAdamId, subscriptionToken, postSubscribeAction) {
+ super("ArcadeAction");
+ this.productIdentifier = productIdentifier;
+ this.appAdamId = appAdamId;
+ this.postSubscribeAction = postSubscribeAction;
+ this.subscriptionToken = subscriptionToken;
+ }
+}
+export class FamilyCircleAction extends Action {
+ constructor(clientName, eventType, additionalParameters) {
+ super("FamilyCircleAction");
+ this.clientName = clientName;
+ this.eventType = eventType;
+ this.additionalParameters = additionalParameters;
+ }
+}
+/** @public */
+export class BlankAction extends Action {
+ constructor() {
+ super("BlankAction");
+ }
+}
+/** @public */
+export class SearchAdAction extends Action {
+ constructor(action) {
+ super("SearchAdAction");
+ this.action = action;
+ }
+}
+/** @public */
+export class RateLimitedAction extends Action {
+ constructor(actionKey, primaryAction) {
+ super("RateLimitedAction");
+ this.actionKey = actionKey;
+ this.primaryAction = primaryAction;
+ }
+}
+/** @public */
+export class ArcadeSubscriptionStateAction extends Action {
+ constructor(notSubscribedAction, purchasingAction, subscribedAction, unknownAction) {
+ super("ArcadeSubscriptionStateAction");
+ this.notSubscribedAction = notSubscribedAction;
+ this.purchasingAction = purchasingAction;
+ this.subscribedAction = subscribedAction;
+ this.unknownAction = unknownAction;
+ }
+}
+/**
+ * @public
+ * An action for changing the page segment in the Arcade See All Games page.
+ */
+export class ArcadeSeeAllGamesPageSegmentChangeAction extends Action {
+ constructor(facet, selectedOption) {
+ super("ArcadeSeeAllGamesPageSegmentChangeAction");
+ this.facet = facet;
+ this.selectedOption = selectedOption;
+ }
+}
+/** @public */
+export class GameCenterPlayerProfileAction extends Action {
+ constructor(playerId) {
+ super("GameCenterPlayerProfileAction");
+ this.playerId = playerId;
+ }
+}
+/** @public */
+export class GameCenterAchievementsAction extends Action {
+ constructor(bundleId) {
+ super("GameCenterAchievementsAction");
+ this.bundleId = bundleId;
+ }
+}
+/** @public */
+export class LegacyGameCenterInvitePlayerAction extends Action {
+ constructor(contactId) {
+ super("GameCenterInvitePlayerAction");
+ this.contactId = contactId;
+ }
+}
+/** @public */
+export class GameCenterInvitePlayerAction extends Action {
+ constructor(invitationType) {
+ super("GameCenterInvitePlayerAction");
+ this.invitationType = invitationType;
+ }
+}
+/** @public */
+export class GameCenterDenylistPlayerAction extends Action {
+ constructor(contactId) {
+ super("GameCenterDenylistPlayerAction");
+ this.contactId = contactId;
+ }
+}
+/** @public */
+export class PageFacetsChangeAction extends Action {
+ constructor(filterParameter) {
+ super("PageFacetsChangeAction");
+ this.filterParameter = filterParameter;
+ }
+}
+/** @public */
+export class EngagementToggleAction extends Action {
+ constructor(identifier, value) {
+ super("EngagementToggleAction");
+ this.identifier = identifier;
+ this.value = value;
+ }
+}
+/** @public */
+export class ShelfBasedPageScrollAction extends Action {
+ constructor(shelfId, notPurchasedShelfId, purchasedShelfId, adamId, index, clicksOnScroll) {
+ super("ShelfBasedPageScrollAction");
+ this.shelfId = shelfId;
+ this.notPurchasedShelfId = notPurchasedShelfId;
+ this.purchasedShelfId = purchasedShelfId;
+ this.adamId = adamId;
+ this.index = index;
+ this.clicksOnScroll = clicksOnScroll;
+ }
+}
+/** @public */
+export class InvalidateAllWidgetsAction extends Action {
+ constructor() {
+ super("InvalidateAllWidgetsAction");
+ }
+}
+/** @public */
+export class AppLaunchTrampolineAction extends Action {
+ constructor(bundleId, payloadUrl, fallbackAction) {
+ super("AppLaunchTrampolineAction");
+ this.bundleId = bundleId;
+ this.payloadUrl = payloadUrl;
+ this.fallbackAction = fallbackAction;
+ }
+}
+/** @public */
+export class CreateCalendarEventAction extends Action {
+ constructor(startDate, endDate, isAllDay, name, location, notes, url, notAuthorizedAction, availability) {
+ super("CreateCalendarEventAction");
+ this.startDate = startDate;
+ this.endDate = endDate;
+ this.isAllDay = isAllDay;
+ this.name = name;
+ this.location = location;
+ this.notes = notes;
+ this.url = url;
+ this.notAuthorizedAction = notAuthorizedAction;
+ this.availability = availability;
+ }
+}
+/** @public
+ *
+ */
+export class CopyTextAction extends Action {
+ constructor(text) {
+ super("CopyTextAction");
+ this.text = text;
+ }
+}
+/** @public
+ *
+ */
+export class ClearAppUsageDataAction extends Action {
+ constructor() {
+ super("ClearAppUsageDataAction");
+ }
+}
+/** @public
+ * An action describing a request to delete all recent searches from on-device storage.
+ */
+export class ClearSearchHistoryAction extends Action {
+ constructor() {
+ super("ClearSearchHistoryAction");
+ }
+}
+/**
+ * @public
+ * An action describing an ad interaction, that passes data on to native ad instrumentation
+ * via Ad Platforms frameworks.
+ */
+export class AdInteractionAction extends Action {
+ constructor(adActionMetrics) {
+ super("AdInteractionAction");
+ this.adActionMetrics = adActionMetrics;
+ }
+}
+/**
+ * @public
+ * An action describes a begin of crossfire referral flow, that will pass the `ReferrerData` on to native to mark the beginning of crossfire flow.
+ */
+export class CrossfireReferralAction extends Action {
+ constructor(referrerData) {
+ super("CrossfireReferralAction");
+ this.referrerData = referrerData;
+ }
+}
+//# sourceMappingURL=actions.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/actions/base-action.js b/node_modules/@jet-app/app-store/tmp/src/api/models/actions/base-action.js
new file mode 100644
index 0000000..fa6da41
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/actions/base-action.js
@@ -0,0 +1,15 @@
+import * as base from "../base";
+import { ActionMetrics } from "../metrics/metrics";
+/** @public */
+export class Action extends base.ViewModel {
+ constructor(actionClass, actionMetrics = new ActionMetrics()) {
+ super();
+ this.title = null;
+ this.artwork = null;
+ this.presentationStyle = [];
+ this.actionClass = actionClass;
+ this.$kind = actionClass;
+ this.actionMetrics = actionMetrics;
+ }
+}
+//# sourceMappingURL=base-action.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/actions/flow-action.js b/node_modules/@jet-app/app-store/tmp/src/api/models/actions/flow-action.js
new file mode 100644
index 0000000..2317142
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/actions/flow-action.js
@@ -0,0 +1,26 @@
+import { Action } from "./base-action";
+/** @public */
+export class FlowBackAction extends Action {
+ constructor(dismissal) {
+ super("FlowBackAction");
+ this.dismissal = dismissal;
+ }
+}
+/** @public */
+export class FlowAction extends Action {
+ constructor(flowPage, pageUrl) {
+ super("FlowAction");
+ this.$kind = "flowAction";
+ this.page = flowPage;
+ this.pageUrl = pageUrl;
+ this.pageData = null;
+ this.referrerData = undefined;
+ this.presentationContext = "infer";
+ this.animationBehavior = "infer";
+ this.origin = "inapp";
+ }
+}
+export function isFlowAction(action) {
+ return action.$kind === "flowAction";
+}
+//# sourceMappingURL=flow-action.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/ad-incidents.js b/node_modules/@jet-app/app-store/tmp/src/api/models/ad-incidents.js
new file mode 100644
index 0000000..d62caa5
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/ad-incidents.js
@@ -0,0 +1,26 @@
+/**
+ * Models for Ad Incidents that may occur while building model.
+ */
+import { Model } from "./base";
+/**
+ * @public
+ * Base Incident Type
+ */
+export class AdIncident extends Model {
+ constructor(incidentType) {
+ super();
+ this.incidentType = incidentType;
+ }
+}
+/**
+ * @public
+ * Type of Ad Incident where Ad was discarded due to some reason
+ */
+export class DiscardAdIncident extends AdIncident {
+ constructor(instanceId, reason) {
+ super("discard");
+ this.instanceId = instanceId;
+ this.reason = reason;
+ }
+}
+//# sourceMappingURL=ad-incidents.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/annotation.js b/node_modules/@jet-app/app-store/tmp/src/api/models/annotation.js
new file mode 100644
index 0000000..92f41ba
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/annotation.js
@@ -0,0 +1,50 @@
+import * as base from "./base";
+/**
+ * The `Annotation` model describes a category of information about an app,
+ * such as supported languages, app size, content ratings, in-app-purchase
+ * info, etc.
+ *
+ * `Annotations` always have a title and summary, and most also have an `items`
+ * array of `AnnotationItem`s with more fine-grained details.
+ *
+ * @public
+ */
+export class Annotation extends base.Model {
+ constructor(title, items, summary, linkAction) {
+ super();
+ this.title = title;
+ this.summary = summary;
+ this.items = items;
+ this.items_V3 = [];
+ this.shouldAlwaysPresentExpanded = false;
+ this.linkAction = linkAction;
+ }
+}
+/**
+ * A single item used to show more details in an `Annotation`. This could be an
+ * In-App Purchase, supported language list, a link to an editorial item, etc.
+ * @public
+ */
+export class AnnotationItem extends base.Model {
+ constructor(text, options = {}) {
+ super();
+ this.text = text;
+ this.heading = options.heading;
+ this.headingArtworks = options.headingArtworks;
+ this.listText = options.listText;
+ this.textPairs = options.textPairs;
+ }
+}
+/**
+ * Used on tvOS to visually group related `Annotation` objects into a column.
+ * @public
+ */
+export class AnnotationGroup extends base.Model {
+ constructor(title, annotations, forceExpanded) {
+ super();
+ this.title = title;
+ this.annotations = annotations;
+ this.forceExpanded = forceExpanded;
+ }
+}
+//# sourceMappingURL=annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/app-promotions.js b/node_modules/@jet-app/app-store/tmp/src/api/models/app-promotions.js
new file mode 100644
index 0000000..cf0c242
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/app-promotions.js
@@ -0,0 +1,186 @@
+import * as base from "./base";
+import * as models from "./index";
+import * as metrics from "./metrics/metrics";
+/** @public */
+export class AppEventFormattedDate extends models.Model {
+ constructor(showLiveIndicator, displayFromDate, displayText, countdownToDate, countdownStringKey) {
+ super();
+ this.showLiveIndicator = showLiveIndicator;
+ this.displayFromDate = displayFromDate;
+ this.displayText = displayText;
+ this.countdownToDate = countdownToDate;
+ this.countdownStringKey = countdownStringKey;
+ }
+}
+/** @public */
+export var AppEventBadgeKind;
+(function (AppEventBadgeKind) {
+ AppEventBadgeKind["available"] = "available";
+ AppEventBadgeKind["happening"] = "happening";
+ AppEventBadgeKind["live"] = "live";
+})(AppEventBadgeKind || (AppEventBadgeKind = {}));
+export var AppPromotionType;
+(function (AppPromotionType) {
+ AppPromotionType["AppEvent"] = "appEvent";
+ AppPromotionType["ContingentOffer"] = "contingentOffer";
+ AppPromotionType["OfferItem"] = "offerItem";
+})(AppPromotionType || (AppPromotionType = {}));
+/** @public */
+export class AppPromotion extends base.ViewModel {
+ constructor(promotionType) {
+ super();
+ this.promotionType = promotionType;
+ }
+}
+/** @public */
+export class AppEvent extends AppPromotion {
+ constructor(appEventId, moduleArtwork, moduleVideo, title, subtitle, detail, startDate, endDate, appEventBadgeKind, kind, requirements, lockup, hideLockupWhenNotInstalled, formattedDates, mediaOverlayStyle, includeBorderInDarkMode) {
+ super(AppPromotionType.AppEvent);
+ this.appEventId = appEventId;
+ this.moduleArtwork = moduleArtwork;
+ this.moduleVideo = moduleVideo;
+ this.title = title;
+ this.subtitle = subtitle;
+ this.detail = detail;
+ this.startDate = startDate;
+ this.endDate = endDate;
+ this.kind = kind;
+ this.appEventBadgeKind = appEventBadgeKind;
+ this.requirements = requirements;
+ this.lockup = lockup;
+ this.hideLockupWhenNotInstalled = hideLockupWhenNotInstalled;
+ this.formattedDates = formattedDates;
+ this.mediaOverlayStyle = mediaOverlayStyle;
+ this.includeBorderInDarkMode = includeBorderInDarkMode;
+ this.clickAction = null;
+ }
+}
+/** @public */
+export class AppEventDetailPage extends models.Model {
+ constructor(appEvent, artwork, video, shareAction, mediaOverlayStyle, includeBorderInDarkMode) {
+ super();
+ this.appEvent = appEvent;
+ this.artwork = artwork;
+ this.video = video;
+ this.shareAction = shareAction;
+ this.mediaOverlayStyle = mediaOverlayStyle;
+ this.includeBorderInDarkMode = includeBorderInDarkMode;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+/**
+ * Determines if a {@linkcode shelves.Shelf} is actually a {@linkcode AppEventDetailShelf}
+ */
+export function isAppEventDetailShelf(shelf) {
+ return shelf.contentType === "appEventDetail";
+}
+/**
+ * `Shelf` definition that wraps a single {@linkcode AppEventDetailPage}
+ *
+ * This is used to power the "web" client UI for an `app-event` detail page
+ *
+ * @public
+ */
+export class AppEventDetailShelf extends models.Shelf {
+ constructor(detailPage) {
+ super("appEventDetail", null, [detailPage]);
+ }
+}
+/** @public */
+export class AppEventNotificationConfig extends models.Model {
+ constructor(appEventId, title, detail, artworkUrl, displayTime, scheduledAction, notAuthorizedAction, failureAction, destinationUrl, scheduleClickEvent, cancelScheduleClickEvent) {
+ super();
+ this.appEventId = appEventId;
+ this.title = title;
+ this.detail = detail;
+ this.artworkUrl = artworkUrl;
+ this.displayTime = displayTime;
+ this.scheduledAction = scheduledAction;
+ this.notAuthorizedAction = notAuthorizedAction;
+ this.failureAction = failureAction;
+ this.destinationUrl = destinationUrl;
+ this.scheduleClickEvent = scheduleClickEvent;
+ this.cancelScheduleClickEvent = cancelScheduleClickEvent;
+ }
+}
+/** @public */
+export class ContingentOffer extends AppPromotion {
+ constructor(backgroundArtwork, mediaOverlayStyle, isStreamlinedBuy, learnMoreTitle, titleFormatted, subtitle, description, label, badge, additionalInfo, trunkAppIcon, offerLockup) {
+ super(AppPromotionType.ContingentOffer);
+ this.backgroundArtwork = backgroundArtwork;
+ this.isStreamlinedBuy = isStreamlinedBuy;
+ this.mediaOverlayStyle = mediaOverlayStyle;
+ this.titleFormatted = titleFormatted;
+ this.subtitle = subtitle;
+ this.additionalInfo = additionalInfo;
+ this.description = description;
+ this.label = label;
+ this.badge = badge;
+ this.offerLockup = offerLockup;
+ this.trunkAppIcon = trunkAppIcon;
+ this.learnMoreTitle = learnMoreTitle;
+ this.clickAction = null;
+ }
+}
+/** @public */
+export class AppPromotionDetailPage extends base.ViewModel {
+ constructor(promotionType) {
+ super();
+ this.promotionType = promotionType;
+ }
+}
+/** @public */
+export class ContingentOfferDetailPage extends AppPromotionDetailPage {
+ constructor(contingentOffer, artwork, mediaOverlayStyle) {
+ super(AppPromotionType.ContingentOffer);
+ this.contingentOffer = contingentOffer;
+ this.artwork = artwork;
+ this.mediaOverlayStyle = mediaOverlayStyle;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ this.learnMoreActionMetrics = new metrics.ActionMetrics();
+ this.backButtonActionMetrics = new metrics.ActionMetrics();
+ this.closeButtonActionMetrics = new metrics.ActionMetrics();
+ }
+}
+/** @public
+ *
+ *
+ */
+export class OfferItem extends AppPromotion {
+ constructor(moduleArtwork, moduleVideo, mediaOverlayStyle, isStreamlinedBuy, titleFormatted, expiryDateFormatted, subtitle, description, badge, endDate, iapArtwork, offerLockup) {
+ super(AppPromotionType.OfferItem);
+ this.moduleArtwork = moduleArtwork;
+ this.moduleVideo = moduleVideo;
+ this.isStreamlinedBuy = isStreamlinedBuy;
+ this.mediaOverlayStyle = mediaOverlayStyle;
+ this.titleFormatted = titleFormatted;
+ this.description = description;
+ this.badge = badge;
+ this.offerLockup = offerLockup;
+ this.subtitle = subtitle;
+ this.endDate = endDate;
+ this.expiryDateFormatted = expiryDateFormatted;
+ this.iapArtwork = iapArtwork;
+ this.clickAction = null;
+ }
+}
+/** @public */
+export class OfferItemDetailPage extends AppPromotionDetailPage {
+ constructor(offerItem, artwork, video, mediaOverlayStyle, includeBorderInDarkMode, learnMoreTitle, additionalInfo) {
+ super(AppPromotionType.OfferItem);
+ this.offerItem = offerItem;
+ this.artwork = artwork;
+ this.learnMoreTitle = learnMoreTitle;
+ this.video = video;
+ this.mediaOverlayStyle = mediaOverlayStyle;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.additionalInfo = additionalInfo;
+ this.pageRenderMetrics = {};
+ this.learnMoreActionMetrics = new metrics.ActionMetrics();
+ this.backButtonActionMetrics = new metrics.ActionMetrics();
+ this.closeButtonActionMetrics = new metrics.ActionMetrics();
+ }
+}
+//# sourceMappingURL=app-promotions.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/arcade-upsell.js b/node_modules/@jet-app/app-store/tmp/src/api/models/arcade-upsell.js
new file mode 100644
index 0000000..8d8eac3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/arcade-upsell.js
@@ -0,0 +1,18 @@
+/**
+ * Created by Jellenbogen on 11/19/19.
+ */
+import * as base from "./base";
+/** @public */
+export class UpsellBreakout extends base.ViewModel {
+ constructor(details, offerDisplayProperties, displayProperties, offerButtonAction, buttonCallToAction, artwork, video) {
+ super();
+ this.details = details;
+ this.displayProperties = displayProperties;
+ this.offerButtonAction = offerButtonAction;
+ this.buttonCallToAction = buttonCallToAction;
+ this.offerDisplayProperties = offerDisplayProperties;
+ this.artwork = artwork;
+ this.video = video;
+ }
+}
+//# sourceMappingURL=arcade-upsell.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/arcade.js b/node_modules/@jet-app/app-store/tmp/src/api/models/arcade.js
new file mode 100644
index 0000000..a016e3b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/arcade.js
@@ -0,0 +1,240 @@
+import * as models from "./index";
+import * as metrics from "./metrics/metrics";
+export function marketingItemContextFromString(context) {
+ var _a, _b;
+ if (preprocessor.GAMES_TARGET) {
+ switch (context) {
+ case "generic":
+ return "gameCenterGeneric";
+ case "groupingLockup":
+ return "gameCenterLockup";
+ case "arcadeTabHeader":
+ return "gameCenterEditorialPageHeader";
+ case "productPage":
+ return "gameCenterProductPage";
+ case "arcadeComingSoon":
+ return "gameCenterComingSoon";
+ case "gameCenterEditorialPage": // TBD
+ return "gameCenterEditorialPage";
+ default:
+ return (_a = context) !== null && _a !== void 0 ? _a : "gameCenterGeneric";
+ }
+ }
+ switch (context) {
+ case "askToBuy":
+ return "arcadeAskToBuy";
+ case "generic":
+ return "arcadeGeneric";
+ case "groupingLockup":
+ return "arcadeGroupingLockup";
+ case "launchRepair":
+ return "arcadeLaunchRepair";
+ case "productPage":
+ return "arcadeProductPage";
+ case "topShelfATV":
+ return "arcadeTopShelfATV";
+ case "topShelfATVClickThrough":
+ return "arcadeTopShelfATVClickThrough";
+ case "editorialItem":
+ return "arcadeTodayCard";
+ case "editorialItemCanvas":
+ return "arcadeStoryCanvas";
+ case "arcadeComingSoon":
+ return "arcadeComingSoon";
+ case "arcadeTabHeader":
+ return "arcadeTabHeader";
+ case "arcadeTabNavBar":
+ return "arcadeTabNavBar";
+ default:
+ return (_b = context) !== null && _b !== void 0 ? _b : "arcadeGeneric";
+ }
+}
+export function isContextualUpsellContext(context) {
+ if (preprocessor.GAMES_TARGET) {
+ switch (context) {
+ case "gameCenterLockup":
+ case "gameCenterProductPage":
+ return true;
+ default:
+ return false;
+ }
+ }
+ switch (context) {
+ case "arcadeGroupingLockup":
+ case "arcadeProductPage":
+ case "productPage":
+ case "groupingLockup":
+ return true;
+ default:
+ return false;
+ }
+}
+/** @public */
+export class ArcadeSubscribePage extends models.Model {
+ constructor(details, dismissButtonTitle, offerButtonAction, offerDisplayProperties) {
+ super();
+ this.details = details;
+ this.dismissButtonTitle = dismissButtonTitle;
+ this.offerButtonAction = offerButtonAction;
+ this.offerDisplayProperties = offerDisplayProperties;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+/** @public */
+export class ArcadeWelcomeItem extends models.Model {
+ constructor(headline, body, artwork) {
+ super();
+ this.headline = headline;
+ this.body = body;
+ this.artwork = artwork;
+ }
+}
+/** @public */
+export class ArcadeWelcomeContent extends models.Model {
+ constructor(title, subtitle, items, continueAction, familyAction) {
+ super();
+ this.title = title;
+ this.subtitle = subtitle;
+ this.items = items;
+ this.continueAction = continueAction;
+ this.familyAction = familyAction;
+ }
+}
+/** @public */
+export class ArcadeWelcomePage extends models.Model {
+ constructor(individualContent, familyMemberContent) {
+ super();
+ this.individualContent = individualContent;
+ this.familyMemberContent = familyMemberContent;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+/** @public */
+export class MarketingItemRequestInfo extends models.Model {
+ constructor(serviceType, placement, metricsTopic, seed, clientOptions) {
+ super();
+ this.serviceType = serviceType;
+ this.placement = placement;
+ this.seed = seed;
+ this.clientOptions = clientOptions;
+ this.metricsOverlay = { topic: metricsTopic };
+ }
+}
+/** @public */
+export class DynamicUIRequestInfo extends models.Model {
+ constructor(metricsTopic, clientOptions) {
+ super();
+ this.clientOptions = clientOptions;
+ this.metricsOverlay = { topic: metricsTopic };
+ }
+}
+/** @public */
+export class UpsellGridContent extends models.Model {
+ constructor(primaryIcon, icons) {
+ super();
+ this.primaryIcon = primaryIcon;
+ this.icons = icons;
+ }
+}
+/** @public */
+export class AppStoreEngagementTask extends models.Model {
+ constructor(action) {
+ super();
+ this.action = action;
+ }
+}
+/** @public
+ * A model for game category button on Arcade download pack screen.
+ */
+export class ArcadeDownloadPackCategory extends models.ViewModel {
+ constructor(id, title, artwork, gradientStartColor, gradientEndColor) {
+ super();
+ this.id = id;
+ this.title = title;
+ this.artwork = artwork;
+ this.gradientStartColor = gradientStartColor;
+ this.gradientEndColor = gradientEndColor;
+ this.selectActionMetrics = new models.ActionMetrics();
+ this.deselectActionMetrics = new models.ActionMetrics();
+ }
+}
+/** @public
+ * Initial Arcade download pack screen with game categories to select by user.
+ */
+export class ArcadeDownloadPackCategoriesPage extends models.Model {
+ constructor(title, categories, maxNumberOfCategoriesToChoose, maxNumberOfCategoriesToChooseTemplate, numberOfChosenCategoriesTemplate, primaryAction, dismissAction) {
+ super();
+ this.title = title;
+ this.categories = categories;
+ this.maxNumberOfCategoriesToChoose = maxNumberOfCategoriesToChoose;
+ this.maxNumberOfCategoriesToChooseTemplate = maxNumberOfCategoriesToChooseTemplate;
+ this.numberOfChosenCategoriesTemplate = numberOfChosenCategoriesTemplate;
+ this.primaryAction = primaryAction;
+ this.dismissAction = dismissAction;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+/** @public
+ * A lockup model for Arcade download suggestions pack screen with linked Arcade category.
+ */
+export class ArcadeDownloadPackSuggestion extends models.Model {
+ constructor(lockup, categoryId) {
+ super();
+ this.lockup = lockup;
+ this.categoryId = categoryId;
+ }
+}
+/** @public
+ * Follow up Arcade download pack screen with the list of suggested games.
+ */
+export class ArcadeDownloadPackSuggestionsPage extends models.Model {
+ constructor(title, suggestions, getAllAction, getAllButtonStyle, primaryAction, primaryActionKind, primaryActionSecondaryTitle) {
+ super();
+ this.title = title;
+ this.suggestions = suggestions;
+ this.getAllAction = getAllAction;
+ this.getAllButtonStyle = getAllButtonStyle;
+ this.primaryAction = primaryAction;
+ this.primaryActionKind = primaryActionKind;
+ this.primaryActionSecondaryTitle = primaryActionSecondaryTitle;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+/**
+ * Matches untyped string with `ArcadeOnboardingSubscriptionStatus` typed value.
+ * It is used to convert opaque `string` value that is passed across JS bridge from native code.
+ * @param value `string` value with the same content.
+ */
+export function arcadeOnboardingSubscriptionStatusFromString(value) {
+ switch (value) {
+ case "new":
+ return "new";
+ case "existing":
+ return "existing";
+ default:
+ return "unknown";
+ }
+}
+/** @public
+ * Purchase params to use in`ASDPurchaseManager.purchaseBatch` call.
+ */
+export class BatchPurchaseParams {
+ constructor(items, commonBuyParams) {
+ this.items = items;
+ this.commonBuyParams = commonBuyParams;
+ }
+}
+/** @public
+ * Decorated purchase for a product with a separate buyParams map.
+ */
+export class BatchPurchaseItem {
+ constructor(purchase, buyParams) {
+ this.purchase = purchase;
+ this.buyParams = buyParams;
+ }
+}
+//# sourceMappingURL=arcade.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/article-page.js b/node_modules/@jet-app/app-store/tmp/src/api/models/article-page.js
new file mode 100644
index 0000000..9d5d6c0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/article-page.js
@@ -0,0 +1,18 @@
+/**
+ * Created by km on 2/13/17.
+ */
+import * as models from "./base";
+import * as metrics from "./metrics/metrics";
+/** @public */
+export class ArticlePage extends models.Model {
+ constructor(card, shelfModels, shareAction) {
+ super();
+ this.card = card;
+ this.shelves = shelfModels;
+ this.shareAction = shareAction;
+ this.isIncomplete = false;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+//# sourceMappingURL=article-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/base.js b/node_modules/@jet-app/app-store/tmp/src/api/models/base.js
new file mode 100644
index 0000000..8281f6a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/base.js
@@ -0,0 +1,596 @@
+/**
+ * Created by km on 2/13/17.
+ */
+// region Core
+/** @public */
+export class Model {
+ constructor() {
+ this.$incidents = undefined;
+ }
+ /**
+ * Indicates whether this model is valid. Subclasses should override if there are specific
+ * properties that are required for proper functioning
+ * @returns {boolean} Whether the model is valid. Defaults to true.
+ */
+ isValid() {
+ return true;
+ }
+}
+/**
+ * @public
+ * @todo Make this implement JetEngine ViewModel once we migrate ImpressionMetrics
+ */
+export class ViewModel extends Model {
+ constructor(impressionMetrics = null) {
+ super();
+ this.impressionMetrics = impressionMetrics;
+ }
+}
+/** @public */
+export class PurchaseConfiguration extends Model {
+ constructor(buyParams, vendor, appName, bundleId, appPlatforms, isPreorder, excludeAttribution, metricsPlatformDisplayStyle, lineItem, isRedownload, preflightPackageUrl, isArcadeApp, isHalva, supportsVisionOSCompatibleIOSBinary, inAppEventId, extRefApp2, extRefUrl2, additionalHeaders, appCapabilities, isDefaultBrowser, remoteDownloadIdentifiers, hasMacIPAPackage, contentRating) {
+ super();
+ this.buyParams = buyParams;
+ this.vendor = vendor;
+ this.appName = appName;
+ this.bundleId = bundleId;
+ this.appPlatforms = appPlatforms;
+ this.isPreorder = isPreorder === null || isPreorder === undefined ? false : isPreorder;
+ this.excludeAttribution =
+ excludeAttribution === null || excludeAttribution === undefined ? true : excludeAttribution;
+ this.metricsPlatformDisplayStyle = metricsPlatformDisplayStyle;
+ this.isRedownload = isRedownload === null || isRedownload === undefined ? false : isRedownload;
+ this.lineItem = lineItem;
+ this.preflightPackageUrl = preflightPackageUrl === undefined ? null : preflightPackageUrl;
+ this.isArcadeApp = isArcadeApp;
+ this.isHalva = isHalva;
+ this.supportsVisionOSCompatibleIOSBinary = supportsVisionOSCompatibleIOSBinary;
+ this.inAppEventId = inAppEventId;
+ this.extRefApp2 = extRefApp2;
+ this.extRefUrl2 = extRefUrl2;
+ this.additionalHeaders = additionalHeaders;
+ this.appCapabilities = appCapabilities;
+ this.isDefaultBrowser = isDefaultBrowser;
+ this.remoteDownloadIdentifiers = remoteDownloadIdentifiers;
+ this.hasMacIPAPackage = hasMacIPAPackage;
+ this.contentRating = contentRating;
+ }
+}
+/** @public */
+export class OfferDisplayProperties extends Model {
+ constructor(offerType, adamId, bundleId, style, parentAdamId, environment, offerTint, titles, titleSymbolNames, subtitles, hasInAppPurchases, hasExternalPurchases, isDeletableSystemApp, isFree, isPreorder, offerLabelStyle, hasDiscount, contentRating, subscriptionFamilyId, useAdsLocale, priceFormatted, isStreamlinedBuy, appCapabilities, isRedownloadDisallowed = false, isOpenBundleAllowed = false) {
+ super();
+ this.offerType = offerType;
+ this.adamId = adamId;
+ this.bundleId = bundleId;
+ this.parentAdamId = parentAdamId;
+ this.style = style === null || style === undefined ? "infer" : style;
+ this.environment = environment === null || environment === undefined ? "light" : environment;
+ this.offerTint = offerTint === null || offerTint === undefined ? { type: "blue" } : offerTint;
+ this.titles = titles === null || titles === undefined ? {} : titles;
+ this.titleSymbolNames = titleSymbolNames === null || titleSymbolNames === undefined ? {} : titleSymbolNames;
+ this.subtitles = subtitles === null || subtitles === undefined ? {} : subtitles;
+ this.hasInAppPurchases = hasInAppPurchases;
+ this.hasExternalPurchases = hasExternalPurchases;
+ this.isDeletableSystemApp = isDeletableSystemApp;
+ this.isFree = isFree;
+ this.isPreorder = isPreorder;
+ this.offerLabelStyle = offerLabelStyle === null || offerLabelStyle === undefined ? "none" : offerLabelStyle;
+ this.hasDiscount = hasDiscount;
+ this.contentRating = contentRating;
+ this.subscriptionFamilyId = subscriptionFamilyId;
+ this.useAdsLocale = useAdsLocale;
+ this.priceFormatted = priceFormatted;
+ this.isStreamlinedBuy = isStreamlinedBuy;
+ this.appCapabilities = appCapabilities;
+ this.isRedownloadDisallowed = isRedownloadDisallowed;
+ this.isOpenBundleAllowed = isOpenBundleAllowed;
+ }
+ /**
+ * Create new offer display properties changing appearance.
+ * @param {boolean} overrideDisabledStyle When false, an offer style of 'disabled' is maintained. When true, the style is overridden regardless of being disabled to begin with.
+ * @param {OfferStyle} style The new style to apply.
+ * @param {OfferEnvironment} environment The new environment to apply.
+ * @param {OfferTint} offerTint
+ * @return {string} A new instance of OfferDisplayProperties with the desired modifications.
+ */
+ newOfferDisplayPropertiesChangingAppearance(overrideDisabledStyle, style, environment, offerTint) {
+ return new OfferDisplayProperties(this.offerType, this.adamId, this.bundleId, style === null || style === undefined || (!overrideDisabledStyle && this.style === "disabled")
+ ? this.style
+ : style, this.parentAdamId, environment === null || environment === undefined ? this.environment : environment, offerTint === null || offerTint === undefined ? this.offerTint : offerTint, this.titles, this.titleSymbolNames, this.subtitles, this.hasInAppPurchases, this.hasExternalPurchases, this.isDeletableSystemApp, this.isFree, this.isPreorder, this.offerLabelStyle, this.hasDiscount, this.contentRating, this.subscriptionFamilyId, this.useAdsLocale, this.priceFormatted, this.isStreamlinedBuy, this.appCapabilities, this.isRedownloadDisallowed, this.isOpenBundleAllowed);
+ }
+}
+/**
+ * @public
+ * Information needed to personalize offerActions and how they are displayed.
+ */
+export class PersonalizedOfferContext extends Model {
+ constructor(personalizedOfferType, offerAction, offerDisplayProperties) {
+ super();
+ this.offerAction = offerAction;
+ this.offerDisplayProperties = offerDisplayProperties;
+ this.personalizedOfferType = personalizedOfferType;
+ }
+}
+export var ArtworkContentMode;
+(function (ArtworkContentMode) {
+ ArtworkContentMode[ArtworkContentMode["scaleToFill"] = 0] = "scaleToFill";
+ ArtworkContentMode[ArtworkContentMode["scaleAspectFit"] = 1] = "scaleAspectFit";
+ ArtworkContentMode[ArtworkContentMode["scaleAspectFill"] = 2] = "scaleAspectFill";
+ ArtworkContentMode[ArtworkContentMode["redraw"] = 3] = "redraw";
+ ArtworkContentMode[ArtworkContentMode["center"] = 4] = "center";
+ ArtworkContentMode[ArtworkContentMode["top"] = 5] = "top";
+ ArtworkContentMode[ArtworkContentMode["bottom"] = 6] = "bottom";
+ ArtworkContentMode[ArtworkContentMode["left"] = 7] = "left";
+ ArtworkContentMode[ArtworkContentMode["right"] = 8] = "right";
+ ArtworkContentMode[ArtworkContentMode["topLeft"] = 9] = "topLeft";
+ ArtworkContentMode[ArtworkContentMode["topRight"] = 10] = "topRight";
+ ArtworkContentMode[ArtworkContentMode["bottomLeft"] = 11] = "bottomLeft";
+ ArtworkContentMode[ArtworkContentMode["bottomRight"] = 12] = "bottomRight";
+})(ArtworkContentMode || (ArtworkContentMode = {}));
+/** @public */
+export class ArtworkVariant extends Model {
+ constructor(format, quality, supportsWideGamut) {
+ super();
+ this.format = format;
+ this.quality = quality;
+ this.supportsWideGamut = supportsWideGamut;
+ }
+ isValid() {
+ return this.format && this.supportsWideGamut !== undefined;
+ }
+}
+const systemImages = [
+ "app.3.stack.3d",
+ "app.3.stack.3d.fill",
+ "applewatch",
+ "appstore",
+ "hammer",
+ "hammer.fill",
+ "house",
+ "ipad.gen2.landscape",
+ "iphone.gen2",
+ "joystickcontroller",
+ "joystickcontroller.fill",
+ "macbook.gen2",
+ "magnifyingglass",
+ "paintbrush",
+ "paintbrush.fill",
+ "paperplane",
+ "paperplane.fill",
+ "person.crop.square",
+ "rocket",
+ "rocket.fill",
+ "safari",
+ "square.grid.2x2",
+ "square.grid.2x2.fill",
+ "star",
+ "star.fill",
+ "text.rectangle.page",
+ "text.rectangle.page.fill",
+ "tv",
+ "visionpro",
+];
+/**
+ * @public
+ * Type guard to narrow and ensure a string is a valid `SystemImage`.
+ */
+export function isSystemImage(systemImage) {
+ return typeof systemImage === "string" && systemImages.includes(systemImage);
+}
+/** @public */
+export class Artwork extends Model {
+ constructor(template, width, height, variants, backgroundColor, textColor) {
+ super();
+ this.checksum = null;
+ this.backgroundColor = null;
+ this.textColor = null; // Represents a valid text color that can be used alongside this artwork
+ this.style = null;
+ this.crop = "bb";
+ this.contentMode = null;
+ this.imageScale = null;
+ this.template = template;
+ this.width = width;
+ this.height = height;
+ this.variants = variants;
+ this.backgroundColor = backgroundColor;
+ this.textColor = textColor;
+ }
+ isPortrait() {
+ return this.height >= this.width;
+ }
+ isLandscape() {
+ return !this.isPortrait();
+ }
+ isValid() {
+ return this.template !== "" && this.width > 0 && this.height > 0 && this.variants.length > 0;
+ }
+}
+/** @public */
+export class Screenshots extends Model {
+ constructor(artwork, mediaPlatform) {
+ super();
+ this.artwork = artwork;
+ this.mediaPlatform = mediaPlatform;
+ }
+}
+/** @public */
+export class MediaPlatform extends Model {
+ constructor(appPlatform, mediaType, systemImageName, supplementaryAppPlatforms, deviceCornerRadiusFactor, deviceBorderThickness, outerDeviceCornerRadiusFactor) {
+ super();
+ this.appPlatform = appPlatform;
+ this.supplementaryAppPlatforms = supplementaryAppPlatforms || [];
+ this.deviceCornerRadiusFactor = deviceCornerRadiusFactor;
+ this.mediaType = mediaType;
+ this.systemImageName = systemImageName;
+ this.deviceBorderThickness = deviceBorderThickness;
+ this.outerDeviceCornerRadiusFactor = outerDeviceCornerRadiusFactor;
+ }
+ isEqualTo(mediaPlatform) {
+ this.supplementaryAppPlatforms.sort();
+ mediaPlatform.supplementaryAppPlatforms.sort();
+ const supplementaryPlatformsAreEqual = this.supplementaryAppPlatforms.join(",") === mediaPlatform.supplementaryAppPlatforms.join(",");
+ return (mediaPlatform.appPlatform === this.appPlatform &&
+ mediaPlatform.mediaType === this.mediaType &&
+ mediaPlatform.systemImageName === this.systemImageName &&
+ supplementaryPlatformsAreEqual &&
+ mediaPlatform.deviceCornerRadiusFactor === this.deviceCornerRadiusFactor);
+ }
+}
+/**
+ * @public
+ * Generic size class used to describe an size in points
+ */
+export class Size {
+ constructor(width, height) {
+ this.width = width;
+ this.height = height;
+ }
+ static fromNativeSize(nativeSize) {
+ return new Size(nativeSize.width, nativeSize.height);
+ }
+ isEqualTo(size) {
+ return size.width === this.width && size.height === this.height;
+ }
+}
+// endregion
+// region Share
+/** @public */
+export class ShareSheetNotesMetadata extends Model {
+ constructor(itemName, url, developer, category, fileSize, mediaType) {
+ super();
+ this.itemName = itemName;
+ this.url = url;
+ this.developer = developer;
+ this.category = category;
+ this.fileSize = fileSize;
+ this.mediaType = mediaType;
+ }
+}
+/** @public */
+export class ShareSheetArticleMetadata extends Model {
+ constructor(id, text, subtitle, artwork) {
+ super();
+ this.context = "article";
+ this.id = id;
+ this.text = text;
+ this.subtitle = subtitle;
+ this.artwork = artwork;
+ }
+}
+/** @public */
+export class ShareSheetAppEventMetadata extends Model {
+ constructor(text, subtitle, artwork) {
+ super();
+ this.context = "appEvent";
+ this.text = text;
+ this.subtitle = subtitle;
+ this.artwork = artwork;
+ }
+}
+/** @public */
+export class ShareSheetProductMetadata extends Model {
+ constructor(adamId, storeFrontIdentifier, name, platform, icon, screenshots, videos, isMessagesOnlyApp, subtitle, genreName, messagesAppIcon, notesMetadata) {
+ super();
+ this.context = "product";
+ this.adamId = adamId;
+ this.storeFrontIdentifier = storeFrontIdentifier;
+ this.name = name;
+ this.platform = platform;
+ this.icon = icon;
+ this.screenshots = screenshots;
+ this.videos = videos;
+ this.isMessagesOnlyApp = isMessagesOnlyApp;
+ this.subtitle = subtitle;
+ this.genreName = genreName;
+ this.messagesAppIcon = messagesAppIcon;
+ this.notesMetadata = notesMetadata;
+ }
+}
+/** @public */
+export class ShareSheetGenericMetadata extends Model {
+ constructor(text, subtitle, artwork) {
+ super();
+ this.context = "generic";
+ this.text = text;
+ this.subtitle = subtitle;
+ this.artwork = artwork;
+ }
+}
+/** @public */
+export class ShareSheetData extends Model {
+ constructor(metadata, url, shortUrl) {
+ super();
+ this.metadata = metadata;
+ this.url = url;
+ this.shortUrl = shortUrl;
+ }
+}
+// endregion
+// region ReportConcern
+/** @public */
+export class ReportConcernReason extends Model {
+ constructor(reasonId, name, uppercaseName) {
+ super();
+ this.reasonId = reasonId;
+ this.name = name;
+ this.uppercaseName = uppercaseName;
+ }
+}
+/** @public */
+export class StyledText extends Model {
+ constructor(rawText, rawTextType = "text/plain") {
+ super();
+ this.rawText = rawText;
+ this.rawTextType = rawTextType;
+ }
+}
+/** @public */
+export class Paragraph extends ViewModel {
+ constructor(text, mediaType, style) {
+ super();
+ this.text = text;
+ this.mediaType = mediaType || "text/plain";
+ this.style = style || "standard";
+ this.alignment = "localized";
+ this.isCollapsed = false;
+ this.suppressVerticalMargins = false;
+ this.wantsCollapsedNewlines = true;
+ }
+}
+// endregion
+// region Flow Preview
+/** @public */
+export class FlowPreviewActionsConfiguration extends Model {
+ constructor(actionss, offerDisplayProperties = null, offerActionIndex = null) {
+ super();
+ this.actions = actionss;
+ this.offerDisplayProperties = offerDisplayProperties;
+ this.offerActionIndex = offerActionIndex;
+ }
+}
+// region Arcade Footer
+/** @public */
+export class ArcadeFooter extends ViewModel {
+}
+// endregion
+/** @public
+ * Arcade download (starter) pack shelf data model.
+ * Currently used only for iPhone (see `ArcadeDownloadPackShelfController`).
+ */
+export class ArcadeDownloadPackCard extends ViewModel {
+}
+/** @public */
+export class TitleEffect extends Model {
+ constructor(type) {
+ super();
+ this.isFallbackStyle = false;
+ this.type = type;
+ }
+}
+// endregion
+// region Game Center
+/** @public */
+export class GameCenterPlayerGameAchievementSummary extends ViewModel {
+ constructor(bundleId, completedAchievements, totalAchievements, completedText, action, achievements) {
+ super();
+ this.bundleId = bundleId;
+ this.completedAchievements = completedAchievements;
+ this.totalAchievements = totalAchievements;
+ this.completedText = completedText;
+ this.action = action;
+ this.achievements = achievements;
+ }
+}
+/** @public */
+export class GameCenterPlayer extends ViewModel {
+ constructor(playerId, alias, displayName, size, artwork, action) {
+ super();
+ this.playerId = playerId;
+ this.alias = alias;
+ this.displayName = displayName;
+ this.artwork = artwork;
+ this.action = action;
+ this.size = size;
+ }
+}
+/** @public */
+export class GameCenterActivityFeedCard extends ViewModel {
+ constructor(id, avatarImageURL, avatarActionURL, body, supplementaryViewImageURL, supplementaryViewActionURL, adamID) {
+ super();
+ this.id = id;
+ this.avatarImageURL = avatarImageURL;
+ this.avatarActionURL = avatarActionURL;
+ this.body = body;
+ this.supplementaryViewImageURL = supplementaryViewImageURL;
+ this.supplementaryViewActionURL = supplementaryViewActionURL;
+ this.adamID = adamID;
+ }
+}
+/** @public */
+export class SmallContactCard extends ViewModel {
+ constructor(id, title, subtitle, buttonText, contactId, buttonAction, removeButtonAction, shouldShowMessagesBadge) {
+ super();
+ this.id = id;
+ this.title = title;
+ this.subtitle = subtitle;
+ this.buttonText = buttonText;
+ this.contactId = contactId;
+ this.buttonAction = buttonAction;
+ this.removeButtonAction = removeButtonAction;
+ this.shouldShowMessagesBadge = shouldShowMessagesBadge;
+ }
+}
+/** @public */
+export class GameCenterAchievementStatus extends Model {
+ constructor(type) {
+ super();
+ this.type = type;
+ }
+}
+/** @public */
+export class GameCenterAchievement extends Model {
+ constructor(id, title, subtitle, status) {
+ super();
+ this.id = id;
+ this.title = title;
+ this.subtitle = subtitle;
+ this.status = status;
+ }
+}
+/** @public */
+export class GameCenterGameplayHistory extends Model {
+ constructor(adamId, platformId, isArcade, records) {
+ super();
+ this.adamId = adamId;
+ this.platformId = platformId;
+ this.isArcade = isArcade;
+ this.records = records;
+ }
+}
+/** @public */
+export class GameCenterGameplayHistoryRecord extends Model {
+ constructor(playerId, timestamp) {
+ super();
+ this.playerId = playerId;
+ this.timestamp = timestamp;
+ }
+}
+/** @public */
+export class Video extends Model {
+ constructor(videoUrl, preview, videoConfiguration) {
+ super();
+ this.videoUrl = videoUrl;
+ this.preview = preview;
+ this.allowsAutoPlay = videoConfiguration.allowsAutoPlay;
+ this.looping = videoConfiguration.looping;
+ this.canPlayFullScreen = videoConfiguration.canPlayFullScreen;
+ this.playbackControls = { ...videoConfiguration.playbackControls };
+ this.autoPlayPlaybackControls = { ...videoConfiguration.autoPlayPlaybackControls };
+ this.templateMediaEvent = null;
+ }
+}
+export class CombinedFileSize {
+ constructor(fileSizeByDevice, maxDownloadSizeInBytes, maxInstallSizeInBytes, maxEssentialInstallSizeInBytes) {
+ this.fileSizeByDevice = fileSizeByDevice;
+ this.maxDownloadSizeInBytes = maxDownloadSizeInBytes;
+ this.maxInstallSizeInBytes = maxInstallSizeInBytes;
+ this.maxEssentialInstallSizeInBytes = maxEssentialInstallSizeInBytes;
+ }
+}
+/** @public */
+export class PageHeader extends ViewModel {
+ constructor(badge, title, subtitle) {
+ super();
+ this.badge = badge;
+ this.title = title;
+ this.subtitle = subtitle;
+ }
+}
+export class MediaPageHeader extends PageHeader {
+ constructor(badge, title, subtitle, artwork, video, collectionIcons, useGeneratedBackgroundGradient, backgroundColor, style, backgroundStyle) {
+ super(badge, title, subtitle);
+ this.artwork = artwork;
+ this.video = video;
+ this.collectionIcons = collectionIcons;
+ this.useGeneratedBackgroundGradient = useGeneratedBackgroundGradient;
+ this.backgroundColor = backgroundColor;
+ this.style = style;
+ this.backgroundStyle = backgroundStyle;
+ }
+}
+export const EdgeInsetsZero = {
+ top: 0.0,
+ left: 0.0,
+ bottom: 0.0,
+ right: 0.0,
+};
+/**
+ * The `Priority` is used when sorting the metrics to match for a given
+ * today card, so if we're looking for a layout for a given priority we
+ * will match that first
+ */
+export var TodayCardArtworkSizedLayoutMetricsPriority;
+(function (TodayCardArtworkSizedLayoutMetricsPriority) {
+ TodayCardArtworkSizedLayoutMetricsPriority["Mini"] = "mini";
+ TodayCardArtworkSizedLayoutMetricsPriority["ExtraWide"] = "extraWide";
+ TodayCardArtworkSizedLayoutMetricsPriority["None"] = "none";
+})(TodayCardArtworkSizedLayoutMetricsPriority || (TodayCardArtworkSizedLayoutMetricsPriority = {}));
+export class ChartOrCategorySafeAreaLocation {
+}
+export var ChartOrCategorySafeAreaRelativeLocation;
+(function (ChartOrCategorySafeAreaRelativeLocation) {
+ ChartOrCategorySafeAreaRelativeLocation["TopRight"] = "topRight";
+ ChartOrCategorySafeAreaRelativeLocation["TopLeft"] = "topLeft";
+ ChartOrCategorySafeAreaRelativeLocation["MiddleLeft"] = "middleLeft";
+})(ChartOrCategorySafeAreaRelativeLocation || (ChartOrCategorySafeAreaRelativeLocation = {}));
+export var ChartOrCategorySafeAreaSizeConstraint;
+(function (ChartOrCategorySafeAreaSizeConstraint) {
+ ChartOrCategorySafeAreaSizeConstraint["SquareByWidth"] = "squareByWidth";
+ ChartOrCategorySafeAreaSizeConstraint["SquareByHeight"] = "squareByHeight";
+ ChartOrCategorySafeAreaSizeConstraint["FreeForm"] = "freeForm";
+})(ChartOrCategorySafeAreaSizeConstraint || (ChartOrCategorySafeAreaSizeConstraint = {}));
+export class ChartOrCategorySafeAreaLocationDistance {
+}
+export class ChartOrCategorySafeArea {
+ constructor(width, height, asRatioOfSize, location, constraint) {
+ this.width = width;
+ this.height = height;
+ this.asRatioOfSize = asRatioOfSize !== null && asRatioOfSize !== void 0 ? asRatioOfSize : false;
+ this.location = location;
+ this.constraint = constraint;
+ }
+}
+/// The default tile safe area displays the artwork in a square in the top trailing corner of the cell.
+ChartOrCategorySafeArea.defaultTileArtworkSafeArea = {
+ width: 0.4,
+ height: 0.4,
+ asRatioOfSize: true,
+ constraint: ChartOrCategorySafeAreaSizeConstraint.SquareByWidth,
+ location: {
+ relativeLocation: ChartOrCategorySafeAreaRelativeLocation.TopRight,
+ },
+};
+/// The default pill safe area displays the artwork on the middle leading side of the cell.
+ChartOrCategorySafeArea.defaultPillArtworkSafeArea = {
+ width: 0.28,
+ height: 1.0,
+ asRatioOfSize: true,
+ constraint: ChartOrCategorySafeAreaSizeConstraint.FreeForm,
+ location: {
+ relativeLocation: ChartOrCategorySafeAreaRelativeLocation.MiddleLeft,
+ },
+};
+/// The default tile safe area increases the width available to render the text for the cell.
+ChartOrCategorySafeArea.defaultTileTextSafeArea = {
+ width: 0.15,
+ height: 0.0,
+ asRatioOfSize: true,
+};
+/// The default pill safe area increases the width available to render the text for the cell.
+ChartOrCategorySafeArea.defaultPillTextSafeArea = {
+ width: 0.28,
+ height: 0.0,
+ asRatioOfSize: true,
+};
+//# sourceMappingURL=base.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/categories.js b/node_modules/@jet-app/app-store/tmp/src/api/models/categories.js
new file mode 100644
index 0000000..2435540
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/categories.js
@@ -0,0 +1,20 @@
+import * as models from "./index";
+/** @public */
+export class Category extends models.Model {
+ constructor(name, genreId, artwork, ageBandId, children) {
+ super();
+ this.name = name;
+ this.genreId = genreId;
+ this.artwork = artwork;
+ this.ageBandId = ageBandId;
+ this.children = children;
+ }
+}
+/** @public */
+export class CategoryList extends models.Model {
+ constructor(categories) {
+ super();
+ this.categories = categories;
+ }
+}
+//# sourceMappingURL=categories.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/dynamic-generic-page.js b/node_modules/@jet-app/app-store/tmp/src/api/models/dynamic-generic-page.js
new file mode 100644
index 0000000..6037275
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/dynamic-generic-page.js
@@ -0,0 +1,15 @@
+import * as base from "./base";
+import * as metrics from "./metrics/metrics";
+/** @public */
+export class DynamicGenericPage extends base.Model {
+ constructor() {
+ super();
+ this.shelfOrderings = {};
+ this.shelfMapping = {};
+ this.presentationOptions = [];
+ this.isIncomplete = false;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+//# sourceMappingURL=dynamic-generic-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/generic-page.js b/node_modules/@jet-app/app-store/tmp/src/api/models/generic-page.js
new file mode 100644
index 0000000..1ee16fb
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/generic-page.js
@@ -0,0 +1,47 @@
+/**
+ * Created by km on 2/13/17.
+ */
+import * as base from "./base";
+import * as metrics from "./metrics/metrics";
+/** @public */
+export class GenericPage extends base.Model {
+ constructor(shelfModels) {
+ super();
+ this.shelves = shelfModels;
+ this.title = null;
+ this.presentationOptions = [];
+ this.isIncomplete = false;
+ this.pageMetrics = new metrics.PageMetrics();
+ }
+}
+/** @public */
+export class InAppPurchaseInstallPage extends base.Model {
+ constructor() {
+ super();
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+/**
+ * @public
+ * Screen shown in search tab when search bar is focused but empty.
+ */
+export class SearchFocusPage extends GenericPage {
+}
+/**
+ * @public
+ * Initial screen shown in search tab when search bar is unfocused.
+ */
+export class SearchLandingPage extends GenericPage {
+}
+/** @public */
+export class ArcadePage extends GenericPage {
+}
+/** @public */
+export class ArcadeSeeAllGamesPage extends GenericPage {
+ constructor(shelfModels) {
+ super(shelfModels);
+ this.pageSegments = [];
+ }
+}
+//# sourceMappingURL=generic-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/hero-carousel.js b/node_modules/@jet-app/app-store/tmp/src/api/models/hero-carousel.js
new file mode 100644
index 0000000..85b9514
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/hero-carousel.js
@@ -0,0 +1,37 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "./index";
+/** @public */
+export class HeroCarouselItemOverlay extends models.ViewModel {
+ isValid() {
+ const hasButtonRequirements = this.callToActionText !== undefined &&
+ this.callToActionText !== null &&
+ this.clickAction !== undefined &&
+ this.clickAction !== null;
+ const hasTitle = this.titleText !== undefined && this.titleText !== null;
+ const hasLockupRequirements = this.lockup !== undefined && this.lockup !== null;
+ const hasCollectionIcons = this.collectionIcons !== undefined && this.collectionIcons !== null;
+ return hasTitle && (hasLockupRequirements || hasCollectionIcons || hasButtonRequirements);
+ }
+}
+/** @public */
+export class HeroCarouselItem extends models.ViewModel {
+ isValid() {
+ const hasCollectionIcons = isSome(this.collectionIcons) && this.collectionIcons.length > 0;
+ const hasValidArtwork = isSome(this.artwork) && this.artwork.isValid();
+ const hasValidVideo = isSome(this.video) && this.video.isValid();
+ const hasMedia = hasValidArtwork || hasValidVideo || hasCollectionIcons;
+ const hasOverlay = isSome(this.overlay) && this.overlay.isValid();
+ return hasMedia && hasOverlay;
+ }
+}
+/** @public */
+export class HeroCarousel extends models.ViewModel {
+ constructor() {
+ super(...arguments);
+ /// The different items to display in the carousel
+ this.items = [];
+ /// The different items to display in the carousel, in RTL
+ this.rtlItems = [];
+ }
+}
+//# sourceMappingURL=hero-carousel.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/metrics/advert-action-metrics.js b/node_modules/@jet-app/app-store/tmp/src/api/models/metrics/advert-action-metrics.js
new file mode 100644
index 0000000..4560458
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/metrics/advert-action-metrics.js
@@ -0,0 +1,21 @@
+/**
+ * Models for Advert Actions
+ */
+import * as models from "../base";
+/**
+ * @public
+ * Object that provides metrics data for actions pertaining to advert's native instrumentation.
+ */
+export class AdvertActionMetrics extends models.Model {
+ constructor(instanceId, adamId, bundleId, advertType, invocation, purchaseType, reportingDestination) {
+ super();
+ this.instanceId = instanceId;
+ this.adamId = adamId;
+ this.bundleId = bundleId;
+ this.advertType = advertType;
+ this.invocation = invocation;
+ this.purchaseType = purchaseType;
+ this.reportingDestination = reportingDestination;
+ }
+}
+//# sourceMappingURL=advert-action-metrics.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/metrics/metrics.js b/node_modules/@jet-app/app-store/tmp/src/api/models/metrics/metrics.js
new file mode 100644
index 0000000..23e26bd
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/metrics/metrics.js
@@ -0,0 +1,101 @@
+/**
+ * Created by joel on 2/13/17.
+ */
+import * as JetMetrics from "@jet/environment/types/metrics";
+import * as models from "../base";
+// TS only allows extending **type information** in `declare module`s.
+// These members **must** be initialized.
+// TODO: The root cause for this workaround is because the Jet type `PageInvocationPoint` is not a `const enum`.
+// Consider changing the Jet type to a `const enum`, and the compiler will automatically inline these values.
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const extendedPageInvocationPoint = JetMetrics.PageInvocationPoint;
+extendedPageInvocationPoint["search"] = "search";
+extendedPageInvocationPoint["timer"] = "timer";
+extendedPageInvocationPoint["never"] = "never";
+extendedPageInvocationPoint["pageChange"] = "pageChange";
+/** @public */
+export class AppStoreMetricsData {
+ constructor(fields, includingFields, excludingFields, topic, shouldFlush = false) {
+ this.fields = fields;
+ this.includingFields = includingFields;
+ this.excludingFields = excludingFields;
+ this.topic = topic;
+ this.shouldFlush = shouldFlush;
+ }
+}
+/** @public */
+export class LintedMetricsEvent extends models.Model {
+ constructor(fields) {
+ super();
+ this.fields = fields;
+ }
+}
+/** @public */
+export class ActionMetrics extends models.Model {
+ constructor(events) {
+ super();
+ this.data = events || [];
+ this.custom = {};
+ }
+ addMetricsData(data) {
+ this.data.push(data);
+ }
+ addManyMetricsData(dataArray) {
+ for (const data of dataArray) {
+ this.addMetricsData(data);
+ }
+ }
+ clearAll() {
+ this.data.length = 0;
+ }
+}
+/** @public */
+export class PageMetrics extends models.Model {
+ constructor() {
+ super();
+ this.instructions = [];
+ this.custom = {};
+ }
+ addInstruction(instruction) {
+ this.instructions.push(instruction);
+ }
+ addManyInstructions(instructions) {
+ for (const instruction of instructions) {
+ this.addInstruction(instruction);
+ }
+ }
+ addData(data, invocationPoints) {
+ const event = {
+ data,
+ invocationPoints,
+ };
+ this.instructions.push(event);
+ }
+ addManyData(dataArray, invocationPoints) {
+ for (const data of dataArray) {
+ this.addData(data, invocationPoints);
+ }
+ }
+}
+// TODO: This needs to be migrated to the JetEngine version of ImpressionMetrics
+// The primary challenge is ID is required in the JetEngine representation, which will require
+// some refactoring both in JS and native to get right
+/** @public */
+export class ImpressionMetrics {
+ constructor(fields, id, custom) {
+ this.fields = fields;
+ this.id = id;
+ this.custom = custom;
+ }
+}
+/** @public */
+export class FastImpressionMetrics extends ImpressionMetrics {
+ constructor(metrics, isFast) {
+ super(metrics.fields, metrics.id, metrics.custom || {});
+ if (this.custom !== undefined) {
+ this.custom["isFast"] = isFast;
+ }
+ this.isFast = isFast;
+ }
+}
+//# sourceMappingURL=metrics.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/page-facets.js b/node_modules/@jet-app/app-store/tmp/src/api/models/page-facets.js
new file mode 100644
index 0000000..a534c8e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/page-facets.js
@@ -0,0 +1,88 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "./base";
+/** @public */
+export class PageFacetOption extends models.Model {
+ constructor(title, value, metricsValue = null, systemImageName = undefined) {
+ super();
+ this.title = title;
+ this.value = value;
+ this.systemImageName = systemImageName;
+ this.metricsValue = metricsValue;
+ }
+}
+PageFacetOption.defaultValue = "pageFacetsDefaultValue";
+PageFacetOption.trueValue = new PageFacetOption("true", "false");
+PageFacetOption.falseValue = new PageFacetOption("false", "false");
+/** @public */
+export class PageFacetsFacet extends models.Model {
+ constructor(id, parameterName, title, displayType, options = [], defaultOptions = null, metricsParameterName = null, clickAction = null, displayOptionsInline = false, showsSelectedOptions = false, isHiddenFromMenu = false) {
+ super();
+ this.id = id;
+ this.parameterName = parameterName;
+ this.title = title;
+ this.displayType = displayType;
+ this.defaultOptions = defaultOptions;
+ this.options = options;
+ this.metricsParameterName = metricsParameterName;
+ this.clickAction = clickAction;
+ this.displayOptionsInline = displayOptionsInline;
+ this.showsSelectedOptions = showsSelectedOptions;
+ this.isHiddenFromMenu = isHiddenFromMenu;
+ }
+}
+/** @public */
+export class PageFacetsGroup extends models.Model {
+ constructor(facets = [], title = null) {
+ super();
+ this.title = title;
+ this.facets = facets;
+ }
+}
+/** @public */
+export class PageFacets extends models.Model {
+ constructor(facetGroups, allowsResetButton, resetButtonTitle) {
+ super();
+ this.facetGroups = facetGroups;
+ this.allowsResetButton = allowsResetButton;
+ this.resetButtonTitle = resetButtonTitle;
+ }
+ static isDefinedNonNullNonEmpty(object) {
+ return isSome(object) && Object.keys(object).length !== 0;
+ }
+}
+/**
+ * Facet out nil values / join all values together returning the string used for a query parameter value
+ * returns null if resulting string would be empty or selectedOptions is null
+ * @param selectedOptions
+ */
+PageFacets.requestValuesForSelectedFacetOptions = function (selectedOptions) {
+ if (PageFacets.isDefinedNonNullNonEmpty(selectedOptions)) {
+ const facetOptionValue = selectedOptions
+ .filter((option) => {
+ return PageFacets.isDefinedNonNullNonEmpty(option.value);
+ })
+ .map((option) => {
+ return option.value;
+ })
+ .join(",");
+ const allFacetOptionValues = facetOptionValue.split("&");
+ const primaryValue = allFacetOptionValues[0];
+ const additionalQueryValues = allFacetOptionValues.splice(1);
+ const additionalKeyValuePairs = {};
+ for (const additionalQueryValue of additionalQueryValues) {
+ const keyValue = additionalQueryValue.split("=");
+ if (keyValue.length !== 2) {
+ continue;
+ }
+ additionalKeyValuePairs[keyValue[0]] = keyValue[1];
+ }
+ return {
+ value: primaryValue,
+ additionalKeyValuePairs: additionalKeyValuePairs,
+ };
+ }
+ else {
+ return null;
+ }
+};
+//# sourceMappingURL=page-facets.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/page-refresh-policy.js b/node_modules/@jet-app/app-store/tmp/src/api/models/page-refresh-policy.js
new file mode 100644
index 0000000..96a0732
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/page-refresh-policy.js
@@ -0,0 +1,17 @@
+/**
+ * Model for Page Refresh Policy
+ */
+import { Model } from "./base";
+/** @public */
+export class PageRefreshPolicy extends Model {
+ constructor(strategy, updateDelayInterval, timeSinceOnScreenInterval, timeToLiveEndDate, refreshWhileVisible = false, refreshForDeviceDrivenContentChange = false) {
+ super();
+ this.strategy = strategy;
+ this.updateDelayInterval = updateDelayInterval;
+ this.timeSinceOnScreenInterval = timeSinceOnScreenInterval;
+ this.timeToLiveEndDate = timeToLiveEndDate;
+ this.refreshWhileVisible = refreshWhileVisible;
+ this.refreshForDeviceDrivenContentChange = refreshForDeviceDrivenContentChange;
+ }
+}
+//# sourceMappingURL=page-refresh-policy.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/preview-platform.js b/node_modules/@jet-app/app-store/tmp/src/api/models/preview-platform.js
new file mode 100644
index 0000000..387e689
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/preview-platform.js
@@ -0,0 +1,25 @@
+import { isNothing } from "@jet/environment/types/optional";
+/**
+ * All {@linkcode PreviewPlatform}, defined in their order of precidence
+ */
+export const allPreviewPlatforms = ["iphone", "ipad", "mac", "vision", "watch", "tv"];
+/**
+ * Determines if {@linkcode input} is a {@linkcode PreviewPlatform}
+ */
+export function isPreviewPlatform(input) {
+ return allPreviewPlatforms.includes(input);
+}
+/**
+ * Normalize some {@linkcode input} into a {@linkcode PreviewPlatform}, if possible
+ */
+export function normalizePreviewPlaform(input) {
+ if (isNothing(input)) {
+ return undefined;
+ }
+ const normalized = input.toLocaleLowerCase();
+ if (isPreviewPlatform(normalized)) {
+ return normalized;
+ }
+ return undefined;
+}
+//# sourceMappingURL=preview-platform.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/privacy.js b/node_modules/@jet-app/app-store/tmp/src/api/models/privacy.js
new file mode 100644
index 0000000..f866cc5
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/privacy.js
@@ -0,0 +1,75 @@
+import * as models from "./index";
+/** @public */
+export class PrivacyHeader extends models.Model {
+ constructor(bodyText, isDetailHeader, privacyTypes, bodyActions, supplementaryItems, seeDetailsAction) {
+ super();
+ this.bodyText = bodyText;
+ this.isDetailHeader = isDetailHeader;
+ this.privacyTypes = privacyTypes;
+ this.bodyActions = bodyActions;
+ this.supplementaryItems = supplementaryItems;
+ this.seeDetailsAction = seeDetailsAction;
+ }
+}
+/** @public */
+export class PrivacyHeaderSupplementaryItem extends models.Model {
+ constructor(bodyText, action) {
+ super();
+ this.bodyText = bodyText;
+ this.action = action;
+ }
+}
+/** @public */
+export class PrivacyFooter extends models.Model {
+ constructor(bodyText, actions, privacyTypesCount) {
+ super();
+ this.bodyText = bodyText;
+ this.actions = actions;
+ this.privacyTypesCount = privacyTypesCount;
+ }
+}
+/** @public */
+export class PrivacyType extends models.ViewModel {
+ constructor(identifier, title, detail, artwork, style, purposes, categories, clickAction) {
+ super();
+ this.identifier = identifier;
+ this.title = title;
+ this.detail = detail;
+ this.artwork = artwork;
+ this.style = style;
+ this.purposes = purposes;
+ this.categories = categories;
+ this.clickAction = clickAction;
+ this.wantsScrollFocus = false;
+ }
+}
+/** @public */
+export class PrivacyPurpose extends models.Model {
+ constructor(identifier, title, categories) {
+ super();
+ this.identifier = identifier;
+ this.title = title;
+ this.categories = categories;
+ }
+}
+/** @public */
+export class PrivacyCategory extends models.Model {
+ constructor(identifier, title, artwork, style, dataTypes = []) {
+ super();
+ this.identifier = identifier;
+ this.title = title;
+ this.artwork = artwork;
+ this.style = style;
+ this.dataTypes = dataTypes;
+ this.prefersSmallArtwork = false;
+ }
+}
+/** @public */
+export class PrivacyDefinition extends models.Model {
+ constructor(title, definition) {
+ super();
+ this.title = title;
+ this.definition = definition;
+ }
+}
+//# sourceMappingURL=privacy.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/product-media-gallery-page.js b/node_modules/@jet-app/app-store/tmp/src/api/models/product-media-gallery-page.js
new file mode 100644
index 0000000..a736532
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/product-media-gallery-page.js
@@ -0,0 +1,12 @@
+import * as base from "./base";
+import * as metrics from "./metrics/metrics";
+/** @public */
+export class ProductMediaGalleryPage extends base.Model {
+ constructor(productMedia) {
+ super();
+ this.productMedia = productMedia;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+//# sourceMappingURL=product-media-gallery-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/product-page-shelf-ids.js b/node_modules/@jet-app/app-store/tmp/src/api/models/product-page-shelf-ids.js
new file mode 100644
index 0000000..63a4cbf
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/product-page-shelf-ids.js
@@ -0,0 +1,45 @@
+/**
+ * Created by jellenbogen 4/12/21.
+ */
+// This is the set of ProductPageShelfIds that were previously containerized, this is needed
+// for backwards compatibility converting shelfBased to non-shelfBased
+export const legacyProductPageNonShelfSections = new Set([
+ "header",
+ "topLockup",
+ "screenshots",
+]);
+// This is the list of productPageShelfIds that are able to be used on non-shelfBasaed product
+// pages
+export const legacyProductPageKnownShelfIds = new Set([
+ "accessibilityDeveloperLink",
+ "accessibilityFeatures",
+ "accessibilityHeader",
+ "achievements",
+ "actionLinks",
+ "appEvents",
+ "bundleChildren",
+ "bundleParents",
+ "capabilities",
+ "description",
+ "editorialQuote",
+ "featuredIn",
+ "friendsPlaying",
+ "inAppPurchases",
+ "information",
+ "informationRibbon",
+ "notPurchasedLinks",
+ "purchasedLinks",
+ "moreByDeveloper",
+ "mostRecentVersion",
+ "preorderDisclaimer",
+ "privacyFooter",
+ "privacyHeader",
+ "privacyTypes",
+ "reviews",
+ "similarItems",
+ "subscriptions",
+ "textCards",
+ "textLinksShelf",
+ "videos",
+]);
+//# sourceMappingURL=product-page-shelf-ids.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/product.js b/node_modules/@jet-app/app-store/tmp/src/api/models/product.js
new file mode 100644
index 0000000..a5764f3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/product.js
@@ -0,0 +1,58 @@
+/**
+ * Created by km on 2/13/17.
+ */
+import * as base from "./base";
+import * as metrics from "./metrics/metrics";
+import * as shelves from "./shelves";
+/** @public */
+export class Badge extends base.Model {
+ constructor(type, key, content, style, heading, caption, longCaption, accessibilityTitle, accessibilityCaption, leadingValue = "infer") {
+ super();
+ this.type = type;
+ this.key = key;
+ this.content = content;
+ this.heading = heading;
+ this.caption = caption;
+ this.longCaption = longCaption;
+ this.leadingValue = leadingValue;
+ this.accessibilityTitle = accessibilityTitle;
+ this.accessibilityCaption = accessibilityCaption;
+ this.style = style;
+ this.isMonochrome = true;
+ }
+}
+/** @public */
+export class TextCard extends base.Model {
+ constructor(headingType, title, subtitle) {
+ super();
+ this.titleStyle = headingType;
+ this.title = title;
+ this.subtitle = subtitle;
+ this.isExpanded = false;
+ }
+}
+/** @public */
+export class ProductPageSection extends base.Model {
+ constructor(type, shelfId) {
+ super();
+ this.type = type;
+ this.shelfId = shelfId;
+ }
+}
+/** @public */
+export class ProductPageExpandedOfferDetails extends base.Model {
+ constructor(title, subtitle) {
+ super();
+ this.title = title;
+ this.subtitle = subtitle;
+ }
+}
+/** @public */
+export class ProductPage extends shelves.Lockup {
+ constructor() {
+ super();
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+//# sourceMappingURL=product.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/reviews.js b/node_modules/@jet-app/app-store/tmp/src/api/models/reviews.js
new file mode 100644
index 0000000..e71a139
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/reviews.js
@@ -0,0 +1,115 @@
+/**
+ * Created by phare on 3/28/17.
+ */
+import * as base from "./base";
+import * as page from "./generic-page";
+/* tslint:disable:no-unsafe-any */
+/** @public */
+export class ReviewsPageSortOption extends base.Model {
+ constructor(id, title, selectedActionTitle, url) {
+ super();
+ this.id = id;
+ this.title = title;
+ this.selectedActionTitle = selectedActionTitle;
+ this.url = url;
+ }
+}
+/** @public */
+export class ReviewsPage extends page.GenericPage {
+ constructor() {
+ super([]);
+ }
+}
+/** @public */
+export class Ratings extends base.Model {
+}
+/** @public */
+export class Review extends base.ViewModel {
+}
+/** @public */
+export class Response extends base.Model {
+}
+/** @public */
+export class TapToRate extends base.Model {
+ constructor() {
+ super(...arguments);
+ this.componentType = "tapToRate";
+ }
+}
+/** @public */
+export class ProductReviewActions extends base.Model {
+ constructor() {
+ super(...arguments);
+ this.componentType = "productReviewActions";
+ }
+}
+/** @public */
+export class EditorsChoice extends base.Model {
+ constructor(notes) {
+ super();
+ this.showsBadge = false;
+ this.title = null;
+ this.notes = notes;
+ this.isCollapsed = true;
+ }
+}
+/** @public */
+export class ReviewsContainer extends base.Model {
+}
+/** @public */
+export class ProductReviewAction extends base.Model {
+}
+/** @public */
+export class ProductWriteAReview extends base.Model {
+}
+/** @public */
+export class ProductStarRatings extends Ratings {
+ constructor() {
+ super(...arguments);
+ this.componentType = "starRatings";
+ }
+}
+/** @public */
+export class ProductStarRatingsHistogram extends Ratings {
+ constructor() {
+ super(...arguments);
+ this.componentType = "starRatingsHistogram";
+ }
+}
+/** @public */
+export class ProductNoRatings extends Ratings {
+ constructor() {
+ super(...arguments);
+ this.componentType = "noRatings";
+ }
+}
+/** @public */
+export class ProductRatingsAndReviewsMessage extends base.Model {
+ constructor(messageText) {
+ super();
+ this.componentType = "message";
+ this.messageText = messageText;
+ }
+}
+/** @public */
+export class ReviewSummary extends base.ViewModel {
+ constructor(body, bodyNoTitle, subtitle, subtitleArtwork, subtitleArtworkAlignment, bodyMediaType, flowPreviewActionsConfiguration) {
+ super();
+ this.body = body;
+ this.bodyNoTitle = bodyNoTitle;
+ this.subtitle = subtitle;
+ this.subtitleArtwork = subtitleArtwork;
+ this.subtitleArtworkAlignment = subtitleArtworkAlignment;
+ this.bodyMediaType = bodyMediaType;
+ this.flowPreviewActionsConfiguration = flowPreviewActionsConfiguration;
+ }
+}
+/** @public */
+export class ProductReview extends base.ViewModel {
+ constructor() {
+ super(...arguments);
+ this.componentType = "productReview";
+ }
+}
+/* tslint:enable:no-unsafe-any */
+//# sourceMappingURL=reviews.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/search/guided-search-actions.js b/node_modules/@jet-app/app-store/tmp/src/api/models/search/guided-search-actions.js
new file mode 100644
index 0000000..047f60f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/search/guided-search-actions.js
@@ -0,0 +1,31 @@
+/**
+ * Actions for Guided Search Feature
+ */
+import * as actions from "../actions";
+/**
+ * @public
+ * An action that toggles a specified guided search token on and off.
+ *
+ * @note
+ * The same instance of this object must handle both selection and deselection
+ * as it can fire multiple times without JS refresh
+ */
+export class GuidedSearchTokenToggleAction extends actions.Action {
+ constructor(targetToken, searchOrigin) {
+ super("GuidedSearchTokenToggleAction");
+ this.targetToken = targetToken;
+ this.searchOrigin = searchOrigin;
+ }
+}
+/**
+ * @public
+ * An action that changes the selected search entity (replacing existing, if any)
+ */
+export class SearchEntityChangeAction extends actions.Action {
+ constructor(entity, searchOrigin) {
+ super("SearchEntityChangeAction");
+ this.entity = entity;
+ this.searchOrigin = searchOrigin;
+ }
+}
+//# sourceMappingURL=guided-search-actions.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/search/guided-search.js b/node_modules/@jet-app/app-store/tmp/src/api/models/search/guided-search.js
new file mode 100644
index 0000000..cda8c16
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/search/guided-search.js
@@ -0,0 +1,48 @@
+import * as models from "../base";
+/**
+ * @public
+ * Returns the system image for the given search entity.
+ */
+export function searchEntitySystemImage(entity) {
+ switch (entity) {
+ case "developer":
+ return "person.crop.square";
+ case "story":
+ return "appstore";
+ case "watch":
+ return "applewatch";
+ case "arcade":
+ return "joystickcontroller.fill";
+ default:
+ return undefined;
+ }
+}
+/**
+ * @public
+ * Model representing selectable displayed token for Guided Search
+ */
+export class GuidedSearchToken extends models.ViewModel {
+ constructor(value, isSelected, leadingIcon, displayName, clickAction) {
+ super();
+ this.value = value;
+ this.isSelected = isSelected;
+ this.leadingIcon = leadingIcon;
+ this.displayName = displayName;
+ this.clickAction = clickAction;
+ }
+}
+/**
+ * @public
+ * Object storing mapping of:
+ * Search Term + [Set of Guided Search Tokens] = Optimization Term.
+ * I.e. how the search term and token swill be comined for final query term search results are searched for.
+ */
+export class GuidedSearchQuery extends models.Model {
+ constructor(searchTerm, selectedTokens, optimizationTerm) {
+ super();
+ this.searchTerm = searchTerm;
+ this.selectedTokens = selectedTokens;
+ this.optimizationTerm = optimizationTerm;
+ }
+}
+//# sourceMappingURL=guided-search.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/search/search-categories.js b/node_modules/@jet-app/app-store/tmp/src/api/models/search/search-categories.js
new file mode 100644
index 0000000..1d0b588
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/search/search-categories.js
@@ -0,0 +1,96 @@
+import * as dynamicGenericPage from "../dynamic-generic-page";
+import * as models from "../base";
+export class SearchChartsAndCategoriesPage extends dynamicGenericPage.DynamicGenericPage {
+}
+/**
+ * @public
+ * Model representing a piece of UI for a chart or category on the search landing page.
+ */
+export class SearchChartOrCategory extends models.ViewModel {
+ constructor(title, artwork, collectionIcons, backgroundColor, badge, action, density, artworkSafeArea, textSafeArea) {
+ super();
+ this.title = title;
+ this.artwork = artwork;
+ this.collectionIcons = collectionIcons;
+ this.backgroundColor = backgroundColor;
+ this.badge = badge;
+ this.action = action;
+ this.density = density;
+ this.artworkSafeArea = artworkSafeArea;
+ this.textSafeArea = textSafeArea;
+ }
+}
+/**
+ * @public
+ * Model representing a piece of UI for a medium ad lockup with custom creative artwork on the search landing page.
+ */
+export class MediumAdLockupWithAlignedRegionBackground extends models.ViewModel {
+ constructor(lockup, alignedRegionArtwork) {
+ super();
+ this.lockup = lockup;
+ this.alignedRegionArtwork = alignedRegionArtwork;
+ }
+}
+/**
+ * @public
+ * Model representing a piece of UI for a medium ad lockup with screenshots on the search landing page.
+ */
+export class MediumAdLockupWithScreenshotsBackground extends models.ViewModel {
+ constructor(lockup, screenshots, isAnimated, secondaryTextColor, backgroundColor, riverSpeed) {
+ super();
+ this.lockup = lockup;
+ this.screenshots = screenshots;
+ this.isAnimated = isAnimated;
+ this.secondaryTextColor = secondaryTextColor;
+ this.backgroundColor = backgroundColor;
+ this.riverSpeed = riverSpeed;
+ }
+}
+/**
+ * @public
+ * Model representing a piece of UI for a condensed ad lockup on the search landing page.
+ */
+export class CondensedAdLockupWithIconBackground extends models.ViewModel {
+ constructor(lockup, backgroundArtwork) {
+ super();
+ this.lockup = lockup;
+ this.backgroundArtwork = backgroundArtwork;
+ }
+}
+export class SearchShelfAttributes {
+ constructor(id, title, displayStyle, displayCount, hasSeeAll, seeAllLink, searchLandingItemDisplayStyle = null, searchShelfKind) {
+ this.id = id;
+ this.title = title;
+ this.searchLandingItemDisplayStyle = searchLandingItemDisplayStyle;
+ this.displayStyle = displayStyle;
+ this.displayCount = displayCount;
+ this.hasSeeAll = hasSeeAll;
+ this.seeAllLink = seeAllLink;
+ this.searchShelfKind = searchShelfKind;
+ }
+}
+export var SearchLandingPageContentKind;
+(function (SearchLandingPageContentKind) {
+ SearchLandingPageContentKind["Suggestion"] = "Query";
+ SearchLandingPageContentKind["CategoriesAndCharts"] = "EditorialLink";
+ SearchLandingPageContentKind["Apps"] = "AppsLockup";
+ SearchLandingPageContentKind["EditorialCollection"] = "EditorialCollection";
+})(SearchLandingPageContentKind || (SearchLandingPageContentKind = {}));
+export var SearchPageKind;
+(function (SearchPageKind) {
+ SearchPageKind[SearchPageKind["Default"] = 0] = "Default";
+ SearchPageKind[SearchPageKind["CategoriesAndCharts"] = 1] = "CategoriesAndCharts";
+})(SearchPageKind || (SearchPageKind = {}));
+export var GenericSearchPageShelfDisplayStyleDensity;
+(function (GenericSearchPageShelfDisplayStyleDensity) {
+ GenericSearchPageShelfDisplayStyleDensity["Density1"] = "density_1";
+ GenericSearchPageShelfDisplayStyleDensity["Density2"] = "density_2";
+ GenericSearchPageShelfDisplayStyleDensity["Density3"] = "density_3";
+})(GenericSearchPageShelfDisplayStyleDensity || (GenericSearchPageShelfDisplayStyleDensity = {}));
+export var SearchLandingPageShelfItemIconKind;
+(function (SearchLandingPageShelfItemIconKind) {
+ SearchLandingPageShelfItemIconKind["Chiclet"] = "chiclet";
+ SearchLandingPageShelfItemIconKind["App"] = "app";
+ SearchLandingPageShelfItemIconKind["Symbol"] = "symbol";
+})(SearchLandingPageShelfItemIconKind || (SearchLandingPageShelfItemIconKind = {}));
+//# sourceMappingURL=search-categories.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/search/search.js b/node_modules/@jet-app/app-store/tmp/src/api/models/search/search.js
new file mode 100644
index 0000000..4876f03
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/search/search.js
@@ -0,0 +1,283 @@
+/**
+ * Created by km on 2/13/17.
+ */
+import * as actions from "../actions";
+import * as base from "../base";
+import * as metrics from "../metrics/metrics";
+/** @public */
+export class BaseSearchPage extends base.Model {
+ constructor() {
+ super();
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+/** @public */
+export class SearchResults extends BaseSearchPage {
+}
+/**
+ * @public
+ * Message to show user when search results need additional context.
+ * Currently supported by:
+ * - iOS, for NLS Safety UI
+ */
+export class SearchResultsContextCard extends base.ViewModel {
+ constructor(message, action) {
+ super();
+ this.message = message;
+ this.action = action;
+ }
+}
+/** @public */
+export class SearchResultsPage extends BaseSearchPage {
+ constructor(shelfModels = []) {
+ super();
+ /// The shelves of the results page.
+ this.shelves = [];
+ this.shelves = shelfModels;
+ }
+}
+export class SegmentedSearchResultsPage {
+ constructor() {
+ this.selectedSegmentId = null;
+ this.segments = [];
+ }
+}
+export var SegmentedSearchResultsPageSegmentType;
+(function (SegmentedSearchResultsPageSegmentType) {
+ SegmentedSearchResultsPageSegmentType["visionOS"] = "xros";
+ SegmentedSearchResultsPageSegmentType["iOS"] = "ios";
+})(SegmentedSearchResultsPageSegmentType || (SegmentedSearchResultsPageSegmentType = {}));
+export class SegmentedSearchResultsPageSegment {
+}
+/** @public */
+export class SearchFacetSet extends base.Model {
+ constructor(type, values) {
+ super();
+ this.type = type;
+ this.values = values;
+ }
+}
+/** @public */
+export class SearchFacetValue extends base.Model {
+ constructor(name, value, selectedValue = null) {
+ super();
+ this.name = name;
+ this.value = value;
+ this.isSelected = value === selectedValue;
+ }
+}
+/** @public */
+export class SearchAdOpportunity extends base.Model {
+ constructor(instanceId, searchLifecycleEventPayloads, searchAd) {
+ super();
+ this.instanceId = instanceId;
+ this.eventPayloads = searchLifecycleEventPayloads;
+ this.searchAd = searchAd;
+ }
+ /**
+ * Sets the template type on the event payloads for metrics tracking
+ * @param templateType The native ad template type
+ */
+ setTemplateType(templateType) {
+ this.eventPayloads.placed.iAdTemplateType = templateType;
+ }
+ /**
+ * Sets the duplicate position on the event payloads for metrics tracking. This is only expected for search results
+ * @param position The position of the duplicate organic app lockup that appeared in the shelf's contents
+ */
+ setDuplicatePosition(position) {
+ this.eventPayloads.placed.duplicatePosition = position;
+ }
+ /**
+ * Sets the missed reason code on the event payloads for metrics tracking
+ * @param reasonCode The reason that the opportunity was not filled
+ */
+ setMissedOpportunityReason(reasonCode) {
+ this.eventPayloads.placed.missedOpportunityReason = reasonCode;
+ }
+}
+/** @public */
+export class SearchAd extends base.Model {
+ constructor(instanceId, iAdObject, searchAdLifecycleEventPayloads, impressionId, transparencyAction) {
+ super();
+ this.instanceId = instanceId;
+ this.iAd = iAdObject;
+ this.eventPayloads = searchAdLifecycleEventPayloads;
+ this.impressionId = impressionId;
+ this.transparencyAction = transparencyAction;
+ }
+ /**
+ * Sets the template type on the event payloads for metrics tracking
+ * @param templateType The native ad template type
+ */
+ setTemplateType(templateType) {
+ this.eventPayloads.placed.iAdTemplateType = templateType;
+ }
+ /**
+ * Sets the duplicate position on the event payloads for metrics tracking. This is only expected for search results
+ * @param position The position of the duplicate organic app lockup that appeared in the shelf's contents
+ */
+ setDuplicatePosition(position) {
+ this.eventPayloads.placed.duplicatePosition = position;
+ }
+}
+/** @public */
+export class AdTransparencyAction extends actions.Action {
+ constructor(adTransparencyData) {
+ super("AdTransparencyAction");
+ this.adTransparencyData = adTransparencyData;
+ }
+}
+/** @public */
+export class SearchAction extends actions.Action {
+ constructor(title, term, url, origin, entity, source, presentationStyle, referrerData) {
+ super("SearchAction");
+ /**
+ * Whether or not Search should opt-in to spell-checking, which will:
+ * - Correct high-confidence misspellings server-side.
+ * - Suggest queries for low-confidence misspellings
+ *
+ * This defaults to `false`. Currently `true` for user-typed term and hints terms.
+ */
+ this.spellCheckEnabled = false;
+ this.title = title;
+ this.term = term;
+ this.url = url;
+ this.origin = origin;
+ this.entity = entity;
+ this.source = source;
+ this.presentationStyle = presentationStyle !== null && presentationStyle !== void 0 ? presentationStyle : ["textFollowsTintColor"];
+ this.referrerData = referrerData;
+ }
+}
+// endregion
+// region Search Hints
+/** @public */
+export class SearchHintSet extends base.Model {
+ constructor(hints, ghostHintTerm) {
+ super();
+ this.hints = hints;
+ this.ghostHintTerm = ghostHintTerm;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+// endregion
+// region Trending Searches
+/** @public */
+export class TrendingSearches extends base.Model {
+ constructor(title, searches) {
+ super();
+ this.title = title;
+ this.searches = searches;
+ }
+}
+// endregion
+// region Search Message
+/**
+ * @public
+ * Message associated with a set of Search Results.
+ * Currently supported by:
+ * - iOS, for Spell Correction feature.
+ */
+export class SearchResultsMessage extends base.Model {
+ constructor(primaryText, secondaryText, messageClickAction = null) {
+ super();
+ this.primaryText = primaryText;
+ this.secondaryText = secondaryText;
+ this.messageClickAction = messageClickAction;
+ }
+}
+/** @public */
+export class SearchResult extends base.ViewModel {
+ constructor(resultType) {
+ super();
+ this.resultType = resultType;
+ this.clickAction = null;
+ }
+}
+/**
+ * @public
+ * Search result for a single app
+ */
+export class AppSearchResult extends SearchResult {
+ constructor(lockup) {
+ super("content");
+ this.lockup = lockup;
+ }
+}
+/** @public */
+export class InAppPurchaseSearchResult extends SearchResult {
+ constructor(lockup) {
+ super("inAppPurchase");
+ this.lockup = lockup;
+ }
+}
+/** @public */
+export class BundleSearchResult extends SearchResult {
+ constructor(lockup) {
+ super("bundle");
+ this.lockup = lockup;
+ }
+}
+/** @public */
+export class AdvertsSearchResult extends SearchResult {
+ constructor() {
+ super("advert");
+ this.lockups = [];
+ this.displaysScreenshots = true;
+ this.itemBackground = "ad";
+ }
+}
+/** @public */
+export class AppEventSearchResult extends SearchResult {
+ constructor() {
+ super("appEvent");
+ }
+}
+/** @public */
+export class EditorialSearchResult extends SearchResult {
+ constructor(title) {
+ super("editorial");
+ this.title = title;
+ }
+}
+/**
+ * @public
+ * Collection of lockups with shared heading and title.
+ * May include an action to show additional details (e.g. FlowAction to Article)
+ */
+export class SearchLockupCollection extends SearchResult {
+ constructor(heading, title, items, detailAction, headingArtwork) {
+ super("lockupCollection");
+ this.heading = heading;
+ this.headingArtwork = headingArtwork;
+ this.title = title;
+ this.items = items;
+ this.detailAction = detailAction;
+ }
+}
+/**
+ * @public
+ * Guided search suggestions shown mid-scroll alongside other search results.
+ */
+export class GuidedSearchResult extends SearchResult {
+ constructor(title, tokens) {
+ super("guidedSearch");
+ this.title = title;
+ this.tokens = tokens;
+ }
+}
+/**
+ * @public
+ * A model for the search results learn more notice.
+ */
+export class SearchResultsLearnMoreNotice extends base.ViewModel {
+ constructor(linkableText) {
+ super();
+ this.linkableText = linkableText;
+ }
+}
+// endregion
+//# sourceMappingURL=search.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/shelf-based-product.js b/node_modules/@jet-app/app-store/tmp/src/api/models/shelf-based-product.js
new file mode 100644
index 0000000..7291687
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/shelf-based-product.js
@@ -0,0 +1,25 @@
+/**
+ * Created by km on 2/13/17.
+ */
+import * as dynamicGenericPage from "./dynamic-generic-page";
+export const shelfBasedProductPageOrderingIdPurcahsed = "purchasedOrdering";
+export const shelfBasedProductPageOrderingIdNotPurcahsed = "notPurchasedOrdering";
+export const shelfBasedProductPageOrderingIdPurcahsedExpandedMedia = "purchasedOrdering_ExpandedMedia";
+export const shelfBasedProductPageOrderingIdNotPurcahsedExpandedMedia = "notPurchasedOrdering_ExpandedMedia";
+export const shelfBasedProductPageOrderingIdPurcahsedCompact = "purchasedOrdering_Compact";
+export const shelfBasedProductPageOrderingIdNotPurcahsedCompact = "notPurchasedOrdering_Compact";
+export const shelfBasedProductPageOrderingIdPurcahsedExpandedMediaCompact = "purchasedOrdering_ExpandedMedia_Compact";
+export const shelfBasedProductPageOrderingIdNotPurcahsedExpandedMediaCompact = "notPurchasedOrdering_ExpandedMedia_Compact";
+export const shelfBasedProductPageOrderingIdDownloading = "downloadingOrdering";
+export const shelfBasedProductPageOrderingIdDownloadingExpandedMedia = "downloadingOrdering_ExpandedMedia";
+export const shelfBasedProductPageOrderingIdDownloadingCompact = "downloadingOrdering_Compact";
+export const shelfBasedProductPageOrderingIdDownloadingExpandedMediaCompact = "downloadingOrdering_ExpandedMedia_Compact";
+/** @public */
+export class ShelfBasedProductPage extends dynamicGenericPage.DynamicGenericPage {
+ constructor() {
+ super(...arguments);
+ // Reviews
+ this.appPlatforms = [];
+ }
+}
+//# sourceMappingURL=shelf-based-product.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/shelves.js b/node_modules/@jet-app/app-store/tmp/src/api/models/shelves.js
new file mode 100644
index 0000000..5a68c4c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/shelves.js
@@ -0,0 +1,709 @@
+/**
+ * Created by km on 2/13/17.
+ */
+import { isSome } from "@jet/environment/types/optional";
+import * as base from "./base";
+/** @public */
+export class InformationContainer extends base.Model {
+ constructor(annotationGroups) {
+ super();
+ this.annotationGroups = annotationGroups;
+ }
+}
+/** @public */
+export class ImpressionableArtwork extends base.ViewModel {
+ constructor(art) {
+ super();
+ this.art = art;
+ }
+}
+/** @public */
+export class AppShowcase extends base.Model {
+ constructor(type, lockup) {
+ super();
+ this.type = type;
+ this.lockup = lockup;
+ }
+}
+/** @public */
+export class Banner extends base.Model {
+ constructor(message, focusedMessage, action, fullProductAction, leadingArtwork, leadingArtworkTintColor, includeBackgroundBorder, kind, hideCriteria) {
+ super();
+ this.message = message;
+ this.focusedMessage = focusedMessage;
+ this.action = action;
+ this.fullProductAction = fullProductAction;
+ this.leadingArtwork = leadingArtwork;
+ this.leadingArtworkTintColor = leadingArtworkTintColor;
+ this.includeBackgroundBorder = includeBackgroundBorder;
+ this.kind = kind;
+ this.hideCriteria = hideCriteria;
+ }
+}
+/**
+ * @public
+ * Defines which banner to show for various app states so the client can update dynamically.
+ */
+export class AppStateBanner extends base.Model {
+ constructor(unknownBanner, buyBanner, downloadBanner, updateBanner, openBanner) {
+ super();
+ this.unknownBanner = unknownBanner;
+ this.buyBanner = buyBanner;
+ this.downloadBanner = downloadBanner;
+ this.updateBanner = updateBanner;
+ this.openBanner = openBanner;
+ }
+}
+/** @public */
+export class InAppPurchaseShowcase extends base.Model {
+ constructor(lockup) {
+ super();
+ this.lockup = lockup;
+ }
+}
+// endregion
+/** @public */
+export class ProductMediaItem extends base.Model {
+}
+/** @public */
+export class ProductMedia extends base.Model {
+ constructor(items, mediaPlatform, allPlatforms, platformDescription, allPlatformsDescription, allPlatformsDescriptionPlacement) {
+ super();
+ this.items = items;
+ this.mediaPlatform = mediaPlatform;
+ this.allPlatforms = allPlatforms;
+ this.platformDescription = platformDescription;
+ this.allPlatformsDescription = allPlatformsDescription;
+ this.allPlatformsDescriptionPlacement = allPlatformsDescriptionPlacement;
+ }
+}
+/** @public */
+export class ProductCapability extends base.Model {
+ constructor(type, title, caption, captionTrailingArtwork, linkAction, artwork, artworkTintColor) {
+ super();
+ this.type = type;
+ this.title = title;
+ this.caption = caption;
+ this.captionTrailingArtwork = captionTrailingArtwork;
+ this.linkAction = linkAction;
+ this.artwork = artwork;
+ this.artworkTintColor = artworkTintColor;
+ }
+}
+/** @public */
+export class ProductDescription extends base.Model {
+ constructor(paragraph, developerLinks, tags, developerAction) {
+ super();
+ this.paragraph = paragraph;
+ this.developerAction = developerAction;
+ this.tags = tags;
+ this.developerLinks = developerLinks;
+ }
+}
+/** @public */
+export class Footnote extends base.Model {
+ constructor(text) {
+ super();
+ this.text = text;
+ this.presentationStyle = [];
+ this.clickAction = null;
+ }
+}
+/** @public */
+export class LockupContextMenuData extends base.Model {
+}
+/** @public */
+export class Lockup extends base.ViewModel {
+ isValid() {
+ if (!this.title) {
+ return false;
+ }
+ if (!this.icon || !this.icon.isValid()) {
+ return false;
+ }
+ return super.isValid();
+ }
+}
+/** @public */
+export class ScreenshotsLockup extends Lockup {
+ constructor() {
+ super();
+ this.screenshots = [];
+ /// Force a 4 screenshot portrait display style to ensure alignment with landscape screenshots
+ this.screenshotsDisplayStyle = "four-screenshots";
+ }
+ isValid() {
+ const hasScreenshots = this.screenshots && this.screenshots.length;
+ if (!hasScreenshots) {
+ return false;
+ }
+ return super.isValid();
+ }
+}
+/** @public */
+export class MixedMediaLockup extends Lockup {
+ constructor() {
+ super();
+ this.screenshots = [];
+ this.trailers = [];
+ this.overrideLockupPosition = null;
+ this.screenshotsDisplayStyle = "control";
+ this.metadataRibbonItems = [];
+ this.showMetadataInformationInLockup = false;
+ this.alignedRegionArtwork = null;
+ this.alignedRegionVideo = null;
+ }
+}
+export class UpdatesLockup extends Lockup {
+ constructor() {
+ super();
+ this.whatsNew = undefined;
+ this.version = undefined;
+ this.size = undefined;
+ this.externalVersionId = undefined;
+ this.releaseDate = undefined;
+ this.installDate = undefined;
+ }
+}
+/**
+ * @public
+ * The metadata ribbon item used to contain any content of a metadata ribbon view.
+ * Keys will be present based on which metadata ribbon item type is being presented.
+ * This is using specific keys rather than creating them on the fly because the key
+ * names get mangled if they're not defined.
+ */
+export class MetadataRibbonItem extends base.ViewModel {
+ constructor(viewType) {
+ super();
+ this.viewType = viewType;
+ this.moduleType = null;
+ this.labelText = null;
+ this.borderedText = null;
+ this.highlightedText = null;
+ this.starRating = null;
+ this.secondaryViewPlacement = "leading";
+ this.artwork = null;
+ this.maxCharacterCount = null;
+ this.truncationLegibilityCharacterCountThreshold = null;
+ this.allowsTruncation = null;
+ }
+}
+/** @public */
+export class TrailersLockup extends Lockup {
+ isValid() {
+ const hasTrailers = this.trailers && this.trailers.isValid();
+ if (!hasTrailers) {
+ return false;
+ }
+ if (!this.editorialTagline || this.editorialTagline.length === 0) {
+ return false;
+ }
+ return super.isValid();
+ }
+}
+/** @public */
+export class Trailers extends base.Model {
+ constructor(videos, mediaPlatform) {
+ super();
+ if (videos) {
+ this.videos = videos;
+ }
+ if (mediaPlatform) {
+ this.mediaPlatform = mediaPlatform;
+ }
+ }
+ isValid() {
+ return this.videos && this.videos.length > 0 && super.isValid();
+ }
+}
+/** @public */
+export class InAppPurchaseLockup extends Lockup {
+}
+/**
+ * @public
+ * This is a lockup that represents the Arcade *service* (in contrast to lockups for Arcade *apps*)
+ * It is used in:
+ * - Today Card Overlay (iOS)
+ * - Article Persistent Lockup (iOS, macOS)
+ * - Maybe more
+ *
+ * This object does *not* subclass `Lockup`, as we cannot populate the app-specific properties. Decision was made to use a separate codepath altogether for delivering this
+ * fake lockup to views we otherwise display standard `Lockup`, as opposed to trying to reconcile the use-cases at this time.
+ *
+ * This class deliberately maintains two button actions to fire for subscribed and unsubscribed state, respectively, instead of a single hypothetical `ArcadeStateAction`
+ * that switches behavior based on subscription state. This is since the subscription state can switch between model apply time (what is visually represented in UI),
+ * and when it is run in action runner.
+ */
+export class ArcadeLockup extends base.ViewModel {
+}
+export class ImageLockup extends base.ViewModel {
+ constructor(artwork, lockup, caption, title, isDark = false) {
+ super();
+ this.isDark = false;
+ this.artwork = artwork;
+ this.lockup = lockup;
+ this.caption = caption;
+ this.title = title;
+ this.isDark = isDark;
+ }
+ isValid() {
+ return this.lockup.isValid() && this.artwork.isValid();
+ }
+}
+/** @public */
+export class TitledParagraph extends base.Model {
+ constructor(text, style, mediaType) {
+ super();
+ this.text = text;
+ this.style = style;
+ this.mediaType = mediaType;
+ this.wantsCollapsedNewlines = true;
+ }
+}
+/** @public */
+export class EditorialCard extends base.ViewModel {
+ constructor() {
+ super();
+ this.adamId = null;
+ this.caption = null;
+ this.title = null;
+ this.subtitle = null;
+ this.artwork = null;
+ this.shelfBackground = null;
+ this.clickAction = null;
+ this.decorations = [];
+ this.flowPreviewActionsConfiguration = null;
+ this.appEventFormattedDates = null;
+ this.mediaOverlayStyle = null;
+ }
+ isValid() {
+ if (!this.clickAction) {
+ return false;
+ }
+ if (!this.artwork || !this.artwork.isValid()) {
+ return false;
+ }
+ if (!this.caption || !this.title) {
+ return false;
+ }
+ return super.isValid();
+ }
+}
+/**
+ * @public
+ * Used for:
+ * - Arcade Continue Playing.
+ */
+export class VideoCard extends base.ViewModel {
+ constructor() {
+ super();
+ this.flowPreviewActionsConfiguration = null;
+ }
+}
+/**
+ * @public
+ * Used within a `RibbonBar` which is a horizontal scrolling bar that contains multiple `RibbonBarItem`s.
+ * E.g. a `RibbonBarItem` that defines an Arcade category within an Arcade category ribbon bar.
+ *
+ * Each bar item should have a title and a click action alongside with an optional artwork.
+ * Empty artworks are potentially handled by a fallback icon by client side.
+ */
+export class RibbonBarItem extends base.ViewModel {
+ constructor(title, clickAction) {
+ super();
+ this.title = title;
+ this.clickAction = clickAction;
+ this.artwork = null;
+ this.accessibilityLabel = null;
+ }
+}
+/** @public */
+export class Brick extends base.ViewModel {
+ constructor() {
+ super();
+ this.artworks = null;
+ this.accessibilityLabel = null;
+ this.shortEditorialDescription = null;
+ this.clickAction = null;
+ this.personalizationStyle = "none";
+ this.shelfBackground = null;
+ this.flowPreviewActionsConfiguration = null;
+ this.editorialDisplayOptions = {};
+ this.artworkSafeArea = null;
+ this.textSafeArea = null;
+ }
+ isValid() {
+ var _a, _b, _c, _d;
+ const hasValidArtwork = (_b = (_a = this.artworks) === null || _a === void 0 ? void 0 : _a.every((artwork, index) => {
+ return isSome(artwork) && artwork.isValid();
+ })) !== null && _b !== void 0 ? _b : false;
+ const hasCollectionIcons = ((_d = (_c = this.collectionIcons) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0;
+ const hasBackgroundColor = isSome(this.backgroundColor);
+ return (isSome(this.clickAction) && (hasValidArtwork || hasCollectionIcons || hasBackgroundColor) && super.isValid());
+ }
+}
+/** @public */
+export class EditorialLink extends base.ViewModel {
+ constructor(descriptionText, summaryText, clickAction, linkPresentationEnabled = true) {
+ super();
+ this.descriptionText = descriptionText;
+ this.summaryText = summaryText;
+ this.clickAction = clickAction;
+ this.linkPresentationEnabled = linkPresentationEnabled;
+ }
+}
+/** @public */
+export class SearchLink extends base.ViewModel {
+ constructor(title, clickAction, artwork, imageName) {
+ super();
+ this.title = title;
+ this.clickAction = clickAction;
+ this.imageName = imageName;
+ this.artwork = artwork;
+ }
+}
+/** @public */
+export class LinkableText extends base.Model {
+ constructor(styledText, linkedSubstrings) {
+ super();
+ this.styledText = styledText;
+ this.linkedSubstrings = linkedSubstrings;
+ }
+}
+/** @public */
+export class ProductPageLink extends base.Model {
+ constructor(text, clickAction, systemImageName, adamIdForPurchaseHistoryFilter) {
+ super();
+ this.text = text;
+ this.clickAction = clickAction;
+ this.systemImageName = systemImageName;
+ this.adamIdForPurchaseHistoryFilter = adamIdForPurchaseHistoryFilter;
+ }
+}
+/** @public */
+export class PreorderDisclaimer extends base.Model {
+ constructor(disclaimer) {
+ super();
+ this.disclaimer = disclaimer;
+ }
+}
+/** @public */
+export class TitledButtonStack extends base.Model {
+ constructor(buttons) {
+ super();
+ this.buttons = buttons;
+ }
+}
+/** @public */
+export class TitledButton extends base.Model {
+ constructor(title, action) {
+ super();
+ this.title = title;
+ this.action = action;
+ }
+}
+/** @public */
+export class FramedArtwork extends base.ViewModel {
+ constructor(artwork, isFullWidth, captionMediaType = "text/plain", caption = null, ordinal = null, hasRoundedCorners = null) {
+ super();
+ this.artwork = artwork;
+ this.isFullWidth = isFullWidth;
+ this.hasRoundedCorners = typeof hasRoundedCorners === "boolean" ? hasRoundedCorners : !this.isFullWidth;
+ this.ordinal = ordinal;
+ this.caption = caption;
+ this.captionMediaType = captionMediaType;
+ }
+}
+/** @public */
+export class FramedVideo extends base.ViewModel {
+ constructor(video, isFullWidth, captionMediaType = "text/plain", caption = null, ordinal = null, hasRoundedCorners = null) {
+ super();
+ this.video = video;
+ this.isFullWidth = isFullWidth;
+ this.hasRoundedCorners = typeof hasRoundedCorners === "boolean" ? hasRoundedCorners : !this.isFullWidth;
+ this.ordinal = ordinal;
+ this.caption = caption;
+ this.captionMediaType = captionMediaType;
+ }
+}
+/** @public */
+export class RoundedButton extends base.Model {
+ constructor(type, title, hasDivider, action) {
+ super();
+ this.type = type;
+ this.title = title;
+ this.hasDivider = hasDivider;
+ this.action = action;
+ }
+}
+/** @public */
+export class Quote extends base.ViewModel {
+ constructor(text, credit, artwork, isFullWidthArtwork) {
+ super();
+ this.text = text;
+ this.credit = credit;
+ this.artwork = artwork;
+ this.isFullWidthArtwork = isFullWidthArtwork || false;
+ }
+}
+/** @public */
+export class EditorialQuote extends base.ViewModel {
+ constructor(text, attribution) {
+ super();
+ this.text = text;
+ this.attribution = attribution;
+ }
+}
+/** @public */
+export class HorizontalRule extends base.Model {
+ constructor(style, ruleColor, isFullWidth) {
+ super();
+ this.style = style;
+ this.color = ruleColor;
+ this.isFullWidth = isFullWidth;
+ }
+}
+/**
+ * @public
+ * A horizontal scrolling bar that contains multiple `RibbonBarItem`s.
+ *
+ * E.g. category ribbon bar displaying different Arcade categories in a horizontal scrolling bar.
+ */
+export class RibbonBar extends base.ViewModel {
+ constructor(items) {
+ super();
+ this.items = items;
+ }
+}
+/** @public */
+export class InformationRibbon extends base.ViewModel {
+ constructor(badges, hasTopSeparator, hasBottomSeparator, separatorsAreFullWidth, alignment) {
+ super();
+ this.badges = badges;
+ this.hasTopSeparator = hasTopSeparator;
+ this.hasBottomSeparator = hasBottomSeparator;
+ this.separatorsAreFullWidth = separatorsAreFullWidth;
+ this.alignment = alignment;
+ }
+}
+// region Client Control Button
+/**
+ * @public
+ * A client control button is an button appearing within Article pages.
+ * This is currently used so MAS editorial articles to link into OS Updates in preferences.
+ */
+export class ClientControlButton extends base.ViewModel {
+ constructor(title, buttonAction) {
+ super();
+ this.title = title;
+ this.buttonAction = buttonAction;
+ }
+}
+/** @public */
+export class BreakoutDetails extends base.Model {
+ constructor(title, description, badgeType, callToActionButtonAction, backgroundStyle, textAlignment) {
+ super();
+ this.badgeType = badgeType;
+ this.badge = badgeType.title;
+ this.title = title;
+ this.description = description;
+ this.callToActionButtonAction = callToActionButtonAction;
+ this.backgroundStyle = backgroundStyle;
+ this.textAlignment = textAlignment;
+ }
+}
+/** @public */
+export class LargeHeroBreakout extends base.ViewModel {
+ constructor(details, detailsDisplayProperties, heading, artwork, video, collectionIcons, backgroundColor) {
+ super();
+ this.details = details;
+ this.detailsDisplayProperties = detailsDisplayProperties;
+ this.heading = heading;
+ this.artwork = artwork;
+ this.video = video;
+ this.collectionIcons = collectionIcons;
+ this.backgroundColor = backgroundColor;
+ this.editorialDisplayOptions = {};
+ }
+}
+/** @public */
+export class SmallBreakout extends base.ViewModel {
+ constructor(details, iconArtwork, backgroundColor) {
+ super();
+ this.details = details;
+ this.iconArtwork = iconArtwork;
+ this.backgroundColor = backgroundColor;
+ }
+}
+/**
+ * The `Placeholder` is a dummy model used so a shelf with content type Placeholder can be populated with items representing the amount of placeholders to show
+ */
+export class Placeholder extends base.ViewModel {
+}
+// region ArcadeShowcase
+/**
+ * @public
+ * The `ArcadeShowcase` is a model that is similar to the upsell breakout on the grouping pages, but appears inline in article pages.
+ * This module is specialized for Arcade service, since we don't have other subscription services currently.
+ * On some platforms, it may display some icon artwork.
+ */
+export class ArcadeShowcase extends base.ViewModel {
+ /**
+ * Initialize arcade showcase with required values
+ * @param unsubscribedAction Action to run in non-subscribed state.
+ * @param subscribedAction Action to run in subscribed state.
+ */
+ constructor(unsubscribedAction, subscribedAction) {
+ super();
+ this.unsubscribedAction = unsubscribedAction;
+ this.subscribedAction = subscribedAction;
+ }
+}
+/** @public */
+export class GameCenterReengagement extends base.ViewModel {
+ constructor(badgeGlyph, badge, title, subtitle, achievement, lockup, backgroundColor, backgroundArtwork, heroAction) {
+ super();
+ this.badgeGlyph = badgeGlyph;
+ this.badge = badge;
+ this.title = title;
+ this.subtitle = subtitle;
+ this.achievement = achievement;
+ this.backgroundColor = backgroundColor;
+ this.backgroundArtwork = backgroundArtwork;
+ this.lockup = lockup;
+ this.heroAction = heroAction;
+ }
+}
+export class UnifiedMessage extends base.ViewModel {
+ constructor(placement, context, deliveryMethod) {
+ super();
+ this.placement = placement;
+ this.context = context;
+ this.deliveryMethod = deliveryMethod !== null && deliveryMethod !== void 0 ? deliveryMethod : "pushAndPull";
+ }
+}
+export const genericShelfBatchGroupBase = "shelfBatchGroup_";
+export var IncompleteShelfFetchStrategy;
+(function (IncompleteShelfFetchStrategy) {
+ /**
+ * Indicates the shelf should be fetched as soon as the page is loaded,
+ * this is the behavior we've always had, where all incomplete shelves are
+ * fetched after the page loads
+ */
+ IncompleteShelfFetchStrategy["OnPageLoad"] = "onPageLoad";
+ /**
+ * Indicates the shelf should be fetched when it is about to be scrolled into view
+ */
+ IncompleteShelfFetchStrategy["OnShelfWillAppear"] = "onShelfWillAppear";
+})(IncompleteShelfFetchStrategy || (IncompleteShelfFetchStrategy = {}));
+// Describes the snapping behaviour for horizontally scrolling shelves.
+export var ShelfHorizontalScrollTargetBehavior;
+(function (ShelfHorizontalScrollTargetBehavior) {
+ // The default behaviour, where scrolling will snap to the leading edge of the view.
+ ShelfHorizontalScrollTargetBehavior["ViewAligned"] = "viewAligned";
+ // Scrolling will snap so that views are center aligned in their container. Primarily
+ // used for large components that take up most of the container.
+ ShelfHorizontalScrollTargetBehavior["CenterAligned"] = "centerAligned";
+})(ShelfHorizontalScrollTargetBehavior || (ShelfHorizontalScrollTargetBehavior = {}));
+/** @public */
+export class Shelf extends base.ViewModel {
+ constructor(contentType, marker = null, items = null) {
+ super();
+ // - Item Properties
+ this.contentType = contentType;
+ this.marker = marker;
+ this.items = items || [];
+ this.header = undefined;
+ // - Incomplete Shelf Properties
+ this.url = null;
+ this.mergeWhenFetched = false;
+ this.fetchStrategy = IncompleteShelfFetchStrategy.OnPageLoad;
+ this.batchGroup = null;
+ // - Overflow Properties
+ this.seeAllAction = null;
+ // - Footer Properties
+ this.footerTitle = null;
+ this.footerAction = null;
+ // - Presentation Properties
+ this.eyebrow = null;
+ this.eyebrowArtwork = null;
+ this.footerStyle = null;
+ this.title = null;
+ this.titleArtwork = null;
+ this.subtitle = null;
+ this.isHorizontal = false;
+ this.isHidden = false;
+ this.rowsPerColumn = null;
+ this.background = { type: "none" };
+ this.contentsMetadata = { type: "none" };
+ // - Personalization & Filtering Properties
+ this.isPersonalized = false;
+ this.shouldFilterApps = false;
+ }
+}
+/** @public */
+export var ShelfBackgroundGradientLocation;
+(function (ShelfBackgroundGradientLocation) {
+ ShelfBackgroundGradientLocation["TopLeading"] = "topLeading";
+ ShelfBackgroundGradientLocation["Top"] = "top";
+ ShelfBackgroundGradientLocation["TopTrailing"] = "topTrailing";
+ ShelfBackgroundGradientLocation["Trailing"] = "trailing";
+ ShelfBackgroundGradientLocation["BottomTrailing"] = "bottomTrailing";
+ ShelfBackgroundGradientLocation["Bottom"] = "bottom";
+ ShelfBackgroundGradientLocation["BottomLeading"] = "bottomLeading";
+ ShelfBackgroundGradientLocation["Leading"] = "leading";
+})(ShelfBackgroundGradientLocation || (ShelfBackgroundGradientLocation = {}));
+export var GroupDisplayStyle;
+(function (GroupDisplayStyle) {
+ GroupDisplayStyle["Grid"] = "grid";
+ GroupDisplayStyle["Hero"] = "hero";
+ GroupDisplayStyle["Standard"] = "standard";
+})(GroupDisplayStyle || (GroupDisplayStyle = {}));
+/// The different artwork types we can display in the header
+export var ShelfHeaderArtworkType;
+(function (ShelfHeaderArtworkType) {
+ /// The image displayed is a content icon
+ ShelfHeaderArtworkType["Icon"] = "icon";
+ /// The image displayed is a category image
+ ShelfHeaderArtworkType["Category"] = "category";
+})(ShelfHeaderArtworkType || (ShelfHeaderArtworkType = {}));
+/** @public */
+export class EditorialStoryCard extends base.ViewModel {
+ constructor(title, artwork, video, heading, badge, description, clickAction) {
+ super();
+ this.title = title;
+ this.artwork = artwork;
+ this.video = video;
+ this.heading = heading;
+ this.badge = badge;
+ this.description = description;
+ this.clickAction = clickAction;
+ this.shelfBackground = null;
+ this.collectionIcons = null;
+ this.editorialDisplayOptions = {};
+ }
+}
+/** @public */
+export class PosterLockup extends Lockup {
+ isValid() {
+ const hasPosterArtwork = this.posterArtwork;
+ const hasPosterVideo = this.posterVideo;
+ // We require at least one type of poster media.
+ if (!(hasPosterArtwork || hasPosterVideo)) {
+ return false;
+ }
+ return super.isValid();
+ }
+}
+/** @public */
+export class PageTab extends base.Model {
+}
+/** @public */
+export class PageTabs extends base.Model {
+ constructor() {
+ super();
+ /// The id is not from a payload and should be unique to the container
+ this.id = random.nextUUID();
+ }
+}
+//# sourceMappingURL=shelves.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/today-page.js b/node_modules/@jet-app/app-store/tmp/src/api/models/today-page.js
new file mode 100644
index 0000000..58017fa
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/today-page.js
@@ -0,0 +1,300 @@
+import * as base from "./base";
+import * as metrics from "./metrics/metrics";
+export var AdPlacementBehavior;
+(function (AdPlacementBehavior) {
+ AdPlacementBehavior["insertIntoShelf"] = "insertIntoShelf";
+ AdPlacementBehavior["replaceOrganic"] = "replaceOrganic";
+ AdPlacementBehavior["dropAd"] = "dropAd";
+})(AdPlacementBehavior || (AdPlacementBehavior = {}));
+/** @public */
+export class TodayCardMedia extends base.ViewModel {
+ constructor(kind) {
+ super();
+ this.kind = kind;
+ }
+ /**
+ * @returns The best background color for the media. This is used to
+ * determine the color the section background gradients. If undefined, the
+ * default section background color will be used.
+ */
+ bestBackgroundColor() {
+ return undefined;
+ }
+}
+/** @public */
+export class TodayCardMediaWithArtwork extends TodayCardMedia {
+ constructor(kind, artworks, videos, artworkLayoutsWithMetrics) {
+ super(kind);
+ this.artworks = artworks;
+ this.videos = videos;
+ this.artworkLayoutsWithMetrics = artworkLayoutsWithMetrics;
+ }
+ bestBackgroundColor() {
+ var _a, _b, _c;
+ return (_b = (_a = this.videos[0]) === null || _a === void 0 ? void 0 : _a.preview.backgroundColor) !== null && _b !== void 0 ? _b : (_c = this.artworks[0]) === null || _c === void 0 ? void 0 : _c.backgroundColor;
+ }
+}
+/** @public */
+export class TodayCardMediaHero extends TodayCardMediaWithArtwork {
+ constructor(artworks, videos) {
+ super("hero", artworks, videos, []);
+ }
+ isValid() {
+ const isArtworkValid = this.artworks.every((artwork) => artwork.isValid()) && this.artworks.length > 0;
+ const isVideoValid = this.videos.every((video) => video.isValid()) && this.videos.length > 0;
+ return (isArtworkValid || isVideoValid) && super.isValid();
+ }
+}
+/** @public */
+export class TodayCardMediaArtwork extends TodayCardMediaWithArtwork {
+ constructor(artworks, videos, artworkLayoutsWithMetrics, titleBackingGradient) {
+ super("artwork", artworks, videos, artworkLayoutsWithMetrics);
+ this.titleBackingGradient = titleBackingGradient;
+ }
+}
+/** @public */
+export class TodayCardMediaAppIcon extends TodayCardMedia {
+ constructor(icon) {
+ super("appIcon");
+ this.icon = icon;
+ }
+ bestBackgroundColor() {
+ return this.icon.backgroundColor;
+ }
+}
+/** @public */
+export class TodayCardMediaBrandedSingleApp extends TodayCardMediaWithArtwork {
+ constructor(icon, artworks, videos, artworkLayoutsWithMetrics, titleBackingGradient) {
+ super("brandedSingleApp", artworks, videos, artworkLayoutsWithMetrics);
+ this.icon = icon;
+ this.titleBackingGradient = titleBackingGradient;
+ }
+ bestBackgroundColor() {
+ var _a;
+ return (_a = super.bestBackgroundColor()) !== null && _a !== void 0 ? _a : this.icon.backgroundColor;
+ }
+}
+/** @public */
+export class TodayCardMediaList extends TodayCardMediaWithArtwork {
+ constructor(lockups, artworks, videos, artworkLayoutsWithMetrics, marketingText, isMediaDark) {
+ super("list", artworks, videos, artworkLayoutsWithMetrics);
+ this.lockups = lockups;
+ this.marketingText = marketingText;
+ this.isMediaDark = isMediaDark;
+ }
+}
+/** @public */
+export class TodayCardMediaMultiApp extends TodayCardMedia {
+ constructor(lockups, additionalText) {
+ super("multiApp");
+ this.lockups = lockups;
+ this.additionalText = additionalText;
+ }
+}
+/** @public */
+export class TodayCardInAppPurchase extends TodayCardMedia {
+ constructor(lockup) {
+ super("inAppPurchase");
+ this.lockup = lockup;
+ }
+ bestBackgroundColor() {
+ return this.lockup.icon.backgroundColor;
+ }
+}
+/** @public */
+export class TodayCardMediaRiver extends TodayCardMedia {
+ constructor(lockups) {
+ super("river");
+ this.lockups = lockups;
+ this.lockupImpressionLimit = 10;
+ }
+}
+/** @public */
+export class TodayCardMediaGrid extends TodayCardMedia {
+ constructor(lockups, artworkGridType) {
+ super("grid");
+ this.lockups = lockups;
+ this.artworkGridType = artworkGridType;
+ this.lockupImpressionLimit = 3;
+ }
+}
+/** @public */
+export class TodayCardMediaVideo extends TodayCardMediaWithArtwork {
+ constructor(videos, artworkLayoutsWithMetrics, description) {
+ super("video", [], videos, artworkLayoutsWithMetrics);
+ this.description = description;
+ }
+}
+/** @public */
+export class TodayCardMediaAppEvent extends TodayCardMediaWithArtwork {
+ constructor(formattedDates, startDate, tintColor, artworks, videos, artworkLayoutsWithMetrics, blurStyle) {
+ super("appEvent", artworks, videos, artworkLayoutsWithMetrics);
+ this.formattedDates = formattedDates;
+ this.startDate = startDate;
+ this.tintColor = tintColor;
+ this.blurStyle = blurStyle;
+ }
+}
+/** @public */
+export class TodayCardMediaMediumLockupWithAlignedRegion extends TodayCardMedia {
+ constructor(mediumAdLockupWithAlignedRegionBackground) {
+ super("mediumLockupWithAlignedRegion");
+ this.mediumAdLockupWithAlignedRegionBackground = mediumAdLockupWithAlignedRegionBackground;
+ }
+}
+/** @public */
+export class TodayCardMediaMediumLockupWithScreenshots extends TodayCardMedia {
+ constructor(mediumAdLockupWithScreenshotsBackground) {
+ super("mediumLockupWithScreenshots");
+ this.mediumAdLockupWithScreenshotsBackground = mediumAdLockupWithScreenshotsBackground;
+ }
+}
+/**
+ * @public
+ * Media for a today card with a single lockup.
+ */ export class TodayCardMediaSingleLockup extends TodayCardMedia {
+ constructor(condensedAdLockupWithIconBackground) {
+ super("singleLockup");
+ this.condensedAdLockupWithIconBackground = condensedAdLockupWithIconBackground;
+ }
+ /**
+ * @returns The best background color for the media. This is used to
+ * determine the color the section background gradients. If undefined, the
+ * default section background color will be used.
+ */
+ bestBackgroundColor() {
+ var _a;
+ return (_a = this.condensedAdLockupWithIconBackground) === null || _a === void 0 ? void 0 : _a.backgroundArtwork.backgroundColor;
+ }
+}
+/** @public */
+export class TodayCardOverlay extends base.Model {
+ constructor(kind) {
+ super();
+ this.kind = kind;
+ }
+}
+/** @public */
+export class TodayCardLockupOverlay extends TodayCardOverlay {
+ constructor(lockup) {
+ super("lockup");
+ this.lockup = lockup;
+ this.displaysIcon = true;
+ }
+}
+/**
+ * @public
+ * Variant of `TodayCardLockupOverlay` with additional functionality,
+ * like optionally showing a transient `TodayCardParagraphOverlay` style overlay
+ */
+export class TodayCardMarketingLockupOverlay extends TodayCardOverlay {
+ constructor(lockup, paragraph, hideBackground, artworkBackgroundColor) {
+ super("marketingLockup");
+ this.lockup = lockup;
+ this.paragraph = paragraph;
+ this.hideBackground = hideBackground;
+ this.artworkBackgroundColor = artworkBackgroundColor;
+ }
+}
+/** @public */
+export class TodayCardLockupListOverlay extends TodayCardOverlay {
+ constructor(lockups) {
+ super("lockupList");
+ this.lockups = lockups;
+ }
+}
+/** @public */
+export class TodayCardParagraphOverlay extends TodayCardOverlay {
+ constructor(paragraph, style) {
+ super("paragraph");
+ this.paragraph = paragraph;
+ this.style = style;
+ }
+}
+/** @public */
+export class TodayCardActionOverlay extends TodayCardOverlay {
+ constructor(action) {
+ super("action");
+ this.action = action;
+ }
+}
+/** @public */
+export class TodayCardThreeLineOverlay extends TodayCardOverlay {
+ constructor(heading, title, description) {
+ super("threeLine");
+ this.heading = heading;
+ this.title = title;
+ this.description = description;
+ }
+}
+/**
+ * @public
+ * Overlay for Acquisition Editorial Items that promote the Arcade Service.
+ */
+export class TodayCardArcadeLockupOverlay extends TodayCardOverlay {
+ constructor(arcadeLockup) {
+ super("arcadeLockup");
+ this.arcadeLockup = arcadeLockup;
+ }
+}
+/**
+ * @public
+ * Overlay for App Event lockups.
+ */
+export class TodayCardAppEventLockupOverlay extends TodayCardOverlay {
+ constructor(lockup) {
+ super("appEventLockup");
+ this.lockup = lockup;
+ }
+}
+// endregion
+// region Cards
+/** @public */
+export class TodayCard extends base.ViewModel {
+ constructor() {
+ super();
+ this.heading = undefined;
+ this.title = undefined;
+ this.shortTitle = undefined;
+ this.titleArtwork = undefined;
+ this.media = undefined;
+ this.heroMedia = undefined;
+ this.overlay = undefined;
+ this.style = undefined;
+ this.clickAction = undefined;
+ this.backgroundColor = undefined;
+ this.inlineDescription = undefined;
+ this.collapsedHeading = undefined;
+ this.editorialDisplayOptions = undefined;
+ this.supportsMediaMirroring = undefined;
+ }
+}
+/** @public */
+export class InlineTodayCards extends base.ViewModel {
+ constructor(cards) {
+ super();
+ this.cards = cards;
+ }
+}
+// endregion
+/** @public */
+export class TodayPage extends base.Model {
+ constructor(shelfModels, onboardingCardIds, title, tabTitle, titleDetail, shortTitleDetail) {
+ super();
+ this.shelves = shelfModels;
+ this.title = title;
+ this.titleDetail = titleDetail;
+ this.shortTitleDetail = shortTitleDetail;
+ this.tabTitle = tabTitle;
+ this.nextPage = null;
+ if (onboardingCardIds) {
+ this.onboardingCardIds = onboardingCardIds;
+ }
+ else {
+ this.onboardingCardIds = [];
+ }
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+//# sourceMappingURL=today-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/top-charts.js b/node_modules/@jet-app/app-store/tmp/src/api/models/top-charts.js
new file mode 100644
index 0000000..52f229f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/top-charts.js
@@ -0,0 +1,63 @@
+/**
+ * Created by km on 2/13/17.
+ */
+import * as models from "./index";
+import * as metrics from "./metrics/metrics";
+/** @public */
+export class TopChartSegment extends models.Model {
+ constructor(shortName, longName, chart, shelfModels) {
+ super();
+ this.shortName = shortName;
+ this.longName = longName;
+ this.chart = chart;
+ this.shelves = shelfModels;
+ this.nextPage = null;
+ this.pageMetrics = new metrics.PageMetrics();
+ this.pageRenderMetrics = {};
+ }
+}
+/** @public */
+export class TopChartCategory extends models.Category {
+ constructor(category, url, children) {
+ super(category.name, category.genreId, category.artwork, category.ageBandId, children);
+ this.shortName = this.name;
+ this.longName = this.name;
+ this.url = url;
+ }
+}
+/**
+ * Shared "Top Charts" view-model
+ *
+ * @see {@linkcode TopChartsPage} for when "Top Charts" should be rendered as a page
+ * @see {@linkcode ChartsHubChart} for when a "chart" exists on a page with other "charts"
+ */
+export class TopChartsPage extends models.Model {
+ constructor(genreId, ageBandId, title, segments, categoriesButtonTitle, categories) {
+ super();
+ this.genreId = genreId;
+ this.ageBandId = ageBandId;
+ this.title = title;
+ this.segments = segments;
+ this.categoriesButtonTitle = categoriesButtonTitle;
+ this.categories = categories;
+ this.initialSegmentIndex = 0;
+ }
+}
+/**
+ * A single chart within a {@linkcode ChartsHubPage}
+ */
+export class ChartsHubChart extends TopChartsPage {
+}
+/**
+ * View-model for the "Charts Hub" page
+ *
+ * This is used by the "web" client to power the charts overview, which includes
+ * multiple charts in the same view
+ */
+export class ChartsHubPage extends models.Model {
+ constructor(charts) {
+ super();
+ this.charts = charts;
+ }
+}
+//# sourceMappingURL=top-charts.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/uber.js b/node_modules/@jet-app/app-store/tmp/src/api/models/uber.js
new file mode 100644
index 0000000..1fd7d78
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/uber.js
@@ -0,0 +1,19 @@
+import * as base from "./base";
+/** @public */
+export class Uber extends base.Model {
+ constructor(style) {
+ super();
+ this.style = style;
+ }
+ isValidUber(isTV, isVision) {
+ function isDefinedNonNullNonEmpty(object) {
+ return object !== undefined && object !== null && Object.keys(object).length !== 0;
+ }
+ return (isDefinedNonNullNonEmpty(this.artwork) ||
+ isDefinedNonNullNonEmpty(this.compactArtwork) ||
+ isDefinedNonNullNonEmpty(this.video) ||
+ isDefinedNonNullNonEmpty(this.compactVideo) ||
+ ((isTV || isVision) && isDefinedNonNullNonEmpty(this.iconArtwork)));
+ }
+}
+//# sourceMappingURL=uber.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/models/web-renderable-page.js b/node_modules/@jet-app/app-store/tmp/src/api/models/web-renderable-page.js
new file mode 100644
index 0000000..67be95f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/models/web-renderable-page.js
@@ -0,0 +1,4 @@
+export function injectSEOData(page, seoData) {
+ page.seoData = seoData;
+}
+//# sourceMappingURL=web-renderable-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/typings/constants.js b/node_modules/@jet-app/app-store/tmp/src/api/typings/constants.js
new file mode 100644
index 0000000..7c04c8c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/typings/constants.js
@@ -0,0 +1,21 @@
+import { makeMetatype } from "@jet/environment/util/metatype";
+export const storeMetrics = makeMetatype("app-store:storeMetrics");
+export const amsEngagement = makeMetatype("app-store:amsEngagement");
+export const device = makeMetatype("app-store:device");
+export const user = makeMetatype("app-store:user");
+export const player = makeMetatype("games:player");
+export const metricsIdentifiers = makeMetatype("app-store:metricsIdentifiers");
+export const arcade = makeMetatype("app-store:arcade");
+export const gameCenter = makeMetatype("app-store:gameCenter");
+export const resilientDeepLinks = makeMetatype("app-store:resilientDeepLinks");
+export const ads = makeMetatype("app-store:ads");
+export const onDeviceRecommendationsManager = makeMetatype("app-store:onDeviceRecommendationsManager");
+export const onDeviceSearchHistoryManager = makeMetatype("app-store:onDeviceSearchHistoryManager");
+export const featureFlags = makeMetatype("app-store:featureFlags");
+export const mediaToken = makeMetatype("app-store:mediaTokenService");
+export const adsLocalizer = makeMetatype("app-store:adsLocalizer");
+export const appDistribution = makeMetatype("app-store:appDistribution");
+export const timeoutManager = makeMetatype("app-store:timeoutManager");
+export const treatmentStore = makeMetatype("app-store:treatmentStore");
+export const userDefaults = makeMetatype("app-store:userDefaults");
+//# sourceMappingURL=constants.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/api/util.js b/node_modules/@jet-app/app-store/tmp/src/api/util.js
new file mode 100644
index 0000000..a6d199c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/api/util.js
@@ -0,0 +1,16 @@
+/**
+ * Ensures that Adam IDs are always represented with the same format
+ *
+ * "ID" segments in a URL typically include an `id` prefix that should
+ * not be present when using the value as an identifier for a resource
+ * in the Media APi.
+ *
+ * This function strips the prefix if present
+ */
+export function normalizeAdamID(raw) {
+ if (raw.startsWith("id")) {
+ return raw.substring(2);
+ }
+ return raw;
+}
+//# sourceMappingURL=util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/accessibility/accessibility-common.js b/node_modules/@jet-app/app-store/tmp/src/common/accessibility/accessibility-common.js
new file mode 100644
index 0000000..eed6d29
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/accessibility/accessibility-common.js
@@ -0,0 +1,70 @@
+import { isNothing, isSome } from "@jet/environment";
+import { unreachable } from "../../foundation/util/errors";
+import { isNotEmpty } from "../../foundation/util/array-util";
+let suppressedAccessibilityApps = null;
+/**
+ * Intiailizes `suppressedAccessibilityApps` with whatever is in the bag for `suppressedAccessibilityAppIds`, so that it only needs to be mapped to a Set once.
+ */
+function initialize(objectGraph) {
+ if (suppressedAccessibilityApps !== null) {
+ return;
+ }
+ suppressedAccessibilityApps = new Set();
+ for (const appId of objectGraph.bag.suppressedAccessibilityAppIds) {
+ suppressedAccessibilityApps.add(appId);
+ }
+}
+/**
+ * Returns whether the accessibility support feature is enabled.
+ */
+export function isProductAccessibilityLabelsEnabled(objectGraph) {
+ return (objectGraph.featureFlags.isEnabled("product_accessibility_support_2025A") &&
+ objectGraph.bag.enableAppAccessibilityLabels);
+}
+/**
+ * Returns whether we should suppress the accessibility information for a given adamId.
+ */
+export function shouldSuppressAccessibilityLabelsForAdamId(objectGraph, adamId) {
+ initialize(objectGraph);
+ return isSome(suppressedAccessibilityApps) && isNotEmpty(adamId) && suppressedAccessibilityApps.has(adamId);
+}
+/**
+ * Returns whether we should suppress the accessibility information for a given bundleId.
+ */
+export function shouldSuppressAccessibilityLabelsForBundleId(objectGraph, bundleId) {
+ initialize(objectGraph);
+ // We always want to suppress accessibility information for macOS installers, as accessibility labels don't apply.
+ if (bundleId.startsWith("com.apple.InstallAssistant.")) {
+ return true;
+ }
+ return isSome(suppressedAccessibilityApps) && isNotEmpty(bundleId) && suppressedAccessibilityApps.has(bundleId);
+}
+/**
+ * Returns the device family we want to display the accessibility labels for, based on `AppPlatform` for the first set
+ * of screenshots we display on the product page.
+ */
+export function deviceFamilyForAccessibilityLabels(platform) {
+ if (isNothing(platform)) {
+ return null;
+ }
+ switch (platform) {
+ case "phone":
+ return "iphone";
+ case "pad":
+ return "ipad";
+ case "mac":
+ return "mac";
+ case "tv":
+ return "tvos";
+ case "vision":
+ return "realityDevice";
+ case "watch":
+ return "watch";
+ case "messages":
+ // If we are surfacing Messages metadata, we want to hide the labels.
+ return null;
+ default:
+ unreachable(platform);
+ }
+}
+//# sourceMappingURL=accessibility-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/account/account-links-regex-parser.js b/node_modules/@jet-app/app-store/tmp/src/common/account/account-links-regex-parser.js
new file mode 100644
index 0000000..b6d2ad2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/account/account-links-regex-parser.js
@@ -0,0 +1,35 @@
+import { DynamicUIRequestInfo, FlowAction } from "../../api/models";
+/**
+ * Run a given URL through all the different web view regexes we have in the bag and return a
+ * flow action with the correct page type for this url. This is essentially what AMSURLParser
+ * is doing, however that does not work for App Store the way we route URLs.
+ *
+ * @param objectGraph The App Store object graph.
+ * @param urlString The string to create the appropriate FlowAction for.
+ * @returns A FlowAction appropriate for the provided URL.
+ */
+export function flowActionForAccountURL(objectGraph, urlString) {
+ // Dynamic UI
+ const dynamicUIRegexPatterns = objectGraph.bag.dynamicUIRegexStrings;
+ for (const pattern of dynamicUIRegexPatterns) {
+ const regexPattern = pattern.replace(/\//g, "\\/");
+ const dynamicUIPattern = new RegExp(regexPattern);
+ if (dynamicUIPattern.test(urlString)) {
+ const action = new FlowAction("dynamicUI", urlString);
+ action.pageData = new DynamicUIRequestInfo(objectGraph.bag.metricsTopic);
+ return action;
+ }
+ }
+ // Web UI regex check
+ const webViewRegexPatterns = objectGraph.bag.webViewRegexStrings;
+ for (const pattern of webViewRegexPatterns) {
+ const regexPattern = pattern.replace(/\//g, "\\/");
+ const webViewPattern = new RegExp(regexPattern);
+ if (webViewPattern.test(urlString)) {
+ return new FlowAction("webView", urlString);
+ }
+ }
+ /// Last of all try to allow the finance view controller to handle this url
+ return new FlowAction("finance", urlString);
+}
+//# sourceMappingURL=account-links-regex-parser.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/ads/ad-common.js b/node_modules/@jet-app/app-store/tmp/src/common/ads/ad-common.js
new file mode 100644
index 0000000..6216dba
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/ads/ad-common.js
@@ -0,0 +1,433 @@
+/**
+ * Common utilities for dealing with Ads.
+ */
+import { isNothing, isSome } from "@jet/environment";
+import { Lockup, TodayCard, TodayCardMediaMediumLockupWithAlignedRegion, TodayCardMediaMediumLockupWithScreenshots, TodayCardMediaSingleLockup, } from "../../api/models";
+import { isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, isNullOrEmpty, } from "../../foundation/json-parsing/server-data";
+import { attributeAsString } from "../../foundation/media/attributes";
+import { IAdSearchInformation } from "../metrics/helpers/models";
+import { platformSupportsAdverts } from "../search/search-ads";
+import { shouldTodayAdBeCondensed } from "../../foundation/experimentation/today-ad-experiments";
+import { getTemplateTypeForMediumAdFromLockupWithCustomCreative, getTemplateTypeForMediumAdFromLockupWithScreenshots, searchAdMissedOpportunityFromId, } from "../lockups/ad-lockups";
+import { contentAttributeAsString } from "../content/attributes";
+import { currentLocation, currentPosition } from "../metrics/helpers/location";
+// region placement logic
+/**
+ * A helper function to check if the given AdPlacement is enabled.
+ * @param objectGraph The object graph.
+ * @param adPlacementKey The ad placement to check. Matches values sent in the bag.
+ * @returns A boolean indicating if the provided AdPlacement is enabled.
+ */
+export function isAdPlacementEnabled(objectGraph, placementType) {
+ if (!platformSupportsAdverts(objectGraph)) {
+ return false;
+ }
+ switch (placementType) {
+ case "searchLanding":
+ // In transitioning from the legacy bag key `isSearchLandingAdsEnabled` to the new key `enabledAdPlacements`,
+ // we will enable SLP ads if either of the keys indicates ads are enabled for SLP.
+ const slpPlacementBagValue = adPlacementBagValueForAdPlacementType(placementType);
+ if (isSome(slpPlacementBagValue)) {
+ return (objectGraph.bag.isSearchLandingAdsEnabled ||
+ objectGraph.bag.enabledAdPlacements.includes(slpPlacementBagValue));
+ }
+ else {
+ return objectGraph.bag.isSearchLandingAdsEnabled;
+ }
+ case "searchResults":
+ return true; // Legacy
+ case "today":
+ const todayBagValue = adPlacementBagValueForAdPlacementType(placementType);
+ if (isNull(todayBagValue)) {
+ return false;
+ }
+ return objectGraph.bag.enabledAdPlacements.includes(todayBagValue) && isSome(todayAdStyle(objectGraph));
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ const placementBagValue = adPlacementBagValueForAdPlacementType(placementType);
+ if (isNull(placementBagValue)) {
+ return false;
+ }
+ return objectGraph.bag.enabledAdPlacements.includes(placementBagValue);
+ default:
+ return false;
+ }
+}
+/**
+ * Get the style to use for the Today ad.
+ * A return type of `undefined` indicates the ad style is unsupported on the client.
+ * @param objectGraph The Object Graph.
+ * @returns A style, or undefined if it's unsupported.
+ */
+export function todayAdStyle(objectGraph) {
+ if (objectGraph.bag.todayAdMediumLockupScreenshotEnabled) {
+ return "mediumLockup";
+ }
+ if (objectGraph.bag.todayAdCondensedEnabled) {
+ // Only supported on iPhone.
+ if (!objectGraph.client.isPhone) {
+ return undefined;
+ }
+ return "singleLockup";
+ }
+ if (shouldTodayAdBeCondensed(objectGraph)) {
+ return "singleLockup";
+ }
+ else {
+ return undefined;
+ }
+}
+/**
+ * Get the timeout of the ad fetch for a given placement from the bag values.
+ * @param objectGraph The object graph.
+ * @param placementType The ad placement.
+ * @param isDeltaTimeout Whether the timeout we're looking for is a delta timeout between parallel organic and ad requests completing.
+ * See `Ads.setTimeoutForCurrentOnDeviceAdFetch` for more details on how this is used.
+ * @returns The timeout.
+ */
+export function adFetchTimeoutForPlacement(objectGraph, placementType, isDeltaTimeout) {
+ var _a, _b, _c, _d, _e;
+ const timeouts = objectGraph.bag.adPlacementTimeouts;
+ const defaultTimeout = 0.3;
+ switch (placementType) {
+ case "searchResults":
+ return isDeltaTimeout ? null : (_a = timeouts === null || timeouts === void 0 ? void 0 : timeouts["search-results-in-seconds"]) !== null && _a !== void 0 ? _a : defaultTimeout;
+ case "searchLanding":
+ return isDeltaTimeout ? null : (_b = objectGraph.bag.searchLandingAdFetchTimeout) !== null && _b !== void 0 ? _b : defaultTimeout; // Legacy value.
+ case "today":
+ // Today uses its timeout value as a delta after the organic request finishes.
+ return isDeltaTimeout ? (_c = timeouts === null || timeouts === void 0 ? void 0 : timeouts["today-in-seconds"]) !== null && _c !== void 0 ? _c : defaultTimeout : null;
+ case "productPageYMAL":
+ return isDeltaTimeout ? null : (_d = timeouts === null || timeouts === void 0 ? void 0 : timeouts["product-page-ymal-in-seconds"]) !== null && _d !== void 0 ? _d : defaultTimeout;
+ case "productPageYMALDuringDownload":
+ return isDeltaTimeout ? null : (_e = timeouts === null || timeouts === void 0 ? void 0 : timeouts["product-page-ymal-during-download-in-seconds"]) !== null && _e !== void 0 ? _e : defaultTimeout;
+ default:
+ return defaultTimeout;
+ }
+}
+/**
+ * Convert the value describing an ad placement from the App Store representation to the bag representation.
+ * @param placementType The bag value for different ad placements
+ * @returns The equivalent bag placement value as a string.
+ */
+function adPlacementBagValueForAdPlacementType(placementType) {
+ switch (placementType) {
+ case "searchResults":
+ return "search-results";
+ case "searchLanding":
+ return "search-landing";
+ case "today":
+ return "today";
+ case "productPageYMAL":
+ return "product-page-ymal";
+ case "productPageYMALDuringDownload":
+ return "product-page-ymal-during-download";
+ default:
+ return undefined;
+ }
+}
+/**
+ * The minimum number of landscape media items for a Today ad placement.
+ * The app must satisfy this or `todayAdPlacementMinimumPortraitMedia` to be shown.
+ */
+export const todayAdPlacementMinimumLandscapeMedia = 5;
+/**
+ * The minimum portrait media for a Today ad placement.
+ * The app must satisfy this or `todayAdPlacementMinimumLandscapeMedia` to be shown.
+ */
+export const todayAdPlacementMinimumPortraitMedia = 4;
+/**
+ * Get a count of the media in each orientation specifically for a Today ad placement.
+ * This is specific to Today because the placement only supports one video.
+ * @param trailers A set of trailers for an app.
+ * @param screenshots A set of screenshots for an app.
+ * @returns A count of media in each orientation, respecting the placement's rules.
+ */
+export function getMediaOrientationCountsForTodayPlacement(trailers, screenshots) {
+ var _a, _b, _c, _d;
+ // Grab the first array of platform screenshots and trailers - the ones the ad will display.
+ const platformScreenshotsArtwork = (_b = (_a = screenshots[0]) === null || _a === void 0 ? void 0 : _a.artwork) !== null && _b !== void 0 ? _b : [];
+ const platformTrailersVideos = (_d = (_c = trailers[0]) === null || _c === void 0 ? void 0 : _c.videos) !== null && _d !== void 0 ? _d : [];
+ // Split media into orientations.
+ const portraitScreenshots = platformScreenshotsArtwork.filter((artwork) => artwork.isPortrait());
+ const landscapeScreenshots = platformScreenshotsArtwork.filter((artwork) => artwork.isLandscape());
+ // Take a maximum of one video of each orientation.
+ const portraitVideos = platformTrailersVideos.filter((video) => video.preview.isPortrait()).slice(0, 1);
+ const landscapeVideos = platformTrailersVideos.filter((video) => video.preview.isLandscape()).slice(0, 1);
+ const portraitCount = portraitScreenshots.length + portraitVideos.length;
+ const landscapeCount = landscapeScreenshots.length + landscapeVideos.length;
+ return {
+ landscape: landscapeCount,
+ portrait: portraitCount,
+ };
+}
+/**
+ * A convenience method to determine if a position is ad eligible.
+ * @param placementType The type of placement for an ad.
+ * @param locationTracker The location tracker being used to build the items. Used to determine the current shelf and position within the shelf.
+ * @returns A boolean indicating if the current position is considered ad eligible.
+ */
+export function isAdEligible(placementType, locationTracker) {
+ var _a;
+ if (isNothing(locationTracker) || isNothing(placementType)) {
+ return false;
+ }
+ const shelfId = (_a = currentLocation(locationTracker)) === null || _a === void 0 ? void 0 : _a.id;
+ if (isNothing(shelfId)) {
+ return false;
+ }
+ const eligibleIndex = adEligibleIndexForType(placementType, shelfId);
+ if (isNothing(eligibleIndex)) {
+ return false;
+ }
+ const index = currentPosition(locationTracker);
+ return index === eligibleIndex;
+}
+/**
+ * Inserts a missed opportunity on the correct slot in the page if an ad isn't already present. This looks for
+ * types conforming to SearchAdOpportunityProviding and handles all of the construction for the metadata associated
+ * with a SearchAdOpportunity.
+ * @param objectGraph The object graph.
+ * @param shelves Completed shelves that are constructed for the page.
+ * @param placementType The location where the ad will be shown.
+ * @param shelfIdentifier The specific shelf identifier we should be evaluating against. This is not inferred to support things like Today Page that can use a shelf-per-item.
+ * @param pageInformation Metrics information for the page.
+ */
+export function applySearchAdMissedOpportunityToShelvesIfNeeded(objectGraph, shelves, placementType, shelfIdentifier, pageInformation) {
+ var _a, _b, _c;
+ if (!platformSupportsAdverts(objectGraph) || isNull(pageInformation.iAdInfo)) {
+ return;
+ }
+ const adEligibleIndex = adEligibleIndexForType(placementType, shelfIdentifier);
+ if (isNothing(adEligibleIndex)) {
+ return;
+ }
+ // Opportunities aren't present when we've recorded certain missed opportunities
+ let missedOpportunityReason = null;
+ if (typeof pageInformation.iAdInfo.pageFields.iAdMissedOpportunityReason === "string") {
+ missedOpportunityReason = pageInformation.iAdInfo.pageFields.iAdMissedOpportunityReason;
+ }
+ if (isNothing(missedOpportunityReason) ||
+ missedOpportunityReason.length === 0 ||
+ missedOpportunityReason === "EDITORIALTAKEOVER" ||
+ missedOpportunityReason === "SLPLOAD") {
+ return;
+ }
+ const allShelfItems = [];
+ for (const shelf of shelves) {
+ // searchResult is handled in a separate non-shelf workflow
+ const isValidContentType = shelf.contentType === "smallLockup" || shelf.contentType === "todayCard";
+ if (!isValidContentType) {
+ continue;
+ }
+ const nextShelfItems = shelf.items;
+ if (isDefinedNonNull(nextShelfItems) && nextShelfItems.length > 0) {
+ allShelfItems.push(...nextShelfItems);
+ }
+ }
+ if (allShelfItems.length <= adEligibleIndex) {
+ return;
+ }
+ const adEligibleShelfItem = allShelfItems[adEligibleIndex];
+ const isTodayCard = adEligibleShelfItem instanceof TodayCard;
+ const isSmallLockup = adEligibleShelfItem instanceof Lockup;
+ const shelfItemMedia = isTodayCard ? adEligibleShelfItem.media : null;
+ const lockupTypeIsMediumScreenshotFormat = isDefinedNonNull(shelfItemMedia) && shelfItemMedia instanceof TodayCardMediaMediumLockupWithScreenshots;
+ const lockupTypeIsMediumCreativeFormat = isDefinedNonNull(shelfItemMedia) && shelfItemMedia instanceof TodayCardMediaMediumLockupWithAlignedRegion;
+ const lockupHasPlacedCondensedTodayAd = isDefinedNonNull(shelfItemMedia) &&
+ shelfItemMedia instanceof TodayCardMediaSingleLockup &&
+ isDefinedNonNull(shelfItemMedia.condensedAdLockupWithIconBackground.lockup.searchAdOpportunity);
+ const lockupHasPlacedMediumAdScreenshots = lockupTypeIsMediumScreenshotFormat &&
+ isDefinedNonNull(shelfItemMedia.mediumAdLockupWithScreenshotsBackground.lockup.searchAdOpportunity);
+ const lockupHasPlacedMediumAdCreative = lockupTypeIsMediumCreativeFormat &&
+ isDefinedNonNull(shelfItemMedia.mediumAdLockupWithAlignedRegionBackground.lockup.searchAdOpportunity);
+ const lockupHasPlacedSmallLockup = isSmallLockup && isDefinedNonNull(adEligibleShelfItem.searchAdOpportunity);
+ // If we've already processed the ad data and placed it, we don't want to indicate that there's a missed opportunity
+ if (lockupHasPlacedCondensedTodayAd ||
+ lockupHasPlacedMediumAdScreenshots ||
+ lockupHasPlacedMediumAdCreative ||
+ lockupHasPlacedSmallLockup) {
+ return;
+ }
+ adEligibleShelfItem.searchAdOpportunity = searchAdMissedOpportunityFromId(objectGraph, pageInformation);
+ (_a = adEligibleShelfItem.searchAdOpportunity) === null || _a === void 0 ? void 0 : _a.setMissedOpportunityReason(missedOpportunityReason !== null && missedOpportunityReason !== void 0 ? missedOpportunityReason : "NOAD");
+ if (lockupTypeIsMediumScreenshotFormat) {
+ (_b = adEligibleShelfItem.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.setTemplateType(getTemplateTypeForMediumAdFromLockupWithScreenshots(shelfItemMedia.mediumAdLockupWithScreenshotsBackground.screenshots[0]));
+ }
+ else if (lockupTypeIsMediumCreativeFormat) {
+ adEligibleShelfItem.searchAdOpportunity.setTemplateType(getTemplateTypeForMediumAdFromLockupWithCustomCreative());
+ }
+ else {
+ (_c = adEligibleShelfItem.searchAdOpportunity) === null || _c === void 0 ? void 0 : _c.setTemplateType("APPLOCKUP");
+ }
+}
+/**
+ * This is a bit of a workaround for `isAdEligible` function in this file. Ideally this is bag-driven, but I need to figure out
+ * how to make it work. `shelfIdentifier` doesn't seem to work correctly, except for the YMAL ad on PP
+ * @param placementType The ad placement type to evaluate for ad eligibility
+ * @param shelfIdentifier The identifier for the shelf to evaluate for ad eligibility
+ * @returns The equivalent bag placement value as a string.
+ */
+function adEligibleIndexForType(placementType, shelfIdentifier) {
+ var _a;
+ const adEligibleBagRepresentation = {
+ today: [
+ {
+ shelfIdentifier: "today",
+ adEligibleIndex: 1,
+ },
+ ],
+ productPageYMAL: [
+ {
+ shelfIdentifier: "customers-also-bought-apps",
+ adEligibleIndex: 0,
+ },
+ ],
+ searchLanding: [
+ {
+ shelfIdentifier: "R8802",
+ adEligibleIndex: 0,
+ },
+ ],
+ searchResults: [
+ {
+ shelfIdentifier: "search-results",
+ adEligibleIndex: 0,
+ },
+ ],
+ };
+ const matchingSlot = ((_a = adEligibleBagRepresentation[placementType]) !== null && _a !== void 0 ? _a : []).find((element) => {
+ return element.shelfIdentifier === shelfIdentifier;
+ });
+ if (isDefinedNonNullNonEmpty(matchingSlot) && isDefinedNonNull(matchingSlot.adEligibleIndex)) {
+ return matchingSlot.adEligibleIndex;
+ }
+ else {
+ return undefined;
+ }
+}
+// endregion
+// region iad data
+export function iadInfoFromOnDeviceAdResponse(objectGraph, placementType, adResponse, flattenedTodayFeed = null) {
+ var _a, _b;
+ if (!platformSupportsAdverts(objectGraph) || isNull(adResponse)) {
+ return null;
+ }
+ return new IAdSearchInformation(objectGraph, placementType, IAdSearchInformation.createInitialSlotInfos(objectGraph, placementType, (_a = adResponse === null || adResponse === void 0 ? void 0 : adResponse.onDeviceAd) === null || _a === void 0 ? void 0 : _a.positionInfo, flattenedTodayFeed), adResponse.iAdId, adResponse.clientRequestId, undefined, (_b = adResponse.onDeviceAd) === null || _b === void 0 ? void 0 : _b.positionInfo);
+}
+/**
+ * Key added to `Data`'s `attributes` field so downstream builders can use this field.
+ * This is currently generated by JS when we create `SponsoredSearchAdverts`
+ */
+export const instanceIdAttributeKey = "jet_native_advert_instanceid";
+/**
+ * Returns the native advert id if one was annotated during `applyNativeAdvertData`
+ */
+export function advertInstanceIdForData(objectGraph, data) {
+ return attributeAsString(data, instanceIdAttributeKey);
+}
+/**
+ * Decorates `instanceId` that identifies an ad to given `Data` for consumption in builders.
+ */
+export function decorateAdInstanceIdOnData(objectGraph, data, instanceId) {
+ if (isDefinedNonNullNonEmpty(data === null || data === void 0 ? void 0 : data.attributes)) {
+ data.attributes[instanceIdAttributeKey] = instanceId;
+ }
+}
+// endregion
+// region ad localization
+/**
+ * Whether the available localization data in an ad is valid for the defined `adsOverrideLanguage`, if any.
+ * If no `adsOverrideLanguage` is set, `true` is returned.
+ * The logic for a valid set of data differs based on the passed in placement and if an ad display style
+ * is defined (for SRP only).
+ * @param objectGraph The Object Graph.
+ * @param data The app data.
+ * @param onDeviceAd Optionally, an `OnDeviceAdvert` - this is only used as a fallback for SLP ads,
+ * where the meta resource object is attached to the original ad request, but not to the subsequent
+ * hydration request. For this case only, we must use a combination of these two requests to identify
+ * whether the localization is valid.
+ * @param adDisplayStyle: Optionally, an ad display style - this is used for SRP ads, where the data used
+ * in the ad UI depends on which ad display style is being used.
+ * @returns A boolean indicating if the available localization data is valid.
+ */
+export function isAdLocalizationValid(objectGraph, data, onDeviceAd, adDisplayStyle) {
+ var _a, _b, _c, _d, _e;
+ const adsOverrideLanguage = objectGraph.bag.adsOverrideLanguage;
+ // No localization requirements if there is no adsOverrideLanguage.
+ if (isNullOrEmpty(adsOverrideLanguage) || isNullOrEmpty(data)) {
+ return true;
+ }
+ let metaResource = (_a = data.meta) === null || _a === void 0 ? void 0 : _a.resource;
+ // If the `metaResource in `data` is missing, attempt to fall back to the `onDeviceAd` values
+ // which are provided for SLP.
+ if (isNullOrEmpty(metaResource) && isDefinedNonNullNonEmpty(onDeviceAd)) {
+ metaResource = (_e = (_d = (_c = (_b = onDeviceAd === null || onDeviceAd === void 0 ? void 0 : onDeviceAd.appMetadata) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.meta) === null || _e === void 0 ? void 0 : _e.resource;
+ }
+ // If `metaResource` still don't exist, we can't validate localization.
+ if (isNullOrEmpty(metaResource)) {
+ // The meta resource attributes object is missing, assume we don't have the correct localizations.
+ return false;
+ }
+ // Title
+ // Title is always used. Check it's localized.
+ const titleLocale = attributeAsString(metaResource, "name.locale");
+ if (titleLocale !== adsOverrideLanguage) {
+ return false;
+ }
+ // Subtitle
+ // Subtitle is constructed using either the subtitle field, or falling back to the category.
+ // If the subtitle text is present, we need to validate the locale. If not, we don't.
+ const subtitle = contentAttributeAsString(objectGraph, data, "subtitle");
+ const subtitleLocale = contentAttributeAsString(objectGraph, metaResource, "subtitle.locale");
+ if (isDefinedNonNullNonEmpty(subtitle) && subtitleLocale !== adsOverrideLanguage) {
+ return false;
+ }
+ // SRP advertising text
+ // SRP ads can optionally provide a 3rd set of text, `advertisingText`.
+ // It's only used when the `adDisplayStyle` is `TEXT`.
+ if (adDisplayStyle === "TEXT" /* SearchAdDisplayStyle.TEXT */) {
+ // The text used can be defined by the ad payload inside the `data`. We need to use that path
+ // to get both the text and the locale for that text.
+ const iAdTextKey = attributeAsString(data, "iad.format.text");
+ if (isSome(iAdTextKey) && iAdTextKey !== "none") {
+ let attributePath;
+ const attributePathLocale = iAdTextKey;
+ // In the case where the path is "description", we need to use different paths for the text and the locale.
+ // This is due to a Media API limitation where the locale for "description" is just under that key, rather
+ // than "description.standard", which is the actual path for the text content.
+ if (iAdTextKey === "description") {
+ attributePath = "description.standard";
+ }
+ else {
+ attributePath = iAdTextKey;
+ }
+ const advertisingText = contentAttributeAsString(objectGraph, data, attributePath);
+ const advertisingTextLocale = contentAttributeAsString(objectGraph, metaResource, attributePathLocale.concat(".locale"));
+ if (isDefinedNonNullNonEmpty(advertisingText) && advertisingTextLocale !== adsOverrideLanguage) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+// endregion
+// region metrics
+/**
+ * Retrieve the eligible slot positions for a given ad placement type.
+ * @param objectGraph The Object Graph.
+ * @param placementType The placement the ad is for.
+ * @returns An array of eligible slot positions for the provided placement.
+ */
+export function eligibleSlotPositionsForAdPlacement(objectGraph, placementType) {
+ if (isNothing(placementType)) {
+ return undefined;
+ }
+ const placementTypeBagValue = adPlacementBagValueForAdPlacementType(placementType);
+ if (isNothing(placementTypeBagValue)) {
+ return undefined;
+ }
+ return objectGraph.bag.adPlacementEligibleSlotPositions[placementTypeBagValue];
+}
+// endregion
+//# sourceMappingURL=ad-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/ads/ad-incident-recorder.js b/node_modules/@jet-app/app-store/tmp/src/common/ads/ad-incident-recorder.js
new file mode 100644
index 0000000..6dac34a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/ads/ad-incident-recorder.js
@@ -0,0 +1,116 @@
+/**
+ * Ad Incident Recorder manages the Journey and Figaro events when rendering a page.
+ * This exists so managing journey and figaro metrics is a bit more sane.
+ */
+"use strict";
+import { isNothing } from "@jet/environment";
+import { DiscardAdIncident } from "../../api/models";
+import { isDefinedNonNull, isNull, isNullOrEmpty } from "../../foundation/json-parsing/server-data";
+import { advertInstanceIdForData } from "./ad-common";
+/**
+ * Create a new ad incident recorder
+ */
+export function newRecorder(objectGraph, iAdInfo) {
+ return {
+ incidents: [],
+ iAdInfo: iAdInfo,
+ };
+}
+/**
+ * Get recorded incidents from recorder
+ */
+export function recordedIncidents(objectGraph, recorder) {
+ if (isNull(recorder) || isNullOrEmpty(recorder.incidents)) {
+ return null;
+ }
+ return recorder.incidents;
+}
+/**
+ * Record that building a lockup from data failed.
+ */
+export function recordLockupFromDataFailed(objectGraph, recorder, lockupData) {
+ var _a, _b;
+ const instanceId = advertInstanceIdForData(objectGraph, lockupData);
+ if (isNothing(instanceId)) {
+ return;
+ }
+ const incident = objectGraph.props.enabled("advertSlotReporting")
+ ? null
+ : new DiscardAdIncident(instanceId, "advertDataMalformed");
+ addIncident(recorder, incident);
+ (_a = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _a === void 0 ? void 0 : _a.setMissedOpportunity(objectGraph, "METADATA", (_b = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _b === void 0 ? void 0 : _b.placementType);
+}
+/**
+ * Record events that may occur when we try to fetch an on-device ad + data
+ */
+export function recordAdResponseEventsIfNeeded(objectGraph, recorder, response) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
+ if (isNullOrEmpty(response === null || response === void 0 ? void 0 : response.failureReason)) {
+ return; // No failure to report
+ }
+ const instanceId = (_a = response === null || response === void 0 ? void 0 : response.onDeviceAd) === null || _a === void 0 ? void 0 : _a.instanceId;
+ const placementType = (_b = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _b === void 0 ? void 0 : _b.placementType;
+ switch (response === null || response === void 0 ? void 0 : response.failureReason) {
+ case "mapiFetchFail":
+ if (isDefinedNonNull(instanceId)) {
+ const incident = objectGraph.props.enabled("advertSlotReporting")
+ ? null
+ : new DiscardAdIncident(instanceId, "advertDataMalformed");
+ addIncident(recorder, incident);
+ }
+ (_c = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _c === void 0 ? void 0 : _c.setMissedOpportunity(objectGraph, "METADATA", placementType);
+ break;
+ case "cppAssetsMissing":
+ if (isDefinedNonNull(instanceId)) {
+ const incident = objectGraph.props.enabled("advertSlotReporting")
+ ? null
+ : new DiscardAdIncident(instanceId, "cppAssetsMissing");
+ addIncident(recorder, incident);
+ }
+ (_d = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _d === void 0 ? void 0 : _d.setMissedOpportunity(objectGraph, "METADATA", placementType);
+ break;
+ case "insufficientAssets":
+ if (isDefinedNonNull(instanceId)) {
+ const incident = objectGraph.props.enabled("advertSlotReporting")
+ ? null
+ : new DiscardAdIncident(instanceId, "insufficientAssets");
+ addIncident(recorder, incident);
+ }
+ (_e = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _e === void 0 ? void 0 : _e.setMissedOpportunity(objectGraph, "METADATA", placementType);
+ break;
+ case "noAdAvailable":
+ // no journey metric
+ (_f = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _f === void 0 ? void 0 : _f.setMissedOpportunity(objectGraph, "NOAD", placementType);
+ break;
+ case "timeout":
+ // no journey metric
+ (_g = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _g === void 0 ? void 0 : _g.setMissedOpportunity(objectGraph, "APSLA", placementType);
+ break;
+ case "localizationNotAvailable":
+ if (isDefinedNonNull(instanceId)) {
+ const incident = objectGraph.props.enabled("advertSlotReporting")
+ ? null
+ : new DiscardAdIncident(instanceId, "localizationNotAvailable");
+ addIncident(recorder, incident);
+ }
+ (_h = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _h === void 0 ? void 0 : _h.setMissedOpportunity(objectGraph, "NOLOC", placementType);
+ break;
+ case "policyAdDrop":
+ // no journey metric
+ (_j = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _j === void 0 ? void 0 : _j.setMissedOpportunity(objectGraph, "ODP_NOAD", placementType);
+ break;
+ default:
+ break;
+ }
+}
+// region Internals
+/**
+ * Add incident on `AdIncidentRecorder`
+ */
+function addIncident(recorder, incident) {
+ if (isNull(recorder) || isNull(incident)) {
+ return;
+ }
+ recorder.incidents.push(incident);
+}
+//# sourceMappingURL=ad-incident-recorder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/ads/ad-stitcher.js b/node_modules/@jet-app/app-store/tmp/src/common/ads/ad-stitcher.js
new file mode 100644
index 0000000..c9fc6b9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/ads/ad-stitcher.js
@@ -0,0 +1,73 @@
+/**
+ * Handles stitching ads into pages.
+ *
+ * At a high level:
+ * 1. Setup `AdStitcher` w/ content to stitch.
+ * 2. Pass to builder
+ * 3. Destructively consume tasks during building.
+ */
+"use strict";
+import { isNull } from "../../foundation/json-parsing/server-data";
+/**
+ * Creates a string identifier representing the position information for an ad stitch task.
+ * @param positionInfo The position information object to create an identifier for.
+ * @returns The string identifier.
+ */
+function stringIdentifierForPositionInfo(positionInfo) {
+ return `${positionInfo.shelfIdentifier}.${positionInfo.slot}`;
+}
+// region exports
+/**
+ * Create a new ad stitcher
+ */
+export function newAdStitcher() {
+ return {
+ tasks: {},
+ };
+}
+/// Add task
+export function addTask(stitcher, task) {
+ if (isNull(stitcher)) {
+ return;
+ }
+ const positionIdentifier = stringIdentifierForPositionInfo(task.positionInfo);
+ stitcher.tasks[positionIdentifier] = task;
+}
+/**
+ * Consume a single task, if available, for a given position information.
+ * @param stitcher The relevant ad stitcher.
+ * @param positionInfo The position information to check for an available task.
+ * @returns A task for the position, or null if unavailable.
+ */
+export function consumeTask(stitcher, positionInfo) {
+ if (isNull(stitcher)) {
+ return null;
+ }
+ const positionIdentifier = stringIdentifierForPositionInfo(positionInfo);
+ const task = stitcher.tasks[positionIdentifier];
+ if (isNull(task)) {
+ return null;
+ }
+ delete stitcher.tasks[positionIdentifier];
+ return task;
+}
+/**
+ * Consume all tasks for a given shelf identifier. Useful where ad data is being stitched in prior to view models being built.
+ * @param stitcher The relevant ad stitcher.
+ * @param shelfIdentifier The identifier for the shelf to get all available tasks for.
+ * @returns An array of relevant tasks for the shelf.
+ */
+export function consumeTasksForShelfIdentifier(stitcher, shelfIdentifier) {
+ if (isNull(stitcher)) {
+ return [];
+ }
+ const tasksForShelf = [];
+ Object.entries(stitcher.tasks).forEach(([key, value]) => {
+ if (key.startsWith(shelfIdentifier)) {
+ tasksForShelf.push(value);
+ delete stitcher.tasks[key];
+ }
+ });
+ return tasksForShelf;
+}
+//# sourceMappingURL=ad-stitcher.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js b/node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js
new file mode 100644
index 0000000..04f3e01
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-fetch.js
@@ -0,0 +1,286 @@
+/**
+ * Handles fetching Ads for all on-device ad placements
+ * This is currently SLP, Today, Product Page.
+ *
+ * # Ad fetching for on-device placements
+ * On-Device placements are unpersonalized and rely on CDN caching. To show ads, we:
+ * 1. Fetch from an on-device cache of ads (managed by PromotedContent framework) for the specific placement
+ * 2. Fetching ad data from MAPI
+ * 2. Stitch ad onto page data...
+ */
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { Request } from "../../foundation/media/data-fetching";
+import { dataFromDataContainer } from "../../foundation/media/data-structure";
+import { fetchData } from "../../foundation/media/network";
+import { buildURLFromRequest } from "../../foundation/media/url-builder";
+import { Parameters } from "../../foundation/network/url-constants";
+import { offerDataFromData } from "../offers/offers";
+import { productVariantDataForData, shouldFetchCustomAttributes } from "../product-page/product-page-variants";
+import { todayTabODPTimeoutUseCase } from "../personalization/on-device-recommendations-today";
+import { setTimeoutForRequestKey } from "../util/timeout-manager-util";
+import { adLogger } from "../search/search-ads";
+import * as adCommon from "./ad-common";
+import { getSelectedCustomCreativeId } from "../search/custom-creative";
+import { isSome } from "@jet/environment";
+// region exports
+/**
+ * Fetch ads for the given placement type leveraging on-device ads cache.
+ * @param objectGraph the object graph.
+ * @param placementType the placement type to fetch the ad for.
+ * @param adamId the adamId of the app for which the product page is being viewed, to provide a relevant ad. Only required for product page placements.
+ * @returns a promise containing an ad.
+ */
+export async function fetchAds(objectGraph, placementType, adamId) {
+ const timeout = adCommon.adFetchTimeoutForPlacement(objectGraph, placementType, false);
+ const request = new Request(objectGraph);
+ switch (placementType) {
+ case "today":
+ request.usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ switch (adCommon.todayAdStyle(objectGraph)) {
+ case "mediumLockup":
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ if (objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) {
+ request.includingAttributes(["customScreenshotsByTypeForAd", "adCreativeArtwork"]);
+ }
+ else {
+ request.includingAttributes(["customScreenshotsByTypeForAd"]);
+ }
+ }
+ else {
+ request.includingAttributes(["customScreenshotsByTypeForAd"]);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ request.usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ break;
+ default:
+ break;
+ }
+ /**
+ * Ad not available content filtering
+ */
+ const adsOverrideLanguage = objectGraph.bag.adsOverrideLanguage;
+ if (serverData.isDefinedNonNullNonEmpty(adsOverrideLanguage)) {
+ request.enablingFeature("adsLocaleMetadata").addingQuery("l", adsOverrideLanguage);
+ }
+ const requestMetaFields = buildURLFromRequest(objectGraph, request).query;
+ try {
+ const onDeviceResponse = await objectGraph.ads.fetchOnDeviceAdPlacement(placementType, timeout, requestMetaFields, adamId);
+ return await handleAdResponse(objectGraph, onDeviceResponse, placementType);
+ }
+ catch {
+ return null;
+ }
+}
+/**
+ * Handle the response from an on device ad request.
+ * @param objectGraph The App Store Object Graph.
+ * @param onDeviceResponse The response from the on device ad fetcher.
+ * @param placementType The placement this request was for.
+ * @returns A promise containing an ad.
+ */
+async function handleAdResponse(objectGraph, onDeviceResponse, placementType) {
+ var _a, _b, _c, _d, _e, _f;
+ if (serverData.isNullOrEmpty(onDeviceResponse.clientRequestId)) {
+ onDeviceResponse.clientRequestId = objectGraph.random.nextUUID();
+ adLogger(objectGraph, `clientRequestId was nil. Assigned ${onDeviceResponse.clientRequestId}`);
+ }
+ const aggregateResponse = {
+ clientRequestId: onDeviceResponse.clientRequestId,
+ iAdId: onDeviceResponse.iAdId,
+ placementType: (_b = (_a = onDeviceResponse === null || onDeviceResponse === void 0 ? void 0 : onDeviceResponse.ad) === null || _a === void 0 ? void 0 : _a.placementType) !== null && _b !== void 0 ? _b : placementType,
+ };
+ // Failed w/o Ad from device.
+ if (onDeviceResponse.failureReason) {
+ aggregateResponse.failureReason = onDeviceResponse.failureReason;
+ return aggregateResponse;
+ }
+ // Set the basic ad info received on the response.
+ aggregateResponse.onDeviceAd = onDeviceResponse.ad;
+ // Ad requests should return with at least some basic app metadata.
+ // Note: On pre-SydneyC builds, this is expected to be null for the SLP placement.
+ let mediaResponse = (_c = onDeviceResponse.ad) === null || _c === void 0 ? void 0 : _c.appMetadata;
+ // Get the currently available app data from the data container.
+ const appData = dataFromDataContainer(objectGraph, mediaResponse);
+ // We should only attempt to fetch the full app data if the app data provided to us via
+ // Promoted Content is incomplete. This should only be the case for SLP ads - Chainlink
+ // placements should arrive with complete app data.
+ // We check a couple of attributes here as a way to be sure it's hydrated. Sometimes
+ // a single attribute can be misleading.
+ if (serverData.isNullOrEmpty((_d = appData === null || appData === void 0 ? void 0 : appData.attributes) === null || _d === void 0 ? void 0 : _d.name) ||
+ serverData.isNullOrEmpty((_e = appData === null || appData === void 0 ? void 0 : appData.attributes) === null || _e === void 0 ? void 0 : _e.platformAttributes) ||
+ serverData.isNullOrEmpty(offerDataFromData(objectGraph, appData))) {
+ try {
+ const adRequest = createRequestForOnDeviceAd(objectGraph, onDeviceResponse.ad);
+ mediaResponse = await fetchData(objectGraph, adRequest);
+ }
+ catch (e) {
+ adLogger(objectGraph, `fetchAds request failed - ${e}`);
+ aggregateResponse.failureReason = "mapiFetchFail";
+ }
+ }
+ // The app data should now be complete, set it on the response.
+ if (serverData.isDefinedNonNullNonEmpty((_f = dataFromDataContainer(objectGraph, mediaResponse)) === null || _f === void 0 ? void 0 : _f.attributes)) {
+ aggregateResponse.mediaResponse = decorateiAdAttributeFromOnDeviceAdResponse(objectGraph, mediaResponse, onDeviceResponse);
+ // Check the localization is valid for the ad.
+ if (!adCommon.isAdLocalizationValid(objectGraph, dataFromDataContainer(objectGraph, mediaResponse), aggregateResponse.onDeviceAd)) {
+ adLogger(objectGraph, `fetchAds request failed - localization not available`);
+ aggregateResponse.failureReason = "localizationNotAvailable";
+ }
+ const metadataFailReason = checkAppMetadataIsValidForPlacement(objectGraph, aggregateResponse, placementType);
+ if (serverData.isDefinedNonNull(metadataFailReason)) {
+ adLogger(objectGraph, `fetchAds request failed - ${metadataFailReason}`);
+ aggregateResponse.failureReason = metadataFailReason;
+ }
+ }
+ return aggregateResponse;
+}
+/**
+ * Indicates that an organic request kicked off parallel to an ad fetch has completed.
+ * This gives us an opportunity to enforce a timeout on the ad request for the time beyond the organic request.
+ * @param objectGraph The object graph.
+ * @param placementType The placement for which the parallel organic request finished.
+ */
+export function parallelOrganicRequestDidFinish(objectGraph, placementType) {
+ const timeout = adCommon.adFetchTimeoutForPlacement(objectGraph, placementType, true);
+ if (serverData.isNull(timeout)) {
+ return;
+ }
+ objectGraph.ads.setTimeoutForCurrentOnDeviceAdFetch(placementType, timeout);
+ setTimeoutForRequestKey(objectGraph, timeout, todayTabODPTimeoutUseCase);
+}
+// endregion
+// region internals
+/**
+ * Create an request for on-device adverts
+ * @param ad The on device ad to fetch MAPI data for.
+ */
+function createRequestForOnDeviceAd(objectGraph, ad) {
+ const request = new Request(objectGraph)
+ .withIdOfType(ad.adamId, "apps")
+ .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph))
+ .includingAttributes(["customScreenshotsByTypeForAd"]);
+ if (serverData.isDefinedNonNullNonEmpty(ad.cppIds)) {
+ request.addingQuery(Parameters.productVariantID, ad.cppIds[0]);
+ }
+ // If there is an `adsOverrideLanguage`, attach it to this request too.
+ const adsOverrideLanguage = objectGraph.bag.adsOverrideLanguage;
+ if (serverData.isDefinedNonNullNonEmpty(adsOverrideLanguage)) {
+ request.addingQuery("l", adsOverrideLanguage);
+ }
+ return request;
+}
+/**
+ * Decorate `iad` attribute with contents of `OnDeviceAdvert`.
+ *
+ * @param mediaResponse The data from of MAPI request
+ * @param ad Ad data that was fetched independent of response.
+ */
+function decorateiAdAttributeFromOnDeviceAdResponse(objectGraph, mediaResponse, adResponse) {
+ const adData = dataFromDataContainer(objectGraph, mediaResponse);
+ if (serverData.isNullOrEmpty(adData) || serverData.isNull(adData.attributes)) {
+ adLogger(objectGraph, "decorateiAdAttributeFromOnDeviceAd cannot decorate for malformed response");
+ return null; // The data is incompatible with `iad` decoration. Return `null` to let builder report error.
+ }
+ const onDeviceAd = adResponse.ad;
+ const lineItem = `${onDeviceAd.adamId}|${onDeviceAd.metadata}`;
+ // Create `IAdAttributes` and stitch onto `mediaResponse`
+ const iadAttributes = {
+ clientRequestId: adResponse.clientRequestId,
+ impressionId: onDeviceAd.impressionId,
+ metadata: onDeviceAd.metadata,
+ privacy: onDeviceAd.privacy,
+ lineItem: lineItem,
+ };
+ const metaContainer = dataFromDataContainer(objectGraph, onDeviceAd.appMetadata);
+ if (serverData.isDefinedNonNullNonEmpty(adData.meta) &&
+ serverData.isDefinedNonNullNonEmpty(metaContainer) &&
+ serverData.isDefinedNonNullNonEmpty(metaContainer.meta)) {
+ adData.meta.passthroughAdInfo = metaContainer.meta.passthroughAdInfo;
+ if (isSome(onDeviceAd === null || onDeviceAd === void 0 ? void 0 : onDeviceAd.alignedRegionDetails)) {
+ adData.meta.alignedRegionDetails = onDeviceAd === null || onDeviceAd === void 0 ? void 0 : onDeviceAd.alignedRegionDetails[0];
+ }
+ }
+ switch (onDeviceAd.placementType) {
+ case "today":
+ // Enable images for specific placements.
+ const imagesEnabled = adCommon.todayAdStyle(objectGraph) === "mediumLockup";
+ iadAttributes.format = {
+ images: imagesEnabled,
+ text: "",
+ userRating: false,
+ };
+ break;
+ case "searchLanding":
+ iadAttributes.format = {
+ images: true,
+ text: "",
+ userRating: false,
+ };
+ break;
+ default:
+ break;
+ }
+ adData.attributes["iad"] = iadAttributes;
+ adCommon.decorateAdInstanceIdOnData(objectGraph, adData, onDeviceAd.instanceId);
+ return mediaResponse;
+}
+/**
+ * Checks whether the metadata returned for the app is considered valid for the given placement.
+ * Some ad placements have special rules to be shown. These rules are validated here.
+ * @param objectGraph The App Store object graph.
+ * @param adResponse The ad response.
+ * @param placementType The placement the given ad data is to be placed in.
+ * @returns A fail reason if there is one. Otherwise null.
+ */
+function checkAppMetadataIsValidForPlacement(objectGraph, adResponse, placementType) {
+ switch (placementType) {
+ case "today":
+ return checkAppMetadataIsValidForToday(objectGraph, adResponse);
+ default:
+ return null;
+ }
+}
+/**
+ * Checks the metadata returned for the app is considered valid **specifically** for the Today placement.
+ *
+ * Today ad placements must:
+ * - Include a valid cppId in the `meta` field of the data that matches the cppId in the ad result, and
+ * - Have enough assets to fulfill the template requirement:
+ * - Portrait: at least 4 assets, one of which can be a video.
+ * - Landscape: at least 5 assets, one of which can be a video.
+ *
+ * @param objectGraph The App Store object graph.
+ * @param adResponse The ad response.
+ * @returns A fail reason if there is one. Otherwise null.
+ */
+function checkAppMetadataIsValidForToday(objectGraph, adResponse) {
+ var _a, _b, _c;
+ const adData = dataFromDataContainer(objectGraph, adResponse.mediaResponse);
+ const productVariantData = productVariantDataForData(objectGraph, adData);
+ const hasCPP = (_b = (_a = adResponse.onDeviceAd) === null || _a === void 0 ? void 0 : _a.cppIds) === null || _b === void 0 ? void 0 : _b.includes(productVariantData.productPageId);
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ const alignedRegionDetails = (_c = adResponse.onDeviceAd) === null || _c === void 0 ? void 0 : _c.alignedRegionDetails;
+ const creativeID = serverData.asString(alignedRegionDetails === null || alignedRegionDetails === void 0 ? void 0 : alignedRegionDetails[0], "apAssetId");
+ const selectedCustomCreativeId = getSelectedCustomCreativeId(adData);
+ const hasCreative = creativeID === selectedCustomCreativeId;
+ // First check there is a cppId for the ad that matches the `meta`.
+ if (!hasCPP && !hasCreative) {
+ // Then check if there is appMetadata for the custom creative ad.
+ return "cppAssetsMissing";
+ }
+ }
+ else {
+ if (!hasCPP) {
+ return "cppAssetsMissing";
+ }
+ }
+ return null;
+}
+// endregion
+//# sourceMappingURL=on-device-ad-fetch.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-stitch.js b/node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-stitch.js
new file mode 100644
index 0000000..4cded1e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/ads/on-device-ad-stitch.js
@@ -0,0 +1,98 @@
+/**
+ * Performs ad stitching for Search Landing page using `AdStitcher`
+ */
+"use strict";
+import { isNothing, isSome } from "@jet/environment";
+import { dataFromDataContainer } from "../../foundation/media/data-structure";
+import * as adStitch from "./ad-stitcher";
+// region Constants
+export const searchLandingPagePositionInfo = {
+ shelfIdentifier: "first",
+ slot: 0,
+};
+/// shelf identifier for ad stitcher position info
+export const searchLandingPageAdShelfIdentifier = "SLPPage";
+// region Setup
+/**
+ * Get a positionInfo from a locationTracker and index.
+ * @param locationTracker A location tracker to indicate the current parsing position.
+ * @param index The index of the current position.
+ * @returns A constructed `AdStitcherPositionInfo`.
+ */
+export function todayPositionInfoForLocationTrackerAndIndex(locationTracker, index) {
+ return {
+ shelfIdentifier: locationTracker.rootPosition.toString(),
+ slot: index,
+ };
+}
+/**
+ * Creates an Ad stitcher for on device adverts *specifically for the product page YMAL shelf*
+ * This is specific to this position as it hardcodes a shelf identifier.
+ * @param adResponse Ad response to configure stitcher with.
+ */
+export function adStitcherForOnDeviceProductPageYMALAdvertData(objectGraph, adResponse) {
+ var _a;
+ const rawPositionInfo = (_a = adResponse === null || adResponse === void 0 ? void 0 : adResponse.onDeviceAd) === null || _a === void 0 ? void 0 : _a.positionInfo;
+ if (isNothing(rawPositionInfo) || isNothing(adResponse)) {
+ return null;
+ }
+ let shelfIdentifier;
+ switch (adResponse.placementType) {
+ case "productPageYMAL":
+ shelfIdentifier = "customers-also-bought-apps";
+ break;
+ case "productPageYMALDuringDownload":
+ shelfIdentifier = "customers-also-bought-apps-download";
+ break;
+ default:
+ break;
+ }
+ if (isNothing(shelfIdentifier)) {
+ return null;
+ }
+ // The slot as provided by ad platforms is one-based - adjust it so we're working with zero-based numbers.
+ const adjustedRawSlot = rawPositionInfo.slot - 1;
+ const positionInfo = {
+ shelfIdentifier: shelfIdentifier,
+ slot: adjustedRawSlot,
+ };
+ return adStitcherForOnDeviceAdvertDataAndPositionInfo(objectGraph, adResponse, positionInfo);
+}
+/**
+ * Creates an Ad stitcher for on device adverts *specifically for search landing page*
+ * This is specific to SLP because we have a hardcoded position.
+ * @param adResponse Ad response to configure stitchcher with.
+ * @param landingPageResponse Search page response to configure the positionInfo.
+ */
+export function adStitcherForOnDeviceSLPAdvertData(objectGraph, adResponse, landingPageResponse) {
+ var _a;
+ const adMeta = (landingPageResponse === null || landingPageResponse === void 0 ? void 0 : landingPageResponse.meta) || null;
+ const slot = (_a = adMeta === null || adMeta === void 0 ? void 0 : adMeta.adDisplayStyle) === null || _a === void 0 ? void 0 : _a.slot;
+ if (isSome(slot)) {
+ return adStitcherForOnDeviceAdvertDataAndPositionInfo(objectGraph, adResponse, {
+ shelfIdentifier: searchLandingPageAdShelfIdentifier,
+ slot: slot,
+ });
+ }
+ else {
+ return adStitcherForOnDeviceAdvertDataAndPositionInfo(objectGraph, adResponse, searchLandingPagePositionInfo);
+ }
+}
+function adStitcherForOnDeviceAdvertDataAndPositionInfo(objectGraph, adResponse, positionInfo) {
+ const mediaResponse = adResponse === null || adResponse === void 0 ? void 0 : adResponse.mediaResponse;
+ if (isNothing(mediaResponse) || isSome(adResponse === null || adResponse === void 0 ? void 0 : adResponse.failureReason)) {
+ return null;
+ }
+ const stitcher = adStitch.newAdStitcher();
+ /**
+ * Stitch ad data to first lockup
+ */
+ const firstAdData = dataFromDataContainer(objectGraph, mediaResponse);
+ const firstLockupAdTask = {
+ data: firstAdData,
+ positionInfo: positionInfo,
+ };
+ adStitch.addTask(stitcher, firstLockupAdTask);
+ return stitcher;
+}
+//# sourceMappingURL=on-device-ad-stitch.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-events/app-events-common.js b/node_modules/@jet-app/app-store/tmp/src/common/app-events/app-events-common.js
new file mode 100644
index 0000000..cf5fd18
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-events/app-events-common.js
@@ -0,0 +1,83 @@
+import { GenericPage, AppEventDetailShelf, Shelf, Banner } from "../../api/models";
+import { attributeAsString } from "../../foundation/media/attributes";
+import { defaultAdditionalPlatformsForClient, Request } from "../../foundation/media/data-fetching";
+import { dataFromDataContainer } from "../../foundation/media/data-structure";
+import { relationshipData } from "../../foundation/media/relationships";
+import { appEventOrPromotionStartDateFromData } from "../app-promotions/app-event";
+import { appEventDetailPageFromData } from "../app-promotions/app-event-detail";
+import { supportedAppPlatformsFromData } from "../content/content";
+import { newLocationTracker } from "../metrics/helpers/location";
+import { metricsPageInformationFromMediaApiResponse } from "../metrics/helpers/page";
+import { create as createBanner } from "../product-page/banner";
+/**
+ * Generates a {@linkcode Request} to the `app-events` Media API endpoint
+ */
+export function makeAppEventPageRequest(objectGraph, intent) {
+ const mediaApiRequest = new Request(objectGraph)
+ .withIdOfType(intent.id, "app-events")
+ .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph))
+ .includingRelationships(["app"]);
+ if (objectGraph.client.isWeb) {
+ mediaApiRequest.includingScopedAttributes("app-events", ["description", "productArtwork", "productVideo"]);
+ mediaApiRequest.includingScopedAvailableIn("app-events", ["past"]);
+ }
+ return mediaApiRequest;
+}
+/**
+ * Builds a `GenericPage` that represents an App Event detail page
+ *
+ * This is used by the "web" client to build the view-model for the standalone
+ * "app event" pages
+ */
+export function makeShelfBasedAppEventDetailPage(objectGraph, container) {
+ const data = dataFromDataContainer(objectGraph, container);
+ if (!data) {
+ return null;
+ }
+ const parentApp = relationshipData(objectGraph, data, "app");
+ if (!parentApp) {
+ return null;
+ }
+ const metricsOptions = {
+ pageInformation: metricsPageInformationFromMediaApiResponse(objectGraph, "EventDetails", data.id, container),
+ locationTracker: newLocationTracker(),
+ };
+ const appEvent = appEventOrPromotionStartDateFromData(objectGraph, data, parentApp, false, // `hideLockupWhenNotInstalled`
+ false, // `includeClickAction`
+ "light", // `offerEnvironment`
+ "infer", // `offerStyle`
+ false, // `includeCrossLinkStyles`
+ metricsOptions, true, // `allowEndedEvents`
+ true, // `includeLockupClickAction`
+ null, // `editorialKind`
+ false, // `isArcadePage`
+ false);
+ if (!appEvent || appEvent instanceof Date) {
+ return null;
+ }
+ const appEventDetailPage = appEventDetailPageFromData(objectGraph, data, parentApp, appEvent, metricsOptions, true, // `includeLockupClickAction`
+ null, // `referrerData`
+ false);
+ if (!appEventDetailPage) {
+ return null;
+ }
+ const shelves = [];
+ // Build the "banner" if necessary
+ const bannerContext = {
+ appPlatforms: supportedAppPlatformsFromData(objectGraph, data),
+ offerButtonShouldBeDisabled: true,
+ metricsPageInformation: metricsOptions.pageInformation,
+ metricsLocationTracker: metricsOptions.locationTracker,
+ webBrowser: false,
+ };
+ const maybeBanner = createBanner(objectGraph, parentApp, bannerContext);
+ if (maybeBanner instanceof Banner) {
+ shelves.push(new Shelf("banner", null, [maybeBanner]));
+ }
+ const appEventDetailShelf = new AppEventDetailShelf(appEventDetailPage);
+ shelves.push(appEventDetailShelf);
+ const page = new GenericPage(shelves);
+ page.canonicalURL = attributeAsString(data, "url");
+ return page;
+}
+//# sourceMappingURL=app-events-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event-detail.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event-detail.js
new file mode 100644
index 0000000..ea5b203
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event-detail.js
@@ -0,0 +1,131 @@
+import * as models from "../../api/models";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as color from "../../foundation/util/color-util";
+import * as objects from "../../foundation/util/objects";
+import { addClickEventToAction } from "../metrics/helpers/clicks";
+import { MetricsReferralContext } from "../metrics/metrics-referral-context";
+import * as sharing from "../sharing";
+import * as appPromotionCommon from "./app-promotions-common";
+import { isNothing } from "@jet/environment";
+import { makeAppEventPageIntent } from "../../api/intents/app-event-page-intent";
+import { getLocale } from "../locale";
+import { getPlatform } from "../preview-platform";
+/**
+ * Create a flow action for navigating to the app event detail page.
+ * @param data The data blob
+ * @param parentAppData The associated parent app data
+ * @param appEvent The source app event
+ * @param baseMetricsOptions The base metrics options
+ * @param animationBehavior The animation behaviour for presenting the modal page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData Referrer data from an incoming deep link
+ */
+export function appEventDetailPageFlowActionFromData(objectGraph, data, parentAppData, appEvent, baseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData) {
+ const page = appEventDetailPageFromData(objectGraph, data, parentAppData, appEvent, baseMetricsOptions, includeLockupClickAction, referrerData, false);
+ const action = new models.FlowAction("appEventDetail");
+ action.title = appEvent.title;
+ action.pageData = page;
+ action.animationBehavior = animationBehavior;
+ if (baseMetricsOptions && baseMetricsOptions.pageInformation) {
+ action.referrerUrl = baseMetricsOptions.pageInformation.pageUrl;
+ }
+ if (objectGraph.client.isWeb) {
+ action.destination = makeAppEventPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: appEvent.appEventId,
+ });
+ action.pageUrl = mediaAttributes.attributeAsString(data, "url");
+ }
+ return action;
+}
+/**
+ * Creates an app event detail page
+ * @param objectGraph The object graph
+ * @param data The data blob
+ * @param parentAppData The data blob for the related parent app
+ * @param appEvent The source app event
+ * @param baseMetricsOptions The base metrics options to use for the detail page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData The referrer data
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ */
+export function appEventDetailPageFromData(objectGraph, data, parentAppData, appEvent, baseMetricsOptions, includeLockupClickAction, referrerData, isArcadePage) {
+ var _a, _b;
+ const artwork = appPromotionCommon.artworkFromData(objectGraph, data, "productArtwork");
+ const video = appPromotionCommon.videoFromData(objectGraph, data, "productVideo", true, true);
+ const copy = objects.shallowCopyOf(appEvent);
+ const selectedArtwork = (_a = video === null || video === void 0 ? void 0 : video.preview) !== null && _a !== void 0 ? _a : artwork;
+ let mediaOverlayStyle = "dark";
+ let isArtworkDark = true;
+ let includeBorderInDarkMode = false;
+ if (serverData.isDefinedNonNull(selectedArtwork)) {
+ isArtworkDark = color.isDarkColor(selectedArtwork.backgroundColor);
+ includeBorderInDarkMode = color.isDarkColor(selectedArtwork.backgroundColor, 10);
+ mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ }
+ const pageInformation = metricsHelpersPage.pageInformationForAppPromotionDetailPage(objectGraph, models.AppPromotionType.AppEvent, data.id, parentAppData.id, referrerData, (_b = baseMetricsOptions.recoMetricsData) !== null && _b !== void 0 ? _b : null);
+ const metricsOptions = {
+ ...baseMetricsOptions,
+ pageInformation: pageInformation,
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ targetType: "EventDetails",
+ };
+ const clickOptions = {
+ ...metricsOptions,
+ id: parentAppData.id,
+ inAppEventId: data.id,
+ relatedSubjectIds: [parentAppData.id],
+ };
+ copy.notificationConfig = appPromotionCommon.notificationConfigFromData(objectGraph, data, appEvent, clickOptions, false);
+ const shareAction = shareActionFromData(objectGraph, data, appEvent, clickOptions);
+ const offerEnvironment = isArtworkDark ? "dark" : "light";
+ const lockup = appPromotionCommon.lockupFromData(objectGraph, data, parentAppData, copy.title, offerEnvironment, "transparent", false, metricsOptions, includeLockupClickAction, referrerData, isArcadePage, false);
+ if (serverData.isNull(lockup)) {
+ return null;
+ }
+ copy.lockup = lockup;
+ const page = new models.AppEventDetailPage(copy, artwork, video, shareAction, mediaOverlayStyle, includeBorderInDarkMode);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation, (fields) => {
+ if (serverData.isDefinedNonNullNonEmpty(referrerData)) {
+ MetricsReferralContext.shared.addReferralContextToMetricsFieldsIfNecessary(fields);
+ }
+ });
+ return page;
+}
+/**
+ * Creates an action for sharing an app event
+ * @param data: The data blob
+ * @param appEvent The source app event
+ * @param metricsClickOptions The click options used for the containing context.
+ */
+function shareActionFromData(objectGraph, data, appEvent, metricsClickOptions) {
+ var _a, _b;
+ const url = mediaAttributes.attributeAsString(data, "url");
+ if (isNothing(url) || url.length === 0) {
+ return null;
+ }
+ // Prefer the module artwork first, otherwise fall back to the video preview frame, if available.
+ const artwork = (_a = appEvent.moduleArtwork) !== null && _a !== void 0 ? _a : (_b = appEvent.moduleVideo) === null || _b === void 0 ? void 0 : _b.preview;
+ let subtitle = objectGraph.loc.string("SHARE_APP_EVENT_SUBTITLE");
+ if (subtitle === "SHARE_APP_EVENT_SUBTITLE") {
+ subtitle = appEvent.subtitle;
+ }
+ const shareData = sharing.shareSheetDataForAppEvent(objectGraph, appEvent.title, subtitle, url, undefined, artwork);
+ if (!serverData.isDefinedNonNull(shareData)) {
+ return null;
+ }
+ const activities = sharing.shareSheetActivitiesForAppEvent(objectGraph, appEvent, url);
+ const action = new models.ShareSheetAction(shareData, activities);
+ addClickEventToAction(objectGraph, action, {
+ ...metricsClickOptions,
+ targetType: "lockup",
+ actionType: "share",
+ idType: "its_id",
+ });
+ return action;
+}
+//# sourceMappingURL=app-event-detail.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event.js
new file mode 100644
index 0000000..94eb7fe
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-event.js
@@ -0,0 +1,243 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as color from "../../foundation/util/color-util";
+import * as dateUtil from "../../foundation/util/date-util";
+import * as appPromotionCommon from "./app-promotions-common";
+/**
+ * Creates an app event object or a Date, if the event should not yet be shown, from the given data.
+ * @param data The data blob
+ * @param parentAppData The related parent app of this app event. If not provided will be derived from `data`.
+ * @param hideLockupWhenNotInstalled Whether to hide the lockup chin when the app is installed
+ * @param includeClickAction Whether to generate a click action for the app event
+ * @param offerEnvironment The preferred environment for the offer
+ * @param offerStyle The preferred style of the offer
+ * @param includeCrossLinkTitles Whether the cross link titles will be displayed when the app is installed
+ * @param allowMissingParentApp Whether to still create the app event if the parent app is missing
+ * @param baseMetricsOptions The base metrics options
+ * @param allowEndedEvents Whether events in the past are allowed
+ * @param includeLockupClickAction Whether to include the click action for the lockup
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ * @param allowUnpublishedAppEventPreviews Whether or not to allow app event previews
+ * @returns an AppEvent, or a Date if the event's `promotionStartDate` is in the future.
+ */
+export function appEventOrPromotionStartDateFromData(objectGraph, data, parentAppData, hideLockupWhenNotInstalled, includeClickAction, offerEnvironment, offerStyle, includeCrossLinkTitles, baseMetricsOptions, allowEndedEvents, includeLockupClickAction, editorialKind, isArcadePage, allowUnpublishedAppEventPreviews) {
+ var _a, _b, _c;
+ if (data.type !== "app-events") {
+ return null;
+ }
+ const promotionStartDateString = mediaAttributes.attributeAsString(data, "promotionStartDate");
+ if (isNothing(promotionStartDateString) || promotionStartDateString.length === 0) {
+ return null;
+ }
+ const promotionStartDate = new Date(promotionStartDateString);
+ if (isNothing(promotionStartDate)) {
+ return null;
+ }
+ const todayDate = new Date();
+ const isFutureDate = promotionStartDate.getTime() > todayDate.getTime();
+ if (isFutureDate && !allowUnpublishedAppEventPreviews) {
+ return promotionStartDate;
+ }
+ // Artwork / video
+ const moduleArtwork = appPromotionCommon.artworkFromData(objectGraph, data, "lockupArtwork");
+ const moduleVideo = appPromotionCommon.videoFromData(objectGraph, data, "lockupVideo", false, false);
+ if (isNothing(moduleArtwork) && serverData.isNullOrEmpty(moduleVideo)) {
+ return null;
+ }
+ const selectedArtwork = (_a = moduleVideo === null || moduleVideo === void 0 ? void 0 : moduleVideo.preview) !== null && _a !== void 0 ? _a : moduleArtwork;
+ const isArtworkDark = color.isDarkColor(selectedArtwork === null || selectedArtwork === void 0 ? void 0 : selectedArtwork.backgroundColor);
+ const mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ const includeBorderInDarkMode = color.isDarkColor(selectedArtwork === null || selectedArtwork === void 0 ? void 0 : selectedArtwork.backgroundColor, 10);
+ // Other required fields
+ const title = mediaAttributes.attributeAsString(data, "name");
+ let kind = mediaAttributes.attributeAsString(data, "kind");
+ if (serverData.isDefinedNonNullNonEmpty(editorialKind)) {
+ kind = editorialKind;
+ }
+ const startDateString = mediaAttributes.attributeAsString(data, "startDate");
+ if (isNothing(title) ||
+ title.length === 0 ||
+ isNothing(kind) ||
+ kind.length === 0 ||
+ isNothing(startDateString) ||
+ startDateString.length === 0) {
+ return null;
+ }
+ const startDate = new Date(startDateString);
+ if (isNothing(startDate)) {
+ return null;
+ }
+ // Description
+ const detail = (_b = mediaAttributes.attributeAsString(data, "description.standard")) !== null && _b !== void 0 ? _b : "";
+ // Lockup
+ const resolvedParentAppData = parentAppData !== null && parentAppData !== void 0 ? parentAppData : mediaRelationship.relationshipData(objectGraph, data, "app");
+ let lockup = null;
+ if (isSome(resolvedParentAppData)) {
+ lockup = appPromotionCommon.lockupFromData(objectGraph, data, resolvedParentAppData, title, offerEnvironment, offerStyle, includeCrossLinkTitles, baseMetricsOptions, includeLockupClickAction, null, isArcadePage, true);
+ }
+ if (isNothing(lockup)) {
+ objectGraph.console.warn(`Parent app for event ${data.id} is missing, returning null.`);
+ return null;
+ }
+ // Requirements
+ const requirements = mediaAttributes.attributeAsString(data, "requirement");
+ // Supplementary optional fields
+ const subtitle = mediaAttributes.attributeAsString(data, "subtitle");
+ const endDateString = mediaAttributes.attributeAsString(data, "endDate");
+ let endDate;
+ if (isSome(endDateString) && endDateString.length > 0) {
+ endDate = new Date(endDateString);
+ }
+ const hasEventEnded = endDate !== undefined && endDate.getTime() <= todayDate.getTime();
+ if (hasEventEnded && !allowEndedEvents) {
+ return null;
+ }
+ // Formatted dates
+ const badgeKindString = (_c = mediaAttributes.attributeAsString(data, "badgeKind")) !== null && _c !== void 0 ? _c : undefined;
+ const badgeKind = badgeKindFromString(badgeKindString, startDate, endDate);
+ const formattedDates = formattedDatesWithKind(objectGraph, badgeKind, startDate, endDate);
+ const appEvent = new models.AppEvent(data.id, moduleArtwork !== null && moduleArtwork !== void 0 ? moduleArtwork : undefined, moduleVideo !== null && moduleVideo !== void 0 ? moduleVideo : undefined, title, subtitle !== null && subtitle !== void 0 ? subtitle : undefined, detail, startDate, endDate, badgeKind, kind, requirements !== null && requirements !== void 0 ? requirements : undefined, lockup, hideLockupWhenNotInstalled, formattedDates, mediaOverlayStyle, includeBorderInDarkMode);
+ // Notifications
+ if (serverData.isDefinedNonNull(resolvedParentAppData)) {
+ const clickOptions = {
+ ...baseMetricsOptions,
+ id: resolvedParentAppData.id,
+ inAppEventId: data.id,
+ relatedSubjectIds: [resolvedParentAppData.id],
+ };
+ appEvent.notificationConfig = appPromotionCommon.notificationConfigFromData(objectGraph, data, appEvent, clickOptions, true);
+ }
+ // Click action
+ if (includeClickAction && serverData.isDefinedNonNull(resolvedParentAppData)) {
+ appEvent.clickAction = appPromotionCommon.detailPageClickActionFromData(objectGraph, data, resolvedParentAppData, appEvent, baseMetricsOptions, includeLockupClickAction);
+ }
+ return appEvent;
+}
+/**
+ * Determines the badge kind from the given string.
+ * @param badgeKindString The raw badge kind string
+ * @param startDate The start date of the app event
+ * @param endDate The end date of the app event, if any
+ * @returns An AppEventBadgeKind
+ */
+export function badgeKindFromString(badgeKindString, startDate, endDate) {
+ let badgeKind = (badgeKindString !== null && badgeKindString !== void 0 ? badgeKindString : models.AppEventBadgeKind.live);
+ if (badgeKind === models.AppEventBadgeKind.live && serverData.isDefinedNonNull(endDate)) {
+ // If the event is longer than 6 hours, override with "happening now" so that
+ // the blinking red live dot is less prevalent.
+ const difference = endDate.getTime() - startDate.getTime();
+ const sixHoursDifference = 1000 * 60 * 60 * 6;
+ if (difference > sixHoursDifference) {
+ badgeKind = models.AppEventBadgeKind.happening;
+ }
+ }
+ return badgeKind;
+}
+/**
+ * Generates a list of all the possible date variants for a given app event. The native
+ * client will look at this list and determine which variant to display, based
+ * on the current time. The reason for sending down all variants is that the client will
+ * continue to update the display as time progresses, so it's feasible that the client will
+ * cross over from date variant to the next (eg. TOMORROW 12:00 PM -> TODAY 12:00 PM).
+ * @param badgeKind The kind of badge used
+ * @param startDate The start date of the app event
+ * @param endDate The end date of the app event, if any
+ */
+export function formattedDatesWithKind(objectGraph, badgeKind, startDate, endDate) {
+ const formattedDates = [];
+ const startMidnight = dateUtil.convertLocalDateToLocalMidnight(startDate);
+ if (isNothing(startMidnight)) {
+ // This is only possible if `startDate` is "nothing", which means we can't proceed to create formatted dates anyway.
+ return [];
+ }
+ // Event starts 7+ days from now
+ // Example: JAN 15
+ const introductionDateFormat = objectGraph.loc.string("AppEvents.FormattedDate.SevenDaysOrMore.DateFormat");
+ const introductionText = objectGraph.loc.uppercased(objectGraph.loc.formatDate(introductionDateFormat, startDate));
+ const introductionFormattedDate = new models.AppEventFormattedDate(false, undefined, introductionText !== null && introductionText !== void 0 ? introductionText : undefined, undefined, undefined);
+ formattedDates.push(introductionFormattedDate);
+ // Event starts 2-6 days from now
+ // Example: FRI 12:00 PM
+ const sixDaysPrior = new Date(startMidnight);
+ sixDaysPrior.setDate(sixDaysPrior.getDate() - 6);
+ const sixDaysPriorDateFormat = objectGraph.loc.string("AppEvents.FormattedDate.SixDaysOrLess.DateFormat");
+ const sixDaysPriorText = objectGraph.loc.formatDate(sixDaysPriorDateFormat, startDate);
+ const sixDaysPriorTextUppercased = objectGraph.loc.uppercased(sixDaysPriorText);
+ const sixDaysPriorFormattedDate = new models.AppEventFormattedDate(false, sixDaysPrior, sixDaysPriorTextUppercased !== null && sixDaysPriorTextUppercased !== void 0 ? sixDaysPriorTextUppercased : undefined, undefined, undefined);
+ formattedDates.push(sixDaysPriorFormattedDate);
+ // Event starts tomorrow
+ // Example: TOMORROW 12:00 PM
+ const oneDayPrior = new Date(startMidnight);
+ oneDayPrior.setDate(oneDayPrior.getDate() - 1);
+ const tomorrowSentence = objectGraph.loc.string("AppEvents.FormattedDate.Tomorrow");
+ const tomorrowText = objectGraph.loc.formatDateInSentence(tomorrowSentence, "j:mm", startDate);
+ const tomorrowTextUppercased = objectGraph.loc.uppercased(tomorrowText);
+ const tomorrowFormattedDate = new models.AppEventFormattedDate(false, oneDayPrior, tomorrowTextUppercased !== null && tomorrowTextUppercased !== void 0 ? tomorrowTextUppercased : undefined, undefined, undefined);
+ formattedDates.push(tomorrowFormattedDate);
+ // Event starts today
+ // If the event starts within 1 hour of midnight, we don't need to show this case
+ // as it will be superceded by the 1 hour countdown
+ // Example: TODAY 12:00 PM, TONIGHT 7:00 PM
+ if (startDate.getHours() > 1 || (startDate.getHours() === 1 && startDate.getMinutes() > 0)) {
+ let todaySentence;
+ if (startDate.getHours() >= 19) {
+ todaySentence = objectGraph.loc.string("AppEvents.FormattedDate.Tonight");
+ }
+ else {
+ todaySentence = objectGraph.loc.string("AppEvents.FormattedDate.Today");
+ }
+ const todayText = objectGraph.loc.formatDateInSentence(todaySentence, "j:mm", startDate);
+ const todayTextUppercased = objectGraph.loc.uppercased(todayText);
+ const todayFormattedDate = new models.AppEventFormattedDate(false, startMidnight !== null && startMidnight !== void 0 ? startMidnight : undefined, todayTextUppercased !== null && todayTextUppercased !== void 0 ? todayTextUppercased : undefined, undefined, undefined);
+ formattedDates.push(todayFormattedDate);
+ }
+ // Event is starting in 1 hour or less
+ // Example: STARTS IN 59 MIN / AVAILABLE IN 59 MIN
+ const oneHourPrior = new Date(startDate);
+ oneHourPrior.setHours(oneHourPrior.getHours() - 1);
+ let countdownStringKey;
+ switch (badgeKind) {
+ case models.AppEventBadgeKind.available:
+ countdownStringKey = "AppEvents.FormattedDate.AvailableIn.MinutesCountdown";
+ break;
+ case models.AppEventBadgeKind.happening:
+ case models.AppEventBadgeKind.live:
+ default:
+ countdownStringKey = "AppEvents.FormattedDate.StartsIn.MinutesCountdown";
+ break;
+ }
+ const oneHourPriorFormattedDate = new models.AppEventFormattedDate(false, oneHourPrior, undefined, startDate, countdownStringKey);
+ formattedDates.push(oneHourPriorFormattedDate);
+ // Event is happening now
+ // Example: LIVE, HAPPENING NOW, NOW AVAILABLE
+ let liveText;
+ let showLiveIndicator;
+ switch (badgeKind) {
+ case models.AppEventBadgeKind.available:
+ liveText = objectGraph.loc.string("AppEvents.FormattedDate.NowAvailable");
+ showLiveIndicator = false;
+ break;
+ case models.AppEventBadgeKind.happening:
+ liveText = objectGraph.loc.string("AppEvents.FormattedDate.HappeningNow");
+ showLiveIndicator = false;
+ break;
+ case models.AppEventBadgeKind.live:
+ default:
+ liveText = objectGraph.loc.string("AppEvents.FormattedDate.Live");
+ showLiveIndicator = true;
+ break;
+ }
+ const liveFormattedDate = new models.AppEventFormattedDate(showLiveIndicator, startDate, liveText, undefined, undefined);
+ formattedDates.push(liveFormattedDate);
+ // Event has ended
+ // Example: EVENT ENDED
+ if (endDate !== null) {
+ const eventEndedFormattedDate = new models.AppEventFormattedDate(false, endDate, objectGraph.loc.string("AppEvents.FormattedDate.EventEnded"), undefined, undefined);
+ formattedDates.push(eventEndedFormattedDate);
+ }
+ return formattedDates;
+}
+//# sourceMappingURL=app-event.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotion.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotion.js
new file mode 100644
index 0000000..84afb2e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotion.js
@@ -0,0 +1,120 @@
+import * as models from "../../api/models";
+import * as appEventDetail from "./app-event-detail";
+import * as contingentOfferDetail from "./contingent-offer-detail";
+import * as offerItemDetail from "./offer-item-detail";
+import * as appEventModel from "./app-event";
+import * as contingentOfferModel from "./contingent-offer";
+import * as offerItemModel from "./offer-item";
+/**
+ * @param data The MAPI data to determine the promotion type
+ * @returns The App Promotion type, or null if the type is unknown
+ */
+export function promotionTypeFromData(data) {
+ switch (data.type) {
+ case "app-events":
+ return models.AppPromotionType.AppEvent;
+ case "contingent-items":
+ return models.AppPromotionType.ContingentOffer;
+ case "offer-items":
+ return models.AppPromotionType.OfferItem;
+ default:
+ return null;
+ }
+}
+/**
+ * @param data The MAPI data to determine the promotion type
+ * @returns The target type for metrics events for the given app promotion
+ */
+export function metricsTargetTypeFromData(data) {
+ switch (data.type) {
+ case "app-events":
+ return "eventModule";
+ case "contingent-items":
+ return "module";
+ case "offer-items":
+ return "module";
+ default:
+ return null;
+ }
+}
+/**
+ * @param data The MAPI data to determine the MetricsKind
+ * @returns The metrics kind for metrics events for the given app promotion
+ */
+export function metricsKindFromData(data) {
+ switch (data.type) {
+ case "app-events":
+ return "inAppEvent";
+ case "contingent-items":
+ return "contingentPriceOffer";
+ case "offer-items":
+ return "winbackPriceOffer";
+ default:
+ return null;
+ }
+}
+/**
+ * Creates an app promotion (App Event or Contingent Offer) object or a Date, if the event should not yet be shown, from the given data.
+ * @param data The data blob
+ * @param parentAppData The related parent app of this app promotion. If not provided will be derived from `data`.
+ * @param hideLockupWhenNotInstalled Whether to hide the lockup chin when the app is installed
+ * @param includeClickAction Whether to generate a click action for the app promotion
+ * @param offerEnvironment The preferred environment for the offer
+ * @param offerStyle The preferred style of the offer
+ * @param includeCrossLinkTitles Whether the cross link titles will be displayed when the app is installed
+ * @param allowMissingParentApp Whether to still create the app event if the parent app is missing
+ * @param baseMetricsOptions The base metrics options
+ * @param allowEndedEvents Whether events in the past are allowed
+ * @param includeLockupClickAction Whether to include the click action for the lockup
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ * @param allowUnpublishedAppEventPreviews Whether or not to allow event previews
+ * @returns an AppPromotion, or a Date if the event's `promotionStartDate` is in the future.
+ */
+export function appPromotionOrDateFromData(objectGraph, data, parentAppData, hideLockupWhenNotInstalled, includeClickAction, offerEnvironment, offerStyle, includeCrossLinkTitles, baseMetricsOptions, allowEndedEvents, includeLockupClickAction, editorialKind, isArcadePage, allowUnpublishedAppEventPreviews) {
+ const promotionType = promotionTypeFromData(data);
+ const promotionBaseMetricsOptions = {
+ ...baseMetricsOptions,
+ targetType: metricsTargetTypeFromData(data),
+ };
+ switch (promotionType) {
+ case models.AppPromotionType.AppEvent:
+ return appEventModel.appEventOrPromotionStartDateFromData(objectGraph, data, parentAppData, hideLockupWhenNotInstalled, includeClickAction, offerEnvironment, offerStyle, includeCrossLinkTitles, promotionBaseMetricsOptions, allowEndedEvents, includeLockupClickAction, editorialKind, isArcadePage, allowUnpublishedAppEventPreviews);
+ case models.AppPromotionType.ContingentOffer:
+ return contingentOfferModel.contingentOfferFromData(objectGraph, data, parentAppData, offerEnvironment, offerStyle, promotionBaseMetricsOptions, includeLockupClickAction, isArcadePage);
+ case models.AppPromotionType.OfferItem:
+ return offerItemModel.offerItemFromData(objectGraph, data, parentAppData, offerEnvironment, offerStyle, promotionBaseMetricsOptions, includeLockupClickAction, isArcadePage);
+ default:
+ return null;
+ }
+}
+/**
+ * Create a flow action for navigating to the app promotion detail page.
+ * @param data The data blob
+ * @param parentAppData The associated parent app data
+ * @param appPromotion The source app promotion
+ * @param baseMetricsOptions The base metrics options
+ * @param animationBehavior The animation behaviour for presenting the modal page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData Referrer data from an incoming deep link
+ */
+export function detailPageFlowActionFromData(objectGraph, data, parentAppData, appPromotion, baseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData) {
+ const promotionType = promotionTypeFromData(data);
+ const promotionBaseMetricsOptions = {
+ ...baseMetricsOptions,
+ targetType: metricsTargetTypeFromData(data),
+ };
+ switch (promotionType) {
+ case models.AppPromotionType.AppEvent:
+ const appEvent = appPromotion;
+ return appEventDetail.appEventDetailPageFlowActionFromData(objectGraph, data, parentAppData, appEvent, promotionBaseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData);
+ case models.AppPromotionType.ContingentOffer:
+ const contingentOffer = appPromotion;
+ return contingentOfferDetail.contingentOfferDetailPageFlowActionFromData(objectGraph, data, parentAppData, contingentOffer, promotionBaseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData);
+ case models.AppPromotionType.OfferItem:
+ const offerItem = appPromotion;
+ return offerItemDetail.offerItemDetailPageFlowActionFromData(objectGraph, data, parentAppData, offerItem, promotionBaseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData);
+ default:
+ return null;
+ }
+}
+//# sourceMappingURL=app-promotion.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-common.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-common.js
new file mode 100644
index 0000000..ffeac86
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-common.js
@@ -0,0 +1,385 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as platformAttributes from "../../foundation/media/platform-attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import { Host, Parameters, Protocol } from "../../foundation/network/url-constants";
+import * as urls from "../../foundation/network/urls";
+import * as objects from "../../foundation/util/objects";
+import * as videoDefaults from "../constants/video-constants";
+import * as contentArtwork from "../content/artwork/artwork";
+import * as contentAttributes from "../content/attributes";
+import * as content from "../content/content";
+import * as lockups from "../lockups/lockups";
+import * as metricsBuilder from "../metrics/builder";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersMisc from "../metrics/helpers/misc";
+import * as metricsHelpersModels from "../metrics/helpers/models";
+import * as appPromotionModel from "./app-promotion";
+import { formattedTextFromData } from "./contingent-offer";
+/**
+ * Convenience function for determining if app events are enabled.
+ */
+export function appEventsAreEnabled(objectGraph) {
+ return objectGraph.bag.enableAppEvents && (objectGraph.client.isiOS || objectGraph.client.isWeb);
+}
+/**
+ * Convenience function for determining if contingent items are enabled.
+ */
+export function appContingentItemsAreEnabled(objectGraph) {
+ const isContingentEnabledInBag = objectGraph.bag.enableContingentOffers;
+ return isContingentEnabledInBag && objectGraph.client.isiOS;
+}
+/**
+ * Convenience function for determining if offer items (Winback) items are enabled.
+ */
+export function appOfferItemsAreEnabled(objectGraph) {
+ return objectGraph.bag.enableOfferItems && objectGraph.client.isiOS;
+}
+/**
+ * Creates the artwork suitable for an app promotion
+ * @param data The data blob
+ * @param artworkKey The key used to derive the artwork from the data blob
+ */
+export function artworkFromData(objectGraph, data, artworkKey) {
+ const artworkData = mediaAttributes.attributeAsDictionary(data, artworkKey);
+ if (isNothing(artworkData)) {
+ return null;
+ }
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 0 /* content.ArtworkUseCase.Default */,
+ withJoeColorPlaceholder: true,
+ cropCode: "sr",
+ });
+ return artwork;
+}
+/**
+ * Creates the artwork suitable for an app promotion from the platform attributes
+ * @param data The data blob
+ * @param artworkKey The key used to derive the artwork from the data blob
+ */
+export function artworkFromPlatformData(objectGraph, data, artworkKey) {
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
+ if (isNothing(attributePlatform)) {
+ return null;
+ }
+ const artworkData = platformAttributes.platformAttributeAsDictionary(data, attributePlatform, artworkKey);
+ if (isNothing(artworkData)) {
+ return null;
+ }
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 0 /* content.ArtworkUseCase.Default */,
+ withJoeColorPlaceholder: true,
+ cropCode: "sr",
+ });
+ return artwork;
+}
+/**
+ * Creates the video suitable for an app promotion
+ * @param objectGraph
+ * @param data The data blob
+ * @param videoKey The key used to derive the video from the data blob
+ * @param canPlayFullScreen Whether the video should support full-screen playback
+ * @param isFullPage whether this video is being used on the full page
+ */
+export function videoFromData(objectGraph, data, videoKey, canPlayFullScreen, isFullPage) {
+ // Preview artwork
+ const previewArtwork = artworkFromData(objectGraph, data, `${videoKey}.previewFrame`);
+ if (serverData.isNull(previewArtwork)) {
+ return null;
+ }
+ // Video URL
+ const videoUrl = mediaAttributes.attributeAsString(data, `${videoKey}.video`);
+ if (serverData.isNull(videoUrl)) {
+ return null;
+ }
+ const autoplayPlaybackControls = {
+ muteUnmute: true,
+ };
+ const configuration = {
+ allowsAutoPlay: true,
+ looping: true,
+ canPlayFullScreen: canPlayFullScreen,
+ playbackControls: isFullPage ? videoDefaults.standardControls(objectGraph) : {},
+ autoPlayPlaybackControls: isFullPage ? autoplayPlaybackControls : {},
+ };
+ const video = new models.Video(videoUrl, previewArtwork, configuration);
+ video.canPlayFullScreen = canPlayFullScreen;
+ video.allowsAutoPlay = true;
+ video.looping = true;
+ return video;
+}
+/**
+ * Creates the lockup for an app event or contingent offer
+ * @param objectGraph The object graph.
+ * @param promotionData The data blob
+ * @param parentAppData The related parent app of this app promotion
+ * @param title The title of the app promotion
+ * @param offerEnvironment The preferred environment for the offer
+ * @param offerStyle The preferred style of the offer
+ * @param includeCrossLinkTitles Whether the cross link titles will be displayed when the app is installed
+ * @param baseMetricsOptions The base metrics options for the lockup
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData Referrer data from an incoming deep link
+ * @param isArcadePage Whether or not this is presented on the Arcade page.
+ * @param includeModuleClickLocation Whether or not this to push the module location to the location tracker.
+
+ */
+export function lockupFromData(objectGraph, promotionData, parentAppData, title, offerEnvironment, offerStyle, includeCrossLinkTitles, baseMetricsOptions, includeLockupClickAction, referrerData, isArcadePage, includeModuleClickLocation) {
+ var _a, _b, _c;
+ if (isNothing(promotionData) || isNothing(parentAppData)) {
+ // if (serverData.isNullOrEmpty(promotionData) || serverData.isNullOrEmpty(parentAppData)) {
+ return null;
+ }
+ const promotionType = appPromotionModel.promotionTypeFromData(promotionData);
+ // Push a content location, so that the lockup action has both the containing card (eg. event module)
+ // lockup location included.
+ const contentMetricsOptions = {
+ ...baseMetricsOptions,
+ id: promotionData.id,
+ relatedSubjectIds: [parentAppData.id],
+ idType: "its_id",
+ };
+ const lockupMetrics = {
+ ...baseMetricsOptions,
+ id: parentAppData.id,
+ relatedSubjectIds: [parentAppData.id],
+ targetType: "lockup",
+ idType: "its_id",
+ kind: null,
+ softwareType: null,
+ title: (_a = mediaAttributes.attributeAsString(parentAppData, "name")) !== null && _a !== void 0 ? _a : "",
+ excludeAttribution: serverData.isNullOrEmpty(referrerData),
+ };
+ if (promotionType === models.AppPromotionType.AppEvent) {
+ contentMetricsOptions["inAppEventId"] = promotionData.id;
+ lockupMetrics["inAppEventId"] = promotionData.id;
+ }
+ // If our base metrics options are in fact content metrics options, we want to carry across
+ // the ID and ID type. This specifically caters for heros / editorial cards.
+ if (metricsHelpersModels.isContentMetricsOptions(baseMetricsOptions)) {
+ contentMetricsOptions.id = baseMetricsOptions.id;
+ contentMetricsOptions.idType = baseMetricsOptions.idType;
+ }
+ if (includeModuleClickLocation) {
+ const locationTitle = promotionType === models.AppPromotionType.ContingentOffer
+ ? (_b = formattedTextFromData(objectGraph, promotionData)) === null || _b === void 0 ? void 0 : _b.rawTitle
+ : mediaAttributes.attributeAsString(promotionData, "name");
+ metricsHelpersLocation.pushContentLocation(objectGraph, contentMetricsOptions, locationTitle !== null && locationTitle !== void 0 ? locationTitle : "");
+ }
+ const externalDeepLinkUrl = mediaAttributes.attributeAsString(promotionData, "deepLink");
+ const lockupOptions = {
+ metricsOptions: lockupMetrics,
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ externalDeepLinkUrl: externalDeepLinkUrl !== null && externalDeepLinkUrl !== void 0 ? externalDeepLinkUrl : undefined,
+ crossLinkSubtitle: includeCrossLinkTitles ? title : undefined,
+ offerEnvironment: offerEnvironment,
+ offerStyle: offerStyle,
+ skipDefaultClickAction: !includeLockupClickAction,
+ includeBetaApps: true,
+ referrerData: referrerData !== null && referrerData !== void 0 ? referrerData : undefined,
+ shouldHideArcadeHeader: objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab") && isArcadePage,
+ parentAppData: parentAppData,
+ useJoeColorIconPlaceholder: true,
+ overrideArtworkTextColorKey: "textColor4",
+ };
+ const resolvedData = promotionType === models.AppPromotionType.AppEvent ? parentAppData : promotionData;
+ const lockup = lockups.lockupFromData(objectGraph, resolvedData, lockupOptions);
+ if (includeModuleClickLocation) {
+ metricsHelpersLocation.popLocation(baseMetricsOptions.locationTracker);
+ }
+ if (serverData.isNull(lockup)) {
+ return null;
+ }
+ if (includeCrossLinkTitles) {
+ lockup.crossLinkTitle = (_c = objectGraph.loc.uppercased(lockup.title)) !== null && _c !== void 0 ? _c : undefined;
+ }
+ return lockup;
+}
+export function notificationConfigFromData(objectGraph, data, appEvent, baseMetricsOptions, includeScheduledAction) {
+ // If the event has already started, we cannot set a notification reminder
+ if (appEvent.startDate.getTime() <= Date.now()) {
+ return null;
+ }
+ if (isNothing(appEvent.lockup)) {
+ return null;
+ }
+ const title = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_TITLE").replace("{appTitle}", appEvent.lockup.title);
+ const detail = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_DETAIL").replace("{eventTitle}", appEvent.title);
+ const displayTime = appEvent.startDate;
+ const icon = appEvent.lockup.icon;
+ const artworkUrl = appEvent.lockup.icon.template
+ .replace("{w}", `${icon.width}`)
+ .replace("{h}", `${icon.height}`)
+ .replace("{c}", "wd") // iOS rounded corners
+ .replace("{f}", "png");
+ // Notification scheduled action
+ let scheduledAction;
+ if (includeScheduledAction) {
+ scheduledAction = new models.AlertAction("toast");
+ scheduledAction.title = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_TOAST_TITLE");
+ scheduledAction.message = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_TOAST_DETAIL");
+ scheduledAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://bell.fill");
+ }
+ // The below if statement can be removed in Sydro timeframe
+ // Notifications not authorized action
+ let notAuthorizedAction;
+ if (objectGraph.bag.newEventsForODJAreEnabled) {
+ // When we have ODJs active we send a metrics click event to signal the Alert button was tapped. ODJ picks this up as a signal to
+ // show a half/full sheet notifications upsell to the user
+ const notAuthorizedMetricsAction = new models.BlankAction();
+ // Schedule click data
+ const scheduleClickFieldsNotAuthed = metricsHelpersMisc.fieldsFromPageInformation(baseMetricsOptions.pageInformation);
+ scheduleClickFieldsNotAuthed["actionType"] = "notifyActivateNotificationsDisabled";
+ scheduleClickFieldsNotAuthed["location"] = metricsHelpersLocation.createContentLocation(objectGraph, {
+ ...baseMetricsOptions,
+ id: data.id,
+ }, "");
+ // We want to actively remove the topic from this click event so it doesn't leave the device and is only consumed by ODJ
+ scheduleClickFieldsNotAuthed["topic"] = "";
+ const scheduleClickDataNotAuthed = metricsBuilder.createMetricsClickData(objectGraph, appEvent.lockup.adamId, "lockup", scheduleClickFieldsNotAuthed);
+ notAuthorizedMetricsAction.actionMetrics.addMetricsData(scheduleClickDataNotAuthed);
+ notAuthorizedAction = notAuthorizedMetricsAction;
+ }
+ else {
+ const notAuthorizedAlertAction = new models.AlertAction("default");
+ notAuthorizedAlertAction.title = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_NOT_AUTHORIZED_TITLE");
+ notAuthorizedAlertAction.message = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_NOT_AUTHORIZED_DETAIL");
+ notAuthorizedAlertAction.isCancelable = true;
+ notAuthorizedAlertAction.buttonTitles = [objectGraph.loc.string("ACTION_SETTINGS")];
+ // NOTE: This URL only works on iOS. If this feature is expanded beyond iOS, this code will need to be split per-platform.
+ notAuthorizedAlertAction.buttonActions = [
+ new models.ExternalUrlAction("prefs:root=NOTIFICATIONS_ID&path=com.apple.AppStore", true),
+ ];
+ notAuthorizedAction = notAuthorizedAlertAction;
+ }
+ const failureAction = new models.AlertAction("default");
+ failureAction.title = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_FAILURE_TITLE");
+ failureAction.message = objectGraph.loc.string("APP_EVENTS_NOTIFICATION_FAILURE_DETAIL");
+ failureAction.isCancelable = true;
+ // App launch trampoline URL
+ const appLaunchTrampolineUrl = new urls.URL()
+ .set("protocol", Protocol.storeKitUIServiceAppStore)
+ .param(Parameters.appId, appEvent.lockup.adamId)
+ .param(Parameters.bundleId, appEvent.lockup.bundleId)
+ .param(Parameters.appEventId, appEvent.appEventId);
+ appLaunchTrampolineUrl.host = Host.launchApp;
+ const externalDeepLinkUrl = mediaAttributes.attributeAsString(data, "deepLink");
+ if (isSome(externalDeepLinkUrl) && (externalDeepLinkUrl === null || externalDeepLinkUrl === void 0 ? void 0 : externalDeepLinkUrl.length) > 0) {
+ appLaunchTrampolineUrl.param(Parameters.appEventDeepLink, encodeURIComponent(externalDeepLinkUrl));
+ }
+ // Schedule click data
+ const scheduleClickFields = metricsHelpersMisc.fieldsFromPageInformation(baseMetricsOptions.pageInformation);
+ scheduleClickFields["actionType"] = "notifyActivate";
+ scheduleClickFields["location"] = metricsHelpersLocation.createContentLocation(objectGraph, {
+ ...baseMetricsOptions,
+ id: data.id,
+ }, "");
+ const scheduleClickData = metricsBuilder.createMetricsClickData(objectGraph, appEvent.lockup.adamId, "lockup", scheduleClickFields);
+ // Cancel schedule click data
+ const cancelScheduleClickFields = objects.shallowCopyOf(scheduleClickFields);
+ cancelScheduleClickFields["actionType"] = "notifyDeactivate";
+ const cancelScheduleClickData = metricsBuilder.createMetricsClickData(objectGraph, appEvent.lockup.adamId, "lockup", cancelScheduleClickFields);
+ return new models.AppEventNotificationConfig(data.id, title, detail, artworkUrl, displayTime, scheduledAction, notAuthorizedAction, failureAction, appLaunchTrampolineUrl.build(), scheduleClickData, cancelScheduleClickData);
+}
+/**
+ * Create a click action for navigating to the contingent offer detail page.
+ * @param data The data blob
+ * @param parentAppData The associated parent app data
+ * @param appPromotion The source app promotion
+ * @param baseMetricsOptions The base metrics options
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ */
+export function detailPageClickActionFromData(objectGraph, data, parentAppData, appPromotion, baseMetricsOptions, includeLockupClickAction) {
+ const action = appPromotionModel.detailPageFlowActionFromData(objectGraph, data, parentAppData, appPromotion, baseMetricsOptions, "infer", includeLockupClickAction, null);
+ if (isNothing(action)) {
+ return undefined;
+ }
+ const clickOptions = {
+ id: data.id,
+ actionDetails: {
+ action: "Open",
+ contentType: appPromotionModel.metricsTargetTypeFromData(data),
+ },
+ relatedSubjectIds: [parentAppData.id],
+ ...baseMetricsOptions,
+ };
+ const promotionType = appPromotionModel.promotionTypeFromData(data);
+ if (promotionType === models.AppPromotionType.AppEvent) {
+ clickOptions["inAppEventId"] = data.id;
+ }
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, clickOptions);
+ return action;
+}
+/**
+ * Creates the app events or contingent offers objects from the given data
+ * @param objectGraph The object graph
+ * @param appPromotionDataItems The array of app event / contingent offer data blobs.
+ * @param parentAppData The data for the parent app, if any.
+ * @param hideLockupWhenNotInstalled If true, the lockup will be hidden when the app is not locally installed
+ * @param includeCrossLinkTitles Whether the cross link titles will be displayed when the app is installed
+ * @param baseMetricsOptions The base metrics options for the app promotions
+ * @param allowEndedEvents Whether or not ended events should be returned
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ * @param allowUnpublishedAppEventPreviews Whether or not to allow app event previews
+ * @returns an DisplayableAppPromotions object including the relevant App Promotions, as well as an optional Date for when the next App Event should be visible.
+ */
+export function appPromotionsFromData(objectGraph, appPromotionDataItems, parentAppData = null, hideLockupWhenNotInstalled, includeCrossLinkTitles, baseMetricsOptions, allowEndedEvents, includeLockupClickAction, isArcadePage, allowUnpublishedAppEventPreviews) {
+ var _a;
+ const appPromotions = [];
+ let nextAppEventPromotionStartDate;
+ for (const data of appPromotionDataItems) {
+ const appPromotionOrDate = appPromotionModel.appPromotionOrDateFromData(objectGraph, data, parentAppData, hideLockupWhenNotInstalled, true, "light", "infer", includeCrossLinkTitles, baseMetricsOptions, allowEndedEvents, includeLockupClickAction, null, isArcadePage, allowUnpublishedAppEventPreviews);
+ if (serverData.isNull(appPromotionOrDate)) {
+ continue;
+ }
+ if (appPromotionOrDate instanceof Date) {
+ // Set the next event promotion start date if we don't yet have one, or it's sooner than the current one.
+ if (isNothing(nextAppEventPromotionStartDate) ||
+ appPromotionOrDate.getTime() < nextAppEventPromotionStartDate.getTime()) {
+ nextAppEventPromotionStartDate = appPromotionOrDate;
+ }
+ continue;
+ }
+ const appPromotionItem = appPromotionOrDate;
+ // Metrics
+ const impressionOptions = {
+ ...baseMetricsOptions,
+ id: data.id,
+ kind: appPromotionModel.metricsKindFromData(data),
+ targetType: appPromotionModel.metricsTargetTypeFromData(data),
+ title: (_a = appPromotionItem.title) !== null && _a !== void 0 ? _a : "",
+ softwareType: null,
+ };
+ const resolvedParentAppData = parentAppData !== null && parentAppData !== void 0 ? parentAppData : mediaRelationship.relationshipData(objectGraph, data, "app");
+ if (serverData.isDefinedNonNull(resolvedParentAppData)) {
+ impressionOptions.relatedSubjectIds = [resolvedParentAppData.id];
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, appPromotionItem, impressionOptions);
+ metricsHelpersLocation.nextPosition(impressionOptions.locationTracker);
+ appPromotions.push(appPromotionItem);
+ }
+ return {
+ appPromotions: appPromotions,
+ nextAppEventPromotionStartDate: nextAppEventPromotionStartDate,
+ };
+}
+/**
+ * Replaces keys inside a templated string with their computed values.
+ * @param templateString A templated string with keys that need to be replaced
+ * @param templateKeys A map of string keys to replacement strings
+ * @returns A filled out string with no keys
+ */
+export function replacingTemplatedKeys(templateString, templateKeys) {
+ let returnString = templateString !== null && templateString !== void 0 ? templateString : "";
+ Object.keys(templateKeys).forEach((element) => {
+ returnString = returnString.replace(element, templateKeys[element]);
+ });
+ return returnString;
+}
+// endregion
+//# sourceMappingURL=app-promotions-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-shelf.js
new file mode 100644
index 0000000..4949afa
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-shelf.js
@@ -0,0 +1,120 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as refresh from "../refresh/page-refresh-controller";
+import * as appPromotionsCommon from "./app-promotions-common";
+/**
+ * Builder for the app events shelf, on the product page.
+ * @param data The container data
+ * @param shelfMetrics The product page shelf metrics.
+ * @return The built shelf or null
+ */
+export function appPromotionsShelfForProductPage(objectGraph, data, shelfMetrics, refreshController) {
+ var _a;
+ if (!appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
+ return null;
+ }
+ let eventAndOffersData = mediaRelationship.relationshipViewsCollection(data, "events-and-offers");
+ if (isNothing(eventAndOffersData) || eventAndOffersData.length === 0) {
+ eventAndOffersData = mediaRelationship.relationshipCollection(data, "app-events");
+ }
+ if (isNothing(eventAndOffersData) || eventAndOffersData.length === 0) {
+ return null;
+ }
+ const recoMetricsData = mediaRelationship.relationshipViewsContainer(data, "events-and-offers");
+ const metricsOptions = {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ recoMetricsData: (_a = mediaDataStructure.metricsFromMediaApiObject(recoMetricsData)) !== null && _a !== void 0 ? _a : undefined,
+ };
+ const hasOffers = eventAndOffersData.some((dataItem) => dataItem.type !== "app-events");
+ const titleKey = hasOffers ? "ProductPage.Section.AppEventsAndOffers.Title" : "ProductPage.Section.AppEvents.Title";
+ const shelfTitle = objectGraph.loc.string(titleKey);
+ const shelfId = hasOffers ? "EventsAndOffers" : "Events";
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ recoMetricsData: metricsOptions.recoMetricsData,
+ targetType: "swoosh",
+ id: shelfId,
+ idType: "none",
+ }, shelfTitle);
+ const displayableAppEvents = appPromotionsCommon.appPromotionsFromData(objectGraph, eventAndOffersData, data, !hasOffers, false, metricsOptions, false, false, false, false);
+ if (isSome(displayableAppEvents.nextAppEventPromotionStartDate)) {
+ refresh.addNextPreferredContentRefreshDate(displayableAppEvents.nextAppEventPromotionStartDate, refreshController);
+ }
+ const appEvents = displayableAppEvents.appPromotions;
+ if (appEvents.length === 0) {
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ return null;
+ }
+ const shelf = appEventsShelf(objectGraph, appEvents, shelfTitle);
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "swoosh", shelfId, "none", metricsOptions.recoMetricsData);
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ return shelf;
+}
+/**
+ * Builder for the app events shelf, in a today article.
+ * @param objectGraph
+ * @param dataItems The array of data blobs
+ * @param metricsPageInformation The metrics page information.
+ * @param metricsLocationTracker The metrics location tracker.
+ * @param context
+ * @return The built shelf or null
+ */
+export function appEventsShelfForArticle(objectGraph, dataItems, metricsPageInformation, metricsLocationTracker, context) {
+ if (!appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
+ return null;
+ }
+ if (serverData.isNullOrEmpty(dataItems)) {
+ return null;
+ }
+ const metricsOptions = {
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ };
+ const displayableAppEvents = appPromotionsCommon.appPromotionsFromData(objectGraph, dataItems, null, false, false, metricsOptions, true, true, false, context.allowUnpublishedAppEventPreviews);
+ if (isSome(displayableAppEvents.nextAppEventPromotionStartDate) && isSome(context === null || context === void 0 ? void 0 : context.refreshController)) {
+ refresh.addNextPreferredContentRefreshDate(displayableAppEvents.nextAppEventPromotionStartDate, context === null || context === void 0 ? void 0 : context.refreshController);
+ }
+ const appEvents = displayableAppEvents.appPromotions;
+ if (appEvents.length === 0) {
+ return null;
+ }
+ const shelf = appEventsShelf(objectGraph, appEvents, undefined);
+ // Items should be centered in the article context.
+ shelf.isHorizontal = false;
+ // We don't really need this shelf impression, but without it the contained items
+ // won't be impressed as it is a horizontal shelf
+ const shelfMetricsOptions = {
+ ...metricsOptions,
+ id: "",
+ kind: null,
+ softwareType: null,
+ targetType: "swoosh",
+ title: "",
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ return shelf;
+}
+/**
+ * Convenience function for creating a shelf from a list of appEvents
+ * @param objectGraph
+ * @param appEvents The array of app events
+ * @param title The title for the shelf
+ */
+function appEventsShelf(objectGraph, appEvents, title) {
+ const shelfType = "appPromotion";
+ const shelf = new models.Shelf(shelfType);
+ shelf.isHorizontal = true;
+ shelf.title = title;
+ shelf.items = appEvents;
+ return shelf;
+}
+// endregion
+//# sourceMappingURL=app-promotions-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer-detail.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer-detail.js
new file mode 100644
index 0000000..64e75db
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer-detail.js
@@ -0,0 +1,106 @@
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as color from "../../foundation/util/color-util";
+import * as objects from "../../foundation/util/objects";
+import * as metricsBuilder from "../metrics/builder";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import { MetricsReferralContext } from "../metrics/metrics-referral-context";
+import * as appPromotionCommon from "./app-promotions-common";
+/**
+ * Create a flow action for navigating to the contingent offer detail page.
+ * @param data The data blob
+ * @param parentAppData The associated parent app data
+ * @param contingentOffer The source contingent offer
+ * @param baseMetricsOptions The base metrics options
+ * @param animationBehavior The animation behavior for presenting the modal page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData Referrer data from an incoming deep link
+ */
+export function contingentOfferDetailPageFlowActionFromData(objectGraph, data, parentAppData, contingentOffer, baseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData) {
+ const page = contingentOfferDetailPageFromData(objectGraph, data, parentAppData, contingentOffer, baseMetricsOptions, includeLockupClickAction, referrerData, false);
+ const action = new models.FlowAction("contingentOfferDetail");
+ action.title = contingentOffer.title;
+ action.pageData = page;
+ action.animationBehavior = animationBehavior;
+ if (baseMetricsOptions && baseMetricsOptions.pageInformation) {
+ action.referrerUrl = baseMetricsOptions.pageInformation.pageUrl;
+ }
+ return action;
+}
+/**
+ * Creates an contingent offer detail page
+ * @param objectGraph The object graph
+ * @param data The data blob
+ * @param parentAppData The data blob for the related parent app
+ * @param contingentOffer The source contingent offer
+ * @param baseMetricsOptions The base metrics options to use for the detail page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData The referrer data
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ */
+export function contingentOfferDetailPageFromData(objectGraph, data, parentAppData, contingentOffer, baseMetricsOptions, includeLockupClickAction, referrerData, isArcadePage) {
+ var _a, _b;
+ const artwork = appPromotionCommon.artworkFromPlatformData(objectGraph, data, "productArtwork");
+ const copy = objects.shallowCopyOf(contingentOffer);
+ let mediaOverlayStyle = "dark";
+ let isArtworkDark = true;
+ if (serverData.isDefinedNonNull(artwork)) {
+ isArtworkDark = color.isDarkColor(artwork.backgroundColor);
+ mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ }
+ const pageInformation = metricsHelpersPage.pageInformationForAppPromotionDetailPage(objectGraph, models.AppPromotionType.ContingentOffer, data.id, parentAppData.id, referrerData, (_a = baseMetricsOptions.recoMetricsData) !== null && _a !== void 0 ? _a : null);
+ const metricsOptions = {
+ ...baseMetricsOptions,
+ pageInformation: pageInformation,
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ };
+ const offerEnvironment = isArtworkDark ? "dark" : "light";
+ const lockup = appPromotionCommon.lockupFromData(objectGraph, data, parentAppData, (_b = copy.title) !== null && _b !== void 0 ? _b : undefined, offerEnvironment, "transparent", false, metricsOptions, includeLockupClickAction, referrerData, isArcadePage, false);
+ if (serverData.isNull(lockup)) {
+ return null;
+ }
+ copy.offerLockup = lockup;
+ copy.trunkAppIcon = contingentOffer.trunkAppIcon;
+ const page = new models.ContingentOfferDetailPage(copy, artwork !== null && artwork !== void 0 ? artwork : undefined, mediaOverlayStyle);
+ page.backButtonActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "back", pageInformation, metricsOptions.locationTracker));
+ page.learnMoreActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "LearnMore", pageInformation, metricsOptions.locationTracker, contingentOffer.learnMoreTitle));
+ page.closeButtonActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "close", pageInformation, metricsOptions.locationTracker));
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation, (fields) => {
+ if (serverData.isDefinedNonNullNonEmpty(referrerData)) {
+ MetricsReferralContext.shared.addReferralContextToMetricsFieldsIfNecessary(fields);
+ }
+ });
+ return page;
+}
+function createButtonActionMetricsForTarget(objectGraph, targetId, pageInformation, locationTracker, title) {
+ let actionType;
+ switch (targetId) {
+ case "LearnMore":
+ actionType = "navigate";
+ break;
+ case "back":
+ actionType = "back";
+ break;
+ case "close":
+ actionType = "dismiss";
+ break;
+ default:
+ break;
+ }
+ const eventFields = {
+ targetType: "button",
+ actionType,
+ targetId,
+ idType: undefined,
+ location: metricsHelpersLocation.createContentLocation(objectGraph, {
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ targetType: "button",
+ id: targetId,
+ }, title !== null && title !== void 0 ? title : targetId),
+ };
+ const event = metricsBuilder.createMetricsClickData(objectGraph, targetId, "button", eventFields);
+ return event;
+}
+//# sourceMappingURL=contingent-offer-detail.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer.js
new file mode 100644
index 0000000..8cc3cd7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/contingent-offer.js
@@ -0,0 +1,189 @@
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as appPromotionCommon from "./app-promotions-common";
+import * as offerFormatting from "../offers/offer-formatting";
+import * as offers from "../offers/offers";
+import * as color from "../../foundation/util/color-util";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import { contentAttributeAsDictionary } from "../content/attributes";
+import * as content from "../content/content";
+/**
+ * Creates a contingent offer object.
+ * @param data The data blob
+ * @param parentAppData The related parent app of this app event. If not provided will be derived from `data`.
+ * @param offerEnvironment The preferred environment for the offer
+ * @param offerStyle The preferred style of the offer
+ * @param baseMetricsOptions The base metrics options
+ * @param allowEndedEvents Whether events in the past are allowed
+ * @param includeLockupClickAction Whether to include the click action for the lockup
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ * @returns an ContingentOffer or null
+ */
+export function contingentOfferFromData(objectGraph, data, parentAppData, offerEnvironment, offerStyle, baseMetricsOptions, includeLockupClickAction, isArcadePage) {
+ var _a, _b, _c;
+ if (data.type !== "contingent-items") {
+ return null;
+ }
+ // Artwork
+ const moduleArtwork = appPromotionCommon.artworkFromPlatformData(objectGraph, data, "lockupArtwork");
+ if (serverData.isNull(moduleArtwork)) {
+ return null;
+ }
+ const isArtworkDark = color.isDarkColor(moduleArtwork.backgroundColor);
+ const mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ // Labels
+ const badge = mediaAttributes.attributeAsString(data, "badge");
+ const subtitle = mediaAttributes.attributeAsString(data, "subtitle");
+ const label = mediaAttributes.attributeAsString(data, "label");
+ const additionalInfoLabel = objectGraph.loc.string("ContingentOffer.AdditionalInfoButton.Title");
+ if (isNothing(badge) || isNothing(label)) {
+ return null;
+ }
+ // The discounted offer
+ const branch = mediaRelationship.relationshipData(objectGraph, data, "branch");
+ if (isNothing(branch) || serverData.isNullOrEmpty(serverData.asDictionary(branch, "meta.contingentItemOffer"))) {
+ return null;
+ }
+ const formattedText = formattedTextFromData(objectGraph, data);
+ if (isNothing(formattedText) || isNothing(formattedText === null || formattedText === void 0 ? void 0 : formattedText.title)) {
+ return null;
+ }
+ // The Apps
+ const resolvedBranchAppData = mediaRelationship.relationshipData(objectGraph, data, "branch-app");
+ const supportsStreamlinedBuy = mediaAttributes.attributeAsBooleanOrFalse(resolvedBranchAppData, "supportsStreamlinedBuy");
+ const offerLockup = appPromotionCommon.lockupFromData(objectGraph, data, resolvedBranchAppData, "", offerEnvironment, offerStyle, false, baseMetricsOptions, includeLockupClickAction, null, isArcadePage, true);
+ if (isNothing(offerLockup)) {
+ return null;
+ }
+ // Trunk Lockup. The app you need to be subscribed to in order to get the discounted offer
+ const resolvedTrunkAppData = mediaRelationship.relationshipData(objectGraph, data, "trunk-app");
+ let trunkAppIcon;
+ if (isSome(resolvedTrunkAppData) && isSome(subtitle) && (subtitle === null || subtitle === void 0 ? void 0 : subtitle.length) > 0) {
+ trunkAppIcon =
+ (_a = appPromotionCommon.artworkFromPlatformData(objectGraph, resolvedTrunkAppData, "artwork")) !== null && _a !== void 0 ? _a : appPromotionCommon.artworkFromData(objectGraph, resolvedTrunkAppData, "artwork");
+ }
+ // When displaying a first party offer the trunk artwork is in a different location
+ if (isSome(subtitle) && (subtitle === null || subtitle === void 0 ? void 0 : subtitle.length) > 0) {
+ trunkAppIcon = (_b = firstPartyTrunkLockupFromData(objectGraph, data)) !== null && _b !== void 0 ? _b : trunkAppIcon;
+ }
+ // Offer Strings
+ const additionalInfo = formattedAdditionalInfoFromData(objectGraph, data, branch);
+ const contingentOffer = new models.ContingentOffer(moduleArtwork, mediaOverlayStyle, supportsStreamlinedBuy, additionalInfoLabel, formattedText.title, subtitle !== null && subtitle !== void 0 ? subtitle : undefined, (_c = formattedText.description) !== null && _c !== void 0 ? _c : undefined, label, badge, additionalInfo, trunkAppIcon !== null && trunkAppIcon !== void 0 ? trunkAppIcon : undefined, offerLockup);
+ /// The raw title is used for metrics purposes
+ contingentOffer.title = formattedText.rawTitle;
+ // Detail page click action
+ if (serverData.isDefinedNonNull(resolvedBranchAppData)) {
+ contingentOffer.clickAction = appPromotionCommon.detailPageClickActionFromData(objectGraph, data, resolvedBranchAppData, contingentOffer, baseMetricsOptions, includeLockupClickAction);
+ }
+ return contingentOffer;
+}
+/**
+ * Creates the Title and description of the contingent offer out of a templated string
+ * @param data A data blob for the contingent-item
+ * @returns Creates a formatted string of the tile including the subscription price and period
+ */
+export function formattedTextFromData(objectGraph, data) {
+ var _a, _b, _c;
+ const branch = mediaRelationship.relationshipData(objectGraph, data, "branch");
+ if (isNothing(branch)) {
+ return undefined;
+ }
+ const regularOffer = offers.offerDataFromData(objectGraph, branch);
+ const discountedOffer = serverData.asDictionary(branch, "meta.contingentItemOffer");
+ if (serverData.isNullOrEmpty(regularOffer) || serverData.isNullOrEmpty(discountedOffer)) {
+ return undefined;
+ }
+ // Price Strings
+ // Adding \u{2060} (Word Joiner) prevents the string from breaking on the slash character
+ const regularPrice = (_a = formattedPrice(objectGraph, serverData.asString(regularOffer, "recurringSubscriptionPeriod"), serverData.asNumber(regularOffer, "numOfPeriods"), serverData.asString(regularOffer, "priceFormatted"))) === null || _a === void 0 ? void 0 : _a.replace("/", "/\u{2060}");
+ const discountedPrice = (_b = formattedPrice(objectGraph, serverData.asString(discountedOffer, "recurringSubscriptionPeriod"), serverData.asNumber(discountedOffer, "numOfPeriods"), serverData.asString(discountedOffer, "priceFormatted"))) === null || _b === void 0 ? void 0 : _b.replace("/", "/\u{2060}");
+ if (serverData.isNullOrEmpty(regularPrice) || serverData.isNullOrEmpty(discountedPrice)) {
+ return null;
+ }
+ const title = mediaAttributes.attributeAsString(data, "name");
+ const titleKeys = {
+ "@@discountedPrice@@/@@recurringSubscriptionPeriod@@": discountedPrice,
+ "@@regularPrice@@/@@recurringSubscriptionPeriod@@": regularPrice,
+ "@@discountedPricePerRecurringSubscriptionPeriod@@": discountedPrice,
+ "@@regularPricePerRecurringSubscriptionPeriod@@": regularPrice,
+ };
+ let titleTemplate = title !== null && title !== void 0 ? title : "";
+ Object.keys(titleKeys).forEach((element) => {
+ titleTemplate = titleTemplate.replace(element, titleKeys[element]);
+ });
+ const htmlRegex = /<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g;
+ const rawTitle = titleTemplate.replace(htmlRegex, "");
+ const formattedTitle = new models.Paragraph(titleTemplate, "text/x-apple-as3-nqml", "appPromotionTitle");
+ const developerDetailText = (_c = mediaAttributes.attributeAsString(data, "description.standard")) !== null && _c !== void 0 ? _c : "";
+ const detailTemplate = objectGraph.loc.string("ContingentOffer.Description.Format");
+ const descriptionKeys = {
+ "@@BranchName@@": mediaAttributes.attributeAsString(branch, "name"),
+ "@@RegularPrice@@": regularPrice,
+ "@@DiscountedPrice@@": discountedPrice,
+ };
+ let description = detailTemplate;
+ Object.keys(descriptionKeys).forEach((element) => {
+ description = description.replace(element, descriptionKeys[element]);
+ });
+ const formattedDescription = [description, developerDetailText].join(" ");
+ return {
+ title: formattedTitle,
+ rawTitle,
+ description: formattedDescription,
+ };
+}
+/**
+ * Creates a readable subscription price
+ * @param recurringSubscriptionPeriod The subscription recurrence in the server format. (eg P1M)
+ * @param price A price string in a readable format like $3.99.
+ * @returns A formatted string eg $3.99/month
+ */
+function formattedPrice(objectGraph, recurringSubscriptionPeriod, numberOfPeriods, price) {
+ const subscriptionRecurrence = offerFormatting.subscriptionRecurrenceForServerRecurrence(objectGraph, recurringSubscriptionPeriod, numberOfPeriods);
+ if (isNothing(subscriptionRecurrence) || isNothing(price)) {
+ return undefined;
+ }
+ return offerFormatting.priceDurationString(objectGraph, subscriptionRecurrence.type, subscriptionRecurrence.periodDuration, price);
+}
+/**
+ * Creates the additional info text of the contingent offer out of a templated string
+ * @param data A data blob
+ * @param branch A data blob containing the offer also known as the branch
+ * @param trunk A data blob containing the required subscription app also known as the trunk
+ * @returns Creates a formatted paragraph containing the contents of the additional info page.
+ */
+function formattedAdditionalInfoFromData(objectGraph, data, branch) {
+ const skuNameTitle = "<b>" + mediaAttributes.attributeAsString(branch, "name") + "</b>";
+ const skuDescription = mediaAttributes.attributeAsString(branch, "description.standard") + "<br>";
+ const termsTitle = "<b>" + objectGraph.loc.string("ContingentOffer.Terms.Title") + "</b>";
+ const terms = mediaAttributes.attributeAsString(data, "additionalTerms");
+ return new models.Paragraph([skuNameTitle, skuDescription, termsTitle, terms].join("<br>"), "text/x-apple-as3-nqml");
+}
+/**
+ * If the contingent offer has a an associated trunk use that instead of the trunk-app
+ * @param data The data blob
+ * @returns Creates a lockup to use as the TrunkLockup
+ */
+function firstPartyTrunkLockupFromData(objectGraph, data) {
+ var _a, _b;
+ const firstPartyTrunkData = serverData.asInterface(data, "meta.associations.trunks");
+ if (isNothing(firstPartyTrunkData)) {
+ return null;
+ }
+ const firstPartyApp = firstPartyTrunkData.data[0];
+ const shouldUseTrunkArtwork = (_b = (_a = firstPartyApp === null || firstPartyApp === void 0 ? void 0 : firstPartyApp.meta) === null || _a === void 0 ? void 0 : _a["useTrunkArtwork"]) !== null && _b !== void 0 ? _b : false;
+ if (shouldUseTrunkArtwork) {
+ const editorialArtwork = contentAttributeAsDictionary(objectGraph, firstPartyApp, "editorialArtwork.brandLogo");
+ if (isNothing(editorialArtwork)) {
+ return null;
+ }
+ return content.artworkFromApiArtwork(objectGraph, editorialArtwork, {
+ useCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ style: "roundedRect",
+ });
+ }
+ return null;
+}
+//# sourceMappingURL=contingent-offer.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item-detail.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item-detail.js
new file mode 100644
index 0000000..ef400cb
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item-detail.js
@@ -0,0 +1,147 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as objects from "../../foundation/util/objects";
+import * as metricsBuilder from "../metrics/builder";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import { MetricsReferralContext } from "../metrics/metrics-referral-context";
+import * as appPromotionCommon from "./app-promotions-common";
+/**
+ * Create a flow action for navigating to the offer detail page.
+ * @param data The data blob
+ * @param parentAppData The associated parent app data
+ * @param offerItem The source offer object
+ * @param baseMetricsOptions The base metrics options
+ * @param animationBehavior The animation behavior for presenting the modal page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData Referrer data from an incoming deep link
+ */
+export function offerItemDetailPageFlowActionFromData(objectGraph, data, parentAppData, offerItem, baseMetricsOptions, animationBehavior, includeLockupClickAction, referrerData) {
+ const page = offerItemDetailPageFromData(objectGraph, data, parentAppData, offerItem, baseMetricsOptions, includeLockupClickAction, referrerData, false);
+ const action = new models.FlowAction("offerItemDetail");
+ action.title = offerItem.title;
+ action.pageData = page;
+ action.animationBehavior = animationBehavior;
+ if (baseMetricsOptions && baseMetricsOptions.pageInformation) {
+ action.referrerUrl = baseMetricsOptions.pageInformation.pageUrl;
+ }
+ return action;
+}
+/**
+ * Creates an offer item detail page
+ * @param objectGraph The object graph
+ * @param data The data blob
+ * @param parentAppData The data blob for the related parent app
+ * @param offerItem The source offer
+ * @param baseMetricsOptions The base metrics options to use for the detail page
+ * @param includeLockupClickAction Whether to generate a click action for the lockup
+ * @param referrerData The referrer data
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ */
+export function offerItemDetailPageFromData(objectGraph, offerItemData, parentAppData, offerItem, baseMetricsOptions, includeLockupClickAction, referrerData, isArcadePage) {
+ var _a, _b;
+ let artwork = appPromotionCommon.artworkFromPlatformData(objectGraph, offerItemData, "productArtwork");
+ // rdar://126775681 (Remove force moduleArtwork to be null as MAPI still sends down artwork when it should not)
+ // The below `if` check should be uncommented when this is removed - it was commented as strict null checking is smart enough to know that `artwork` will always be null.
+ artwork = null;
+ const mediaOverlayStyle = "dark";
+ const isArtworkDark = true;
+ const includeBorderInDarkMode = false;
+ // if (serverData.isDefinedNonNullNonEmpty(artwork)) {
+ // isArtworkDark = color.isDarkColor(artwork?.backgroundColor);
+ // mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ // includeBorderInDarkMode = color.isDarkColor(artwork?.backgroundColor, 10);
+ // }
+ const copy = objects.shallowCopyOf(offerItem);
+ const pageInformation = metricsHelpersPage.pageInformationForAppPromotionDetailPage(objectGraph, models.AppPromotionType.OfferItem, offerItemData.id, parentAppData.id, referrerData, (_a = baseMetricsOptions.recoMetricsData) !== null && _a !== void 0 ? _a : null);
+ const metricsOptions = {
+ ...baseMetricsOptions,
+ pageInformation: pageInformation,
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ };
+ const offerEnvironment = isArtworkDark ? "dark" : "light";
+ const lockup = appPromotionCommon.lockupFromData(objectGraph, offerItemData, parentAppData, (_b = copy.title) !== null && _b !== void 0 ? _b : undefined, offerEnvironment, "transparent", false, metricsOptions, includeLockupClickAction, referrerData, isArcadePage, false);
+ if (serverData.isNull(lockup)) {
+ return null;
+ }
+ copy.offerLockup = lockup;
+ const additionalInfoLabel = objectGraph.loc.string("Winback.AdditionalInfoButton.Title");
+ const iapData = mediaRelationship.relationshipData(objectGraph, offerItemData, "salables");
+ const redemptionExpirationDate = mediaAttributes.attributeAsString(offerItemData, "redemptionExpirationDate");
+ let additionalInfo;
+ if (isSome(redemptionExpirationDate) && isSome(iapData)) {
+ const endDate = new Date(redemptionExpirationDate);
+ additionalInfo = formattedAdditionalInfoFromData(objectGraph, offerItemData, iapData, endDate);
+ }
+ const page = new models.OfferItemDetailPage(copy, artwork !== null && artwork !== void 0 ? artwork : undefined, undefined, mediaOverlayStyle, includeBorderInDarkMode, additionalInfoLabel, additionalInfo);
+ page.backButtonActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "back", pageInformation, metricsOptions.locationTracker));
+ page.learnMoreActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "LearnMore", pageInformation, metricsOptions.locationTracker, additionalInfoLabel));
+ page.closeButtonActionMetrics.addMetricsData(createButtonActionMetricsForTarget(objectGraph, "close", pageInformation, metricsOptions.locationTracker));
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation, (fields) => {
+ if (serverData.isDefinedNonNullNonEmpty(referrerData)) {
+ MetricsReferralContext.shared.addReferralContextToMetricsFieldsIfNecessary(fields);
+ }
+ });
+ return page;
+}
+function createButtonActionMetricsForTarget(objectGraph, targetId, pageInformation, locationTracker, title) {
+ let actionType;
+ switch (targetId) {
+ case "LearnMore":
+ actionType = "navigate";
+ break;
+ case "back":
+ actionType = "back";
+ break;
+ case "close":
+ actionType = "dismiss";
+ break;
+ default:
+ break;
+ }
+ const eventFields = {
+ targetType: "button",
+ actionType,
+ targetId,
+ idType: undefined,
+ location: metricsHelpersLocation.createContentLocation(objectGraph, {
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ targetType: "button",
+ id: targetId,
+ }, title !== null && title !== void 0 ? title : targetId),
+ };
+ const event = metricsBuilder.createMetricsClickData(objectGraph, targetId, "button", eventFields);
+ return event;
+}
+/**
+ * Creates the additional info text of the promotion out of a templated string
+ * @param data A data blob
+ * @param iapData A data blob containing the iap offer
+ * @returns Creates a formatted paragraph containing the contents of the additional info page.
+ */
+function formattedAdditionalInfoFromData(objectGraph, data, iapData, endDate) {
+ if (isNothing(iapData)) {
+ return undefined;
+ }
+ const skuName = mediaAttributes.attributeAsString(iapData, "name");
+ const skuNameFormatted = isSome(skuName) ? "<b>" + skuName + "</b>" : undefined;
+ const skuDescription = mediaAttributes.attributeAsString(iapData, "description.standard");
+ const skuDescriptionFormatted = isSome(skuDescription) ? skuDescription + "<br>" : undefined;
+ const termsTitle = "<b>" + objectGraph.loc.string("Promotion.Terms.Title") + "</b>";
+ let terms;
+ const redemptionDateFormat = objectGraph.loc.string("OfferItems.FormattedDate.RedemptionDate.DateFormat");
+ if (isSome(skuName) && isSome(endDate)) {
+ const templateKeyMap = {
+ "@@redemptionDate@@": objectGraph.loc.formatDate(redemptionDateFormat, endDate),
+ "@@skuName@@": skuName,
+ };
+ terms = appPromotionCommon.replacingTemplatedKeys(mediaAttributes.attributeAsString(data, "additionalTerms"), templateKeyMap);
+ }
+ const combinedStrings = [skuNameFormatted, skuDescriptionFormatted, termsTitle, terms].filter(isSome).join("<br>");
+ return new models.Paragraph(combinedStrings, "text/x-apple-as3-nqml");
+}
+//# sourceMappingURL=offer-item-detail.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item.js b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item.js
new file mode 100644
index 0000000..989ab6d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/offer-item.js
@@ -0,0 +1,206 @@
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as appPromotionCommon from "./app-promotions-common";
+import * as dateUtil from "../../foundation/util/date-util";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as offerFormatting from "../offers/offer-formatting";
+import * as offers from "../offers/offers";
+import * as content from "../content/content";
+import * as contentArtwork from "../content/artwork/artwork";
+/**
+ * Creates an offer item object.
+ * @param offerItemData The data blob
+ * @param parentAppData The related parent app of this app event. If not provided will be derived from `data`.
+ * @param offerEnvironment The preferred environment for the offer
+ * @param offerStyle The preferred style of the offer
+ * @param baseMetricsOptions The base metrics options
+ * @param allowEndedEvents Whether events in the past are allowed
+ * @param includeLockupClickAction Whether to include the click action for the lockup
+ * @param isArcadePage Whether or not this is presented on the Arcade page
+ * @returns an Offer Item or null
+ */
+export function offerItemFromData(objectGraph, offerItemData, parentAppData, offerEnvironment, offerStyle, baseMetricsOptions, includeLockupClickAction, isArcadePage) {
+ var _a, _b, _c, _d, _e, _f;
+ if (offerItemData.type !== "offer-items") {
+ return null;
+ }
+ const kind = mediaAttributes.attributeAsString(offerItemData, "kind");
+ if (kind !== "resubscription" && kind !== "winback") {
+ // Only resubscriptions are supported
+ return null;
+ }
+ const isArtworkDark = false;
+ const mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ // The Offers
+ const discountedIAP = mediaRelationship.relationshipData(objectGraph, offerItemData, "salables");
+ if (isNothing(discountedIAP)) {
+ return null;
+ }
+ const discountedOffer = serverData.asDictionary(discountedIAP, "meta.discountOffer");
+ const regularOffer = offers.offerDataFromData(objectGraph, discountedIAP);
+ if (isNothing(discountedOffer)) {
+ return null;
+ }
+ const resolvedParentApp = (_a = parentAppData !== null && parentAppData !== void 0 ? parentAppData : mediaRelationship.relationshipData(objectGraph, offerItemData, "app")) !== null && _a !== void 0 ? _a : mediaRelationship.relationshipData(objectGraph, discountedIAP, "app");
+ const supportsStreamlinedBuy = mediaAttributes.attributeAsBooleanOrFalse(resolvedParentApp, "supportsStreamlinedBuy");
+ // Offer Validity
+ const endDateString = mediaAttributes.attributeAsString(offerItemData, "redemptionExpirationDate");
+ if (isNothing(endDateString) || !isOfferValid(endDateString)) {
+ return null;
+ }
+ const endDate = new Date(endDateString);
+ let formattedExpiryDateShortString = formattedExpiryDate(objectGraph, endDate);
+ if (isNothing(formattedExpiryDateShortString)) {
+ formattedExpiryDateShortString = objectGraph.loc.string("OfferItems.Available.Now");
+ }
+ // IAP Artwork
+ let iapArtwork = content.iconFromData(objectGraph, discountedIAP, {
+ useCase: 3 /* content.ArtworkUseCase.LockupIconLarge */,
+ withJoeColorPlaceholder: true,
+ overrideTextColorKey: "textColor4",
+ });
+ /// A fallback artwork for rare cases where no artwork is found.
+ /// This should not happen in production
+ if (serverData.isNullOrEmpty(iapArtwork)) {
+ const blackColor = {
+ type: "rgb",
+ red: 0.0 / 255,
+ green: 0.0 / 255,
+ blue: 0.0 / 255,
+ alpha: 1,
+ };
+ iapArtwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://questionmark.circle", 200, 200, blackColor);
+ iapArtwork.style = "iap";
+ }
+ const subscriptionRecurrence = offerFormatting.subscriptionRecurrenceForServerRecurrence(objectGraph, serverData.asString(discountedOffer, "recurringSubscriptionPeriod"), serverData.asNumber(discountedOffer, "numOfPeriods"));
+ const discountDuration = isSome(subscriptionRecurrence)
+ ? offerFormatting.durationCountString(objectGraph, subscriptionRecurrence.type, subscriptionRecurrence.periodDuration * subscriptionRecurrence.periodCount)
+ : undefined;
+ // Price Strings
+ // Adding \u{2060} (Word Joiner) prevents the string from breaking on the slash character
+ const regularPrice = (_b = formattedPrice(objectGraph, serverData.asString(regularOffer, "recurringSubscriptionPeriod"), serverData.asNumber(regularOffer, "numOfPeriods"), serverData.asString(regularOffer, "priceFormatted"))) === null || _b === void 0 ? void 0 : _b.replace("/", "/\u{2060}");
+ const discountedPrice = (_c = formattedPrice(objectGraph, serverData.asString(discountedOffer, "recurringSubscriptionPeriod"), serverData.asNumber(discountedOffer, "numOfPeriods"), serverData.asString(discountedOffer, "priceFormatted"))) === null || _c === void 0 ? void 0 : _c.replace("/", "/\u{2060}");
+ /// A string map of the templated string in an offer
+ /// Fallback to the templated string if the string does not exist
+ const redemptionDateFormat = objectGraph.loc.string("OfferItems.FormattedDate.RedemptionDate.DateFormat");
+ const templateKeyMap = {
+ "@@redemptionDate@@": objectGraph.loc.formatDate(redemptionDateFormat, endDate),
+ "@@skuName@@": (_d = mediaAttributes.attributeAsString(discountedIAP, "name")) !== null && _d !== void 0 ? _d : "@@skuName@@",
+ "@@discountedPrice@@": discountedPrice !== null && discountedPrice !== void 0 ? discountedPrice : "@@discountedPrice@@",
+ "@@regularPricePerDuration@@": regularPrice !== null && regularPrice !== void 0 ? regularPrice : "@@regularPricePerDuration@@",
+ "@@discountDuration@@": discountDuration !== null && discountDuration !== void 0 ? discountDuration : "@@discountDuration@@",
+ "@@payUpfrontPrice@@": (_e = serverData.asString(discountedOffer, "priceFormatted")) !== null && _e !== void 0 ? _e : "@@payUpfrontPrice@@",
+ };
+ // Labels
+ const badge = mediaAttributes.attributeAsString(offerItemData, "badge");
+ if (isNothing(badge)) {
+ return null;
+ }
+ const title = appPromotionCommon.replacingTemplatedKeys(mediaAttributes.attributeAsString(offerItemData, "title"), templateKeyMap);
+ const formattedTitle = new models.Paragraph(title, "text/x-apple-as3-nqml", "appPromotionTitle");
+ const subtitle = appPromotionCommon.replacingTemplatedKeys(mediaAttributes.attributeAsString(offerItemData, "subtitle"), templateKeyMap);
+ const description = appPromotionCommon.replacingTemplatedKeys((_f = mediaAttributes.attributeAsString(offerItemData, "details")) !== null && _f !== void 0 ? _f : "", templateKeyMap);
+ // Offer Lockup
+ let offerLockup;
+ if (serverData.isDefinedNonNull(resolvedParentApp)) {
+ offerLockup = appPromotionCommon.lockupFromData(objectGraph, offerItemData, resolvedParentApp, "", offerEnvironment, offerStyle, false, baseMetricsOptions, includeLockupClickAction, null, isArcadePage, true);
+ }
+ if (isNothing(offerLockup)) {
+ return null;
+ }
+ const offerItem = new models.OfferItem(null, null, mediaOverlayStyle, supportsStreamlinedBuy, formattedTitle, formattedExpiryDateShortString, subtitle, description, badge, endDate, iapArtwork, offerLockup);
+ offerItem.title = title;
+ // Detail page click action
+ if (serverData.isDefinedNonNull(resolvedParentApp)) {
+ offerItem.clickAction = appPromotionCommon.detailPageClickActionFromData(objectGraph, offerItemData, resolvedParentApp, offerItem, baseMetricsOptions, includeLockupClickAction);
+ }
+ return offerItem;
+}
+/**
+ * Creates a readable subscription price
+ * @param recurringSubscriptionPeriod The subscription recurrence in the server format. (eg P1M)
+ * @param numberOfPeriods The number of periods the subscription recurs for.
+ * @param price A price string in a readable format like $3.99.
+ * @returns A formatted string eg $3.99/month
+ */
+function formattedPrice(objectGraph, recurringSubscriptionPeriod, numberOfPeriods, price) {
+ const subscriptionRecurrence = offerFormatting.subscriptionRecurrenceForServerRecurrence(objectGraph, recurringSubscriptionPeriod, numberOfPeriods);
+ if (isNothing(subscriptionRecurrence) || isNothing(price)) {
+ return null;
+ }
+ return offerFormatting.priceDurationString(objectGraph, subscriptionRecurrence === null || subscriptionRecurrence === void 0 ? void 0 : subscriptionRecurrence.type, subscriptionRecurrence === null || subscriptionRecurrence === void 0 ? void 0 : subscriptionRecurrence.periodDuration, price);
+}
+/**
+ * Checks the validity of a app promotion based on end date
+ * @param endDateString A string representation of the end date
+ * @returns bool of if the offer is valid or not
+ */
+function isOfferValid(endDateString) {
+ if (isNothing(endDateString) || serverData.isNullOrEmpty(endDateString)) {
+ return false;
+ }
+ const endDate = new Date(endDateString);
+ if (serverData.isNull(endDate)) {
+ return false;
+ }
+ const todayDate = new Date();
+ const hasEventEnded = endDate.getTime() <= todayDate.getTime();
+ if (hasEventEnded) {
+ return false;
+ }
+ return true;
+}
+/**
+ * Creates the availability label for a app promotion
+ * @param startDate The start date
+ * @param endDate The end date
+ * @returns string of the format the date should be shown
+ */
+function formattedExpiryDate(objectGraph, endDate) {
+ if (isNothing(endDate)) {
+ return null;
+ }
+ const endMidnight = dateUtil.convertLocalDateToLocalMidnight(endDate);
+ const currentDate = new Date();
+ const daysLeft = dateUtil.numberOfDaysBetween(currentDate, endMidnight);
+ if (isNothing(daysLeft)) {
+ return null;
+ }
+ const isSameDate = dateUtil.areLocalDatesSameDay(currentDate, endDate);
+ if (daysLeft > 90) {
+ return objectGraph.loc.string("OfferItems.Available.Now");
+ }
+ // Event ends 5+ days from now
+ // Example: EXPIRES DEC 31
+ if (daysLeft > 5) {
+ const expiryDateFormat = currentDate.getFullYear() !== endDate.getFullYear()
+ ? objectGraph.loc.string("OfferItems.FormattedDate.NextYear.DateFormat")
+ : objectGraph.loc.string("OfferItems.FormattedDate.FiveDaysOrMore.DateFormat");
+ const expiryDateString = objectGraph.loc.uppercased(objectGraph.loc.formatDate(expiryDateFormat, endDate));
+ if (isNothing(expiryDateString)) {
+ return null;
+ }
+ return objectGraph.loc
+ .string("OfferItems.FormattedDate.FiveDaysOrMore.Title")
+ .replace("@@date@@", expiryDateString);
+ }
+ // Event ends 2-5 days from now
+ // Example: EXPIRES IN 3 Days
+ if (daysLeft > 1) {
+ const fiveDaysPriorDateFormat = objectGraph.loc
+ .string("OfferItems.FormattedDate.FiveDaysOrLess.Title")
+ .replace("@@count@@", objectGraph.loc.formattedCount(daysLeft));
+ return fiveDaysPriorDateFormat;
+ }
+ // Event ends 1 day from now
+ // Example: EXPIRES TOMORROW
+ if (daysLeft === 1 && !isSameDate) {
+ return objectGraph.loc.string("OfferItems.FormattedDate.Tomorrow.Title");
+ }
+ // Event ends 1 day from now
+ // Example: EXPIRES TONIGHT
+ return objectGraph.loc.string("OfferItems.FormattedDate.Today.Title");
+}
+//# sourceMappingURL=offer-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-common.js b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-common.js
new file mode 100644
index 0000000..440d350
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-common.js
@@ -0,0 +1,441 @@
+/**
+ * Created by ls on 5/15/18.
+ */
+import { Color, isSome } from "@jet/environment";
+import * as models from "../../api/models/index";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { isDefinedNonNull } from "../../foundation/json-parsing/server-data";
+import * as mediaFetching from "../../foundation/media/data-fetching";
+import * as mediaNetwork from "../../foundation/media/network";
+import * as mediaRelationships from "../../foundation/media/relationships";
+import { Path, Protocol } from "../../foundation/network/url-constants";
+import * as urls from "../../foundation/network/urls";
+import * as artworkBuilder from "../content/artwork/artwork";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as productPageVariants from "../product-page/product-page-variants";
+import { MetricsIdentifierType } from "../../foundation/metrics/metrics-identifiers-cache";
+import { named } from "../../foundation/util/color-util";
+import { makeRoutableArcadeSeeAllPageIntent } from "../../api/intents/routable-arcade-see-all-page-intent";
+import { getPlatform } from "../preview-platform";
+import { getLocale } from "../locale";
+import { makeArcadeSeeAllCanonicalUrl } from "./arcade-see-all-routing";
+import { shouldUsePrerenderedIconArtwork } from "../content/content";
+// endregion
+// region Arcade Navigation Actions
+/**
+ * Creates a flow action for going to the page to see all Arcade games.
+ * Defaults to sorting by release date (descending)
+ * @param {ArcadeSeeAllGamesPageSort} sort The order which the games in response will be sorted in.
+ * @param metricsPageInformation
+ * @param metricsLocationTracker
+ * @returns {FlowAction} Flow action to Arcade see all games page.
+ */
+export function seeAllArcadeGamesPageFlowAction(objectGraph, sort = "releaseDate", metricsPageInformation, metricsLocationTracker, title = undefined, id = undefined, idType = undefined, targetType = "button") {
+ const seeAllGamesUrl = urls.URL.fromComponents(Protocol.internal, null, `/${Path.arcadeSeeAllGames}`, {
+ sort: sort,
+ });
+ const flowAction = new models.FlowAction("arcadeSeeAllGames", seeAllGamesUrl.build());
+ flowAction.title = title !== null && title !== void 0 ? title : objectGraph.loc.string("Arcade.SeeAllGames.Button.Title");
+ if (objectGraph.client.isWeb) {
+ const destination = makeRoutableArcadeSeeAllPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ });
+ const pageUrl = makeArcadeSeeAllCanonicalUrl(objectGraph, destination);
+ flowAction.destination = destination;
+ flowAction.pageUrl = pageUrl;
+ }
+ const itemId = id !== null && id !== void 0 ? id : (objectGraph.client.isVision ? "SeeAllGames" : "arcade-see-all-games-button");
+ const seeAllClickOptions = {
+ id: itemId,
+ idType: idType,
+ targetType: targetType,
+ actionType: "navigate",
+ actionContext: "Arcade",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, flowAction, seeAllClickOptions);
+ return flowAction;
+}
+/**
+ * Create a flow action for opening the Arcade Subscribe page with optional parameters
+ * @param context The context in which the Arcade Subscribe page is being opened for, e.g. where the flow action was initiated from.
+ * @param contextualAppId Optional app ID to associate with flow, if any. This is used for flow into contextual upsell sheet.
+ * @returns {FlowAction} Flow action to `arcadeSubscribe` page.
+ */
+export function arcadeSubscribePageFlowAction(objectGraph, context, contextualAppId, purchaseSuccessAction, options) {
+ var _a, _b, _c, _d;
+ const upsellRequestInfo = new models.MarketingItemRequestInfo("arcade", context, objectGraph.bag.metricsTopic, contextualAppId);
+ upsellRequestInfo.purchaseSuccessAction = purchaseSuccessAction;
+ upsellRequestInfo.carrierLinkSuccessAction = purchaseSuccessAction;
+ const action = new models.FlowAction("upsellMarketingItem");
+ if (isSome((_b = (_a = options === null || options === void 0 ? void 0 : options.pageInformation) === null || _a === void 0 ? void 0 : _a.searchTermContext) === null || _b === void 0 ? void 0 : _b.term)) {
+ upsellRequestInfo.metricsOverlay["searchTerm"] = (_c = options.pageInformation.searchTermContext) === null || _c === void 0 ? void 0 : _c.term;
+ }
+ const metricsIdentifierFields = (_d = objectGraph.metricsIdentifiersCache) === null || _d === void 0 ? void 0 : _d.getMetricsFieldsForTypes([
+ MetricsIdentifierType.user,
+ MetricsIdentifierType.client,
+ ]);
+ if (isSome(metricsIdentifierFields)) {
+ upsellRequestInfo.metricsOverlay = {
+ ...upsellRequestInfo.metricsOverlay,
+ ...metricsIdentifierFields,
+ };
+ }
+ action.pageData = upsellRequestInfo;
+ if (serverData.isDefinedNonNull(options)) {
+ metricsHelpersClicks.addClickEventToArcadeBuyInitiateAction(objectGraph, action, options);
+ }
+ return action;
+}
+/**
+ * Action to open the main Arcade page on each platform.
+ * Depending on the platform, this can be a tab change action or open action to separate Arcade app.
+ */
+export function openArcadeMainAction(objectGraph, metricsPageInformation, metricsLocationTracker, popToRoot) {
+ if (objectGraph.client.isTV) {
+ return openTVArcadeAppAction(objectGraph);
+ }
+ else {
+ const arcadeTabChangeAction = new models.TabChangeAction("arcade");
+ if (serverData.isDefinedNonNull(popToRoot)) {
+ arcadeTabChangeAction.popToRoot = popToRoot;
+ }
+ /*
+ * Presidio / Yukon timeframe workaround for <rdar://problem/53600942> Allow deserialized TabChangeAction to have use `title` property from JS instead of always using `nil`
+ * We're wrapping a single `TabChangeAction` within `CompoundAction` since `TabChangeAction` deserialization drops the JS provided title.
+ *
+ * Tracking removing this workaround in:
+ * <rdar://problem/53601182> Arcade: Remove workaround for having a tab change action with a title
+ */
+ return new models.CompoundAction([arcadeTabChangeAction]);
+ }
+}
+/**
+ * Creates an action to open Arcade app on tvOS.
+ */
+export function openTVArcadeAppAction(objectGraph) {
+ const url = "com.apple.Arcade://";
+ return new models.ExternalUrlAction(url);
+}
+// endregion
+/**
+ * Creates an action to open GamesUI.
+ */
+export function openGamesUIAction(objectGraph, target = { playNow: {} }) {
+ return new models.OpenGamesUIAction(target);
+}
+/**
+ * Creates Game Center header.
+ */
+export function makeGameCenterHeader(objectGraph, title = undefined, subtitle = undefined, useTitleArtwork = undefined) {
+ let eyebrowArtwork;
+ if (objectGraph.client.isTV) {
+ eyebrowArtwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://gamecenter.fill", 16, 16);
+ }
+ else {
+ eyebrowArtwork = artworkBuilder.createArtworkForResource(objectGraph, "resource://GameCenterEyebrow", 16, 16);
+ }
+ const isShelfHeaderEnabled = objectGraph.featureFlags.isEnabled("shelf_header");
+ const isGameCenterShelfHeaderEnabled = objectGraph.featureFlags.isEnabled("game_center_shelf_header");
+ const isGSEUIEnabled = objectGraph.featureFlags.isGSEUIEnabled("de7bbd8e");
+ if (isGSEUIEnabled) {
+ const configuration = {
+ eyebrowColor: named("secondaryText"),
+ includeSeparator: !isShelfHeaderEnabled,
+ prefersShelfHeader: isGameCenterShelfHeaderEnabled,
+ };
+ const eyebrow = objectGraph.loc.string("GAME_CENTER");
+ const shelfHeader = {
+ eyebrow: eyebrow,
+ eyebrowArtwork: eyebrowArtwork,
+ eyebrowArtworkType: models.ShelfHeaderArtworkType.Icon,
+ title: title,
+ subtitle: subtitle,
+ configuration: configuration,
+ };
+ return shelfHeader;
+ }
+ else {
+ const configuration = {
+ eyebrowColor: isGSEUIEnabled ? named("systemBlue") : undefined,
+ includeSeparator: !isShelfHeaderEnabled,
+ prefersShelfHeader: isGameCenterShelfHeaderEnabled,
+ };
+ if (isSome(useTitleArtwork) && useTitleArtwork) {
+ const shelfHeader = {
+ title: title,
+ titleArtwork: eyebrowArtwork,
+ titleArtworkType: models.ShelfHeaderArtworkType.Icon,
+ subtitle: subtitle,
+ configuration: configuration,
+ };
+ return shelfHeader;
+ }
+ else {
+ const eyebrow = objectGraph.loc.uppercased(objectGraph.loc.string("GAME_CENTER"));
+ const shelfHeader = {
+ eyebrow: eyebrow,
+ eyebrowArtwork: eyebrowArtwork,
+ eyebrowArtworkType: models.ShelfHeaderArtworkType.Icon,
+ title: title,
+ subtitle: subtitle,
+ configuration: configuration,
+ };
+ return shelfHeader;
+ }
+ }
+}
+// region Arcade Catalog MAPI Requests
+/**
+ * Base request for all MAPI requests fetching a set of Arcade games from catalog.
+ * This request has a server-defined implicit limit value (e.g. 100).
+ * This request should be bare-bones. If additional attributes are needed, it should be chained to this request.
+ *
+ * @returns {mediaFetching.Request} Request object for fetching Arcade apps from MAPI catalog endpoint.
+ */
+export function arcadeAppsRequest(objectGraph, includeComingSoon = false) {
+ let request = new mediaFetching.Request(objectGraph).forType("arcade-apps").includingAgeRestrictions();
+ if (includeComingSoon) {
+ request = request.addingQuery("with", "comingSoonApps");
+ }
+ // For visionOS, we need icons for bincompat games.
+ if (objectGraph.client.isVision) {
+ request = request.includingAdditionalPlatforms(["iphone", "ipad"]);
+ request.attributeIncludes.add("compatibilityControllerRequirement");
+ }
+ return request;
+}
+/**
+ * Request for fetching a set of arcade apps for displaying a set of Arcade Icons, e.g. for Arcade Grouping Footer, Contextual Upsell Icon Grid, and iOS Arcade Showcase.
+ * This request is a very special request with MAPI data containing only `artwork` attribute. Data is meant to be used *as-is* for showing a set of icons only.
+ *
+ * Requirements:
+ * - Icon Artwork for apps only. Additional metadata should be pruned if possible.
+ *
+ * @param limit Limit of apps. This should be configured to fit the view's needs.
+ */
+export function arcadeAppsRequestForIcons(objectGraph, limit) {
+ return arcadeAppsRequest(objectGraph)
+ .withSparseLimit(limit)
+ .asPartialResponseLimitedToFields(["artwork"])
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph));
+}
+// endregion
+// region Arcade Upsell Request
+export function arcadeUpsellRequest(objectGraph, context, contextualAppId) {
+ return arcadeUpsellMarketingItemRequest(objectGraph, context, contextualAppId);
+}
+export function arcadeUpsellMarketingItemRequest(objectGraph, context, contextualAppId) {
+ // We always want `context` to be provided, but some callers will provide this value from an messy source, e.g. extracted from URL param. Fall back to `generic` just in case.
+ if (serverData.isNullOrEmpty(context)) {
+ context = models.marketingItemContextFromString("generic");
+ }
+ const request = new mediaFetching.Request(objectGraph)
+ .forType("upsellMarketingItem")
+ .addingQuery("serviceType", "arcade")
+ .addingQuery("placement", context)
+ .includingMetaKeys("marketing-items", ["metrics"])
+ .includingRelationships(["contents"])
+ .includingAttributes(["marketingArtwork", "marketingVideo"])
+ .includingAgeRestrictions();
+ // Append app id that is promoted, if any.
+ if (serverData.isDefinedNonNull(contextualAppId)) {
+ request.addingQuery("seed", contextualAppId);
+ }
+ return request;
+}
+// endregion
+/**
+ * Grab recently played games.
+ * @param objectGraph The object graph.
+ * @param {number} limit The number of recently played games to return.
+ * @param {boolean} shouldHydrateAppsData Whether the apps data should be fetched from media api.
+ * @param {number} timeout A timeout in seconds.
+ * @returns {DataContainer} The media api data container with the recently played games.
+ */
+export async function getRecentlyPlayedGames(objectGraph, limit = null, shouldHydrateAppsData = false, timeout = null) {
+ return await new Promise((resolve, reject) => {
+ const isRecentlyPlayedGamesSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isTV;
+ // Check if the client supports recently played games.
+ if (!isRecentlyPlayedGamesSupported) {
+ resolve(null);
+ return;
+ }
+ const getRecentlyPlayGamesPromise = objectGraph.arcade.getRecentlyPlayedGamesWithTimeout(timeout);
+ // Get recently played games
+ getRecentlyPlayGamesPromise
+ .then((adamIds) => {
+ // Only perform request when there are recently played games.
+ if (serverData.isNull(adamIds) || adamIds.length === 0) {
+ resolve(null);
+ return;
+ }
+ // Enforce limit (if any).
+ if (serverData.isNumber(limit) && adamIds.length > limit) {
+ adamIds = adamIds.slice(0, limit);
+ }
+ if (shouldHydrateAppsData) {
+ // Fetch data for recently played games.
+ const attributes = [
+ "editorialArtwork",
+ "editorialVideo",
+ "description",
+ "minimumOSVersion",
+ "minPlayers",
+ "maxPlayers",
+ "remoteControllerRequirement",
+ "requiresGameController",
+ "supportsSharePlay",
+ ];
+ if (objectGraph.client.isVision) {
+ attributes.push("compatibilityControllerRequirement");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ const mediaApiRequest = new mediaFetching.Request(objectGraph)
+ .withIdsOfType(adamIds, "apps")
+ .includingAttributes(attributes);
+ const fetchDataPromise = mediaNetwork.fetchData(objectGraph, mediaApiRequest);
+ fetchDataPromise.then((dataContainer) => resolve(dataContainer), (reason) => {
+ objectGraph.console.log(`getRecentlyPlayedGames() failed when calling mediaNetwork.fetchData() with reason: ${reason}`);
+ resolve(null);
+ });
+ }
+ else {
+ // Create an incomplete data container to be fetched later.
+ const dataContainer = {
+ data: [],
+ };
+ adamIds.forEach((adamId) => {
+ dataContainer.data.push({
+ id: adamId,
+ type: "apps",
+ });
+ });
+ resolve(dataContainer);
+ }
+ })
+ .catch((reason) => {
+ objectGraph.console.log(`getRecentlyPlayedGames() failed with: ${reason}`);
+ resolve(null);
+ });
+ });
+}
+/**
+ * Convenience function to build a `ArcadeUpsellData` representation from upsell relationship joined to some data.
+ * @seealso `upsellFromContentsOfUpsellResponse`
+ * @param objectGraph The object graph
+ * @param data Data containing `upsell` relationship to build `ArcadeUpsellData` with
+ */
+export function upsellFromRelationshipOf(objectGraph, data) {
+ // Data to extract.
+ let marketingItemData = null;
+ const upsellDataContainer = objectGraph.client.isVision || preprocessor.GAMES_TARGET
+ ? mediaRelationships.relationship(data, "contents")
+ : mediaRelationships.relationship(data, "upsell") ||
+ mediaRelationships.relationship(data, "marketing-items");
+ if (serverData.isNullOrEmpty(upsellDataContainer) || serverData.isNullOrEmpty(upsellDataContainer.data)) {
+ return null;
+ }
+ // Create marketing items array from data container.
+ const marketingItems = upsellDataContainer.data
+ .map((item) => {
+ if (item.type === "marketing-items") {
+ return item;
+ }
+ else {
+ return null;
+ }
+ })
+ .filter((item) => isDefinedNonNull(item));
+ // Return null if there are NOT any marketing items.
+ if (serverData.isNullOrEmpty(marketingItems)) {
+ return null;
+ }
+ const timeout = objectGraph.bag.marketingItemSelectionTimeout;
+ // If there is only one marketing item or timeout is zero, set this as the data.
+ // Otherwise get a single marketing item by calling AMS.
+ if (marketingItems.length === 1 || timeout === 0) {
+ marketingItemData = marketingItems[0];
+ }
+ else {
+ try {
+ marketingItemData = objectGraph.arcade.getMarketingItemWithTimeout(marketingItems, timeout);
+ }
+ catch {
+ // Default to first item if the call was timed out.
+ marketingItemData = marketingItems[0];
+ }
+ }
+ // Return null if marketing item is null.
+ if (serverData.isNull(marketingItemData)) {
+ return null;
+ }
+ return {
+ marketingItemData: marketingItemData,
+ };
+}
+/**
+ * Convenience function to build a `ArcadeUpsellData` representation from contents of a engagement/upsell response.
+ * This doesn't really belong in
+ * @seealso `upsellFromRelationshipOf`
+ * @param arcadeUpsellResponse Response from the engagement/upsell endpoint.
+ */
+export function upsellFromContentsOfUpsellResponse(objectGraph, arcadeUpsellResponse) {
+ if (!arcadeUpsellResponse) {
+ return null;
+ }
+ let marketingItemData = null;
+ const responseDataArray = serverData.asArrayOrEmpty(arcadeUpsellResponse, "results.data");
+ if (responseDataArray.length > 0) {
+ marketingItemData = responseDataArray[0];
+ }
+ /**
+ * `arcadeUpsellResponse` is expected of form:
+ * {
+ * content: { mediaDataStructure.Data }
+ * }
+ * matching what is provided via the appropriate `action=<usage>` param from engagement/upsell endpoint.
+ */
+ if (!serverData.isDefinedNonNull(marketingItemData)) {
+ return null;
+ }
+ return {
+ marketingItemData: marketingItemData,
+ };
+}
+// endregion
+// region Arcade Games For You Request
+/**
+ * Request for fetching list of recommended Arcade apps from recommendations API.
+ * @param objectGraph The App Store object graph.
+ * @param limit Maximum number of apps to fetch.
+ * @returns {Request} Request object for fetching recommended Arcade apps from
+ * personalized recommendations endpoint.
+ */
+export function arcadeGamesForYouRequest(objectGraph, limit) {
+ let request = new mediaFetching.Request(objectGraph)
+ .forType("personal-recommendations")
+ .addingQuery("sparseLimit[contents]", `${limit}`)
+ .addingQuery("include[personal-recommendations]", "contents")
+ .addingQuery("filter[kind]", "arcadeGamesForYou")
+ .includingAgeRestrictions();
+ // For visionOS, we need to include bincompat apps.
+ if (objectGraph.client.isVision) {
+ request = request.includingAdditionalPlatforms(["iphone", "ipad"]);
+ }
+ return request;
+}
+// endregion
+// The color to use for Arcade content.
+export const arcadeColor = Color.fromRGB(1, 90 / 255, 80 / 255);
+//# sourceMappingURL=arcade-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-games-facets.js b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-games-facets.js
new file mode 100644
index 0000000..2418b5f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-games-facets.js
@@ -0,0 +1,245 @@
+/**
+ * Build methods for Arcade See All Games Facets.
+ */
+import * as models from "../../api/models";
+import { isDefinedNonNull, isDefinedNonNullNonEmpty, isNullOrEmpty } from "../../foundation/json-parsing/server-data";
+import { Parameters } from "../../foundation/network/url-constants";
+import * as metricsClickHelpers from "../metrics/helpers/clicks";
+// All Facet Options
+export const sortReleaseDateFacetOptionValue = `-releaseDateByDay&sort=name`;
+export const sortLastUpdatedFacetOptionValue = `-lastUpdatedByDay&sort=name`;
+export const sortNameFacetOptionValue = `alphabet&sort=name`;
+export const sortCategoryFacetOptionValue = `category&sort=-releaseDate`;
+export const sortFacetOptionParameter = "groupBy";
+export const comingSoonFacetOptionParameter = "with";
+export const comingSoonAppsFacetOptionValue = "comingSoonApps";
+export const comingSoonGroupingFacetOptionValue = "comingSoonGrouping";
+/**
+ * The facet for filtering bincompat games in visionOS
+ * By default, not passing this parameter will includes both bincompat and native games.
+ * To filter native games only, passing `filter[platform]=reality`
+ */
+export const binCompatGamesFacetOptionParameter = "filter[platform]";
+// The facet option for toggling video preview in lockups.
+// This facet is used for toggle `isCompactMode` locally, it don't match with any media api parameter.
+export const gamePreviewsFacetOptionParameter = "gamePreviews";
+export const facetOptionsParameterMapping = {};
+let areFacetsInitialized = false;
+let sortReleaseDateFacetOption = null;
+let sortLastUpdatedFacetOption = null;
+let sortNameFacetOption = null;
+let sortCategoryFacetOption = null;
+let ageRatingDefaultFacetOption = null;
+let ageRating4PlusFacetOption = null;
+let ageRating9PlusFacetOption = null;
+let ageRating12PlusFacetOption = null;
+let controllerSupportFacetOption = null;
+let multiplayerSupportFacetOption = null;
+let comingSoonFacetOption = null;
+let includeBinCompatGamesFacetOption = null;
+let onlyNativeGamesFacetOption = null;
+let gamePreviewsFacetOption = null;
+// Mapping of external facet parameters to media api paramter values
+const facetParameterMediaApiMapping = {};
+facetParameterMediaApiMapping[Parameters.sort] = sortFacetOptionParameter;
+facetParameterMediaApiMapping[Parameters.ageRating] = "filter[ageRating]";
+facetParameterMediaApiMapping[Parameters.controllerSupport] = "filter[supportsGameController]";
+facetParameterMediaApiMapping[Parameters.multiplayerSupport] = "filter[isMultiplayer]";
+facetParameterMediaApiMapping[Parameters.comingSoon] = comingSoonFacetOptionParameter;
+facetParameterMediaApiMapping[Parameters.binCompatGames] = binCompatGamesFacetOptionParameter;
+function initializeFacets(objectGraph) {
+ if (areFacetsInitialized) {
+ return;
+ }
+ areFacetsInitialized = true;
+ sortReleaseDateFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_SORTS_RELEASE_DATE"), sortReleaseDateFacetOptionValue, "releaseDate");
+ sortLastUpdatedFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_SORTS_LAST_UPDATED"), sortLastUpdatedFacetOptionValue, "lastUpdated");
+ sortNameFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_SORTS_NAME"), sortNameFacetOptionValue, "name");
+ sortCategoryFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_SORTS_CATEGORY"), sortCategoryFacetOptionValue, "category");
+ ageRatingDefaultFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_DEFAULT_ALL"), null);
+ ageRating4PlusFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_FILTERS_AGE_VALUE_4PLUS"), "4Plus");
+ ageRating9PlusFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_FILTERS_AGE_VALUE_9PLUS"), "9Plus");
+ ageRating12PlusFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_FILTERS_AGE_VALUE_12PLUS"), "12Plus");
+ controllerSupportFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_FILTERS_CONTROLLER_SUPPORT"), "true", null, "gamecontroller");
+ multiplayerSupportFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_FILTERS_MULTIPLAYER"), "true", null, "person.2");
+ comingSoonFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_FILTERS_COMING_SOON"), "true");
+ if (objectGraph.client.isVision) {
+ includeBinCompatGamesFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_FILTERS_BIN_COMPAT_GAMES"), null);
+ onlyNativeGamesFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_FILTERS_BIN_COMPAT_GAMES"), "realityDevice");
+ gamePreviewsFacetOption = new models.PageFacetOption(objectGraph.loc.string("PAGE_FACETS_FILTERS_GAME_PREVIEWS"), null);
+ }
+ facetOptionsParameterMapping[Parameters.sort] = {
+ releaseDate: [sortReleaseDateFacetOption],
+ lastUpdated: [sortLastUpdatedFacetOption],
+ name: [sortNameFacetOption],
+ category: [sortCategoryFacetOption],
+ };
+ facetOptionsParameterMapping[Parameters.ageRating] = {
+ "4plus": [ageRating4PlusFacetOption],
+ "9plus": [ageRating9PlusFacetOption],
+ "12plus": [ageRating12PlusFacetOption],
+ };
+ facetOptionsParameterMapping[Parameters.controllerSupport] = {
+ true: [controllerSupportFacetOption],
+ false: [],
+ };
+ facetOptionsParameterMapping[Parameters.multiplayerSupport] = {
+ true: [multiplayerSupportFacetOption],
+ false: [],
+ };
+ facetOptionsParameterMapping[Parameters.comingSoon] = {
+ true: [comingSoonFacetOption],
+ false: [],
+ };
+ if (objectGraph.client.isVision) {
+ facetOptionsParameterMapping[Parameters.binCompatGames] = {
+ true: [includeBinCompatGamesFacetOption],
+ false: [onlyNativeGamesFacetOption],
+ };
+ facetOptionsParameterMapping[Parameters.gamePreviews] = {
+ true: [gamePreviewsFacetOption],
+ false: [],
+ };
+ }
+}
+/**
+ *
+ * Create Page Facets. Theres platform specific variations here.
+ */
+export function createArcadeSeeAllGamesFacets(objectGraph) {
+ initializeFacets(objectGraph);
+ const isEnabledVisionSAGSegmentedNavFeature = objectGraph.featureFlags.isEnabled("see_all_games_segmented_nav_2024A");
+ const ageRatingsFacetTitle = objectGraph.client.isMac
+ ? objectGraph.loc.string("PAGE_FACETS_AGE_RATINGS_TITLE")
+ : objectGraph.loc.string("PAGE_FACETS_FILTERS_AGE_RATINGS");
+ const ageRatingsFacet = new models.PageFacetsFacet(Parameters.ageRating, "filter[ageRating]", ageRatingsFacetTitle, "singleSelection", [ageRatingDefaultFacetOption, ageRating4PlusFacetOption, ageRating9PlusFacetOption, ageRating12PlusFacetOption], [ageRatingDefaultFacetOption], "age", pageFacetChangeAction(objectGraph, "sort"));
+ // Sorts
+ const isEnabledArcadeSeeAllGamesUplift = objectGraph.featureFlags.isEnabled("arcade_see_all_games_menu_uplift");
+ const isFeatureFlagEnabled = (objectGraph.client.isiOS && isEnabledArcadeSeeAllGamesUplift) ||
+ (objectGraph.client.isVision && isEnabledVisionSAGSegmentedNavFeature);
+ const sortsFacet = new models.PageFacetsFacet(Parameters.sort, sortFacetOptionParameter, objectGraph.loc.string("PAGE_FACETS_SORT_BY_TITLE"), "singleSelection", [sortReleaseDateFacetOption, sortLastUpdatedFacetOption, sortNameFacetOption, sortCategoryFacetOption], isFeatureFlagEnabled ? [sortReleaseDateFacetOption] : null, "sort", pageFacetChangeAction(objectGraph, "sort"));
+ // Controllers
+ const controllerSupportFacetOptionFacetTitle = objectGraph.client.isMac
+ ? objectGraph.loc.string("PAGE_FACETS_FILTERS_CONTROLLERS")
+ : objectGraph.loc.string("PAGE_FACETS_FILTERS_CONTROLLER_SUPPORT");
+ const controllerSupportFacetOptionFacet = new models.PageFacetsFacet(Parameters.controllerSupport, "filter[supportsGameController]", controllerSupportFacetOptionFacetTitle, "toggle", [controllerSupportFacetOption], null, "controllerSupport", pageFacetChangeAction(objectGraph, "sort"));
+ const multiplayerFacetTitle = objectGraph.client.isMac
+ ? objectGraph.loc.string("PAGE_FACETS_FILTERS_MULTIPLAYER")
+ : objectGraph.loc.string("PAGE_FACETS_FILTERS_MULTIPLAYER_GAMES");
+ const multiplayerSupportFacetOptionFacet = new models.PageFacetsFacet(Parameters.multiplayerSupport, "filter[isMultiplayer]", multiplayerFacetTitle, "toggle", [multiplayerSupportFacetOption], null, "multiplayer", pageFacetChangeAction(objectGraph, "sort"));
+ const comingSoonFacet = new models.PageFacetsFacet(Parameters.comingSoon, comingSoonFacetOptionParameter, objectGraph.loc.string("PAGE_FACETS_FILTERS_COMING_SOON"), "toggle", [comingSoonFacetOption], [comingSoonFacetOption], "comingSoon", pageFacetChangeAction(objectGraph, "comingSoon"));
+ comingSoonFacet.isHiddenFromMenu = isFeatureFlagEnabled;
+ const pageFacets = new models.PageFacets([], false, null);
+ if (objectGraph.client.isVision) {
+ sortsFacet.displayOptionsInline = true;
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([sortsFacet], objectGraph.loc.string("PAGE_FACETS_SORT_BY_TITLE_LOWERCASE")));
+ ageRatingsFacet.showsSelectedOptions = true;
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([controllerSupportFacetOptionFacet, multiplayerSupportFacetOptionFacet, ageRatingsFacet], objectGraph.loc.string("PAGE_FACETS_FILTER_BY_TITLE_LOWERCASE")));
+ const binCompatGamesFacet = createBinCompatGamesFacet(objectGraph);
+ const gamePreviewsFacet = new models.PageFacetsFacet(Parameters.gamePreviews, gamePreviewsFacetOptionParameter, objectGraph.loc.string("PAGE_FACETS_FILTERS_GAME_PREVIEWS"), "toggle", [gamePreviewsFacetOption], [gamePreviewsFacetOption], "gamePreviews", pageFacetChangeAction(objectGraph, "gamePreviews"));
+ const includeGroupsTitle = objectGraph.client.isVision && isEnabledVisionSAGSegmentedNavFeature
+ ? objectGraph.loc.string("PAGE_FACETS_SHOW_TITLE_LOWERCASE")
+ : objectGraph.loc.string("PAGE_FACETS_INCLUDE_TITLE_LOWERCASE");
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([binCompatGamesFacet, gamePreviewsFacet, comingSoonFacet], includeGroupsTitle));
+ }
+ else {
+ switch (objectGraph.client.deviceType) {
+ case "mac":
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([ageRatingsFacet]));
+ if (objectGraph.bag.enableComingSoonToggle) {
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([comingSoonFacet], objectGraph.loc.string("PAGE_FACETS_INCLUDE_TITLE")));
+ }
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([controllerSupportFacetOptionFacet, multiplayerSupportFacetOptionFacet], objectGraph.loc.string("PAGE_FACETS_SUPPORTS_TITLE")));
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([sortsFacet]));
+ break;
+ case "tv":
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([ageRatingsFacet], objectGraph.loc.string("PAGE_FACETS_AGE_RATINGS_TITLE_LOWERCASE")));
+ const filterGroupFacets = [
+ controllerSupportFacetOptionFacet,
+ multiplayerSupportFacetOptionFacet,
+ ];
+ if (objectGraph.bag.enableComingSoonToggle) {
+ filterGroupFacets.push(comingSoonFacet);
+ }
+ pageFacets.facetGroups.push(new models.PageFacetsGroup(filterGroupFacets, objectGraph.loc.string("PAGE_FACETS_FILTERS_TITLE")));
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([sortsFacet], objectGraph.loc.string("PAGE_FACETS_SORT_BY_TITLE_LOWERCASE")));
+ break;
+ default:
+ sortsFacet.displayOptionsInline = true;
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([sortsFacet], objectGraph.loc.string("PAGE_FACETS_SORT_BY_TITLE")));
+ ageRatingsFacet.showsSelectedOptions = true;
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([controllerSupportFacetOptionFacet, multiplayerSupportFacetOptionFacet, ageRatingsFacet], objectGraph.loc.string("PAGE_FACETS_FILTER_BY_TITLE")));
+ if (objectGraph.bag.enableComingSoonToggle) {
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([comingSoonFacet], objectGraph.loc.string("PAGE_FACETS_INCLUDE_TITLE")));
+ }
+ break;
+ }
+ }
+ return pageFacets;
+}
+function createBinCompatGamesFacet(objectGraph) {
+ const binCompatGamesFacet = new models.PageFacetsFacet(Parameters.binCompatGames, binCompatGamesFacetOptionParameter, objectGraph.loc.string("PAGE_FACETS_FILTERS_BIN_COMPAT_GAMES"), "toggle", [includeBinCompatGamesFacetOption], [includeBinCompatGamesFacetOption], "binCompatGames", pageFacetChangeAction(objectGraph, "binCompatGames"));
+ // Hiding this facet from the Filters menu, because this facet will be control by the page segmented picker.
+ binCompatGamesFacet.isHiddenFromMenu = objectGraph.featureFlags.isEnabled("see_all_games_segmented_nav_2024A");
+ return binCompatGamesFacet;
+}
+export function createDefaultSelectedFacetOptions(objectGraph, urlParameters = {}) {
+ initializeFacets(objectGraph);
+ const selectedFacetOptions = {
+ "filter[ageRating]": [ageRatingDefaultFacetOption],
+ };
+ selectedFacetOptions[sortFacetOptionParameter] = [sortReleaseDateFacetOption];
+ selectedFacetOptions[comingSoonFacetOptionParameter] = [comingSoonFacetOption];
+ if (objectGraph.client.isVision) {
+ selectedFacetOptions[binCompatGamesFacetOptionParameter] = [includeBinCompatGamesFacetOption];
+ selectedFacetOptions[gamePreviewsFacetOptionParameter] = [gamePreviewsFacetOption];
+ }
+ const availableParameters = [
+ Parameters.sort,
+ Parameters.ageRating,
+ Parameters.controllerSupport,
+ Parameters.multiplayerSupport,
+ Parameters.comingSoon,
+ ];
+ if (objectGraph.client.isVision) {
+ availableParameters.push(Parameters.binCompatGames);
+ }
+ for (const parameter of availableParameters) {
+ const facetOption = urlParameters[parameter];
+ if (isNullOrEmpty(facetOption)) {
+ continue;
+ }
+ const selectedFacetOption = facetOptionsParameterMapping[parameter][facetOption];
+ const mediaApiFacetName = facetParameterMediaApiMapping[parameter];
+ if (isDefinedNonNull(selectedFacetOption) && isDefinedNonNullNonEmpty(mediaApiFacetName)) {
+ selectedFacetOptions[`${mediaApiFacetName}`] = selectedFacetOption;
+ }
+ }
+ return selectedFacetOptions;
+}
+function pageFacetChangeAction(objectGraph, facetParameter) {
+ const action = new models.BlankAction();
+ metricsClickHelpers.addClickEventToPageFacetsChangeAction(objectGraph, action, facetParameter);
+ return action;
+}
+export function createPageSegments(objectGraph) {
+ const isSegmentedNavEnabled = objectGraph.client.isVision && objectGraph.featureFlags.isEnabled("see_all_games_segmented_nav_2024A");
+ if (!isSegmentedNavEnabled) {
+ return [];
+ }
+ else {
+ const binCompatGamesFacet = createBinCompatGamesFacet(objectGraph);
+ const allGamesPageSegment = {
+ id: "all_games",
+ title: objectGraph.loc.string("Arcade.SeeAllGames.PageSegment.AllGames.Title"),
+ segmentAction: new models.ArcadeSeeAllGamesPageSegmentChangeAction(binCompatGamesFacet, includeBinCompatGamesFacetOption),
+ };
+ const visionGamesOnlyPageSegment = {
+ id: "vision",
+ title: objectGraph.loc.string("Arcade.SeeAllGames.PageSegment.AppleVisionGames.Title"),
+ segmentAction: new models.ArcadeSeeAllGamesPageSegmentChangeAction(binCompatGamesFacet, undefined),
+ };
+ return [allGamesPageSegment, visionGamesOnlyPageSegment];
+ }
+}
+//# sourceMappingURL=arcade-see-all-games-facets.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-request.js b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-request.js
new file mode 100644
index 0000000..6f20944
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-request.js
@@ -0,0 +1,78 @@
+import { PageFacets } from "../../api/models";
+import { isDefinedNonNullNonEmpty, isNullOrEmpty } from "../../foundation/json-parsing/server-data";
+import { Parameters } from "../../foundation/network/url-constants";
+import { shouldUsePrerenderedIconArtwork } from "../content/content";
+import { binCompatGamesFacetOptionParameter, comingSoonAppsFacetOptionValue, comingSoonFacetOptionParameter, comingSoonGroupingFacetOptionValue, facetOptionsParameterMapping, sortCategoryFacetOptionValue, sortFacetOptionParameter, sortLastUpdatedFacetOptionValue, sortNameFacetOptionValue, sortReleaseDateFacetOptionValue, } from "./arcade-see-all-games-facets";
+export function defaultRequestAttributes(objectGraph) {
+ const attributes = [
+ "editorialArtwork",
+ "editorialVideo",
+ "isAppleWatchSupported",
+ "requiredCapabilities",
+ "videoPreviewsByType",
+ "screenshotsByType",
+ ];
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.client.isVision) {
+ attributes.push("compatibilityControllerRequirement");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ return attributes;
+}
+const defaultSparseCount = 4;
+export function prepareRequestWithSelectedFacets(request, selectedFacetOptions) {
+ let includeSparseCount = false;
+ for (const key of Object.keys(selectedFacetOptions)) {
+ // We need to choose the correct value for the coming soon parameter if coming soon is enabled.
+ if (key === comingSoonFacetOptionParameter && isDefinedNonNullNonEmpty(selectedFacetOptions[key])) {
+ const selectedSortValue = selectedFacetOptions[sortFacetOptionParameter];
+ if (isDefinedNonNullNonEmpty(selectedSortValue)) {
+ switch (selectedSortValue[0].value) {
+ case sortReleaseDateFacetOptionValue:
+ case sortLastUpdatedFacetOptionValue:
+ selectedFacetOptions[key][0].value = comingSoonGroupingFacetOptionValue;
+ break;
+ case sortNameFacetOptionValue:
+ selectedFacetOptions[key][0].value = comingSoonAppsFacetOptionValue;
+ break;
+ case sortCategoryFacetOptionValue:
+ selectedFacetOptions[key][0].value = comingSoonAppsFacetOptionValue;
+ includeSparseCount = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ // We need to choose the correct value for the bin compat games parameter if it is disabled.
+ if (key === binCompatGamesFacetOptionParameter && isNullOrEmpty(selectedFacetOptions[key])) {
+ selectedFacetOptions[key] = facetOptionsParameterMapping[Parameters.binCompatGames].false;
+ }
+ }
+ // We include the sparse count when dealing with the category sort.
+ if (includeSparseCount) {
+ request.withSparseCount(defaultSparseCount);
+ }
+ for (const key of Object.keys(selectedFacetOptions)) {
+ const requestValues = PageFacets.requestValuesForSelectedFacetOptions(selectedFacetOptions[key]);
+ if (isDefinedNonNullNonEmpty(requestValues)) {
+ if (isDefinedNonNullNonEmpty(requestValues.value)) {
+ request.addingQuery(key, requestValues.value);
+ }
+ for (const additionalKey of Object.keys(requestValues.additionalKeyValuePairs)) {
+ request.addingQuery(additionalKey, requestValues.additionalKeyValuePairs[additionalKey]);
+ }
+ }
+ }
+}
+//# sourceMappingURL=arcade-see-all-request.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-routing.js b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-routing.js
new file mode 100644
index 0000000..99b8df7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-see-all-routing.js
@@ -0,0 +1,5 @@
+import { makeRoutableArcadeSeeAllPageIntent } from "../../api/intents/routable-arcade-see-all-page-intent";
+import { generateRoutes } from "../util/generate-routes";
+const { routes, makeCanonicalUrl } = generateRoutes(makeRoutableArcadeSeeAllPageIntent, "/{platform}/arcade/see-all");
+export { routes as arcadeSeeAllRoutes, makeCanonicalUrl as makeArcadeSeeAllCanonicalUrl };
+//# sourceMappingURL=arcade-see-all-routing.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-upsell.js b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-upsell.js
new file mode 100644
index 0000000..5c5d30e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/arcade/arcade-upsell.js
@@ -0,0 +1,257 @@
+//
+// arcade-upsell.ts
+// AppStoreKit
+//
+// Created by Jonathan Ellenbogen on 11/19/19.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as color from "../../foundation/util/color-util";
+import * as heroCommon from "../grouping/hero/hero-common";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersUtil from "../metrics/helpers/util";
+import * as offers from "../offers/offers";
+import * as breakoutsCommon from "./breakouts-common";
+import { ArcadeSubscriptionStateAction } from "../../api/models";
+import { isFeatureEnabledForCurrentUser } from "../util/lottery";
+export function createUpsellBreakout(objectGraph, upsellData, metricsOptions, style = "white") {
+ if (serverData.isNullOrEmpty(upsellData) ||
+ (serverData.isNullOrEmpty(upsellData.attributes) && !objectGraph.client.isVision)) {
+ return null;
+ }
+ const heroVideoData = heroCommon.heroVideoFromData(objectGraph, upsellData, true);
+ const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, upsellData);
+ const backgroundColor = heroVideoData.backgroundColor || heroArtworkData.backgroundColor;
+ const detailPosition = breakoutsCommon.detailPositionFromData(objectGraph, upsellData, true, true);
+ const supportsMaterial = objectGraph.client.isTV;
+ const wantsBlur = breakoutsCommon.wantsBlur(objectGraph, breakoutsCommon.detailBackgroundStyleFromData(objectGraph, upsellData, supportsMaterial, true, true), true);
+ const details = createBreakoutDetailsFromData(objectGraph, upsellData, null, supportsMaterial, wantsBlur, "wordmark");
+ let buttonCallToAction = null;
+ if (objectGraph.client.deviceType !== "tv") {
+ buttonCallToAction = details.description;
+ details.description = null;
+ }
+ const offerTint = offerTintFromMarketingItem(objectGraph, upsellData);
+ const offerDisplayProperties = new models.OfferDisplayProperties("arcade", objectGraph.bag.arcadeAppAdamId, null, style, null, "arcade", offerTint, null, null, null, null, null, null, null, null, null, null, null, objectGraph.bag.arcadeProductFamilyId);
+ const displayProperties = {
+ backgroundColor: backgroundColor,
+ wantsMaterialDetailBackground: false,
+ wantsBlur: wantsBlur,
+ badgeColor: null,
+ titleColor: null,
+ descriptionColor: null,
+ callToActionColor: null,
+ textAlignment: null,
+ detailsPosition: detailPosition,
+ };
+ const upsellBreakout = new models.UpsellBreakout(details, offerDisplayProperties, displayProperties, null, buttonCallToAction, heroArtworkData.artwork, heroVideoData.video);
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, upsellData, upsellBreakout.details.title, {
+ ...metricsOptions,
+ targetType: "upsellBreakout",
+ });
+ impressionOptions.displaysArcadeUpsell = true;
+ metricsHelpersImpressions.addImpressionFields(objectGraph, upsellBreakout, impressionOptions);
+ // Push the breakout here so that the click action has the breakout in its location
+ // but we do not want to add it to the overall location tracker, so pop it right after adding it to the button
+ // action
+ // <rdar://problem/60883269> Metrics: Arcade: Container values requested in Location field
+ metricsHelpersLocation.pushContentLocation(objectGraph, impressionOptions, upsellBreakout.details.title);
+ upsellBreakout.offerButtonAction = arcadeOfferButtonActionFromData(objectGraph, upsellData, models.marketingItemContextFromString("arcadeTabHeader"), metricsOptions);
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ return upsellBreakout;
+}
+export function pageTitleEffectFromData(objectGraph, upsellData) {
+ const heroVideoData = heroCommon.heroVideoFromData(objectGraph, upsellData, true);
+ const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, upsellData);
+ const titleEffectArtwork = heroVideoData.artworkData || heroArtworkData.artworkData;
+ return breakoutsCommon.titleEffectFromArtwork(objectGraph, titleEffectArtwork, true);
+}
+export function callToActionLabelFromData(objectGraph, data) {
+ return breakoutsCommon.callToActionLabelFromData(objectGraph, data);
+}
+export function artworkDictionaryFromData(objectGraph, data) {
+ return breakoutsCommon.artworkDictionaryFromData(objectGraph, data);
+}
+export function artworkDataFromData(objectGraph, data, attributePath) {
+ const dictionary = artworkDictionaryFromData(objectGraph, data);
+ return serverData.asDictionary(dictionary, attributePath);
+}
+export function titleFromData(objectGraph, data) {
+ return mediaAttributes.attributeAsString(data, "title");
+}
+export function descriptionFromData(objectGraph, data) {
+ return mediaAttributes.attributeAsString(data, "subtitle");
+}
+export function createBreakoutDetailsFromData(objectGraph, data, contextualAppDataOrNull, supportsMaterial, wantsBlur, badgeType) {
+ let badge = null;
+ switch (badgeType) {
+ case "wordmark":
+ badge = {
+ type: "wordmark",
+ };
+ break;
+ case "text":
+ const badgeTitle = mediaAttributes.attributeAsString(data, "badge");
+ if ((badgeTitle === null || badgeTitle === void 0 ? void 0 : badgeTitle.length) > 0) {
+ badge = {
+ type: "text",
+ title: badgeTitle,
+ };
+ }
+ else {
+ badge = {
+ type: "none",
+ };
+ }
+ break;
+ default:
+ badge = {
+ type: "none",
+ };
+ break;
+ }
+ let backgroundStyle = breakoutsCommon.detailBackgroundStyleFromData(objectGraph, data, supportsMaterial, true, true);
+ const detailPosition = breakoutsCommon.detailPositionFromData(objectGraph, data, true, true);
+ if (wantsBlur) {
+ backgroundStyle = "dark";
+ }
+ const details = new models.BreakoutDetails(titleFromData(objectGraph, data), descriptionFromData(objectGraph, data), badge, null, backgroundStyle, breakoutsCommon.detailTextAlignmentForDetailPosition(objectGraph, detailPosition, data, true));
+ return details;
+}
+// region Offer Action
+export function arcadeOfferButtonActionFromData(objectGraph, upsellData, context, metricsOptions) {
+ const arcadeUpsellData = {
+ marketingItemData: upsellData,
+ };
+ // "arcadeTabHeader" context covers both hero and navbar button on the Arcade page
+ const showStarterPackOnboarding = context === models.marketingItemContextFromString("arcadeTabHeader") &&
+ objectGraph.bag.arcadeDownloadPackPostSubscribeTrigger &&
+ isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.arcadeDownloadPackRolloutRate);
+ return arcadeActionFromUpsellData(objectGraph, arcadeUpsellData, callToActionLabelFromData(objectGraph, upsellData), metricsOptions, showStarterPackOnboarding);
+}
+export function arcadeActionFromUpsellData(objectGraph, data, title, baseMetricsOptions, showStarterPackOnboarding) {
+ const marketingItemData = data.marketingItemData;
+ if (!serverData.isDefinedNonNull(marketingItemData)) {
+ return null;
+ }
+ const offerData = offers.offerDataFromMarketingItem(objectGraph, marketingItemData);
+ const isLinkAction = serverData.asString(offerData, "kind") === "link";
+ const linkUrlString = serverData.asString(offerData, "url");
+ if (isLinkAction && linkUrlString) {
+ const linkAction = arcadeLinkActionFromMarketingItemLinkUrl(objectGraph, linkUrlString);
+ const linkClickOptions = {
+ id: objectGraph.bag.arcadeAppAdamId,
+ actionType: "buy",
+ actionContext: "Arcade",
+ contextualAdamId: objectGraph.bag.arcadeAppAdamId,
+ offerType: "subscribe",
+ targetType: "button",
+ mercuryMetricsData: metricsHelpersUtil.marketingItemTopLevelBaseFieldsFromData(objectGraph, marketingItemData),
+ ...baseMetricsOptions,
+ };
+ linkAction.title = title;
+ metricsHelpersClicks.addClickEventToAction(objectGraph, linkAction, linkClickOptions);
+ return linkAction;
+ }
+ else {
+ const arcadeAction = arcadeBuyActionFromMarketingItemData(objectGraph, marketingItemData, title, baseMetricsOptions);
+ if (showStarterPackOnboarding) {
+ const downloadPackOnboarding = new models.FlowAction("arcadeDownloadPackCategories");
+ const subscriptionStatus = "new";
+ downloadPackOnboarding.pageData = subscriptionStatus;
+ downloadPackOnboarding.presentationContext = "presentModalFormSheet";
+ // Passing `undefined` for other than subscribed actions instead of `BlankAction` to record an error.
+ const subscriptionCheckAction = new ArcadeSubscriptionStateAction(undefined, undefined, downloadPackOnboarding, undefined);
+ const action = new models.CompoundAction([arcadeAction, subscriptionCheckAction]);
+ action.title = title;
+ return action;
+ }
+ else {
+ return arcadeAction;
+ }
+ }
+}
+function arcadeLinkActionFromMarketingItemLinkUrl(objectGraph, urlString) {
+ /**
+ * Check against the bag routing regex and dispatch one of the AMS actions.
+ */
+ // Dynamic UI
+ const dynamicUIRegexPatterns = objectGraph.bag.dynamicUIRegexStrings;
+ for (const pattern of dynamicUIRegexPatterns) {
+ const dynamicUIPattern = new RegExp(pattern);
+ if (dynamicUIPattern.test(urlString)) {
+ const action = new models.FlowAction("dynamicUI", urlString);
+ action.pageData = new models.DynamicUIRequestInfo(objectGraph.bag.metricsTopic);
+ return action;
+ }
+ }
+ // Finance UI regex check
+ const financeUIRegexPatterns = objectGraph.bag.financeUIRegexStrings;
+ for (const pattern of financeUIRegexPatterns) {
+ const financeUIPattern = new RegExp(pattern);
+ if (financeUIPattern.test(urlString)) {
+ return new models.FlowAction("finance", urlString);
+ }
+ }
+ // Web UI regex check
+ const webViewRegexPatterns = objectGraph.bag.webViewRegexStrings;
+ for (const pattern of webViewRegexPatterns) {
+ const webViewPattern = new RegExp(pattern);
+ if (webViewPattern.test(urlString)) {
+ return new models.FlowAction("webView", urlString);
+ }
+ }
+ return new models.ExternalUrlAction(urlString, false);
+}
+function arcadeBuyActionFromMarketingItemData(objectGraph, data, title, baseMetricsOptions) {
+ const offerData = offers.offerDataFromMarketingItem(objectGraph, data);
+ const offerName = serverData.asString(offerData, "offerName");
+ const buyParams = serverData.asString(offerData, "buyParams");
+ if (!serverData.isDefinedNonNull(offerName) || !serverData.isDefinedNonNull(buyParams)) {
+ return null;
+ }
+ const offerServiceTypes = serverData.asArrayOrEmpty(offerData, "serviceTypes");
+ const isAristotleOffer = offerServiceTypes.length > 1 && objectGraph.bag.aristotleParentAppAdamId;
+ const parentAdamId = isAristotleOffer ? objectGraph.bag.aristotleParentAppAdamId : objectGraph.bag.arcadeAppAdamId;
+ const arcadeAction = new models.ArcadeAction(offerName, parentAdamId, {
+ buyParams: buyParams,
+ productIdentifier: offerName,
+ pageInformation: baseMetricsOptions.pageInformation,
+ });
+ const metricsClickOptions = {
+ id: parentAdamId,
+ actionType: "buy",
+ targetType: "button",
+ subscriptionSKU: offerName,
+ actionContext: "Arcade",
+ contextualAdamId: parentAdamId,
+ actionDetails: { buyParams: buyParams },
+ offerType: "subscribe",
+ mercuryMetricsData: metricsHelpersUtil.marketingItemTopLevelBaseFieldsFromData(objectGraph, data),
+ ...baseMetricsOptions,
+ };
+ arcadeAction.title = title;
+ metricsHelpersClicks.addClickEventToAction(objectGraph, arcadeAction, metricsClickOptions);
+ return arcadeAction;
+}
+// endregion
+// region Helpers
+function offerTintFromMarketingItem(objectGraph, data) {
+ const templateParameters = breakoutsCommon.templateParametersFromData(objectGraph, data);
+ const fillColor = serverData.asString(templateParameters, "ctaButtonBackgroundColor");
+ const textColor = serverData.asString(templateParameters, "ctaButtonTextColor");
+ if (!serverData.isDefinedNonNull(fillColor) || !serverData.isDefinedNonNull(textColor)) {
+ return { type: "blue", fillColor: null, textColor: null };
+ }
+ return {
+ type: "custom",
+ fillColor: color.fromHex(fillColor),
+ textColor: color.fromHex(textColor),
+ };
+}
+// endregion
+//# sourceMappingURL=arcade-upsell.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/arcade/breakouts-common.js b/node_modules/@jet-app/app-store/tmp/src/common/arcade/breakouts-common.js
new file mode 100644
index 0000000..85d4bb4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/arcade/breakouts-common.js
@@ -0,0 +1,272 @@
+//
+// breakouts-common.ts
+// AppStoreKit
+//
+// Created by Jonathan Ellenbogen on 11/19/19.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import { isNothing } from "@jet/environment";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as platformAttributes from "../../foundation/media/platform-attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as color from "../../foundation/util/color-util";
+import { pageRouter } from "../builders/routing";
+import * as contentAttributes from "../content/attributes";
+import * as heroCommon from "../grouping/hero/hero-common";
+import * as offers from "../offers/offers";
+import * as productPageUtil from "../product-page/product-page-util";
+import { makeProductPageIntent } from "../../api/intents/product-page-intent";
+import { makeRoutableArticlePageIntent } from "../../api/intents/routable-article-page-intent";
+import { getLocale } from "../locale";
+import { makeRoutableArticlePageCanonicalUrl } from "../today/routable-article-page-url-utils";
+import { getPlatform } from "../preview-platform";
+export function detailBackgroundStyleFromData(objectGraph, breakoutData, supportsMaterial = true, isFirstShelf, isUpsell) {
+ const heroVideoData = heroCommon.heroVideoFromData(objectGraph, breakoutData);
+ const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, breakoutData);
+ const backgroundColor = heroVideoData.backgroundColor || heroArtworkData.backgroundColor;
+ const detailPosition = detailPositionFromData(objectGraph, breakoutData, isFirstShelf, isUpsell);
+ const displayBreakoutMaterial = breakoutData.type === "marketing-items"
+ ? serverData.asBooleanOrFalse(templateParametersFromData(objectGraph, breakoutData), "displayMaterial")
+ : mediaAttributes.attributeAsBooleanOrFalse(breakoutData, "displayBreakoutMaterial");
+ const shouldUseMaterialBackground = displayBreakoutMaterial || (objectGraph.client.isTV && detailPosition === "center");
+ if (shouldUseMaterialBackground && supportsMaterial) {
+ return "material";
+ }
+ else {
+ return detailBackgroundStyleFromColor(objectGraph, backgroundColor);
+ }
+}
+export function detailBackgroundStyleFromColor(objectGraph, backgroundColor) {
+ if (!backgroundColor) {
+ return "dark";
+ }
+ return color.isDarkColor(backgroundColor, 50) ? "dark" : "light";
+}
+export function detailPositionFromData(objectGraph, data, isFirstShelf, isUpsell) {
+ // Upsells always position the details in the center.
+ if (objectGraph.client.isPhone || isUpsell) {
+ return "center";
+ }
+ if (objectGraph.client.isTV) {
+ return isFirstShelf ? "center" : "leading";
+ }
+ const breakoutDetailsString = data.type === "marketing-items"
+ ? serverData.asString(templateParametersFromData(objectGraph, data), "textPosition")
+ : mediaAttributes.attributeAsString(data, "breakoutTextAlignment");
+ if (isNothing(breakoutDetailsString) || breakoutDetailsString.length === 0) {
+ return objectGraph.client.isMac ? "center" : "leading";
+ }
+ switch (breakoutDetailsString.toLowerCase()) {
+ case "left":
+ return "leading";
+ case "center":
+ return "center";
+ case "right":
+ return "trailing";
+ default:
+ return "leading";
+ }
+}
+/**
+ * The text alignment to use for a particular detail position on large breakouts
+ * @param position The positioning of the details within the breakout.
+ */
+export function detailTextAlignmentForDetailPosition(objectGraph, position, breakoutData, isUpsell = false) {
+ const isTV = objectGraph.client.isTV;
+ switch (position) {
+ case "leading":
+ return isTV ? "center" : "leading";
+ case "trailing":
+ return isTV ? "center" : "leading";
+ case "center":
+ if (isUpsell && !isTV) {
+ return "center";
+ }
+ else if (isTV) {
+ return "leading";
+ }
+ else {
+ return breakoutData.type === "marketing-items"
+ ? "center"
+ : textAlignmentFromData(objectGraph, breakoutData);
+ }
+ default:
+ return "leading";
+ }
+}
+function textAlignmentFromData(objectGraph, data) {
+ var _a;
+ const breakoutTextAlignmentString = (_a = mediaAttributes.attributeAsString(data, "breakoutTextAlignment")) !== null && _a !== void 0 ? _a : "";
+ switch (breakoutTextAlignmentString.toLowerCase()) {
+ case "left":
+ return "leading";
+ case "center":
+ return "center";
+ case "right":
+ return "trailing";
+ default:
+ return objectGraph.client.isMac ? "center" : "leading";
+ }
+}
+export function templateParametersFromData(objectGraph, data) {
+ if (data.type !== "marketing-items") {
+ return null;
+ }
+ return mediaAttributes.attributeAsDictionary(data, "display.templateParameters");
+}
+export function artworkDictionaryFromData(objectGraph, data) {
+ switch (data.type) {
+ case "editorial-items":
+ return mediaAttributes.attributeAsDictionary(data, "editorialArtwork");
+ case "marketing-items":
+ return mediaAttributes.attributeAsDictionary(data, "marketingArtwork");
+ default:
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
+ return platformAttributes.platformAttributeAsDictionary(data, attributePlatform, "editorialArtwork");
+ }
+}
+export function videoDictionaryFromData(objectGraph, data) {
+ switch (data.type) {
+ case "editorial-items":
+ return mediaAttributes.attributeAsDictionary(data, "editorialVideo");
+ case "marketing-items":
+ return mediaAttributes.attributeAsDictionary(data, "marketingVideo");
+ default:
+ if (preprocessor.GAMES_TARGET) {
+ return mediaAttributes.attributeAsDictionary(data, "editorialVideo");
+ }
+ else {
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
+ return platformAttributes.platformAttributeAsDictionary(data, attributePlatform, "marketingVideo");
+ }
+ }
+}
+export function callToActionLabelFromData(objectGraph, data) {
+ switch (data.type) {
+ case "marketing-items":
+ const offerData = offers.offerDataFromMarketingItem(objectGraph, data);
+ return serverData.asString(offerData, "callToActionLabel");
+ default:
+ return mediaAttributes.attributeAsString(data, "breakoutCallToActionLabel");
+ }
+}
+export function requiresPrimaryContent(objectGraph, data) {
+ const linkData = mediaAttributes.attributeAsDictionary(data, "link");
+ const isLinkAction = serverData.isDefinedNonNullNonEmpty(linkData);
+ const isStoryAction = mediaAttributes.attributeAsBooleanOrFalse(data, "isCanvasAvailable");
+ return !isLinkAction && !isStoryAction;
+}
+export function wantsBlur(objectGraph, backgroundStyle, isInHeroPosition) {
+ return backgroundStyle !== "material" && isInHeroPosition;
+}
+/**
+ * The action to use when displaying a breakout's call to action button
+ * @param data The breakout node data from MAPI
+ */
+export function actionFromData(objectGraph, data) {
+ const linkData = mediaAttributes.attributeAsDictionary(data, "link");
+ const isLinkAction = serverData.isDefinedNonNullNonEmpty(linkData);
+ const isProductAction = mediaAttributes.attributeAsString(data, "kind") === "App";
+ const isStoryAction = mediaAttributes.attributeAsBooleanOrFalse(data, "isCanvasAvailable");
+ const primaryContent = mediaRelationship.relationshipData(objectGraph, data, "primary-content");
+ if (!isLinkAction && !isStoryAction && !mediaAttributes.hasAttributes(primaryContent)) {
+ return null;
+ }
+ let actionUrl = null;
+ if (isLinkAction) {
+ actionUrl = serverData.asString(linkData, "url");
+ }
+ else if (isStoryAction) {
+ actionUrl = mediaAttributes.attributeAsString(data, "url");
+ }
+ else {
+ actionUrl = mediaAttributes.attributeAsString(primaryContent, "url");
+ }
+ if (serverData.isNull(actionUrl)) {
+ return null;
+ }
+ let action = null;
+ if (isLinkAction && serverData.asString(linkData, "target") === "external") {
+ const externalUrlAction = new models.ExternalUrlAction(actionUrl);
+ action = externalUrlAction;
+ }
+ else if (objectGraph.isAvailable(pageRouter)) {
+ const flowPage = objectGraph.required(pageRouter).fetchFlowPage(actionUrl);
+ const flowAction = new models.FlowAction(flowPage);
+ flowAction.pageUrl = actionUrl;
+ if (flowPage === "product") {
+ flowAction.pageData = productPageUtil.createProductPageSidePackFromResponse(objectGraph, primaryContent);
+ }
+ action = flowAction;
+ }
+ else if (objectGraph.client.isWeb && isStoryAction) {
+ const routableArticlePageIntent = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: data.id,
+ });
+ const flowAction = new models.FlowAction("article");
+ flowAction.title = mediaAttributes.attributeAsString(data, "breakoutCallToActionLabel");
+ flowAction.pageUrl = makeRoutableArticlePageCanonicalUrl(objectGraph, routableArticlePageIntent);
+ flowAction.destination = routableArticlePageIntent;
+ action = flowAction;
+ }
+ else if (objectGraph.client.isWeb && isProductAction) {
+ const flowAction = new models.FlowAction("product");
+ flowAction.title = mediaAttributes.attributeAsString(data, "breakoutCallToActionLabel");
+ flowAction.pageUrl = actionUrl;
+ flowAction.destination = makeProductPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: primaryContent.id,
+ });
+ action = flowAction;
+ }
+ if (action) {
+ action.title = callToActionLabelFromData(objectGraph, data);
+ }
+ return action;
+}
+export function titleEffectFromArtwork(objectGraph, editorialArtworkData, isUpsell = false) {
+ if (serverData.isNullOrEmpty(editorialArtworkData)) {
+ return null;
+ }
+ let titleEffect = null;
+ const white = color.named("white");
+ const black = color.named("black");
+ const backgroundColor = color.fromHex(serverData.asString(editorialArtworkData, "bgColor"));
+ const gradientColors = serverData
+ .asArrayOrEmpty(editorialArtworkData, "textGradient")
+ .map((hexColor) => {
+ return color.fromHex(hexColor);
+ });
+ const backgroundStyle = detailBackgroundStyleFromColor(objectGraph, backgroundColor);
+ const hasGradient = gradientColors.length === 2;
+ if (hasGradient) {
+ titleEffect = new models.TitleEffect("horizontalGradient");
+ const startColor = gradientColors[0];
+ const endColor = gradientColors[1];
+ titleEffect.gradientStartColor = color.dynamicWith(startColor, startColor);
+ titleEffect.gradientEndColor = color.dynamicWith(endColor, endColor);
+ titleEffect.filter = "plusLight";
+ if (!isUpsell) {
+ titleEffect.accessoryColor = titleEffect.gradientEndColor;
+ }
+ }
+ else {
+ titleEffect = new models.TitleEffect("color");
+ titleEffect.color = backgroundStyle === "dark" ? white : black;
+ if (!isUpsell) {
+ titleEffect.accessoryColor = titleEffect.color;
+ }
+ titleEffect.isFallbackStyle = true;
+ }
+ // The accessory on tvOS should always be white resulting in a dark accessory.
+ if (objectGraph.client.isTV) {
+ titleEffect.accessoryColor = white;
+ }
+ return titleEffect;
+}
+//# sourceMappingURL=breakouts-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/arcade/render-arcade-see-all-games-page.js b/node_modules/@jet-app/app-store/tmp/src/common/arcade/render-arcade-see-all-games-page.js
new file mode 100644
index 0000000..f1d39e0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/arcade/render-arcade-see-all-games-page.js
@@ -0,0 +1,227 @@
+import * as validation from "@jet/environment/json/validation";
+import { Shelf, Trailers, Video, } from "../../api/models";
+import { metricsFromMediaApiObject, } from "../../foundation/media/data-structure";
+import { hasAttributes } from "../../foundation/media/attributes";
+import { isDefinedNonNullNonEmpty, isNullOrEmpty } from "../../foundation/json-parsing/server-data";
+import { Parameters, Path, Protocol } from "../../foundation/network/url-constants";
+import { URL } from "../../foundation/network/urls";
+import * as lockups from "../../common/lockups/lockups";
+import * as metricsHelpersImpressions from "../../common/metrics/helpers/impressions";
+import * as videoDefaults from "../../common/constants/video-constants";
+import * as content from "../../common/content/content";
+import { newLocationTracker, nextPosition, popLocation, pushContentLocation, } from "../metrics/helpers/location";
+export function createArcadeSeeAllGamesPaginationToken(_objectGraph, requestDescriptor, pageInformation) {
+ const locationTracker = newLocationTracker();
+ const paginationUrl = URL.fromComponents(Protocol.internal, null, `/${Path.arcadeSeeAllGames}/${Path.arcadeSeeAllGamesLoadMore}`).build();
+ const paginationToken = {
+ url: paginationUrl,
+ metricsPageInformation: pageInformation,
+ metricsLocationTracker: locationTracker,
+ remainingGroups: [],
+ lastShelfIndex: 0,
+ isCompactMode: requestDescriptor.isCompactMode,
+ };
+ return paginationToken;
+}
+export function createShelves(objectGraph, groups, paginationToken) {
+ return validation.context("createShelves", () => {
+ const shelves = [];
+ if (groups.length === 0) {
+ return shelves;
+ }
+ let shouldStartPagination = false;
+ for (const group of groups) {
+ // Start pagination if we hit a group who's first item has not been hydrated
+ shouldStartPagination = shouldStartPagination || !hasAttributes(group.data[0]);
+ if (shouldStartPagination) {
+ paginationToken.remainingGroups.push(group);
+ }
+ else {
+ const shelfToken = {
+ index: paginationToken.lastShelfIndex,
+ title: shelfTitleForGroup(objectGraph, group),
+ contentType: shelfContentTypeForMode(objectGraph, paginationToken.isCompactMode),
+ shouldFilter: false,
+ remainingContent: group.data,
+ groupKind: group.kind,
+ isCompactMode: paginationToken.isCompactMode,
+ hasExistingContent: false,
+ isFirstRender: true,
+ metricsPageInformation: paginationToken.metricsPageInformation,
+ metricsLocationTracker: paginationToken.metricsLocationTracker,
+ };
+ const shelf = createShelf(objectGraph, shelfToken);
+ shelves.push(shelf);
+ paginationToken.lastShelfIndex++;
+ }
+ }
+ return shelves;
+ });
+}
+const defaultRowsPerColumn = 3;
+export function createShelf(objectGraph, shelfToken) {
+ const shelfItems = [];
+ const shelf = new Shelf(shelfToken.contentType);
+ shelf.title = shelfToken.title;
+ shelf.presentationHints = { showSupplementaryText: false };
+ if (objectGraph.client.isVision) {
+ shelf.isHorizontal =
+ (shelfToken.groupKind === "comingSoonGrouping" && !shelfToken.isCompactMode) ||
+ shelfToken.groupKind === "category";
+ shelf.presentationHints = {
+ ...shelf.presentationHints,
+ isSAGComingSoon: true,
+ };
+ }
+ else {
+ shelf.isHorizontal = shelfToken.groupKind === "comingSoonGrouping" || shelfToken.groupKind === "category";
+ }
+ const shelfMetricsOptions = {
+ id: `${shelfToken.index}`,
+ kind: null,
+ softwareType: null,
+ targetType: "swoosh",
+ title: shelf.title,
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ idType: "sequential",
+ };
+ /// add impression fields
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ pushContentLocation(objectGraph, shelfMetricsOptions, shelf.title);
+ while (isDefinedNonNullNonEmpty(shelfToken.remainingContent) && hasAttributes(shelfToken.remainingContent[0])) {
+ const lockupData = shelfToken.remainingContent.shift();
+ const lockupOptions = {
+ metricsOptions: {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: metricsFromMediaApiObject(lockupData),
+ },
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfToken.contentType),
+ shouldHideArcadeHeader: objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab"),
+ isContainedInPreorderExclusiveShelf: shelfToken.groupKind === "comingSoonGrouping",
+ };
+ const cropCode = objectGraph.client.isVision ? "sr" : null;
+ const lockup = lockups.mixedMediaLockupFromData(objectGraph, lockupData, lockupOptions, videoDefaults.defaultVideoConfiguration(objectGraph), null, cropCode);
+ const lockupHasVideos = ensureLockupHasVideos(objectGraph, lockup, lockupData);
+ if (!lockupHasVideos) {
+ continue;
+ }
+ lockup.editorialTagline = null;
+ lockup.developerTagline = null;
+ shelfItems.push(lockup);
+ nextPosition(shelfToken.metricsLocationTracker);
+ }
+ shelf.mergeWhenFetched = shelfToken.hasExistingContent;
+ shelfToken.hasExistingContent = shelfToken.hasExistingContent || shelfItems.length > 0;
+ shelf.items = shelfItems;
+ if (isDefinedNonNullNonEmpty(shelfToken.remainingContent)) {
+ shelf.url = shelfUrlForToken(shelfToken);
+ }
+ else if (objectGraph.client.deviceType !== "tv" &&
+ shelfToken.isCompactMode &&
+ shelf.items.length < defaultRowsPerColumn) {
+ shelf.rowsPerColumn = shelf.items.length;
+ }
+ popLocation(shelfToken.metricsLocationTracker);
+ return shelf;
+}
+function shelfContentTypeForMode(objectGraph, isCompactMode) {
+ if (isCompactMode) {
+ return "smallLockup";
+ }
+ else if (objectGraph.client.isTV) {
+ return "mixedMediaLockup";
+ }
+ else {
+ return "appTrailerLockup";
+ }
+}
+function shelfTitleForGroup(objectGraph, group) {
+ let shelfTitle;
+ let dateComponents = null;
+ let groupDateForTitle = null;
+ let sentence;
+ // rdar://104719340 (Arcade See All Games section has a missing name)
+ // Temporary(?) work around for MAPI sometimes not returning a group.name
+ if ((!group.name || group.name.length === 0) && group.kind !== "comingSoonGrouping") {
+ return "";
+ }
+ switch (group.kind) {
+ case "releaseDateByDay":
+ dateComponents = group.name.split("-");
+ groupDateForTitle = new Date(parseInt(dateComponents[0]), parseInt(dateComponents[1]) - 1, parseInt(dateComponents[2]));
+ sentence = objectGraph.loc.string("ALL_GAMES_SECTION_TITLE_RELEASE_DATE_SENTENCE");
+ shelfTitle = objectGraph.loc.formatDateInSentence(sentence, "MMMM d, y", groupDateForTitle);
+ break;
+ case "lastUpdatedByDay":
+ dateComponents = group.name.split("-");
+ groupDateForTitle = new Date(parseInt(dateComponents[0]), parseInt(dateComponents[1]) - 1, parseInt(dateComponents[2]));
+ sentence = objectGraph.loc.string("ALL_GAMES_SECTION_TITLE_LAST_UPDATED_SENTENCE");
+ shelfTitle = objectGraph.loc.formatDateInSentence(sentence, "MMMM d, y", groupDateForTitle);
+ break;
+ case "comingSoonGrouping":
+ shelfTitle = objectGraph.loc.string("Arcade.SeeAllGames.ComingSoonShelf.Title");
+ break;
+ default:
+ shelfTitle = group.name;
+ break;
+ }
+ return shelfTitle;
+}
+function shelfUrlForToken(shelfToken) {
+ if (isNullOrEmpty(shelfToken.remainingContent)) {
+ return null;
+ }
+ return `${Protocol.internal}:/${Path.arcadeSeeAllGames}/${Path.shelf}/?${Parameters.token}=${encodeURIComponent(JSON.stringify(shelfToken))}`;
+}
+// region Fallback Data
+function ensureLockupHasVideos(objectGraph, lockup, lockupData) {
+ // In visionOS, the bin-compat apps usually have trailer from iPad which using 4:3 video
+ // But SAG lockup preferred 16:9 video, so even we already have lockup.trailers, we still want to override it
+ // with the editorial splash video `splashVideo16x9`.
+ // If there is no editorial splash video found, we fallback to the current `lockup.trailers` or create a dump video.
+ if (isNullOrEmpty(lockup.trailers) || objectGraph.client.isVision) {
+ const uberVideo = content.editorialSplashVideoFromData(objectGraph, lockupData);
+ if (isDefinedNonNullNonEmpty(uberVideo)) {
+ const uberTrailer = new Trailers();
+ uberVideo.playbackControls = videoDefaults.standardControls(objectGraph);
+ uberVideo.autoPlayPlaybackControls = videoDefaults.autoPlayControls(objectGraph);
+ uberVideo.canPlayFullScreen = true;
+ uberTrailer.videos = [uberVideo];
+ lockup.trailers = [uberTrailer];
+ }
+ }
+ if (isNullOrEmpty(lockup.trailers)) {
+ /**
+ * <rdar://problem/55328972> See All Games: Bring back screenshots
+ * In the very rare case that an arcade game has absolutely no videos,
+ * that means no app trailer, no editorial video in any form. We're going to create a fake
+ * video that uses the first screenshot as a preview frame. This allows us to
+ * use the same AppTrailersLockup and get uniform sizing for lockups.
+ */
+ addFakeVideoToMixedMediaLockup(lockup);
+ }
+ return isDefinedNonNullNonEmpty(lockup.trailers);
+}
+const fakeVideoURL = "x-as3-internal:/today/test";
+function addFakeVideoToMixedMediaLockup(lockup) {
+ const screenshots = lockup.screenshots[0];
+ if (isNullOrEmpty(screenshots)) {
+ return;
+ }
+ const firstScreenshot = screenshots.artwork[0];
+ const fakeVideo = new Video(fakeVideoURL, firstScreenshot, {
+ allowsAutoPlay: false,
+ looping: false,
+ canPlayFullScreen: false,
+ autoPlayPlaybackControls: {},
+ playbackControls: {},
+ });
+ const fakeTrailers = new Trailers();
+ fakeTrailers.videos = [fakeVideo];
+ lockup.trailers = [fakeTrailers];
+}
+// endregion
+//# sourceMappingURL=render-arcade-see-all-games-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/builders/additional-page-requirement-util.js b/node_modules/@jet-app/app-store/tmp/src/common/builders/additional-page-requirement-util.js
new file mode 100644
index 0000000..242d28c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/builders/additional-page-requirement-util.js
@@ -0,0 +1,36 @@
+import { isNothing } from "@jet/environment";
+import { allMixed } from "../../foundation/util/promise-util";
+export async function fetchPageWithAdditionalPageRequirements(objectGraph, primaryPageRequirement, additionalRequirements) {
+ var _a;
+ // Data Frankensteining Pt 1. - Additional Page Requirements:
+ // Additional requests that are fired *IN PARALLEL* with page request to fetch additional data needed to render page.
+ // Why are these not included in a singular page fetch? I don't know.
+ // This builder API is used to request data that will *definitely* be used, regardless of the content of initial page fetch's response.
+ const additionalRequirementKeys = Object.keys(additionalRequirements);
+ const additionalPageRequirementPromises = additionalRequirementKeys.map(async (key) => {
+ return await additionalRequirements[key];
+ });
+ const allPageRequirements = await allMixed([primaryPageRequirement, ...additionalPageRequirementPromises]);
+ const primaryPageDataResult = allPageRequirements[0];
+ const primaryPageData = primaryPageDataResult.value;
+ if (!primaryPageDataResult.success || isNothing(primaryPageData)) {
+ throw (_a = primaryPageDataResult.error) !== null && _a !== void 0 ? _a : new Error("Unknown primaryPageData error");
+ }
+ const additionalData = {};
+ const additionalPageRequirements = allPageRequirements.slice(1);
+ for (const [index, additionalPageRequirement] of additionalPageRequirements.entries()) {
+ const additionalRequirementKey = additionalRequirementKeys[index];
+ if (additionalPageRequirement.success) {
+ additionalData[additionalRequirementKey] = additionalPageRequirement.value;
+ }
+ else {
+ additionalData[additionalRequirementKey] = null;
+ objectGraph.console.log(`Builder - failed to fetch additionalPageRequirement ${additionalRequirementKey}, reason: ${additionalPageRequirement.error}`);
+ }
+ }
+ return {
+ primaryPageData,
+ additionalData,
+ };
+}
+//# sourceMappingURL=additional-page-requirement-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/builders/pagination.js b/node_modules/@jet-app/app-store/tmp/src/common/builders/pagination.js
new file mode 100644
index 0000000..5f11fc7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/builders/pagination.js
@@ -0,0 +1,51 @@
+/**
+ * Created by km on 10/5/16.
+ */
+/// The suggested maximum items to load per page.
+export const suggestedMaxPerPage = 20;
+/**
+ * Returns a single page of lookup data from a page token.
+ * @param pageToken The page token to extract the ids from.
+ * @returns An array of zero or more ids to lookup.
+ */
+export function getDataForLookup(objectGraph, pageToken) {
+ if (!pageToken || !pageToken.remainingContent) {
+ return [];
+ }
+ return pageToken.remainingContent.slice(0, pageToken.maxPerPage);
+}
+/**
+ * Returns a page token containing the ids to fetch for the next page of data.
+ * @param pageToken The page token to advance.
+ * @param count The number of items to get, or all items if not specified
+ * @returns A new page token.
+ */
+export function nextPageToken(objectGraph, pageToken, count) {
+ const nextToken = { ...pageToken };
+ if (pageToken && pageToken.remainingContent) {
+ const nextContent = nextDataRange(objectGraph, pageToken.remainingContent, pageToken.maxPerPage, count);
+ nextToken.remainingContent = nextContent;
+ }
+ return nextToken;
+}
+/**
+ * Returns a subset of the content from start to the end of the array
+ * @param ids The array of IDs from which to get the sub array
+ * @param start The starting index
+ * @param count The number of items to get, or all items if not specified
+ * @returns An array of all ids from start until the end of the array
+ */
+export function nextDataRange(objectGraph, dataArray, start, count) {
+ return dataArray.slice(start, count !== null && count !== void 0 ? count : dataArray.length);
+}
+export function createMediaPageToken(objectGraph, remaining, url, nextHref = null, pageInformation, locationTracker) {
+ return {
+ remainingContent: remaining,
+ maxPerPage: suggestedMaxPerPage,
+ highestOrdinal: 0,
+ url: url,
+ metricsPageInformation: pageInformation,
+ metricsLocationTracker: locationTracker,
+ };
+}
+//# sourceMappingURL=pagination.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/builders/routing.js b/node_modules/@jet-app/app-store/tmp/src/common/builders/routing.js
new file mode 100644
index 0000000..1655d6d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/builders/routing.js
@@ -0,0 +1,321 @@
+/**
+ * Created by km on 11/17/16.
+ */
+/* This file DOES NOT target nodejs usage. */
+import { isNothing, isSome } from "@jet/environment";
+import { makeMetatype } from "@jet/environment/util/metatype";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { Parameters } from "../../foundation/network/url-constants";
+import * as routingComponents from "../../foundation/routing/routing-components";
+import * as objects from "../../foundation/util/objects";
+import { allOptional, tryAwait } from "../../foundation/util/promise-util";
+import * as productPageCommon from "../product-page/product-page-common";
+// region Routers
+export const pageRouter = makeMetatype("app-store:page-router");
+export class PageRouter {
+ constructor() {
+ this.registeredBuilders = new Set();
+ this.pageRouter = new routingComponents.UrlRouter();
+ this.shelfRouter = new routingComponents.UrlRouter();
+ this.paginationRouter = new routingComponents.UrlRouter();
+ // endregion
+ }
+ /**
+ * Register a page builder containing routes and related handlers.
+ * @param objectGraph
+ * @param {Builder} pageBuilder to register.
+ *
+ * @note Although one router can serve the page, shelf, and pagination routes, we maintain
+ * three instances dedicated to each instead to avoid iterating through each other's routes
+ * during URL matching.
+ */
+ registerPageBuilder(objectGraph, pageBuilder) {
+ if (this.registeredBuilders.has(pageBuilder.builderClass)) {
+ throw new Error(`routing: Registering duplicate builderClass ${pageBuilder.builderClass}`);
+ }
+ this.registeredBuilders.add(pageBuilder.builderClass);
+ this.pageRouter.associate(pageBuilder.pageRoute(objectGraph), pageBuilder);
+ this.shelfRouter.associate(pageBuilder.shelfRoute(objectGraph), pageBuilder);
+ this.paginationRouter.associate(pageBuilder.paginationRoute(objectGraph), pageBuilder);
+ }
+ /**
+ * Register a shelf builder containing routes and related handlers.
+ * @param objectGraph
+ * @param {Builder} shelfBuilder to register.
+ *
+ * @note Although one router can serve the page, shelf, and pagination routes, we maintain
+ * three instances dedicated to each instead to avoid iterating through each other's routes
+ * during URL matching.
+ */
+ registerShelfBuilder(objectGraph, shelfBuilder) {
+ if (this.registeredBuilders.has(shelfBuilder.builderClass)) {
+ throw new Error(`routing: Registering duplicate builderClass ${shelfBuilder.builderClass}`);
+ }
+ this.registeredBuilders.add(shelfBuilder.builderClass);
+ this.shelfRouter.associate(shelfBuilder.shelfRoute(objectGraph), shelfBuilder);
+ }
+ /**
+ * Register a pagination builder containing routes and related handlers.
+ * @param objectGraph
+ * @param {Builder} paginationBuilder to register.
+ *
+ * @note Although one router can serve the page, shelf, and pagination routes, we maintain
+ * three instances dedicated to each instead to avoid iterating through each other's routes
+ * during URL matching.
+ */
+ registerPaginationBuilder(objectGraph, paginationBuilder) {
+ if (this.registeredBuilders.has(paginationBuilder.builderClass)) {
+ throw new Error(`routing: Registering duplicate builderClass ${paginationBuilder.builderClass}`);
+ }
+ this.registeredBuilders.add(paginationBuilder.builderClass);
+ this.paginationRouter.associate(paginationBuilder.paginationRoute(objectGraph), paginationBuilder);
+ }
+ // endregion
+ // region Exported API
+ /**
+ * Determine the type of destination for given `url`.
+ * @param {string} url The url to fetch `flowPage` for.
+ * @returns {FlowPage} The FlowPage for given `url`
+ */
+ fetchFlowPage(url) {
+ const routerResult = this.pageRouter.routedObjectForUrl(url);
+ if (!routerResult.object) {
+ return `unknown`;
+ }
+ const builder = routerResult.object;
+ // Product URLs can go to `writeReview` via deep links in iOS, so the page type may not necessary be `product`
+ // So check that they are not routed to the reviews sections
+ if (builder.builderClass === "ProductBuilder" &&
+ routerResult.parameters[Parameters.action] !== productPageCommon.reviewsAction &&
+ routerResult.parameters[Parameters.action] !== productPageCommon.writeReviewAction) {
+ return "product";
+ }
+ return builder.pageType();
+ }
+ /**
+ * Fetch the contents of a page.
+ * @param objectGraph
+ * @param url The URL to determine what kind of page to load.
+ * @param pageType The meta type of the expected page, e.g. `models.ProductPage`.
+ * This is used to perform a runtime type check to prevent hard to track bugs.
+ * @returns A promise which will resolve into a page of `pageType`.
+ */
+ async fetchPage(objectGraph, url, pageType) {
+ return await this.fetchAction(objectGraph, url, null, false).then(async (pageAction) => {
+ return await new Promise((resolve, reject) => {
+ if (!pageAction) {
+ throw new Error(`Promise resolved to null action for: ${url}`);
+ }
+ if (pageAction.actionClass === "FlowAction") {
+ const pageData = pageAction.pageData;
+ if (!objects.isTypeOf(pageData, pageType)) {
+ // As we have no data of the correct type, check if we have a redirect
+ const pageUrl = pageAction.pageUrl;
+ const isRedirectingToSelf = pageUrl === url;
+ if (pageUrl && !isRedirectingToSelf) {
+ // Re-process this fetch with the new URL.
+ this.fetchPage(objectGraph, pageUrl, pageType)
+ .then((page) => {
+ resolve(page);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ return;
+ }
+ reject(new Error(`pageData is not expected type ${pageType.name}, ${JSON.stringify(pageData)}`));
+ return;
+ }
+ resolve(pageData);
+ return;
+ }
+ else if (pageAction.actionClass === "TabChangeAction") {
+ const tabChangeAction = pageAction;
+ if (tabChangeAction.actions.length === 1 &&
+ tabChangeAction.actions[0].actionClass === "FlowAction") {
+ const pageData = tabChangeAction.actions[0].pageData;
+ if (!objects.isTypeOf(pageData, pageType)) {
+ reject(new Error(`pageData is not expected type ${pageType.name}, ${JSON.stringify(pageData)}`));
+ return;
+ }
+ resolve(pageData);
+ return;
+ }
+ }
+ reject(new Error("Action is not a flowAction or a tabChangeAction that contains a single flowAction."));
+ });
+ });
+ }
+ /**
+ * Fetch an action for a given url, including page data if there is any to return.
+ * @param url The URL to determine which actions to take.
+ * @param referrerData Optional incoming deep link referrer data.
+ * @param isIncomingURL Whether the fetch is for deep link.
+ * @param visitedUrls Optional set of URLs already visited in this redirect chain to prevent cycles.
+ * @returns A promise that will resolve to an Action.
+ */
+ async fetchAction(objectGraph, url, referrerData, isIncomingURL, visitedUrls = new Set()) {
+ var _a;
+ const routerResult = this.pageRouter.routedObjectForUrl(url);
+ if (!routerResult.object) {
+ // Urls fed into `fetchAction` can redirect to supported routes, thus we attempt to
+ // chain the redirect and pipe it back into `fetchAction`.
+ return await this.redirectAndRefetchActionIfPossible(objectGraph, routerResult.normalizedUrl, visitedUrls);
+ }
+ const builder = routerResult.object;
+ return await builder.handlePage(objectGraph, routerResult.normalizedUrl, (_a = routerResult.parameters) !== null && _a !== void 0 ? _a : {}, routerResult.matchedRuleIdentifier, referrerData, isIncomingURL);
+ }
+ /**
+ * Fetch the next page of a page. The returned page will have its `shelves`, `nextPage` properties set.
+ * @param pageToken The page token to use.
+ * @returns A promise that will resolve to a page.
+ */
+ async fetchMoreOfPage(objectGraph, pageToken) {
+ const url = pageToken.url;
+ if (!url) {
+ throw new Error("Page token missing required `url` property");
+ }
+ const routerResult = this.paginationRouter.routedObjectForUrl(url);
+ if (!routerResult.object) {
+ throw new Error(`fetchMoreOfPage: Unhandled pagination url: ${url}`);
+ }
+ const builder = routerResult.object;
+ return await builder.handlePagination(objectGraph, routerResult.normalizedUrl, routerResult.parameters, routerResult.matchedRuleIdentifier, pageToken);
+ }
+ /**
+ * Fetch the contents of multiple shelves, producing a shelf batch object.
+ * @param requests A hash of request keys to URL strings.
+ * @returns A promise that will resolve into multiple shelves.
+ */
+ async fetchShelves(objectGraph, requests) {
+ // eslint-disable-next-line promise/param-names
+ const shelfRequestKeys = Object.keys(requests);
+ const shelfUrls = shelfRequestKeys.map((key) => requests[key]);
+ // Guaranteed never to throw an error
+ const shelfForUrl = async (shelfUrl) => {
+ if (isNothing(shelfUrl)) {
+ throw new Error(`fetchShelves: Null shelf url`);
+ }
+ const routerResult = this.shelfRouter.routedObjectForUrl(shelfUrl);
+ if (isNothing(routerResult.object)) {
+ throw new Error(`fetchShelves: Unhandled shelf url: ${shelfUrl}`);
+ }
+ const builder = routerResult.object;
+ const shelf = await builder.handleShelf(objectGraph, routerResult.normalizedUrl, routerResult.parameters, routerResult.matchedRuleIdentifier);
+ return shelf;
+ };
+ // Map each URL to a promise and make them all optional
+ const resolvedShelves = await allOptional(shelfUrls.map(shelfForUrl));
+ const batch = {
+ shelves: {},
+ errors: {},
+ };
+ for (const [index, resolvedShelf] of resolvedShelves.entries()) {
+ const requestKey = shelfRequestKeys[index];
+ if (isNothing(requestKey)) {
+ // This should never happen as we create a shelf request for each key
+ continue;
+ }
+ if (resolvedShelf.success) {
+ batch.shelves[requestKey] = resolvedShelf.value;
+ }
+ else {
+ // For rejected promises, access the error property
+ batch.errors[requestKey] = resolvedShelf.error;
+ }
+ }
+ const hasResults = Object.keys(batch.shelves).length > 0;
+ if (hasResults) {
+ return batch;
+ }
+ else {
+ const messages = Object.keys(batch.errors).map((key) => batch.errors[key].message);
+ if (messages.length === 0) {
+ throw new Error(`Could not load any shelves: ${JSON.stringify(requests)}`);
+ }
+ else {
+ throw new Error(messages.join("\n"));
+ }
+ }
+ }
+ // endregion
+ // region Redirect
+ /**
+ * Given an `url`, it will fire a GET request and then try to refetch the action for url if
+ * it was redirected. This is used for urls that are unknown, but redirects to a supported url.
+ *
+ * @param objectGraph
+ * @param {string} url to attempt to redirect with. This is expected to be normalized.
+ * @param {PromiseResolveFunction<models.Action>} resolve function to resolve promise to an `Action`.
+ * @param {PromiseRejectFunction} reject function to reject promise to `Error`.
+ * @param {Set<string>} visitedUrls Set of URLs already visited in this redirect chain to prevent cycles.
+ */
+ async redirectAndRefetchActionIfPossible(objectGraph, url, visitedUrls = new Set()) {
+ // Check for redirect cycle
+ const urlString = url.toString();
+ if (visitedUrls.has(urlString)) {
+ throw new Error(`redirectAndRefetchActionIfPossible: Redirect cycle detected for URL: ${urlString}`);
+ }
+ // Add current URL to visited set
+ visitedUrls.add(urlString);
+ const fetchResult = await tryAwait(objectGraph.network.fetch({
+ url: urlString,
+ method: "GET",
+ }));
+ if (!fetchResult.success) {
+ throw new Error(`redirectAndRefetchActionIfPossible: Failed to fetch page at url: ${url}`);
+ }
+ const response = fetchResult.value;
+ if (this.hasGotoURLInResponse(objectGraph, response)) {
+ // If the response body contains a goto url fail silently as we will natively try to load the URL.
+ return new models.BlankAction();
+ }
+ else if (response.status === 200 && response.redirected && response.url) {
+ // Only route if this was a redirect; otherwise we don't know how to handle this page
+ return await this.fetchAction(objectGraph, response.url, null, false, visitedUrls);
+ }
+ else {
+ throw new Error(`redirectAndRefetchActionIfPossible: Unhandled page url: ${url}`);
+ }
+ }
+ /**
+ * Given a `response`, it will check to see if the response body contains a goto URL
+ * A goto URL is returned by the server in a plist as a way of navigating to a new page
+ * AMSURLSession which is the native response handler looks for these URLs and tries to load them natively
+ * @param objectGraph
+ * @param {FetchResponse} the response body from the server
+ */
+ hasGotoURLInResponse(objectGraph, response) {
+ if (serverData.isNullOrEmpty(response.body)) {
+ return false;
+ }
+ try {
+ const responseData = serverData.asJSONValue(objectGraph.plist.parse(response.body));
+ const responseActionKind = serverData.asString(responseData, "action.kind");
+ const responseActionUrl = serverData.asString(responseData, "action.url");
+ if (responseActionKind === "Goto" && isSome(responseActionUrl)) {
+ return true;
+ }
+ return false;
+ }
+ catch {
+ return false;
+ }
+ }
+ // endregion
+ // region Testing
+ /**
+ * Fetch the builder for a given url.
+ * @param url The URL that the builder is associated with.
+ * @returns A builder if any was associated, or null if no match was found.
+ * @note FOR TESTING ONLY.
+ */
+ fetchBuilder(url) {
+ const routerResult = this.pageRouter.routedObjectForUrl(url) ||
+ this.shelfRouter.routedObjectForUrl(url) ||
+ this.paginationRouter.routedObjectForUrl(url);
+ return routerResult ? routerResult.object : null;
+ }
+}
+//# sourceMappingURL=routing.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping-utils.js
new file mode 100644
index 0000000..5d8e1ac
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping-utils.js
@@ -0,0 +1,42 @@
+/**
+ * Adds the required url query params to a media API request for Contingent Items
+ */
+export function configureContingentItemsForMediaRequest(mediaApiRequest) {
+ mediaApiRequest.includingScopedRelationships("contingent-items", ["trunk-app", "branch-app", "branch"]);
+ mediaApiRequest.includingViews(["events-and-offers", "standalone-merchandised-in-apps"]);
+ mediaApiRequest.includingScopedAttributes("contingent-items", ["name", "subtitle", "additionalTerms"]);
+ mediaApiRequest.includingMetaKeys("contingent-items:branch", ["contingentItemOffer"]);
+ mediaApiRequest.includingMetaKeys("contingent-items:association.trunks", ["useTrunkArtwork"]);
+ mediaApiRequest.includingAssociateKeys("contingent-items", ["trunks"]);
+}
+/**
+ * Adds the required url query params to a groupings API request for Contingent Items
+ */
+export function configureContingentItemsForGroupingRequest(mediaApiRequest) {
+ mediaApiRequest.includingScopedRelationships("contingent-items", ["trunk-app", "branch-app", "branch"]);
+ mediaApiRequest.includingScopedAttributes("contingent-items", ["name", "subtitle", "additionalTerms"]);
+ mediaApiRequest.includingMetaKeys("contingent-items:branch", ["contingentItemOffer"]);
+ mediaApiRequest.includingMetaKeys("contingent-items:association.trunks", ["useTrunkArtwork"]);
+ mediaApiRequest.includingAssociateKeys("contingent-items", ["trunks"]);
+}
+export function configureTagsForMediaRequest(mediaApiRequest) {
+ mediaApiRequest.enablingFeature("tags");
+ mediaApiRequest.includingScopedAttributes("tags", ["editorialArtwork"]);
+ mediaApiRequest.addingQuery("associate[tags]", "apps");
+ mediaApiRequest.includingScopedRelationships("editorial-elements", ["contents", "see-all-contents"]);
+}
+/**
+ * Adds the required url query params to a media API request for Winback Offer items
+ */
+export function configureOfferItemsForMediaRequest(mediaApiRequest) {
+ mediaApiRequest.includingScopedRelationships("offer-items", ["salables"]);
+ mediaApiRequest.includingScopedAttributes("offer-items", [
+ "title",
+ "subtitle",
+ "additionalTerms",
+ "redemptionExpirationDate",
+ ]);
+ mediaApiRequest.includingMetaKeys("offer-items:salables", ["discountOffer"]);
+ mediaApiRequest.includingKindsKeys("offer-items", ["winback"]);
+}
+//# sourceMappingURL=url-mapping-utils.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping.js b/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping.js
new file mode 100644
index 0000000..fdaa85b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/builders/url-mapping.js
@@ -0,0 +1,627 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as mediaAPI from "@apple-media-services/media-api";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaDataFetching from "../../foundation/media/data-fetching";
+import * as urlBuilder from "../../foundation/media/url-builder";
+import { Host, Parameters, Path, previewHosts, ProductPageParameters } from "../../foundation/network/url-constants";
+import { configureMediaRequestForGameRecommendations } from "../../gameservicesui/src/foundation/media-api/requests/game-recommendations";
+import * as client from "../../foundation/wrappers/client";
+import { isAdPlacementEnabled } from "../ads/ad-common";
+import * as appPromotionsCommon from "../app-promotions/app-promotions-common";
+import * as privacySuppression from "../privacy/privacy-suppression";
+import * as productPageVariants from "../product-page/product-page-variants";
+import * as mediaRequestUtils from "./url-mapping-utils";
+import { addDeveloperRequestProperties, makeDeveloperRequest } from "../developer/developer-request";
+import { isReviewSummaryEnabled } from "../product-page/reviews";
+import { isProductAccessibilityLabelsEnabled, shouldSuppressAccessibilityLabelsForAdamId, shouldSuppressAccessibilityLabelsForBundleId, } from "../accessibility/accessibility-common";
+import { buildArticlePageRequest } from "../today/article-request";
+import { makeRoutableArticlePageIntent } from "../../api/intents/routable-article-page-intent";
+import { getLocale } from "../locale";
+import { shouldUsePrerenderedIconArtwork } from "../content/content";
+import { AppEventsAttributes } from "../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types";
+import { MediaApiConfiguration } from "../../gameservicesui/src/foundation/media-api/media-api-configuration";
+const appsResourceAttributes = [
+ "description",
+ "fileSizeByDevice",
+ "messagesScreenshots",
+ "minimumOSVersion",
+ "privacyPolicyUrl",
+ "promotionalText",
+ "screenshotsByType",
+ "supportsFunCamera",
+ "supportURLForLanguage",
+ "versionHistory",
+ "videoPreviewsByType",
+ "websiteUrl",
+ "expectedReleaseDateDisplayFormat",
+ "requirementsByDeviceFamily",
+ "remoteControllerRequirement",
+ "backgroundAssetsInfo",
+ "backgroundAssetsInfoWithOptional",
+ "supportsSharePlay",
+ "installSizeByDeviceInBytes",
+ "miniGamesDeepLink",
+ "gameDisplayName",
+];
+// exporting this will be bad in the long run
+// adding for <rdar://problem/39578622> Catagories: Games results in cannot connect to app store.
+// should be removed when we stop handling media api urls in the router
+export function isMediaUrl(objectGraph, url) {
+ const host = url.host;
+ if (serverData.isNull(host)) {
+ return false;
+ }
+ const mediaApiHosts = [
+ objectGraph.bag.mediaHost,
+ objectGraph.bag.mediaEdgeHost(objectGraph),
+ objectGraph.bag.mediaEdgeSearchHost,
+ ];
+ for (const mediaApiHost of mediaApiHosts) {
+ if (serverData.isNull(mediaApiHost)) {
+ continue;
+ }
+ if (host.indexOf(mediaApiHost) !== -1) {
+ return true;
+ }
+ }
+ return false;
+}
+export function hrefToRoutableUrl(objectGraph, href, additionalQueryParams) {
+ if (isNothing(href)) {
+ return undefined;
+ }
+ if (preprocessor.GAMES_TARGET) {
+ const request = new mediaAPI.Request(objectGraph, href);
+ if (isSome(additionalQueryParams)) {
+ for (const [key, value] of Object.entries(additionalQueryParams)) {
+ request.addingQuery(key, value);
+ }
+ }
+ return mediaAPI.buildURLFromRequest(new MediaApiConfiguration(objectGraph), request).toString();
+ }
+ else {
+ const request = new mediaDataFetching.Request(objectGraph, href).addingQueryValues(additionalQueryParams);
+ return urlBuilder.buildURLFromRequest(objectGraph, request).toString();
+ }
+}
+// region Grouping
+export function mediaApiGroupingURLFromHref(objectGraph, href) {
+ const attributes = [
+ "editorialArtwork",
+ "editorialVideo",
+ "isAppleWatchSupported",
+ "requiredCapabilities",
+ "minimumOSVersion",
+ "expectedReleaseDateDisplayFormat",
+ ];
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ // <rdar://problem/56198088> Catalyst apps have clickable 'get' button on 10.14.
+ // - minimumOSVersion required to filter catalyst apps from groupings.
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph, href)
+ .includingAttributes(attributes)
+ .includingAgeRestrictions()
+ .includingMacOSCompatibleIOSAppsWhenSupported();
+ if (appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
+ mediaApiRequest.enablingFeature("appEvents");
+ mediaApiRequest.includingScopedAttributes("app-events", AppEventsAttributes);
+ mediaApiRequest.includingScopedRelationships("app-events", ["app"]);
+ mediaApiRequest.includingScopedRelationships("editorial-item-shelves", ["app-events"]);
+ }
+ const context = mediaDataFetching.defaultGroupingContextForClient(objectGraph);
+ if (serverData.isDefinedNonNull(context)) {
+ mediaApiRequest.addingQuery("contexts", context);
+ }
+ return mediaApiRequest;
+}
+// endregion
+// region Editorial Stories
+export function mappedMediaApiEditorialItemURL(objectGraph, url, isIncomingURL) {
+ const pathComponents = url.pathComponents();
+ if (pathComponents.indexOf("story") < 0 &&
+ pathComponents.indexOf("editorialItem") < 0 &&
+ pathComponents.indexOf("viewEditorialItem") < 0 &&
+ url.host !== Host.spamBlockingExtensions &&
+ url.host !== Host.sharePlayApps &&
+ url.host !== Host.buddyOnboarding) {
+ throw new Error(`Unable to map ${url.build()} to a media api url`);
+ }
+ let editorialItemId;
+ if (url.host === Host.spamBlockingExtensions) {
+ // Populate editorial item ID for spam blocking extensions.
+ editorialItemId = objectGraph.props.asString("spamBlockingExtensionsEditorialItemID");
+ }
+ else if (url.host === Host.sharePlayApps) {
+ editorialItemId = objectGraph.bag.sharePlayAppsEditorialItemId;
+ }
+ else if (url.host === Host.buddyOnboarding) {
+ editorialItemId = objectGraph.bag.buddyOnboardingEditorialItemId;
+ }
+ else {
+ // Otherwise extract the ID from the URL.
+ editorialItemId = extractedIdFromURL(url);
+ }
+ if (!serverData.isNumber(editorialItemId)) {
+ throw new Error(`Unable to map ${url.build()} to a media api url`);
+ }
+ if (!serverData.isNumber(editorialItemId)) {
+ throw new Error(`Unable to map ${url} to a media api url`);
+ }
+ const mediaApiRequest = buildArticlePageRequest(objectGraph, makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ id: editorialItemId,
+ }), isIncomingURL);
+ const editorialCardId = url.query[Parameters.editorialCardId];
+ if (serverData.isDefinedNonNullNonEmpty(editorialCardId)) {
+ mediaApiRequest.withFilter("canvas:cardId", editorialCardId);
+ }
+ const previewParam = url.query[Parameters.preview];
+ if (previewHosts.has(url.host) && (previewParam === null || previewParam === void 0 ? void 0 : previewParam.length) > 0) {
+ mediaApiRequest.addingQuery("preview", previewParam);
+ mediaApiRequest.isStorePreviewRequest = true;
+ }
+ return mediaApiRequest.attributingTo(url.build());
+}
+// endregion
+// region Product
+const entityIdRegex = /id([0-9]+)\/?$/i;
+export function extractedIdFromURL(url) {
+ // 1. Look at the query parameter for the id
+ let extractedId = url.query["id"];
+ // 2. Consider the last path component /id<id>
+ const pathName = url.pathname;
+ if (serverData.isNull(extractedId) && (pathName === null || pathName === void 0 ? void 0 : pathName.length) > 0) {
+ const match = entityIdRegex.exec(pathName);
+ if (match && match.length > 1) {
+ extractedId = match[1];
+ }
+ }
+ // 3. At this point, if the ID is not found, try looking for /<id> instead, since this is what the last path
+ // component looks like in QA now.
+ if (!serverData.isNumber(extractedId)) {
+ const pathComponents = url.pathComponents();
+ if (serverData.isDefinedNonNullNonEmpty(pathComponents)) {
+ extractedId = pathComponents[pathComponents.length - 1];
+ }
+ }
+ return extractedId;
+}
+const productRedirectionTable = {
+ "915249334": "1462947752", // rdar://49929244 (SAD: Redirect from the old 2nd party Shortcuts product page to the new 1st party page on iOS 13)
+};
+export function mappedMediaApiProductURL(objectGraph, url, includeUnlistedApps = false) {
+ var _a, _b;
+ let productId = extractedIdFromURL(url);
+ if (!serverData.isNumber(productId)) {
+ throw new Error(`Unable to map ${url.build()} to a media api url`);
+ }
+ // Redirect to a different product if needed
+ if (productId in productRedirectionTable) {
+ productId = productRedirectionTable[productId];
+ }
+ let resourceType = "apps";
+ let attributes = appsResourceAttributes;
+ let relationships = [];
+ if (preprocessor.GAMES_TARGET) {
+ relationships = ["alternate-apps"];
+ }
+ else {
+ relationships = [
+ "customers-also-bought-apps",
+ "reviews",
+ "app-bundles",
+ "top-in-apps",
+ "related-editorial-items",
+ "alternate-apps",
+ ];
+ }
+ const pathComponents = (_a = url.pathname) === null || _a === void 0 ? void 0 : _a.split("/");
+ const isBundle = isSome(pathComponents) && pathComponents.includes(Path.productBundle);
+ if (isBundle) {
+ resourceType = "app-bundles";
+ attributes = [
+ "screenshotsByType",
+ "videoPreviewsByType",
+ "minimumOSVersion",
+ "requirementsByDeviceFamily",
+ "remoteControllerRequirement",
+ ];
+ relationships = ["apps", "reviews", "related-editorial-items"];
+ }
+ if (!preprocessor.GAMES_TARGET) {
+ relationships.push("developer-other-apps");
+ }
+ if (objectGraph.bag.enablePrivacyNutritionLabels &&
+ !privacySuppression.shouldSuppressPrivacyInformationForAdamId(objectGraph, productId)) {
+ attributes.push("privacy");
+ }
+ if (isProductAccessibilityLabelsEnabled(objectGraph) &&
+ !shouldSuppressAccessibilityLabelsForAdamId(objectGraph, productId)) {
+ attributes.push("accessibility");
+ }
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ if (objectGraph.bag.enableLicenses) {
+ attributes.push("licenses");
+ }
+ // TODO <rdar://problem/49763626> Product Page should not request developer information if supportsArcade is false
+ // We need to pipe the supportsArcade information from the sidepack, through the lockup, to the productPage action URL
+ // until then, isArcade will always be null.
+ // If no arcade affiliation is specified, or the product definitely isArcade, fetch the developer genre.
+ const isArcade = serverData.asBoolean(url.query[Parameters.isArcade]);
+ if (isArcade === null || isArcade) {
+ attributes.push("minPlayers", "maxPlayers", "editorialVideo");
+ relationships.push("developer");
+ }
+ if (objectGraph.client.isVision) {
+ attributes.push("compatibilityControllerRequirement");
+ attributes.push("isHighMotion");
+ if (objectGraph.featureFlags.isEnabled("spatial_controllers_2025A")) {
+ attributes.push("spatialControllerRequirement");
+ }
+ }
+ if (objectGraph.bag.enableSellerInfo) {
+ attributes.push("sellerInfo");
+ }
+ // Extends the API to include Internet Content Provider Info
+ if (objectGraph.bag.enableSellerICPAnnotation) {
+ attributes.push("internetContentProviderInfo");
+ }
+ // Extends the API to include supported Game Center features.
+ if (objectGraph.bag.gameCenterExtendSupportedFeatures) {
+ attributes.push("supportedGameCenterFeatures");
+ }
+ // Extends the API to include Review Summary
+ if (isReviewSummaryEnabled(objectGraph)) {
+ relationships.push("review-summary");
+ }
+ if (preprocessor.GAMES_TARGET) {
+ attributes.push("gamesUrl");
+ attributes.push("isEligibleForGamesApp");
+ }
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph)
+ .withIdOfType(productId, resourceType)
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .includingAgeRestrictions()
+ .includingRelationships(relationships)
+ .includingAttributes(attributes)
+ .includingMacOSCompatibleIOSAppsWhenSupported(true)
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph))
+ .includingViews(["categorizations"]);
+ if (!preprocessor.GAMES_TARGET) {
+ mediaApiRequest.addingRelationshipLimit("reviews", mediaDataFetching.defaultProductPageReviewsLimitForClient(objectGraph));
+ }
+ if (preprocessor.GAMES_TARGET) {
+ mediaApiRequest.includingScopedRelationships("apps", ["activities", "challenges"]);
+ mediaApiRequest.includingViews(["customers-also-bought-games", "developer-other-games"]);
+ mediaApiRequest.enablingFeature("unlistedApps");
+ if (objectGraph.debugSettings.enableHighlightsFromProductPageResponse) {
+ configureMediaRequestForGameRecommendations(mediaApiRequest);
+ }
+ }
+ if (((_b = url.query[Parameters.productVariantID]) === null || _b === void 0 ? void 0 : _b.length) > 0) {
+ mediaApiRequest.addingQuery(Parameters.productVariantID, url.query[Parameters.productVariantID]);
+ }
+ // If a `minExternalVersionId` is specified, pass this through to the MAPI request
+ const minExternalVersionId = url.query[ProductPageParameters.minExternalVersionId];
+ if (serverData.isDefinedNonNull(minExternalVersionId)) {
+ mediaApiRequest.addingQuery(ProductPageParameters.minExternalVersionId, minExternalVersionId);
+ }
+ // Disabling for bundles due to rdar://78542145 ([REG] Pre-Prod JS: SkySeed: App bundle product page does not load)
+ if (appPromotionsCommon.appEventsAreEnabled(objectGraph) && !isBundle) {
+ if (!appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
+ mediaApiRequest.includingScopedRelationships("apps", ["app-events"]);
+ }
+ mediaApiRequest.includingScopedAttributes("app-events", AppEventsAttributes);
+ mediaApiRequest.includingScopedAvailableIn("app-events", ["future"]);
+ }
+ if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
+ mediaRequestUtils.configureContingentItemsForMediaRequest(mediaApiRequest);
+ }
+ if (appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) {
+ mediaRequestUtils.configureOfferItemsForMediaRequest(mediaApiRequest);
+ }
+ if (isAdPlacementEnabled(objectGraph, "productPageYMAL")) {
+ mediaApiRequest.enablingFeature("cabAdSupport");
+ }
+ if (includeUnlistedApps) {
+ mediaApiRequest.enablingFeature("unlistedApps");
+ }
+ if (!appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
+ mediaApiRequest.includingRelationships(["merchandised-in-apps"]);
+ }
+ return mediaApiRequest.attributingTo(url.build());
+}
+// endregion
+// region Developer
+export function mediaApiDeveloperURLFromHref(objectGraph, href) {
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph, href);
+ return addDeveloperRequestProperties(objectGraph, mediaApiRequest);
+}
+export function mappedMediaApiDeveloperURL(objectGraph, url) {
+ const developerId = extractedIdFromURL(url);
+ if (!serverData.isNumber(developerId)) {
+ throw new Error(`Unable to map ${url.build()} to a media api url`);
+ }
+ return makeDeveloperRequest(objectGraph, developerId);
+}
+// endregion
+// region Charts
+/**
+ * Creates a Media API request for one or more charts using provided critera.
+ * @param objectGraph The App Store object graph.
+ * @param genreId The genre for the chart request.
+ * @param charts The list of charts for the request.
+ * @param ageBandId The age band to include in the request.
+ * @param clientIdentifier The identifier of the current client.
+ * @returns A Media API request for one or more charts using provided critera.
+ */
+export function mediaApiChartRequestForGenre(objectGraph, genreId, charts, ageBandId = null, clientIdentifier = objectGraph.host.clientIdentifier) {
+ const attributes = ["isAppleWatchSupported", "requiredCapabilities", "minimumOSVersion"];
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph)
+ .forType("charts")
+ .addingQuery("genre", genreId)
+ .includingAttributes(attributes)
+ .addingQuery("types", "apps")
+ .includingMacOSCompatibleIOSAppsWhenSupported(true)
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph));
+ if (serverData.isDefinedNonNullNonEmpty(charts)) {
+ mediaApiRequest.addingQuery(Parameters.charts, charts);
+ }
+ if (clientIdentifier === client.watchIdentifier) {
+ mediaApiRequest.addingContext("watch");
+ }
+ else if (clientIdentifier === client.messagesIdentifier) {
+ mediaApiRequest.addingContext("messages");
+ }
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ mediaApiRequest.includingAdditionalPlatforms(["iphone", "ipad"]);
+ }
+ if (isSome(ageBandId)) {
+ mediaApiRequest.addingQuery(Parameters.ages, ageBandId);
+ }
+ return mediaApiRequest;
+}
+export function mediaApiChartsRequest(objectGraph, genreId, ageBandId = null) {
+ let request = new mediaDataFetching.Request(objectGraph)
+ .forType("charts")
+ .addingQuery("types", "apps")
+ .addingQuery("genre", genreId)
+ .includingMacOSCompatibleIOSAppsWhenSupported(true);
+ if (ageBandId) {
+ request = request.addingQuery("ages", ageBandId);
+ }
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ request = request.includingAdditionalPlatforms(["iphone", "ipad"]);
+ }
+ return request;
+}
+// endregion
+// region Lookups
+export function lookupURLForProductId(objectGraph, productId, isPurchasesProduct, productVariantID, isBundle, includeUnlistedApps) {
+ const attributes = appsResourceAttributes;
+ if (objectGraph.bag.enablePrivacyNutritionLabels &&
+ !privacySuppression.shouldSuppressPrivacyInformationForAdamId(objectGraph, productId)) {
+ attributes.push("privacy");
+ // The "web" client needs the full privacy information in the initial page load;
+ // unlike other platforms, it does not load the full details on a separate page
+ if (objectGraph.client.isWeb) {
+ attributes.push("privacyDetails");
+ }
+ }
+ if (isProductAccessibilityLabelsEnabled(objectGraph) &&
+ !shouldSuppressAccessibilityLabelsForAdamId(objectGraph, productId)) {
+ attributes.push("accessibility");
+ // The web client requires complete accessibility information on initial page load.
+ // Unlike other platforms that load full details on separate pages,
+ // the web client displays this information in a modal overlay.
+ if (objectGraph.client.isWeb) {
+ attributes.push("accessibilityDetails");
+ }
+ }
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.client.isVision) {
+ attributes.push("compatibilityControllerRequirement");
+ attributes.push("isHighMotion");
+ if (objectGraph.featureFlags.isEnabled("spatial_controllers_2025A")) {
+ attributes.push("spatialControllerRequirement");
+ }
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ if (objectGraph.bag.enableLicenses) {
+ attributes.push("licenses");
+ }
+ // Extends the API to include supported Game Center features.
+ if (objectGraph.bag.gameCenterExtendSupportedFeatures) {
+ attributes.push("supportedGameCenterFeatures");
+ }
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph)
+ .withIdOfType(productId, "apps")
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .includingAttributes(attributes)
+ .includingRelationships([
+ "developer",
+ "customers-also-bought-apps",
+ "developer-other-apps",
+ "reviews",
+ "app-bundles",
+ "top-in-apps",
+ "related-editorial-items",
+ "alternate-apps",
+ ])
+ .addingRelationshipLimit("reviews", mediaDataFetching.defaultProductPageReviewsLimitForClient(objectGraph))
+ .includingMacOSCompatibleIOSAppsWhenSupported(true)
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph));
+ if (objectGraph.client.isWeb) {
+ // Ensure that the "web" client fully hydrates all PDP shelves
+ mediaApiRequest
+ .includingScopedSparseLimit("apps:customers-also-bought-apps", 40)
+ .includingScopedSparseLimit("apps:developer-other-apps", 40)
+ .includingScopedSparseLimit("apps:related-editorial-items", 40);
+ // Ensures web can render the DSA information in the annotation shelf
+ if (objectGraph.bag.enableSellerInfo) {
+ attributes.push("sellerInfo");
+ }
+ }
+ if (isPurchasesProduct) {
+ mediaApiRequest.addingQuery("availability", "redownload");
+ }
+ if (isSome(productVariantID)) {
+ mediaApiRequest.addingQuery(Parameters.productVariantID, productVariantID);
+ }
+ if (includeUnlistedApps) {
+ mediaApiRequest.enablingFeature("unlistedApps");
+ }
+ // Disabling for bundles due to rdar://78542145 ([REG] Pre-Prod JS: SkySeed: App bundle product page does not load)
+ if (appPromotionsCommon.appEventsAreEnabled(objectGraph) && !isBundle) {
+ if (!appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
+ mediaApiRequest.includingScopedRelationships("apps", ["app-events"]);
+ }
+ mediaApiRequest.includingScopedAttributes("app-events", AppEventsAttributes);
+ mediaApiRequest.includingScopedAvailableIn("app-events", ["future"]);
+ }
+ if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
+ mediaRequestUtils.configureContingentItemsForMediaRequest(mediaApiRequest);
+ }
+ if (appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) {
+ mediaRequestUtils.configureOfferItemsForMediaRequest(mediaApiRequest);
+ }
+ if (!appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
+ mediaApiRequest.includingRelationships(["merchandised-in-apps"]);
+ }
+ // Extends the API to include Review Summary
+ if (isReviewSummaryEnabled(objectGraph)) {
+ mediaApiRequest.includingRelationships(["review-summary"]);
+ }
+ if (isAdPlacementEnabled(objectGraph, "productPageYMAL")) {
+ mediaApiRequest.enablingFeature("cabAdSupport");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ return mediaApiRequest;
+}
+export function lookupURLForBundleId(objectGraph, bundleId, productVariantID, includeUnlistedApps) {
+ const attributes = appsResourceAttributes;
+ if (objectGraph.bag.enablePrivacyNutritionLabels &&
+ !privacySuppression.shouldSuppressPrivacyInformationForBundleId(objectGraph, bundleId)) {
+ attributes.push("privacy");
+ }
+ if (isProductAccessibilityLabelsEnabled(objectGraph) &&
+ !shouldSuppressAccessibilityLabelsForBundleId(objectGraph, bundleId)) {
+ attributes.push("accessibility");
+ }
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.client.isVision) {
+ attributes.push("compatibilityControllerRequirement");
+ attributes.push("isHighMotion");
+ if (objectGraph.featureFlags.isEnabled("spatial_controllers_2025A")) {
+ attributes.push("spatialControllerRequirement");
+ }
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ if (objectGraph.bag.enableLicenses) {
+ attributes.push("licenses");
+ }
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph)
+ .forType("apps")
+ .withFilter("bundleId", bundleId)
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .includingRelationships([
+ "developer",
+ "customers-also-bought-apps",
+ "developer-other-apps",
+ "reviews",
+ "app-bundles",
+ "top-in-apps",
+ "related-editorial-items",
+ "alternate-apps",
+ ])
+ .includingAttributes(attributes)
+ .addingRelationshipLimit("reviews", mediaDataFetching.defaultProductPageReviewsLimitForClient(objectGraph))
+ .includingMacOSCompatibleIOSAppsWhenSupported(true)
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph));
+ if (isSome(productVariantID)) {
+ mediaApiRequest.addingQuery(Parameters.productVariantID, productVariantID);
+ }
+ if (isAdPlacementEnabled(objectGraph, "productPageYMAL")) {
+ mediaApiRequest.enablingFeature("cabAdSupport");
+ }
+ if (includeUnlistedApps) {
+ mediaApiRequest.enablingFeature("unlistedApps");
+ }
+ if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
+ mediaApiRequest.includingViews(["standalone-merchandised-in-apps"]);
+ }
+ else {
+ mediaApiRequest.includingRelationships(["merchandised-in-apps"]);
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ return mediaApiRequest;
+}
+export function mediaApiProductSeeAllRequest(objectGraph, productId, relationship) {
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph)
+ .withIdOfType(productId, "apps")
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph))
+ .includingRelationships([relationship, "apps", "developer"])
+ .includingScopedSparseLimit(`apps:${relationship}`, 20);
+ if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
+ mediaRequestUtils.configureContingentItemsForMediaRequest(mediaApiRequest);
+ }
+ if (appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) {
+ mediaRequestUtils.configureOfferItemsForMediaRequest(mediaApiRequest);
+ }
+ return mediaApiRequest;
+}
+// endregion
+//# sourceMappingURL=url-mapping.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/categories.js b/node_modules/@jet-app/app-store/tmp/src/common/categories.js
new file mode 100644
index 0000000..544f932
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/categories.js
@@ -0,0 +1,149 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../api/models";
+import * as serverData from "../foundation/json-parsing/server-data";
+import { isNothing } from "@jet/environment";
+import * as mediaDataFetching from "../foundation/media/data-fetching";
+import * as client from "../foundation/wrappers/client";
+import { contentAttributeAsDictionary } from "./content/attributes";
+import * as content from "./content/content";
+/**
+ * Creates a category list context for child categories of a provided genre, e.g. Games or Apps.
+ *
+ * @param rootGenreId The genreId that describes the parent of the category list, regardless of whether the root is included in the list.
+ * @returns The created category list context, e.g. Apps, Games, or All.
+ */
+export function createContextFromGenre(rootGenreId) {
+ if (rootGenreId === 36 /* constants.GenreIds.Apps */.toString()) {
+ return 1 /* models.CategoryListContext.Apps */;
+ }
+ else if (rootGenreId === 6014 /* constants.GenreIds.Games */.toString()) {
+ return 2 /* models.CategoryListContext.Games */;
+ }
+ else {
+ return 0 /* models.CategoryListContext.All */;
+ }
+}
+/**
+ * Create a category list context for given categories by inspecting the first item, e.g. "All Games", "All Apps".
+ *
+ * @param categoryList The category list to create a category list context for.
+ * @returns The created category list context, e.g. Apps, Games, or All.
+ */
+export function createContextFromList(categoryList) {
+ const categories = categoryList.categories;
+ if (serverData.isDefinedNonNullNonEmpty(categories)) {
+ const firstCategoryGenreId = categories[0].genreId;
+ if (firstCategoryGenreId === 6014 /* constants.GenreIds.Games */.toString()) {
+ return 2 /* models.CategoryListContext.Games */;
+ }
+ else if (firstCategoryGenreId === 36 /* constants.GenreIds.Apps */.toString()) {
+ return 1 /* models.CategoryListContext.Apps */;
+ }
+ }
+ return 0 /* models.CategoryListContext.All */;
+}
+export function createRequest(objectGraph, genreId, clientIdentifier = objectGraph.host.clientIdentifier, additionalPlatforms = []) {
+ if (!genreId) {
+ genreId = objectGraph.client.isMac ? 39 /* constants.GenreIds.MacApps */ : 36 /* constants.GenreIds.Apps */;
+ }
+ const categoriesRequest = new mediaDataFetching.Request(objectGraph)
+ .forType("categories")
+ .includingAdditionalPlatforms(additionalPlatforms)
+ .includingMacOSCompatibleIOSAppsWhenSupported(true)
+ .includingAgeRestrictions()
+ .addingQuery("genre", `${genreId}`);
+ if (clientIdentifier === client.watchIdentifier) {
+ categoriesRequest.addingContext("watch");
+ }
+ else if (clientIdentifier === client.messagesIdentifier) {
+ categoriesRequest.addingContext("messages");
+ }
+ return categoriesRequest;
+}
+/**
+ * Creates a category list model from a server response.
+ *
+ * @param objectGraph The dependency graph for the App Store.
+ * @param response The JSON data returned by the server.
+ * @param includeRoot Whether or not to include the root category in the subcategory list, e.g. "All".
+ * @returns The created category list model.
+ */
+export function categoryListFromApiResponse(objectGraph, response, includeRoot = true) {
+ return validation.context("categoryListFromApiResponse", () => {
+ const categoriesData = serverData.asArrayOrEmpty(response, "results.categories");
+ const rootCategory = categoryFromApiResponse(objectGraph, serverData.asDictionary(categoriesData, "0"), includeRoot);
+ if (!rootCategory) {
+ return null;
+ }
+ return new models.CategoryList(rootCategory.children);
+ });
+}
+/**
+ * Creates a category model from a server response.
+ *
+ * @param objectGraph The dependency graph for the App Store.
+ * @param categoryData The JSON data from the server response for this individual category item.
+ * @param includeRoot Whether or not to include the root category in the child categories, e.g. "All".
+ * @returns The created category model.
+ */
+export function categoryFromApiResponse(objectGraph, categoryData, includeRoot = true) {
+ return validation.context("categoryFromApiResponse", () => {
+ if (!categoryData) {
+ return null;
+ }
+ const name = serverData.asString(categoryData, "name");
+ const genre = serverData.asString(categoryData, "genre");
+ const ages = serverData.asString(categoryData, "ages");
+ const artwork = content.artworkFromApiArtwork(objectGraph, serverData.asDictionary(categoryData, "artwork"), {
+ allowingTransparency: true,
+ useCase: 20 /* content.ArtworkUseCase.CategoryIcon */,
+ });
+ const children = serverData
+ .asArrayOrEmpty(categoryData, "children")
+ .map((childCategory) => {
+ return categoryFromApiResponse(objectGraph, serverData.asDictionary(childCategory), includeRoot);
+ })
+ .filter((category) => serverData.isDefinedNonNull(category));
+ const sortedChildren = sortCategories(objectGraph, children);
+ const label = serverData.asString(categoryData, "label");
+ if (label && includeRoot) {
+ // Add root to children to show "All" category
+ sortedChildren.unshift(new models.Category(label, genre, artwork, ages, []));
+ }
+ return new models.Category(name, genre, artwork, ages, sortedChildren);
+ });
+}
+function sortCategories(objectGraph, categories) {
+ return categories.sort((category1, category2) => {
+ try {
+ return category1.name.localeCompare(category2.name, objectGraph.loc.safeIdentifier, { usage: "sort" });
+ }
+ catch (e) {
+ return 0;
+ }
+ });
+}
+export function searchAdditionalPlatforms(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "pad":
+ return ["iphone"];
+ default:
+ return [];
+ }
+}
+export function categoryArtworkData(objectGraph, data, isMonochrome = false, forceFallbackArtwork = false, allowFallback = true) {
+ const attributePath = isMonochrome ? "contentIconTrimmedMonochrome" : "contentIconTrimmed";
+ const fallbackAttributePath = "brandLogo";
+ const editorialArtwork = contentAttributeAsDictionary(objectGraph, data, "editorialArtwork");
+ if (forceFallbackArtwork) {
+ return serverData.asDictionary(editorialArtwork, fallbackAttributePath);
+ }
+ else {
+ let artworkData = serverData.asDictionary(editorialArtwork, attributePath);
+ if (allowFallback && isNothing(artworkData)) {
+ artworkData = serverData.asDictionary(editorialArtwork, fallbackAttributePath);
+ }
+ return artworkData;
+ }
+}
+//# sourceMappingURL=categories.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/charts/charts-hub.js b/node_modules/@jet-app/app-store/tmp/src/common/charts/charts-hub.js
new file mode 100644
index 0000000..7c2da09
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/charts/charts-hub.js
@@ -0,0 +1,39 @@
+import { ChartsHubChart, ChartsHubPage, FlowAction } from "../../api/models";
+import { makeChartsPageIntent } from "../../api/intents/charts-page-intent";
+import { makeChartsPageURL } from "./charts-page-url";
+import { getLocale } from "../locale";
+import { getPlatform } from "../preview-platform";
+function seeAllActionForChart(objectGraph, chart) {
+ const selectedChart = chart.segments[chart.initialSegmentIndex].chart;
+ const seeAllAction = new FlowAction("topCharts");
+ const destinationIntent = makeChartsPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ genreId: chart.genreId,
+ chart: selectedChart,
+ });
+ seeAllAction.destination = destinationIntent;
+ seeAllAction.pageUrl = makeChartsPageURL(objectGraph, destinationIntent);
+ return seeAllAction;
+}
+/**
+ * Create a {@linkcode TopChart} from a {@linkcode TopChartsPage} without any of the
+ * page-level properties defined
+ */
+function topChartFromPage(objectGraph, page, title) {
+ const chart = new ChartsHubChart(page.genreId, page.ageBandId, title, page.segments, page.categoriesButtonTitle, page.categories);
+ chart.initialSegmentIndex = page.initialSegmentIndex;
+ chart.seeAllAction = seeAllActionForChart(objectGraph, chart);
+ return chart;
+}
+/**
+ * Render a {@linkcode ChartsHubPage} from constituent {@linkcode TopChartsPage}s
+ */
+export function renderChartsHub(objectGraph, appsPage, gamesPage) {
+ const appsCharts = topChartFromPage(objectGraph, appsPage, objectGraph.loc.string("TopCharts.Hub.Apps.Title"));
+ const gamesCharts = topChartFromPage(objectGraph, gamesPage, objectGraph.loc.string("TopCharts.Hub.Games.Title"));
+ const hubPage = new ChartsHubPage([appsCharts, gamesCharts]);
+ hubPage.title = objectGraph.loc.string("TopCharts.Hub.Title");
+ return hubPage;
+}
+//# sourceMappingURL=charts-hub.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/charts/charts-page-model.js b/node_modules/@jet-app/app-store/tmp/src/common/charts/charts-page-model.js
new file mode 100644
index 0000000..cbbecf3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/charts/charts-page-model.js
@@ -0,0 +1,156 @@
+import * as validation from "@jet/environment/json/validation";
+import { unsafeUnwrapOptional as unwrap } from "@jet/environment/types/optional";
+import { Shelf, TopChartSegment } from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { Path, Protocol } from "../../foundation/network/url-constants";
+import { addMetricsEventsToPageWithInformation, metricsPageInformationFromMediaApiResponse, } from "../metrics/helpers/page";
+import { newLocationTracker, nextPosition } from "../metrics/helpers/location";
+import { lockupFromData } from "../lockups/lockups";
+import { shouldFilter } from "../filtering";
+import { createMediaPageToken } from "../builders/pagination";
+/**
+ * Creates short and long display names for the top chart segment,
+ * using server data unless client has overrides in `locKeys.json`.
+ *
+ * @param objectGraph The dependency graph for the App Store.
+ * @param segmentData The top charts segment API object.
+ * @param context The context of the chart, e.g. Apps or Games.
+ * @returns Short and long display names for the top chart segment.
+ */
+function namesFromSegmentData(objectGraph, segmentData, context) {
+ const chart = serverData.asString(segmentData, "chart");
+ // For the web client, regardless of whether the context is Apps or Games, we show "Free" or "Paid".
+ if (objectGraph.client.isWeb) {
+ return chart === "top-free" /* TopChartType.TopFree */
+ ? {
+ shortName: objectGraph.loc.string("TopCharts.Free.ShortName"),
+ longName: objectGraph.loc.string("TopCharts.Free.LongName"),
+ }
+ : {
+ shortName: objectGraph.loc.string("TopCharts.Paid.ShortName"),
+ longName: objectGraph.loc.string("TopCharts.Paid.LongName"),
+ };
+ }
+ // Use server provided names, unless we are in the Games tab or web client.
+ let shortName = serverData.asString(segmentData, "shortName");
+ let longName = serverData.asString(segmentData, "name");
+ if (context !== 2 /* CategoryListContext.Games */) {
+ return { shortName: unwrap(shortName), longName: unwrap(longName) };
+ }
+ // Override server names with client names, e.g. from "Top Free/Paid iPad Apps" to "Top Free/Paid iPad Games" / "Top Free/Paid Vision Pro Games" as needed.
+ const isPad = objectGraph.client.isPad;
+ const isVision = objectGraph.client.isVision;
+ switch (chart) {
+ case "top-free" /* TopChartType.TopFree */:
+ if (isPad) {
+ shortName = objectGraph.loc.string("TopCharts.iPadGames.Free.ShortName"); // Free
+ longName = objectGraph.loc.string("TopCharts.iPadGames.Free.LongName"); // Top Free iPad Games
+ }
+ else if (isVision) {
+ shortName = objectGraph.loc.string("TopCharts.VisionGames.Free.ShortName"); // Free
+ longName = objectGraph.loc.string("TopCharts.VisionGames.Free.LongName"); // Top Free Apple Vision Games
+ }
+ else {
+ shortName = objectGraph.loc.string("TopCharts.Games.Free.ShortName"); // Free Games
+ longName = objectGraph.loc.string("TopCharts.Games.Free.LongName"); // Top Free Games
+ }
+ break;
+ case "top-paid" /* TopChartType.TopPaid */:
+ if (isPad) {
+ shortName = objectGraph.loc.string("TopCharts.iPadGames.Paid.ShortName"); // Paid
+ longName = objectGraph.loc.string("TopCharts.iPadGames.Paid.LongName"); // Top Paid iPad Games
+ }
+ else if (isVision) {
+ shortName = objectGraph.loc.string("TopCharts.VisionGames.Paid.ShortName"); // Paid
+ longName = objectGraph.loc.string("TopCharts.VisionGames.Paid.LongName"); // Top Paid Apple Vision Games
+ }
+ else {
+ shortName = objectGraph.loc.string("TopCharts.Games.Paid.ShortName"); // Paid Games
+ longName = objectGraph.loc.string("TopCharts.Games.Paid.LongName"); // Top Paid Games
+ }
+ break;
+ default:
+ break;
+ }
+ return { shortName: unwrap(shortName), longName: unwrap(longName) };
+}
+/**
+ * Create a lockup from an api chart item.
+ * @param index The index of the lockup in the chart.
+ * @param data The mediaAPI chart item.
+ * @returns A `Lockup` object.
+ */
+function lockupFromApiChartItem(objectGraph, index, data, metricsPageInformation, locationTracker) {
+ return validation.context("lockupFromApiChartItem", () => {
+ return lockupFromData(objectGraph, data, {
+ ordinal: objectGraph.loc.decimal(index + 1),
+ metricsOptions: {
+ pageInformation: metricsPageInformation,
+ locationTracker: locationTracker,
+ },
+ artworkUseCase: 1 /* ArtworkUseCase.LockupIconSmall */,
+ });
+ });
+}
+/**
+ * Creates a top chart segment model object from a top chart segment API object.
+ *
+ * @param objectGraph The dependency graph for the App Store.
+ * @param segmentData The top chart segment API object.
+ * @param response The top charts API object containing all segments.
+ * @param genreId The genre of the chart, e.g. Developer Tools or Board.
+ * @param context The context of the chart, e.g. Apps or Games.
+ * @returns A new top chart segment model object.
+ */
+export function segmentFromData(objectGraph, segmentData, response, genreId, context) {
+ return validation.context("segmentFromData", () => {
+ const { shortName, longName } = namesFromSegmentData(objectGraph, segmentData, context);
+ const chart = serverData.asString(segmentData, "chart");
+ const pageDetails = `${chart} ${longName}`;
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "TopChartsPage", genreId, response, pageDetails);
+ const locationTracker = newLocationTracker();
+ const items = [];
+ let ordinal = 0;
+ const missingIds = [];
+ for (const data of segmentData.data) {
+ const lockup = lockupFromApiChartItem(objectGraph, ordinal, data, pageInformation, locationTracker);
+ if (lockup) {
+ // Filter only if segment has attributes
+ if (shouldFilter(objectGraph, data, 68606 /* Filter.Charts */)) {
+ continue;
+ }
+ items.push(lockup);
+ nextPosition(locationTracker);
+ ordinal++;
+ }
+ else {
+ missingIds.push(data);
+ }
+ }
+ const shelves = [];
+ /// always generate a shelf else we don't load more items when we have none
+ const shelf = new Shelf("smallLockup");
+ shelf.items = items;
+ if (objectGraph.featureFlags.isEnabled("shelves_2_0_top_charts") ||
+ objectGraph.client.isiOS ||
+ objectGraph.client.isTV) {
+ shelf.title = longName;
+ }
+ shelves.push(shelf);
+ const segment = new TopChartSegment(shortName, longName, chart, shelves);
+ // On Vision, include a URL for pagination - we don't use a specific Top Charts view, so we need the
+ // `GenericPageMoreIntent` to be able to find this controller for loading new content.
+ // This can be removed once visionOS adopts `TopChartsPageMoreIntent`.
+ const paginationUrl = objectGraph.client.isVision
+ ? `${Protocol.internal}:/TopChartsBuilder/${Path.lookup}`
+ : undefined;
+ const token = createMediaPageToken(objectGraph, missingIds, paginationUrl, segmentData.next, pageInformation, locationTracker);
+ token.metricsPageInformation = pageInformation;
+ token.metricsLocationTracker = locationTracker;
+ token.highestOrdinal = ordinal;
+ segment.nextPage = token;
+ addMetricsEventsToPageWithInformation(objectGraph, segment, pageInformation);
+ return segment;
+ });
+}
+//# sourceMappingURL=charts-page-model.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/charts/charts-page-url.js b/node_modules/@jet-app/app-store/tmp/src/common/charts/charts-page-url.js
new file mode 100644
index 0000000..2ecc0e2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/charts/charts-page-url.js
@@ -0,0 +1,12 @@
+import { makeChartsPageIntent } from "../../api/intents/charts-page-intent";
+import { makeChartsHubPageIntent } from "../../api/intents/charts-hub-page-intent";
+import { generateRoutes } from "../util/generate-routes";
+/// MARK: Charts Detail Page Routing
+const { routes: chartsPageRoutes, makeCanonicalUrl: makeChartsPageURL } = generateRoutes(makeChartsPageIntent, "/{platform}/charts/{genreId}", [], {
+ optionalQuery: ["chart", "ageBandId"],
+});
+export { chartsPageRoutes, makeChartsPageURL };
+/// MARK: Charts Hub Page Routing
+const { routes: chartsHubPageRoutes, makeCanonicalUrl: makeChartsHubPageURL } = generateRoutes(makeChartsHubPageIntent, "/{platform}/charts");
+export { chartsHubPageRoutes, makeChartsHubPageURL };
+//# sourceMappingURL=charts-page-url.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/constants/video-constants.js b/node_modules/@jet-app/app-store/tmp/src/common/constants/video-constants.js
new file mode 100644
index 0000000..22d3a89
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/constants/video-constants.js
@@ -0,0 +1,34 @@
+/**
+ * Created by jellenbogen on 7/22/19.
+ */
+export function standardControls(objectGraph) {
+ return {
+ prominentPlay: true,
+ fullScreenToggle: true,
+ inlinePlayPause: true,
+ muteUnmute: true,
+ };
+}
+export function autoPlayControls(objectGraph) {
+ return {
+ prominentPlay: true,
+ fullScreenToggle: false,
+ inlinePlayPause: objectGraph.client.isMac,
+ muteUnmute: true,
+ };
+}
+export function defaultVideoConfiguration(objectGraph) {
+ return {
+ playbackControls: standardControls(objectGraph),
+ autoPlayPlaybackControls: autoPlayControls(objectGraph),
+ };
+}
+export function noControls(objectGraph) {
+ return {
+ prominentPlay: false,
+ fullScreenToggle: false,
+ inlinePlayPause: false,
+ muteUnmute: false,
+ };
+}
+//# sourceMappingURL=video-constants.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/content/age-ratings.js b/node_modules/@jet-app/app-store/tmp/src/common/content/age-ratings.js
new file mode 100644
index 0000000..91d95a6
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/content/age-ratings.js
@@ -0,0 +1,105 @@
+import { isSome } from "@jet/environment";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as serverData from "../../foundation/json-parsing/server-data";
+/**
+ * Provides the localized name of the product's age rating, e.g. "12+".
+ * @param objectGraph The App Store object graph.
+ * @param data The Media API data for an app.
+ * @param useLegacyFallback Indicates whether the legacy key should be used if the modern one is missing.
+ * @returns The localized `string` of the product's age rating, or `undefined`.
+ */
+export function name(objectGraph, data, useLegacyFallback = false) {
+ const modernValue = mediaAttributes.attributeAsString(data, "ageRating.name");
+ if (isSome(modernValue)) {
+ return modernValue;
+ }
+ else if (useLegacyFallback) {
+ return mediaAttributes.attributeAsString(data, "contentRatingsBySystem.appsApple.name");
+ }
+ else {
+ return undefined;
+ }
+}
+/**
+ * Provides the product's age rating value, e.g. 300. This value is understood
+ * by systems such as Managed Configuration and Screen Time to enforce content
+ * restrictions.
+ * @param objectGraph The App Store object graph.
+ * @param data The Media API data for an app.
+ * @param useLegacyFallback Indicates whether the legacy key should be used if the modern one is missing.
+ * @returns The `number` value of the product's age rating, or `undefined`.
+ */
+export function value(objectGraph, data, useLegacyFallback = false) {
+ const modernValue = mediaAttributes.attributeAsNumber(data, "ageRating.value");
+ if (isSome(modernValue)) {
+ return modernValue;
+ }
+ else if (useLegacyFallback) {
+ return mediaAttributes.attributeAsNumber(data, "contentRatingsBySystem.appsApple.value");
+ }
+ else {
+ return undefined;
+ }
+}
+/**
+ * Provides the product's age rating description from Media API. This was
+ * historically generated by the client, so has no legacy fallback.
+ * @param objectGraph The App Store object graph.
+ * @param data The Media API data for an app.
+ * @returns The product's age rating description `string`, or `undefined`.
+ */
+export function description(objectGraph, data) {
+ return mediaAttributes.attributeAsString(data, "ageRating.description");
+}
+export function hasInAppControls(objectGraph, data) {
+ const contentLevels = mediaAttributes.attributeAsArrayOrEmpty(data, "ageRating.contentLevels");
+ for (const contentLevel of contentLevels) {
+ if (serverData.asString(contentLevel, "kind") === "IAC") {
+ return true;
+ }
+ }
+ return false;
+}
+/**
+ * Provides the product's developer age guidance URL from Media API. This is an
+ * optional URL the developer can provide in App Store Connect to give further
+ * details on their app's content controls. There is no legacy fallback for
+ * this value.
+ * @param objectGraph The App Store object graph.
+ * @param data The Media API data for an app.
+ * @returns The product's developer age guidance URL `string`, or `undefined`.
+ */
+export function developerAgeGuidanceURL(objectGraph, data) {
+ return mediaAttributes.attributeAsString(data, "ageRating.ageGuidanceUrl");
+}
+/**
+ * Returns the name of an image resource in the App Store bundle corresponding
+ * to the provided MAPI `ageRating` data.
+ * @param objectGraph The App Store object graph.
+ * @param data The Media API data for an app.
+ * @returns The `string` name of an image resource in the bundle, or `undefined`.
+ */
+export function pictogramResource(objectGraph, data) {
+ // Values pulled from https://quip-apple.com/0bq3AiLxhzaW
+ const pictogramResources = new Map([
+ // Brazil
+ ["br.100.official", "br.l.official"],
+ ["br.100", "br.l"],
+ ["br.210.official", "br.10.official"],
+ ["br.210", "br.10"],
+ ["br.300.official", "br.12.official"],
+ ["br.300", "br.12"],
+ ["br.314.official", "br.14.official"],
+ ["br.314", "br.14"],
+ ["br.416.official", "br.16.official"],
+ ["br.416", "br.16"],
+ ["br.618.official", "br.18.official"],
+ ["br.618", "br.18"],
+ ]);
+ const storefront = objectGraph.locale.activeStorefront;
+ const contentLevel = mediaAttributes.attributeAsString(data, "ageRating.value");
+ const isOfficial = mediaAttributes.attributeAsBooleanOrFalse(data, "ageRating.isOfficial");
+ const key = storefront + "." + contentLevel + (isOfficial ? ".official" : "");
+ return pictogramResources.get(key);
+}
+//# sourceMappingURL=age-ratings.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/content/artwork/artwork.js b/node_modules/@jet-app/app-store/tmp/src/common/content/artwork/artwork.js
new file mode 100644
index 0000000..ebf7c71
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/content/artwork/artwork.js
@@ -0,0 +1,71 @@
+import * as modelsBase from "../../../api/models/base";
+export function createArtworkVariantForClient(objectGraph, allowingTransparency = false, supportsWideGamut = false, useCase = 0 /* content.ArtworkUseCase.Default */) {
+ const format = artworkFormatForClient(objectGraph, allowingTransparency);
+ return createArtworkVariantForFormat(objectGraph, format, supportsWideGamut, useCase);
+}
+export function createArtworkVariantForFormat(objectGraph, format, supportsWideGamut = false, useCase = 0 /* content.ArtworkUseCase.Default */) {
+ const quality = qualityForClient(objectGraph, format, useCase);
+ return new modelsBase.ArtworkVariant(format, quality, supportsWideGamut);
+}
+export function createArtworkForResource(objectGraph, resource, width = 0, height = 0, backgroundColor = null, textColor = null, checksum = null) {
+ const artwork = new modelsBase.Artwork(resource, width, height, [
+ createArtworkVariantForClient(objectGraph, false, false, 0 /* content.ArtworkUseCase.Default */),
+ ]);
+ artwork.backgroundColor = backgroundColor;
+ artwork.textColor = textColor;
+ artwork.checksum = checksum;
+ return artwork;
+}
+/**
+ * Create a bundle artwork template for a resource.
+ */
+export function artworkTemplateForBundleImage(resource) {
+ if (preprocessor.GAMES_TARGET) {
+ return `bundleimage://${resource}?bundleid=com.apple.GameStoreKit`;
+ }
+ else {
+ return `resource://${resource}`;
+ }
+}
+/**
+ * Create Artwork for a system image.
+ */
+export function createArtworkForSystemImage(objectGraph, resource) {
+ return createArtworkForResource(objectGraph, `systemimage://${resource}`);
+}
+function artworkFormatForClient(objectGraph, allowsTransparency) {
+ // Determine whether HEIF is supported for the given target.
+ let supportsHEIF;
+ switch (objectGraph.host.clientIdentifier) {
+ case "com.apple.TVAppStore.AppStoreTopShelfExtension":
+ case "com.apple.Arcade.ArcadeTopShelfExtension":
+ case "com.apple.AppStore.Widgets":
+ // Disable HEIF for top shelf
+ supportsHEIF = false;
+ break;
+ default:
+ supportsHEIF = objectGraph.client.supportsHEIF;
+ break;
+ }
+ // HEIF is the image format, HEIC is the container
+ const defaultArtworkFormat = supportsHEIF ? "heic" : "jpeg";
+ const defaultArtworkFormatAllowingTransparency = supportsHEIF ? "heic" : "png";
+ return allowsTransparency ? defaultArtworkFormatAllowingTransparency : defaultArtworkFormat;
+}
+function qualityForClient(objectGraph, artworkFormat, useCase) {
+ switch (artworkFormat) {
+ case "heic": {
+ if (objectGraph.client.isTV && useCase === 21 /* content.ArtworkUseCase.Uber */) {
+ return 70;
+ }
+ else {
+ return 60;
+ }
+ }
+ // JPEG, LCR and PNG
+ default: {
+ return 70;
+ }
+ }
+}
+//# sourceMappingURL=artwork.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/content/artwork/legacy-artwork.js b/node_modules/@jet-app/app-store/tmp/src/common/content/artwork/legacy-artwork.js
new file mode 100644
index 0000000..7607675
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/content/artwork/legacy-artwork.js
@@ -0,0 +1,33 @@
+/**
+ * Created by keithpk on 1/25/17.
+ */
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as artworkBuilder from "./artwork";
+export function closestArtworkMatchingSize(objectGraph, artworkArray, width, height) {
+ const desiredRatio = width / height;
+ let foundAspectRatio = 0;
+ let foundArtwork = null;
+ for (const artwork of artworkArray) {
+ const actualWidth = serverData.asNumber(artwork, "width");
+ const actualHeight = serverData.asNumber(artwork, "height");
+ const actualRatio = actualWidth / actualHeight;
+ if (actualRatio === desiredRatio ||
+ Math.abs(desiredRatio - actualRatio) <= Math.abs(desiredRatio - foundAspectRatio)) {
+ if (!foundArtwork ||
+ (actualWidth <= width && actualWidth > foundArtwork.width) ||
+ (foundArtwork.width > width && actualWidth < foundArtwork.width && actualWidth > foundArtwork.width)) {
+ foundArtwork = artwork;
+ foundAspectRatio = actualRatio;
+ }
+ }
+ }
+ if (foundArtwork) {
+ const artworkUrl = serverData.asString(foundArtwork, "url");
+ const foundWidth = serverData.asNumber(foundArtwork, "width");
+ const foundHeight = serverData.asNumber(foundArtwork, "height");
+ const foundChecksum = serverData.asString(foundArtwork, "checksum");
+ return artworkBuilder.createArtworkForResource(objectGraph, artworkUrl, foundWidth, foundHeight, null, null, foundChecksum);
+ }
+ return null;
+}
+//# sourceMappingURL=legacy-artwork.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/content/attributes.js b/node_modules/@jet-app/app-store/tmp/src/common/content/attributes.js
new file mode 100644
index 0000000..1cae53a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/content/attributes.js
@@ -0,0 +1,328 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as derivedData from "../../foundation/json-parsing/derived-data";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaPlatformAttributes from "../../foundation/media/platform-attributes";
+import { variantAttributeForKey } from "../product-page/product-page-variants";
+import * as contentDeviceFamily from "./device-family";
+/**
+ * Retrieve the specified attribute from the data, coercing it to a JSONData dictionary
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param attributePlatform The specific platform attribute to get content for. Omit to infer from the data's structure.
+ * @param defaultValue The object to return if the path search fails.
+ * @returns The dictionary of data
+ */
+export function contentAttributeAsDictionary(objectGraph, data, attributePath, attributePlatform, defaultValue) {
+ if (!attributePlatform) {
+ attributePlatform = bestAttributePlatformFromData(objectGraph, data);
+ }
+ if (isNothing(attributePlatform)) {
+ return null;
+ }
+ let value = mediaPlatformAttributes.platformAttributeAsDictionary(data, attributePlatform, attributePath);
+ if (!value) {
+ value = mediaAttributes.attributeAsDictionary(data, attributePath, defaultValue);
+ }
+ return value;
+}
+/**
+ * Retrieve the specified attribute from the data as an array, coercing to an empty array if the object is not an array.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param attributePlatformOverride An override platform, from which to fetch the attribute.
+ * @returns {any[]} The attribute value as an array.
+ */
+export function contentAttributeAsArrayOrEmpty(objectGraph, data, attributePath, attributePlatformOverride = undefined) {
+ const attributePlatform = attributePlatformOverride !== null && attributePlatformOverride !== void 0 ? attributePlatformOverride : bestAttributePlatformFromData(objectGraph, data);
+ if (isNothing(attributePlatform)) {
+ return [];
+ }
+ let value = mediaPlatformAttributes.platformAttributeAsArrayOrEmpty(data, attributePlatform, attributePath);
+ if (serverData.isNullOrEmpty(value)) {
+ value = mediaAttributes.attributeAsArrayOrEmpty(data, attributePath);
+ }
+ return value;
+}
+/**
+ * Retrieve the specified attribute from the data as an array.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param attributePlatformOverride An override platform, from which to fetch the attribute.
+ * @returns {any[]} The attribute value as an array.
+ */
+export function contentAttributeAsArray(objectGraph, data, attributePath, attributePlatformOverride = undefined) {
+ const attributePlatform = attributePlatformOverride !== null && attributePlatformOverride !== void 0 ? attributePlatformOverride : bestAttributePlatformFromData(objectGraph, data);
+ if (isNothing(attributePlatform)) {
+ return null;
+ }
+ let value = mediaPlatformAttributes.platformAttributeAsArray(data, attributePlatform, attributePath);
+ if (isNothing(value)) {
+ value = mediaAttributes.attributeAsArray(data, attributePath);
+ }
+ return value;
+}
+/**
+ * Retrieve the specified attribute from the data as a string.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The object path for the attribute.
+ * @param attributePlatformOverride An override platform, from which to fetch the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {string} The attribute value as a string.
+ */
+export function contentAttributeAsString(objectGraph, data, attributePath, attributePlatformOverride = undefined, policy = "coercible") {
+ let value;
+ const attributePlatform = attributePlatformOverride !== null && attributePlatformOverride !== void 0 ? attributePlatformOverride : bestAttributePlatformFromData(objectGraph, data);
+ if (isSome(attributePlatform)) {
+ value = mediaPlatformAttributes.platformAttributeAsString(data, attributePlatform, attributePath, policy);
+ }
+ if (!value) {
+ value = mediaAttributes.attributeAsString(data, attributePath, policy);
+ }
+ return value;
+}
+/**
+ * Retrieve the specified attribute from the data as a boolean.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a boolean.
+ */
+export function contentAttributeAsBoolean(objectGraph, data, attributePath, attributePlatform, policy = "coercible") {
+ if (!attributePlatform) {
+ attributePlatform = bestAttributePlatformFromData(objectGraph, data);
+ }
+ if (isNothing(attributePlatform)) {
+ return null;
+ }
+ let value = mediaPlatformAttributes.platformAttributeAsBoolean(data, attributePlatform, attributePath, policy);
+ if (serverData.isNull(value)) {
+ value = mediaAttributes.attributeAsBoolean(data, attributePath, policy);
+ }
+ return value;
+}
+/**
+ * Retrieve the specified attribute from the data as a boolean, which will be `false` if the attribute does not exist.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param attributePlatform The specific platform attribute to get content for. Omit to infer from the data's structure.
+ * @returns {boolean} The attribute value as a boolean, coercing to `false` if the value is not present..
+ */
+export function contentAttributeAsBooleanOrFalse(objectGraph, data, attributePath, attributePlatform) {
+ if (!attributePlatform) {
+ attributePlatform = bestAttributePlatformFromData(objectGraph, data);
+ }
+ if (isNothing(attributePlatform)) {
+ return false;
+ }
+ let value = mediaPlatformAttributes.platformAttributeAsBoolean(data, attributePlatform, attributePath);
+ if (serverData.isNull(value)) {
+ value = mediaAttributes.attributeAsBooleanOrFalse(data, attributePath);
+ }
+ return value;
+}
+/**
+ * Retrieve the specified attribute from the data as a number.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a number.
+ */
+export function contentAttributeAsNumber(objectGraph, data, attributePath, policy = "coercible") {
+ const attributePlatform = bestAttributePlatformFromData(objectGraph, data);
+ if (isNothing(attributePlatform)) {
+ return null;
+ }
+ let value = mediaPlatformAttributes.platformAttributeAsNumber(data, attributePlatform, attributePath, policy);
+ if (serverData.isNull(value)) {
+ value = mediaAttributes.attributeAsNumber(data, attributePath);
+ }
+ return value;
+}
+/**
+ * Computes the best attribute platform for a given piece of content
+ *
+ * @param {Data} data The media API data representing the content
+ * @returns {AttributePlatform}
+ */
+export function bestAttributePlatformFromData(objectGraph, data, clientIdentifierOverride) {
+ const baseCacheKey = "bestAttributePlatformFromData";
+ const cacheKey = isSome(clientIdentifierOverride) ? `${baseCacheKey}.${clientIdentifierOverride}` : baseCacheKey;
+ return derivedData.value(data, cacheKey, () => {
+ const isIOSOnly = contentDeviceFamily.dataOnlyHasDeviceFamilies(objectGraph, data, ["iphone", "ipad", "ipod"], true);
+ const isTvOnly = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "tvos");
+ const isMacOnly = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "mac");
+ const isWatchOnly = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "watch");
+ const isVisionOnly = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "realityDevice");
+ // 1. The data is for a single platform only.
+ let dedicatedPlatform = null;
+ if (isTvOnly) {
+ dedicatedPlatform = "appletvos";
+ }
+ else if (isMacOnly) {
+ dedicatedPlatform = "osx";
+ }
+ else if (isIOSOnly) {
+ dedicatedPlatform = "ios";
+ }
+ else if (isWatchOnly) {
+ dedicatedPlatform = "watch";
+ }
+ else if (isVisionOnly) {
+ dedicatedPlatform = "xros";
+ }
+ if (!serverData.isNull(dedicatedPlatform)) {
+ return dedicatedPlatform;
+ }
+ // 2. Loop through our preferred ordering of platforms and use the first one that has platformAttributes present.
+ const alternatePlatforms = defaultAttributePlatformOrdering(objectGraph, clientIdentifierOverride);
+ for (const candidatePlatform of alternatePlatforms) {
+ if (mediaPlatformAttributes.hasPlatformAttribute(data, candidatePlatform)) {
+ return candidatePlatform;
+ }
+ }
+ // 3. Catch-All
+ return defaultAttributePlatform(objectGraph);
+ });
+}
+/**
+ * Computes the best attribute platform for a given Media API Marketplace
+ * response. Since Marketplace responses don't contain a top-level
+ * `deviceFamilies` property, this employs an alternative method from
+ * `bestAttributePlatformFromData()` to get the attribute platform.
+ *
+ * @param objectGraph The App Store object graph
+ * @param data The Media API Marketplace response to search for an attribute platform.
+ * @returns The most appropriate attribute platform available for the current client.
+ */
+export function bestAttributePlatformFromMarketplaceData(objectGraph, data) {
+ // 1. Iterate through the client's preferred platform ordering until we
+ // find the first one present in the response.
+ const preferredAttributePlatforms = defaultAttributePlatformOrdering(objectGraph);
+ for (const attributePlatform of preferredAttributePlatforms) {
+ const versionsAttributes = contentAttributeAsDictionary(objectGraph, data, "versionAttributes", attributePlatform);
+ if (serverData.isDefinedNonNullNonEmpty(versionsAttributes)) {
+ return attributePlatform;
+ }
+ }
+ // 2. Catch-All
+ return defaultAttributePlatform(objectGraph);
+}
+/**
+ * The default attribute platform for the current client
+ */
+export function defaultAttributePlatform(objectGraph) {
+ var _a;
+ if ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.attributePlatform) {
+ return objectGraph.activeIntent.attributePlatform;
+ }
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ case "pad":
+ return "ios";
+ case "tv":
+ return "appletvos";
+ case "mac":
+ return "osx";
+ case "watch":
+ return "watch";
+ case "vision":
+ return "xros";
+ default:
+ return null;
+ }
+}
+/**
+ * The preferred ordering to use given our default platform.
+ */
+function defaultAttributePlatformOrdering(objectGraph, clientIdentifierOverride) {
+ const defaultPlatform = defaultAttributePlatform(objectGraph);
+ if (defaultPlatform === null) {
+ // If the `"web"` client is active and there is not an "active intent" to
+ // inform a default platform, fall back to a hard-coded ordering
+ if (objectGraph.client.isWeb) {
+ return ["ios", "osx", "xros", "watch", "appletvos"];
+ }
+ else {
+ return [];
+ }
+ }
+ switch (defaultPlatform) {
+ case "ios":
+ if (clientIdentifierOverride === "VisionAppStore" /* ClientIdentifier.VisionAppStore */ ||
+ clientIdentifierOverride === "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */) {
+ return ["xros", "ios", "appletvos", "osx"];
+ }
+ else {
+ return ["ios", "appletvos", "osx", "xros"];
+ }
+ case "osx":
+ return ["osx", "ios", "appletvos", "xros"];
+ case "appletvos":
+ return ["appletvos", "ios", "osx", "xros"];
+ case "watch":
+ // Per Hiren Kotadia on 2019-2-26, watch platform attributes will always be under ios.
+ // We're going to promote ios to the head of the search list to speed up Media API
+ // response parsing. We'll keep watch as #2 in the list so that if this changes in
+ // the future, it should mostly just work. -km
+ return ["ios", "watch", "osx", "xros"];
+ case "xros":
+ return ["xros", "ios", "appletvos", "osx"];
+ default:
+ return [defaultPlatform];
+ }
+}
+// region Variant Attributes
+/**
+ * Retrieve the attribute for a specific platform's variant attribute as a dictionary, from custom attributes or standard attributes.
+ * @param data Data to get attribute for.
+ * @param productVariantData Variant data to use when finding item.
+ * @param attributeKey The key to fetch in platform attributes. May be converted to custom attribute key.
+ * @param attributePlatform The platform to fetch attribute for. Defaults to current platform if unspecified.
+ */
+export function customAttributeAsDictionary(objectGraph, data, productVariantData, attributeKey, attributePlatform) {
+ // Use `customAttributes.${customAttributeKey}` for platform if present
+ const customAttributeKey = mediaAttributes.attributeKeyAsCustomAttributeKey(attributeKey);
+ if (isNothing(customAttributeKey)) {
+ return null;
+ }
+ const customAttributes = contentAttributeAsDictionary(objectGraph, data, "customAttributes", attributePlatform);
+ const allowNondefaultTreatmentInNondefaultPage = mediaAttributes.attributeAllowsNonDefaultTreatmentInNonDefaultPage(customAttributeKey);
+ const customAttribute = variantAttributeForKey(objectGraph, customAttributes, productVariantData, customAttributeKey, allowNondefaultTreatmentInNondefaultPage);
+ if (serverData.isDefinedNonNullNonEmpty(customAttribute)) {
+ return serverData.asDictionary(customAttribute);
+ }
+ // Otherwise, use `${attributeKey}` for platform.
+ return contentAttributeAsDictionary(objectGraph, data, attributeKey, attributePlatform);
+}
+/**
+ * Retrieve the attribute for a specific platform's variant attribute as a dictionary, from custom attributes or standard attributes.
+ * @param data Data to get attribute for.
+ * @param productVariantData Variant data to use when finding item.
+ * @param attributeKey The key to fetch in platform attributes when custom attributes are not present.
+ * @param attributePlatform The platform to fetch attribute for. Defaults to current platform if unspecified.
+ */
+export function customAttributeAsString(objectGraph, data, productVariantData, attributeKey, attributePlatform) {
+ // Use `customAttributes.${customAttributeKey}` for platform if present
+ const customAttributeKey = mediaAttributes.attributeKeyAsCustomAttributeKey(attributeKey);
+ if (isNothing(customAttributeKey)) {
+ return null;
+ }
+ const customAttributes = contentAttributeAsDictionary(objectGraph, data, "customAttributes", attributePlatform);
+ const allowNondefaultTreatmentInNondefaultPage = mediaAttributes.attributeAllowsNonDefaultTreatmentInNonDefaultPage(customAttributeKey);
+ const customAttribute = variantAttributeForKey(objectGraph, customAttributes, productVariantData, customAttributeKey, allowNondefaultTreatmentInNondefaultPage);
+ if (serverData.isDefinedNonNullNonEmpty(customAttribute)) {
+ return serverData.asString(customAttribute);
+ }
+ // Otherwise, use `${attributeKey}` for platform.
+ return contentAttributeAsString(objectGraph, data, attributeKey);
+}
+// endregion
+//# sourceMappingURL=attributes.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/content/content.js b/node_modules/@jet-app/app-store/tmp/src/common/content/content.js
new file mode 100644
index 0000000..72b5ae1
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/content/content.js
@@ -0,0 +1,2820 @@
+import * as validation from "@jet/environment/json/validation";
+import { isNothing, isSome, unwrapOptional as unwrap } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import * as modelsBase from "../../api/models/base";
+import * as modelsShelves from "../../api/models/shelves";
+import { ads } from "../../api/typings/constants";
+import * as derivedData from "../../foundation/json-parsing/derived-data";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaDataFetching from "../../foundation/media/data-fetching";
+import * as mediaPlatformAttributes from "../../foundation/media/platform-attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as mediaUrlBuilder from "../../foundation/media/url-builder";
+import { Parameters, Path, Protocol } from "../../foundation/network/url-constants";
+import * as color from "../../foundation/util/color-util";
+import * as dateUtil from "../../foundation/util/date-util";
+import { unreachable } from "../../foundation/util/errors";
+import { isDefinedNonNullNonEmpty } from "@apple-media-services/media-api";
+import { editorialCardFromData } from "../../foundation/media/associations";
+import * as client from "../../foundation/wrappers/client";
+import * as videoDefaults from "../constants/video-constants";
+import * as filtering from "../filtering";
+import * as lockups from "../lockups/lockups";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersMedia from "../metrics/helpers/media";
+import * as offers from "../offers/offers";
+import * as productPageVariants from "../product-page/product-page-variants";
+import * as artwork from "./artwork/artwork";
+import * as contentAttributes from "./attributes";
+import * as contentDeviceFamily from "./device-family";
+import * as sad from "./sad";
+import { isFeatureEnabledForCurrentUser } from "../util/lottery";
+class RunnabilityInfo {
+ constructor() {
+ this.runsOnIntel = true;
+ this.runsOnAppleSilicon = true;
+ this.requiresRosetta = false;
+ }
+}
+/**
+ * Determines a reasonable artwork use case from a given shelf style
+ *
+ * @param shelfStyle The shelf style to consider
+ */
+export function artworkUseCaseFromShelfStyle(objectGraph, shelfStyle) {
+ switch (shelfStyle) {
+ case "inAppPurchaseLockup":
+ case "appShowcase":
+ case "smallLockup": {
+ return 1 /* ArtworkUseCase.LockupIconSmall */;
+ break;
+ }
+ case "mediumLockup": {
+ return 2 /* ArtworkUseCase.LockupIconMedium */;
+ break;
+ }
+ case "largeLockup": {
+ return 3 /* ArtworkUseCase.LockupIconLarge */;
+ break;
+ }
+ default: {
+ return 0 /* ArtworkUseCase.Default */;
+ }
+ }
+}
+/**
+ * Convert an API artwork object into an Artwork model object.
+ * @param artwork The artwork in API format.
+ * @returns An `Artwork` object.
+ */
+export function artworkFromApiArtwork(objectGraph, artworkData, options) {
+ return validation.context("artworkFromApiArtwork", () => {
+ var _a, _b, _c;
+ const allowingTransparency = serverData.isDefinedNonNull(options.allowingTransparency)
+ ? options.allowingTransparency
+ : false;
+ const useJoeColorDefault = objectGraph.client.isVision || objectGraph.client.isWeb;
+ const withJoeColorPlaceholder = serverData.isDefinedNonNull(options.withJoeColorPlaceholder)
+ ? options.withJoeColorPlaceholder
+ : useJoeColorDefault;
+ const artworkUrl = serverData.asString(artworkData, "url");
+ if (serverData.isNull(artworkUrl)) {
+ return null;
+ }
+ // Whether wide gamut is supported
+ const supportsWideGamut = serverData.asBooleanOrFalse(artworkData, "hasP3");
+ // Add base variant
+ const variants = [
+ artwork.createArtworkVariantForClient(objectGraph, allowingTransparency, supportsWideGamut, options.useCase),
+ ];
+ // Add layered image variant
+ const supportsLayeredImage = serverData.asBooleanOrFalse(artworkData, "supportsLayeredImage");
+ if (supportsLayeredImage && (objectGraph.client.isTV || objectGraph.client.isVision)) {
+ variants.push(artwork.createArtworkVariantForFormat(objectGraph, "lcr", supportsWideGamut, options.useCase));
+ }
+ // Artwork Placeholder Color
+ // If we indicate the image could be transparent then we don't want a placeholder background
+ let placeholderBackgroundColor = null;
+ if (allowingTransparency) {
+ placeholderBackgroundColor = color.named("clear");
+ }
+ else if (withJoeColorPlaceholder) {
+ const joeColorHexSet = joeColorHexSetFromData(artworkData);
+ const placeholderColorHex = (_b = (_a = options.joeColorPlaceholderSelectionLogic) === null || _a === void 0 ? void 0 : _a.call(options, joeColorHexSet)) !== null && _b !== void 0 ? _b : serverData.asString(artworkData, "bgColor");
+ const apiBackgroundColor = color.fromHex(placeholderColorHex);
+ if (!serverData.isNull(apiBackgroundColor)) {
+ placeholderBackgroundColor = apiBackgroundColor;
+ }
+ }
+ // If we don't want clear, joe color, or joe color fails to parse then fall back to default background
+ if (serverData.isNull(placeholderBackgroundColor) && !objectGraph.client.isVision) {
+ placeholderBackgroundColor = color.named("placeholderBackground");
+ }
+ const textColorKey = (_c = options.overrideTextColorKey) !== null && _c !== void 0 ? _c : "textColor1";
+ const apiTextColor = color.fromHex(serverData.asString(artworkData, textColorKey));
+ const artworkModel = new modelsBase.Artwork(artworkUrl, options.overrideWidth || serverData.asNumber(artworkData, "width"), options.overrideHeight || serverData.asNumber(artworkData, "height"), variants);
+ artworkModel.backgroundColor = placeholderBackgroundColor;
+ artworkModel.checksum = serverData.asString(artworkData, "checksum");
+ if (serverData.isDefinedNonNull(apiTextColor)) {
+ artworkModel.textColor = apiTextColor;
+ }
+ if (serverData.isDefinedNonNull(options.style)) {
+ artworkModel.style = options.style;
+ }
+ if (serverData.isDefinedNonNull(options.cropCode)) {
+ artworkModel.crop = options.cropCode;
+ }
+ if (serverData.isDefinedNonNull(options.contentMode)) {
+ artworkModel.contentMode = options.contentMode;
+ }
+ return artworkModel;
+ });
+}
+export function impressionableAppIconFromData(objectGraph, data, metricsOptions, artworkOptions) {
+ return validation.context("impressionableAppIconFromData", () => {
+ const rawArtwork = iconFromData(objectGraph, data, artworkOptions);
+ if (!serverData.isDefinedNonNull(rawArtwork)) {
+ return null;
+ }
+ const icon = new models.ImpressionableArtwork(rawArtwork);
+ const title = mediaAttributes.attributeAsString(data, "name");
+ const metricsImpressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, title, metricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, icon, metricsImpressionOptions);
+ return icon;
+ });
+}
+/**
+ * Batch method for `impressionableAppIconFromData`. Doesn't push location stack or increment location counter, matching other behavior with other icon grids.
+ * @param dataCollection Data container array with app data.
+ * @param metricsOptions Metrics blob containing information about page and location.
+ * @returns Array of `ImpressionableArtwork`
+ */
+export function impressionableAppIconsFromDataCollection(objectGraph, dataCollection, metricsOptions, artworkOptions) {
+ return validation.context("impressionableAppIconFromData", () => {
+ const icons = [];
+ if (serverData.isNullOrEmpty(metricsOptions.targetType)) {
+ metricsOptions.targetType = "artwork";
+ }
+ for (const data of dataCollection) {
+ const icon = impressionableAppIconFromData(objectGraph, data, metricsOptions, artworkOptions);
+ if (icon) {
+ icons.push(icon);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ }
+ }
+ return icons;
+ });
+}
+/**
+ * Defines possible use cases for SearchChartOrCategoryBrick.
+ */
+export var SearchChartOrCategoryBrickUseCase;
+(function (SearchChartOrCategoryBrickUseCase) {
+ SearchChartOrCategoryBrickUseCase[SearchChartOrCategoryBrickUseCase["seeAllPage"] = 0] = "seeAllPage";
+ SearchChartOrCategoryBrickUseCase[SearchChartOrCategoryBrickUseCase["categoryBreakout"] = 1] = "categoryBreakout";
+ SearchChartOrCategoryBrickUseCase[SearchChartOrCategoryBrickUseCase["other"] = 2] = "other";
+})(SearchChartOrCategoryBrickUseCase || (SearchChartOrCategoryBrickUseCase = {}));
+/**
+ * Gets all possible artwork that this chart or category can show
+ * @param objectGraph
+ * @param data
+ * @param isForSeeAllPage Whether or not the chart or category is on the see-all page or not;
+ * this is because the see-all page should always have the `Density1` style
+ * @param style The style of the chart or category that will be rendered
+ * @returns All permutations of artowrk that the chart or category can show
+ */
+export function searchChartOrCategoryArtworkFromData(objectGraph, data, useCase, style) {
+ const artworkPath = "editorialArtwork.searchCategoryBrick";
+ const artworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, artworkPath);
+ if (serverData.isNullOrEmpty(artworkData)) {
+ return null;
+ }
+ let artworkStyle = style;
+ if (useCase === SearchChartOrCategoryBrickUseCase.seeAllPage) {
+ artworkStyle = models.GenericSearchPageShelfDisplayStyleDensity.Density1;
+ }
+ /// Crops = [LTR crop, RTL crop]
+ /// ContentModes = [ContentMode for LTR crop, ContentMode for RTL crop]
+ /// Note: These must be the same length
+ let crops = [];
+ let contentModes = [];
+ switch (artworkStyle) {
+ /// Tile
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density1:
+ const width = useCase === SearchChartOrCategoryBrickUseCase.categoryBreakout ? "1191" : "2350";
+ artworkData["width"] = width;
+ artworkData["height"] = "670";
+ crops = ["SCB.ApSCBL01", "SCB.ApSCBL03"];
+ contentModes = [modelsBase.ArtworkContentMode.right, modelsBase.ArtworkContentMode.left];
+ break;
+ /// Pill
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density2:
+ artworkData["width"] = "2482";
+ artworkData["height"] = "670";
+ crops = ["SCB.ApSCBS01", "SCB.ApSCBS02"];
+ contentModes = [modelsBase.ArtworkContentMode.left, modelsBase.ArtworkContentMode.right];
+ break;
+ /// Round
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density3:
+ artworkData["width"] = "670";
+ artworkData["height"] = "670";
+ crops = ["cc"];
+ contentModes = [modelsBase.ArtworkContentMode.scaleAspectFit];
+ break;
+ default:
+ break;
+ }
+ return crops.map((crop, index) => {
+ return artworkFromApiArtwork(objectGraph, artworkData, {
+ cropCode: crop,
+ contentMode: index < contentModes.length ? contentModes[index] : null,
+ useCase: 0 /* ArtworkUseCase.Default */,
+ withJoeColorPlaceholder: true,
+ });
+ });
+}
+/**
+ * Create an icon artwork from the provided data.
+ * @param objectGraph The object graph.
+ * @param data The data object to pull icon data from.
+ * @param artworkOptions The options for creating the artwork.
+ * @param clientIdentifierOverride A client identifier override.
+ * @param productVariantData The product variant data to use to select the icon.
+ * @param attributePlatformOverride An override platform, from which to fetch the icon.
+ * @returns An `Artwork` object representing the icon.
+ */
+export function iconFromData(objectGraph, data, artworkOptions, clientIdentifierOverride, productVariantData, attributePlatformOverride = undefined) {
+ return validation.context("iconFromData", () => {
+ if (!data) {
+ validation.unexpectedNull("ignoredValue", "data");
+ return null;
+ }
+ const attributePlatform = attributePlatformOverride !== null && attributePlatformOverride !== void 0 ? attributePlatformOverride : iconAttributePlatform(objectGraph, data, clientIdentifierOverride);
+ const usePrerenderedIconArtwork = shouldUsePrerenderedIconArtwork(objectGraph);
+ // The preferred client identifier to use when selecting the artwork.
+ // This client identifier here ensures that we always prefer pill artwork for messages and circular artwork for watch / vision.
+ // Unless there's an override specified where another artwork type needs to be used (for example, in developer pages).
+ const preferredClientIdentifier = clientIdentifierOverride || objectGraph.host.clientIdentifier;
+ // Watch
+ const watchIcon = watchIconFromData(objectGraph, data, artworkOptions, preferredClientIdentifier, usePrerenderedIconArtwork, attributePlatform);
+ if (isSome(watchIcon)) {
+ return watchIcon;
+ }
+ // Messages
+ const messagesIcon = messagesIconFromData(objectGraph, data, artworkOptions, preferredClientIdentifier, attributePlatform);
+ if (isSome(messagesIcon)) {
+ return messagesIcon;
+ }
+ // In-App Purchases
+ const iapIcon = inAppPurchaseIconFromData(objectGraph, data, artworkOptions);
+ if (isSome(iapIcon)) {
+ return iapIcon;
+ }
+ // Bundles
+ const bundlesIcon = bundlesIconFromData(objectGraph, data, artworkOptions, usePrerenderedIconArtwork);
+ if (isSome(bundlesIcon)) {
+ return bundlesIcon;
+ }
+ // Calculate variant data if one wasn't provided from caller.
+ if (serverData.isNull(productVariantData)) {
+ productVariantData = productPageVariants.productVariantDataForData(objectGraph, data);
+ }
+ const artworkData = contentAttributes.customAttributeAsDictionary(objectGraph, data, productVariantData, "artwork", attributePlatform);
+ // tvOS
+ const tvIcon = tvIconFromData(objectGraph, artworkData, artworkOptions, preferredClientIdentifier, attributePlatform);
+ if (isSome(tvIcon)) {
+ return tvIcon;
+ }
+ // visionOS
+ const visionIcon = visionIconFromData(objectGraph, artworkData, artworkOptions, preferredClientIdentifier, attributePlatform);
+ if (isSome(visionIcon)) {
+ return visionIcon;
+ }
+ // macOS & iOS
+ return macOSOriOSIconFromData(objectGraph, data, artworkData, artworkOptions, usePrerenderedIconArtwork, productVariantData, attributePlatform);
+ });
+}
+/**
+ * Determines if a client is capable of showing pre-rendered icon artwork, and if the relevant
+ * feature / bag flags are enabled.
+ * @param objectGraph Current object graph
+ * @returns True if we should use prerendered icon artwork.
+ */
+export function shouldUsePrerenderedIconArtwork(objectGraph) {
+ const clientSupportsPrerenderedIconArtwork = objectGraph.client.isWatch || objectGraph.client.isiOS || objectGraph.client.isMac || objectGraph.client.isWeb;
+ const isEnabledForUser = isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.iconArtworkRolloutRate);
+ return (isEnabledForUser &&
+ objectGraph.bag.enableIconArtwork &&
+ objectGraph.client.isIconArtworkCapable &&
+ clientSupportsPrerenderedIconArtwork);
+}
+function watchIconFromData(objectGraph, data, artworkOptions, clientIdentifier, usePrerenderedIconArtwork, attributePlatform) {
+ if (clientIdentifier !== client.watchIdentifier &&
+ !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS") &&
+ !objectGraph.client.isWatch) {
+ return null;
+ }
+ // Attempt to use pre-rendered circular icon artwork first, if applicable
+ if (usePrerenderedIconArtwork) {
+ const iconArtworkData = mediaPlatformAttributes.platformAttributeAsDictionary(data, attributePlatform, "circularIconArtwork");
+ if (isSome(iconArtworkData)) {
+ return artworkFromApiArtwork(objectGraph, iconArtworkData, {
+ ...artworkOptions,
+ style: "roundPrerendered",
+ cropCode: "bb",
+ withJoeColorPlaceholder: true,
+ });
+ }
+ }
+ // Fallback to the legacy icon artwork
+ const artworkData = mediaPlatformAttributes.platformAttributeAsDictionary(data, attributePlatform, "circularArtwork");
+ if (isSome(artworkData)) {
+ const style = usePrerenderedIconArtwork ? "roundPrerendered" : "round";
+ const cropCode = usePrerenderedIconArtwork ? "ic" : undefined;
+ return artworkFromApiArtwork(objectGraph, artworkData, {
+ ...artworkOptions,
+ style: style,
+ cropCode: cropCode,
+ withJoeColorPlaceholder: true,
+ });
+ }
+ return null;
+}
+function messagesIconFromData(objectGraph, data, artworkOptions, clientIdentifier, attributePlatform) {
+ const isHiddenFromSpringboard = isHiddenFromSpringboardFromData(objectGraph, data);
+ const hasMessagesExtension = hasMessagesExtensionFromData(objectGraph, data);
+ const shouldShowMessagesIcon = hasMessagesExtension && (clientIdentifier === client.messagesIdentifier || isHiddenFromSpringboard);
+ const artworkData = mediaPlatformAttributes.platformAttributeAsDictionary(data, attributePlatform, "ovalArtwork");
+ if (shouldShowMessagesIcon && serverData.isDefinedNonNull(artworkData)) {
+ return artworkFromApiArtwork(objectGraph, artworkData, {
+ ...artworkOptions,
+ style: "pill",
+ });
+ }
+ return null;
+}
+function inAppPurchaseIconFromData(objectGraph, data, artworkOptions) {
+ if (data.type !== "in-apps") {
+ return null;
+ }
+ const artworkData = mediaAttributes.attributeAsDictionary(data, "artwork");
+ if (isSome(artworkData)) {
+ return artworkFromApiArtwork(objectGraph, artworkData, {
+ ...artworkOptions,
+ style: "iap",
+ });
+ }
+ return null;
+}
+function bundlesIconFromData(objectGraph, data, artworkOptions, usePrerenderedIconArtwork) {
+ if (data.type !== "app-bundles") {
+ return null;
+ }
+ // Attempt to use pre-rendered icon artwork first, if applicable
+ if (usePrerenderedIconArtwork) {
+ const iconArtworkData = mediaAttributes.attributeAsDictionary(data, "iconArtwork");
+ if (isSome(iconArtworkData)) {
+ return artworkFromApiArtwork(objectGraph, iconArtworkData, {
+ ...artworkOptions,
+ style: "roundedRectPrerendered",
+ cropCode: "bb",
+ });
+ }
+ }
+ // Fallback to the legacy icon artwork
+ const artworkData = mediaAttributes.attributeAsDictionary(data, "artwork");
+ if (isSome(artworkData)) {
+ const style = usePrerenderedIconArtwork ? "roundedRectPrerendered" : "roundedRect";
+ const cropCode = usePrerenderedIconArtwork ? "ia" : undefined;
+ return artworkFromApiArtwork(objectGraph, artworkData, {
+ ...artworkOptions,
+ style: style,
+ cropCode: cropCode,
+ allowingTransparency: true,
+ });
+ }
+ return null;
+}
+function tvIconFromData(objectGraph, artworkData, artworkOptions, clientIdentifier, attributePlatform) {
+ if (attributePlatform !== "appletvos" && clientIdentifier !== client.tvIdentifier) {
+ return null;
+ }
+ return artworkFromApiArtwork(objectGraph, artworkData, {
+ ...artworkOptions,
+ style: "tvRect",
+ });
+}
+function visionIconFromData(objectGraph, artworkData, artworkOptions, clientIdentifier, attributePlatform) {
+ if (attributePlatform !== "xros" && clientIdentifier !== "VisionAppStore" /* ClientIdentifier.VisionAppStore */) {
+ return null;
+ }
+ return artworkFromApiArtwork(objectGraph, artworkData, {
+ ...artworkOptions,
+ style: "round",
+ });
+}
+function macOSOriOSIconFromData(objectGraph, data, artworkData, artworkOptions, usePrerenderedIconArtwork, productVariantData, attributePlatform) {
+ const isMac = attributePlatform === "osx";
+ const allowTransparency = isMac && !preprocessor.GAMES_TARGET;
+ // Attempt to use pre-rendered icon artwork first, if applicable
+ if (usePrerenderedIconArtwork) {
+ const iconArtworkData = contentAttributes.customAttributeAsDictionary(objectGraph, data, productVariantData, "iconArtwork", attributePlatform);
+ if (isSome(iconArtworkData)) {
+ return artworkFromApiArtwork(objectGraph, iconArtworkData, {
+ ...artworkOptions,
+ style: "roundedRectPrerendered",
+ cropCode: "bb",
+ allowingTransparency: allowTransparency,
+ });
+ }
+ }
+ // Fallback to the standard icon artwork
+ let style;
+ let cropCode;
+ if (usePrerenderedIconArtwork) {
+ style = "roundedRectPrerendered";
+ cropCode = isMac ? "ib" : "ia";
+ }
+ else {
+ style = isMac ? "unadorned" : "roundedRect";
+ cropCode = "bb";
+ }
+ return artworkFromApiArtwork(objectGraph, artworkData, {
+ ...artworkOptions,
+ style: style,
+ cropCode: cropCode,
+ allowingTransparency: allowTransparency,
+ });
+}
+/**
+ * Determines the best attribute platform to use for the icon.
+ * @param objectGraph Current object graph
+ * @param data The product data
+ * @param clientIdentifierOverride The client identifier override to use, if any
+ * @returns
+ */
+export function iconAttributePlatform(objectGraph, data, clientIdentifierOverride) {
+ switch (clientIdentifierOverride) {
+ case client.watchIdentifier:
+ case client.messagesIdentifier: {
+ return "ios";
+ }
+ case client.tvIdentifier: {
+ return "appletvos";
+ }
+ case "VisionAppStore" /* ClientIdentifier.VisionAppStore */: {
+ return "xros";
+ }
+ default: {
+ return contentAttributes.bestAttributePlatformFromData(objectGraph, data, clientIdentifierOverride);
+ }
+ }
+}
+/**
+ * Determine the media platform, given the app platform and screenshot type.
+ * @param appPlatform The app platform specific to this media.
+ * @param type The response screenshot type, which is applicable for both screenshots and trailers, for this media.
+ * @param supplementaryAppPlatforms
+ * @returns {MediaPlatform} The configured media platform object.
+ * TODO: legacy_export
+ */
+export function mediaPlatformForTypeAndAppPlatform(objectGraph, appPlatform, type, supplementaryAppPlatforms) {
+ if (!appPlatform) {
+ return null;
+ }
+ const systemImageName = systemImageNameForAppPlatform(appPlatform);
+ const deviceCornerRadius = deviceCornerRadiusFactorForMediaType(objectGraph, type);
+ const deviceBorderThickness = deviceBorderThicknessForMediaType(objectGraph, type);
+ const outerDeviceCornerRadius = deviceOuterCornerRadiusFactorForMediaType(objectGraph, type);
+ return new modelsBase.MediaPlatform(appPlatform, type, systemImageName, supplementaryAppPlatforms, deviceCornerRadius, deviceBorderThickness, outerDeviceCornerRadius);
+}
+/**
+ * Configures the trailers object from the platform data.
+ * @param data The platform data to use.
+ * @param videoConfiguration config to use for the trailers
+ * @param metricsOptions The metrics options to use.
+ * @param adamId The adamId for the lockup.
+ * @param isAd Whether the trailers are for an ad lockup. Defaults to false.
+ * @param cropCode The crop code to use for the video preview.
+ * @returns {Trailers} The configured trailers object.
+ */
+export function trailersFromData(objectGraph, data, videoConfiguration, metricsOptions, adamId, isAd = false, cropCode) {
+ const platformVideos = platformVideoPreviewFromData(objectGraph, data, videoConfiguration, null, null, isAd, cropCode);
+ if (!platformVideos) {
+ return null;
+ }
+ const videoPreviews = platformVideos.videos;
+ const trailerVideos = [];
+ if (videoPreviews && videoPreviews.length > 0) {
+ for (const trailerVideo of videoPreviews) {
+ metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, trailerVideo, {
+ ...metricsOptions,
+ id: adamId,
+ });
+ trailerVideos.push(trailerVideo);
+ }
+ }
+ let trailers = null;
+ if (trailerVideos.length > 0) {
+ trailers = new modelsShelves.Trailers();
+ trailers.videos = trailerVideos;
+ trailers.mediaPlatform = platformVideos.mediaPlatform;
+ }
+ return trailers;
+}
+/**
+ * A convenience class for encapsulating a `Video` that is tied to a specific `MediaPlatform`.
+ */
+class PlatformVideos {
+ constructor(videos, mediaPlatform) {
+ this.videos = videos;
+ this.mediaPlatform = mediaPlatform;
+ }
+}
+/**
+ * Finds the best platform video previews to use for the given parameters.
+ * @param data The data from which to derive the platform videos.
+ * @param videoConfiguration A video configuration to use for the videos
+ * @param includedAppPlatforms If provided, restricts the resulting platform videos to only these platforms
+ * @param productVariantData
+ * @param isAd Whether the video preview data is for an ad. Defaults to false.
+ * @param cropCode The crop code to use for the preview artwork.
+ * @returns The best available platform videos.
+ */
+export function platformVideoPreviewFromData(objectGraph, data, videoConfiguration, includedAppPlatforms = null, productVariantData = null, isAd = false, cropCode) {
+ return validation.context("platformVideoPreviewFromData", () => {
+ if (serverData.isNull(productVariantData)) {
+ productVariantData = productPageVariants.productVariantDataForData(objectGraph, data); // create variant data if not provided.
+ }
+ const videoPreviewsByTypeData = videoPreviewsByTypeFromData(objectGraph, data, productVariantData, isAd);
+ const videoPreviewsByType = {};
+ if (!videoPreviewsByTypeData) {
+ return null;
+ }
+ let sortedAppPlatforms = sortedAppPlatformsFromData(objectGraph, data, objectGraph.host.clientIdentifier, objectGraph.client.deviceType);
+ if (serverData.isDefinedNonNull(includedAppPlatforms)) {
+ // If we have a restricted set of included app platforms, use those platforms
+ // to build our sortedAppPlatforms array in the proper sort order
+ const includedSortedAppPlatforms = [];
+ for (const appPlatform of sortedAppPlatforms) {
+ if (includedAppPlatforms.includes(appPlatform)) {
+ includedSortedAppPlatforms.push(appPlatform);
+ }
+ }
+ sortedAppPlatforms = includedSortedAppPlatforms;
+ }
+ if (sortedAppPlatforms.length === 0) {
+ return null;
+ }
+ for (const appPlatform of sortedAppPlatforms) {
+ const types = mediaTypesForAppPlatform(objectGraph, appPlatform, objectGraph.client.screenSize);
+ for (const type of Object.keys(videoPreviewsByTypeData)) {
+ const videosDataForType = serverData.asArrayOrEmpty(videoPreviewsByTypeData, type);
+ const videosForType = [];
+ for (const video of videosDataForType) {
+ const previewFrame = serverData.asDictionary(video, "previewFrame");
+ if (!previewFrame) {
+ validation.unexpectedNull("ignoredValue", "object", `videoPreviewsByType.${type}.previewFrame`);
+ continue;
+ }
+ const videoUrl = serverData.asString(video, "video");
+ if (!videoUrl) {
+ validation.unexpectedNull("ignoredValue", "string", `videoPreviewsByType.${type}.video`);
+ continue;
+ }
+ const preview = artwork.createArtworkForResource(objectGraph, serverData.asString(previewFrame, "url"), serverData.asNumber(previewFrame, "width"), serverData.asNumber(previewFrame, "height"), null, null, serverData.asString(previewFrame, "checksum"));
+ if (serverData.isDefinedNonNull(cropCode)) {
+ preview.crop = cropCode;
+ }
+ videosForType.push(new modelsBase.Video(videoUrl, preview, videoConfiguration));
+ }
+ videoPreviewsByType[type] = videosForType;
+ }
+ for (const type of types) {
+ if (videoPreviewsByType[type]) {
+ return new PlatformVideos(videoPreviewsByType[type], mediaPlatformForTypeAndAppPlatform(objectGraph, appPlatform, type));
+ }
+ }
+ }
+ return null;
+ });
+}
+/**
+ * Configures the videos from some platform data.
+ * @param data The store platform data.
+ * @returns A list of `Video` objects.
+ */
+export function videoPreviewsFromData(objectGraph, data) {
+ return validation.context("videoPreviewsFromApiPlatformData", () => {
+ const platformVideos = platformVideoPreviewFromData(objectGraph, data, videoDefaults.defaultVideoConfiguration(objectGraph));
+ if (platformVideos) {
+ return platformVideos.videos;
+ }
+ else {
+ return [];
+ }
+ });
+}
+/**
+ * Determines the `AppPlatform` to use, in order to determine appropriate `MediaType` for media.
+ * @param {AppPlatform} appPlatform The underlying `AppPlatform`.
+ * @returns {AppPlatform} The `AppPlatform` that we map to in order to select the appropriate `MediaType`.
+ */
+function selectionAppPlatformFromAppPlatform(objectGraph, appPlatform) {
+ if (appPlatform === "messages") {
+ switch (objectGraph.client.deviceType) {
+ case "pad": {
+ return "pad";
+ }
+ default: {
+ return "phone";
+ }
+ }
+ }
+ return appPlatform;
+}
+/**
+ * Provide the caller with an ordered array of screenshots for a given context. The first object can be used in search
+ * lockups, and the array will only contain screenshots for the supported app platforms.
+ *
+ * @param data The api product data (containing supported app platforms and screenshots)
+ * @param useCase
+ * @param includedAppPlatforms Optionally, a list of app platforms to confine the screenshots to.
+ * @param clientIdentifierOverride
+ * @param productVariantData
+ * @param isAd Whether the screenshots are being gathered for an ad lockup. Defaults to false.
+ * @returns An ordered array of screenshots for display on a product page
+ * */
+export function screenshotsFromData(objectGraph, data, useCase, includedAppPlatforms = null, clientIdentifierOverride, productVariantData, isAd = false, cropCode) {
+ return validation.context("screenshotsFromData", () => {
+ const screenshots = [];
+ if (serverData.isNull(productVariantData)) {
+ productVariantData = productPageVariants.productVariantDataForData(objectGraph, data); // resolve if not resolved by caller.
+ }
+ let sortedAppPlatforms = includedAppPlatforms;
+ if (!sortedAppPlatforms || sortedAppPlatforms.length === 0) {
+ const preferredClientIdentifier = clientIdentifierOverride || objectGraph.host.clientIdentifier;
+ let preferredDeviceType = objectGraph.client.deviceType;
+ if (preferredClientIdentifier === client.watchIdentifier) {
+ preferredDeviceType = "watch";
+ }
+ if (clientIdentifierOverride === "VisionAppStore" /* ClientIdentifier.VisionAppStore */ ||
+ clientIdentifierOverride === "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */) {
+ preferredDeviceType = "vision";
+ }
+ sortedAppPlatforms = sortedAppPlatformsFromData(objectGraph, data, preferredClientIdentifier, preferredDeviceType);
+ }
+ for (const appPlatform of sortedAppPlatforms) {
+ const supplementaryAppPlatforms = [];
+ let screenshotData;
+ if (appPlatform === "messages") {
+ screenshotData = messagesScreenshotsFromData(objectGraph, data, "ios");
+ if (supportsFunCameraFromData(objectGraph, data, "ios")) {
+ supplementaryAppPlatforms.push("faceTime");
+ }
+ }
+ else if (appPlatform === "tv" && !objectGraph.host.isTV) {
+ // For tvOS screenshots displayed on other platforms.
+ screenshotData = screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd, "appletvos");
+ }
+ else if (appPlatform === "vision" && !objectGraph.host.isVision) {
+ // For visionOS screenshots displayed on other platforms.
+ screenshotData = screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd, "xros");
+ }
+ else if (appPlatform === "mac" && !objectGraph.host.isMac) {
+ // For Mac screenshots displayed on other platforms.
+ screenshotData = screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd, "osx");
+ }
+ else if ((appPlatform === "phone" || appPlatform === "pad" || appPlatform === "watch") &&
+ !objectGraph.host.isiOS &&
+ !objectGraph.host.isWatch) {
+ // For iPhone / iPad / watch screenshots displayed on other platforms.
+ screenshotData = screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd, "ios");
+ }
+ else {
+ screenshotData = screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd);
+ }
+ if (!screenshotData) {
+ continue;
+ }
+ const bestScreenshots = bestScreenshotData(objectGraph, screenshotData, appPlatform, useCase, supplementaryAppPlatforms, cropCode);
+ if (bestScreenshots) {
+ screenshots.push(bestScreenshots);
+ }
+ }
+ return screenshots;
+ });
+}
+/**
+ * Creates an array of product media from the given screenshots. If videos are desired
+ * to be inserted in the same media row, this must be done elsewhere.
+ * @param objectGraph The object graph.
+ * @param data Apps resource data.
+ * @param screenshots The screenshots objects with which to configure the media.
+ * @return A list of product media objects.
+ */
+function productMediaFromScreenshots(objectGraph, data, screenshots) {
+ const allMedia = [];
+ if (screenshots && screenshots.length > 0) {
+ const allPlatforms = screenshots.map((platformScreenshots) => {
+ return platformScreenshots.mediaPlatform;
+ });
+ for (const screenshotsForPlatform of screenshots) {
+ // Create media items from all the screenshots.
+ const screenshotMediaItems = [];
+ for (const screenshotArtwork of screenshotsForPlatform.artwork) {
+ const screenshotItem = new modelsShelves.ProductMediaItem();
+ screenshotItem.screenshot = screenshotArtwork;
+ screenshotMediaItems.push(screenshotItem);
+ }
+ const platform = screenshotsForPlatform.mediaPlatform;
+ const productMedia = new modelsShelves.ProductMedia(screenshotMediaItems, platform, allPlatforms, descriptionOfMediaPlatform(objectGraph, platform), descriptionOfAllMediaPlatforms(objectGraph, data, allPlatforms), placementOfAllMediaPlatformsDescription(objectGraph, data, allPlatforms));
+ allMedia.push(productMedia);
+ }
+ }
+ return allMedia;
+}
+/**
+ * Build a set of of `ProductMedia` from apps resource
+ * @param data Apps resource data
+ * @param useCase Artwork use case
+ * @param includedAppPlatforms What platforms are included.
+ * @param productVariantData A variant to use. This can be populated as an optimization to avoid re-resolving the same variant data, e.g. in a product page.
+ * @param clientIdentifierOverride
+ */
+export function productMediaFromData(objectGraph, data, useCase, includedAppPlatforms = null, productVariantData = null, clientIdentifierOverride) {
+ const screenshots = screenshotsFromData(objectGraph, data, useCase, includedAppPlatforms, clientIdentifierOverride, productVariantData);
+ return productMediaFromScreenshots(objectGraph, data, screenshots);
+}
+/**
+ * Finds the best screenshot data from a response to use for the given parameters.
+ * @param data The data from which to derive the screenshots.
+ * @param appPlatform The app platform to which the screenshots belong.
+ * @param supplementaryAppPlatforms
+ * @returns The best available screenshots.
+ */
+function bestScreenshotData(objectGraph, data, appPlatform, useCase, supplementaryAppPlatforms, cropCode) {
+ const selectionPlatform = selectionAppPlatformFromAppPlatform(objectGraph, appPlatform);
+ const screenshotTypes = mediaTypesForAppPlatform(objectGraph, selectionPlatform, objectGraph.client.screenSize);
+ let bestScreenshot = null;
+ let bestScreenshotType;
+ for (let i = 0; i < screenshotTypes.length && !serverData.isDefinedNonNullNonEmpty(bestScreenshot); i++) {
+ bestScreenshot = serverData.asArrayOrEmpty(data, screenshotTypes[i]);
+ bestScreenshotType = screenshotTypes[i];
+ }
+ if (serverData.isDefinedNonNullNonEmpty(bestScreenshot)) {
+ const artworks = bestScreenshot.map(function (screenshotArtwork) {
+ return artworkFromApiArtwork(objectGraph, screenshotArtwork, {
+ useCase: useCase,
+ cropCode: cropCode,
+ });
+ });
+ const platform = mediaPlatformForTypeAndAppPlatform(objectGraph, appPlatform, bestScreenshotType, supplementaryAppPlatforms);
+ const screenshots = new modelsBase.Screenshots(artworks, platform);
+ return screenshots;
+ }
+ return null;
+}
+/**
+ * Returns a list of sorted app platforms for displaying screenshots. This contains the sorting logic for screenshots.
+ *
+ * @param data Server data for the app
+ * @param clientIdentifier Identifier of the current client.
+ * @param deviceType Type of the current device.
+ * @returns A sorted list of AppPlatform values to use when displaying
+ * */
+export function sortedAppPlatformsFromData(objectGraph, data, clientIdentifier, deviceType) {
+ return derivedData.value(data, `sortedAppPlatformsFromData.${clientIdentifier}.${deviceType}`, () => {
+ var _a;
+ const supportedAppPlatforms = supportedAppPlatformsFromData(objectGraph, data);
+ const excludedAppPlatforms = [];
+ let sortedAppPlatforms = [];
+ const addAppPlatformIfPossible = function (appPlatform, excludePlatform) {
+ if (sortedAppPlatforms.indexOf(appPlatform) !== -1) {
+ return;
+ }
+ if (excludedAppPlatforms.indexOf(appPlatform) !== -1) {
+ return;
+ }
+ if (supportedAppPlatforms.indexOf(appPlatform) !== -1) {
+ sortedAppPlatforms.push(appPlatform);
+ if (excludePlatform) {
+ excludedAppPlatforms.push(excludePlatform);
+ }
+ }
+ };
+ // If there is an `AppPlatform` associated with the active `Intent`, give
+ // that first priority
+ if ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.appPlatform) {
+ addAppPlatformIfPossible(objectGraph.activeIntent.appPlatform);
+ }
+ if (clientIdentifier === "VisionAppStore" /* ClientIdentifier.VisionAppStore */ ||
+ clientIdentifier === "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */) {
+ addAppPlatformIfPossible("vision");
+ }
+ // Next, priority is given to the client
+ switch (clientIdentifier) {
+ case client.watchIdentifier: {
+ addAppPlatformIfPossible("watch");
+ break;
+ }
+ case client.messagesIdentifier: {
+ addAppPlatformIfPossible("messages");
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ // Next the current device type
+ switch (deviceType) {
+ case "phone": {
+ addAppPlatformIfPossible("phone");
+ break;
+ }
+ case "pad": {
+ addAppPlatformIfPossible("pad");
+ break;
+ }
+ case "tv": {
+ addAppPlatformIfPossible("tv");
+ break;
+ }
+ case "watch": {
+ addAppPlatformIfPossible("watch");
+ break;
+ }
+ case "mac": {
+ addAppPlatformIfPossible("mac");
+ break;
+ }
+ case "vision": {
+ addAppPlatformIfPossible("vision");
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ // For Apple Silicon and visionOS, prefer iPad platform over iPhone
+ if (clientIdentifier === "VisionAppStore" /* ClientIdentifier.VisionAppStore */ ||
+ clientIdentifier === "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */) {
+ addAppPlatformIfPossible("pad");
+ addAppPlatformIfPossible("phone");
+ }
+ if (objectGraph.appleSilicon.isSupportEnabled || objectGraph.client.isVision) {
+ addAppPlatformIfPossible("pad");
+ addAppPlatformIfPossible("phone");
+ }
+ else {
+ addAppPlatformIfPossible("phone");
+ addAppPlatformIfPossible("pad");
+ }
+ addAppPlatformIfPossible("mac");
+ addAppPlatformIfPossible("vision");
+ addAppPlatformIfPossible("tv");
+ addAppPlatformIfPossible("watch");
+ addAppPlatformIfPossible("messages");
+ sortedAppPlatforms = sortedAppPlatforms.filter(function (appPlatform) {
+ return excludedAppPlatforms.indexOf(appPlatform) === -1;
+ });
+ return sortedAppPlatforms;
+ });
+}
+/**
+ * For a given server data, this will return the Game Center features that the app supports
+ *
+ * @param data Server data for the app
+ * @returns An array of supported Game Center features
+ * */
+export function supportedGameCenterFeaturesFromData(data) {
+ var _a;
+ if (isNothing(data)) {
+ return undefined;
+ }
+ return ((_a = derivedData.value(data, "supportedGameCenterFeaturesFromData", () => {
+ const features = [];
+ const supportedGameCenterFeatures = serverData.asArrayOrEmpty(data, "attributes.supportedGameCenterFeatures");
+ if (supportedGameCenterFeatures.includes("achievements")) {
+ features.push("achievements");
+ }
+ if (supportedGameCenterFeatures.includes("challenges")) {
+ features.push("challenges");
+ }
+ if (supportedGameCenterFeatures.includes("leaderboards")) {
+ features.push("leaderboards");
+ }
+ if (supportedGameCenterFeatures.includes("multiplayer-activities")) {
+ features.push("multiplayer-activities");
+ }
+ return features;
+ })) !== null && _a !== void 0 ? _a : undefined);
+}
+/**
+ * For a given server data, returns whether the game is eligible for the Games App
+ * This will default to true since we generally expect apps we view in the Games app to be games.
+ * It will be unusual that this is evaluated to `false`.
+ *
+ * @param data Server data for the app
+ * @returns A boolean indicating whether game is eligible for display
+ * */
+export function isEligibleForGamesApp(data) {
+ var _a;
+ if (isNothing(data)) {
+ return true;
+ }
+ return (_a = serverData.asBoolean(data, "attributes.isEligibleForGamesApp")) !== null && _a !== void 0 ? _a : true;
+}
+/**
+ * For a given server data, this will return the platforms that the app supports
+ *
+ * @param data Server data for the app
+ * @returns An array of supported AppPlatforms
+ * */
+export function supportedAppPlatformsFromData(objectGraph, data) {
+ if (!data) {
+ return null;
+ }
+ return derivedData.value(data, "supportedAppPlatformsFromData", () => {
+ const hasMessagesExtension = hasMessagesExtensionFromData(objectGraph, data, "ios");
+ const isHiddenFromSpringboard = isHiddenFromSpringboardFromData(objectGraph, data);
+ const isAppleWatchSupported = isAppleWatchSupportedFromData(objectGraph, data);
+ const serverDeviceFamilies = mediaAttributes.attributeAsArrayOrEmpty(data, "deviceFamilies");
+ const appPlatforms = [];
+ for (const serverDeviceFamily of serverDeviceFamilies) {
+ switch (serverDeviceFamily) {
+ case "iphone":
+ if (!isHiddenFromSpringboard) {
+ appPlatforms.push("phone");
+ }
+ break;
+ case "ipad":
+ if (!isHiddenFromSpringboard) {
+ appPlatforms.push("pad");
+ }
+ break;
+ case "tvos":
+ appPlatforms.push("tv");
+ break;
+ case "watch":
+ appPlatforms.push("watch");
+ break;
+ case "realityDevice":
+ appPlatforms.push("vision");
+ break;
+ default:
+ break;
+ }
+ }
+ if (hasMessagesExtension) {
+ appPlatforms.push("messages");
+ }
+ if (isAppleWatchSupported) {
+ appPlatforms.push("watch");
+ }
+ if (contentDeviceFamily.dataHasDeviceFamily(objectGraph, data, "mac")) {
+ appPlatforms.push("mac");
+ }
+ return appPlatforms;
+ });
+}
+/**
+ * Returns a localized, user-friendly description of all media platforms. This may be a comma delimited
+ * list of the platforms (including supplementary platforms), or it may be 'Only for ___', depending
+ * on the context.
+ *
+ * The localization keys used by this function are defined natively, and are updated using
+ * `tools/platform-media-localizations.py`. If the key doesn't exist, then the script needs to
+ * be updated to add the new combination/order of platforms.
+ *
+ * For failed attempts to localize the string, this function will fallback to a default order that is
+ * guaranteed to exist.
+ *
+ * @param objectGraph The object graph.
+ * @param data Apps resource data.
+ * @param allPlatforms The list of platforms to describe.
+ * @returns The friendly description of all platforms.
+ */
+export function descriptionOfAllMediaPlatforms(objectGraph, data, allPlatforms) {
+ if (shouldShowOnlyForPlatformDescription(objectGraph, data, allPlatforms)) {
+ const platform = allPlatforms[0];
+ const platformKey = platform.appPlatform.toUpperCase();
+ return objectGraph.loc.string(`ONLY_FOR_${platformKey}_APP`);
+ }
+ // Flatten all platform partial keys, including their supplementary platforms
+ let keys = allPlatforms.reduce((partialResult, platform) => partialResult.concat(platformLocalizationKeys(platform)), []);
+ try {
+ // Attempt to localize the constructed key
+ return objectGraph.loc.tryString(`PLATFORMS_${keys.join("_")}`);
+ }
+ catch (error) {
+ // If the key does not exist, a best attempt fallback string will be provided.
+ const fallbackOrder = ["PHONE", "PAD", "MAC", "VISION", "TV", "WATCH", "MESSAGES", "FACETIME"];
+ keys = fallbackOrder.filter((key) => keys.includes(key));
+ return objectGraph.loc.string(`PLATFORMS_${keys.join("_")}`);
+ }
+}
+/**
+ * Determines where to place the all platforms description, which is visible when the product media is collapsed, or there is only one platform.
+ * This is only used by iOS, visionOS & macOS. For tvOS & watchOS, we always put the media description at the bottom.
+ *
+ * @param objectGraph The object graph.
+ * @param data Apps resource data.
+ * @param allPlatforms The list of platforms to describe.
+ * @returns Where to place the all platforms description.
+ */
+export function placementOfAllMediaPlatformsDescription(objectGraph, data, allPlatforms) {
+ if (shouldShowOnlyForPlatformDescription(objectGraph, data, allPlatforms)) {
+ return "top";
+ }
+ else {
+ return "bottom";
+ }
+}
+/**
+ * Determines whether we want to use the 'Only for ___' text to describe `allPlatforms`.
+ *
+ * @param objectGraph The object graph.
+ * @param data Apps resource data.
+ * @param allPlatforms The list of platforms to describe.
+ * @returns Whether we want to use 'Only for ___' text to describe `allPlatforms`.
+ */
+function shouldShowOnlyForPlatformDescription(objectGraph, data, allPlatforms) {
+ if (allPlatforms.length === 1) {
+ const platform = allPlatforms[0];
+ const supportsMacOSCompatibleIOSBinary = supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, objectGraph.appleSilicon.isSupportEnabled);
+ const supportsVisionOSCompatibleIOSBinary = supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ const runnableAppPlatforms = runnableAppPlatformsForDevice(objectGraph, objectGraph.client.deviceType, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
+ const isRunnableOnCurrentDevice = supportsPlatform(runnableAppPlatforms, platform.appPlatform);
+ const noSupplementaryPlatforms = platform.supplementaryAppPlatforms.length === 0;
+ const isForDifferentDevice = platform.appPlatform !== objectGraph.client.deviceType;
+ if (noSupplementaryPlatforms && isForDifferentDevice && !isRunnableOnCurrentDevice) {
+ return true;
+ }
+ }
+ return false;
+}
+/**
+ * Returns a localized description of a media platform, including any supplementary platforms.
+ * e.g. 'Mac' or 'iMessage, FaceTime'.
+ *
+ * @param objectGraph Object graph, used for localizing the string.
+ * @param allPlatforms The platform to describe.
+ * @returns The friendly description of the platform.
+ */
+export function descriptionOfMediaPlatform(objectGraph, platform) {
+ const keys = platformLocalizationKeys(platform);
+ return objectGraph.loc.string(`PLATFORMS_${keys.join("_")}`);
+}
+/**
+ * Returns an array of partial loc keys that represent a media platform.
+ * This consists the media's app platform + any supplementary platforms.
+ * e.g. ["MAC"] or ["MESSAGES", "FACETIME"]
+ *
+ * @param platform The media platform.
+ * @returns The list of partial loc key that represent the media platform.
+ */
+function platformLocalizationKeys(platform) {
+ const appPlatformKey = platform.appPlatform.toUpperCase();
+ const supplementaryPlatformKeys = platform.supplementaryAppPlatforms.map((supplementaryPlatform) => supplementaryPlatform.toUpperCase());
+ return [appPlatformKey].concat(supplementaryPlatformKeys);
+}
+/**
+ * Determines if a given app has a compatible iOS binary for this client.
+ *
+ * @param {mediaDataStructure.Data} data The product data to use.
+ * @param {boolean} doesClientSupportMacOSCompatibleIOSBinary Whether the client supports macOS compatible iOS binaries
+ * @returns {boolean} True when the app and device are halva.
+ */
+export function supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, doesClientSupportMacOSCompatibleIOSBinary) {
+ let isIOSBinaryMacOSCompatible = mediaAttributes.attributeAsBooleanOrFalse(data, "isIOSBinaryMacOSCompatible");
+ // Override for News in Moltres on Mac
+ if (preprocessor.GAMES_TARGET && data.id === "1066498020" && objectGraph.client.deviceType === "mac") {
+ isIOSBinaryMacOSCompatible = true;
+ }
+ return doesClientSupportMacOSCompatibleIOSBinary && isIOSBinaryMacOSCompatible;
+}
+/**
+ * Determines if a given app has a compatible iOS binary for the current client.
+ *
+ * @param {mediaDataStructure.Data} data The product data to use.
+ * @returns {boolean} True when the app and device are visionOS.
+ */
+export function supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data) {
+ return (objectGraph.client.isVision &&
+ mediaPlatformAttributes.platformAttributeAsBooleanOrFalse(data, "ios", "isXROSCompatible"));
+}
+/**
+ * Determines if a given app has a compatible iOS binary for arbitrary clients.
+ *
+ * @param {mediaDataStructure.Data} data The product data to use.
+ * @returns {boolean} True when the app can run on visionOS.
+ */
+export function supportsVisionOSCompatibleIOSBinaryOnAnyClient(data) {
+ return mediaPlatformAttributes.platformAttributeAsBooleanOrFalse(data, "ios", "isXROSCompatible");
+}
+/**
+ * Determines app binary traits.
+ *
+ * @param {mediaDataStructure.Data} data The product data to use.
+ * @returns {string[]} The app binary traits.
+ */
+export function appBinaryTraitsFromData(objectGraph, data) {
+ if (!objectGraph.client.isiOS) {
+ return undefined;
+ }
+ let appBinaryTraits;
+ if (objectGraph.isAvailable(ads) &&
+ ["debug", "internal"].includes(objectGraph.client.buildType) &&
+ isSome(objectGraph.ads.fetchAppBinaryTraitsOverride)) {
+ // use client override for debugging internal builds
+ appBinaryTraits = objectGraph.ads.fetchAppBinaryTraitsOverride();
+ }
+ if (isNothing(appBinaryTraits)) {
+ // parse from server response
+ appBinaryTraits = mediaPlatformAttributes.platformAttributeAsArrayOrEmpty(data, "ios", "appBinaryTraits");
+ }
+ return appBinaryTraits;
+}
+/**
+ * Determines whether the product has external browser engine.
+ * @param objectGraph Current object graph
+ * @param data The product data
+ * @returns True if the product has external browser engine
+ */
+export function hasExternalBrowserForData(objectGraph, data) {
+ var _a;
+ const appBinaryTraits = appBinaryTraitsFromData(objectGraph, data);
+ const externalBrowserTraits = new Set(["uses-non-webkit-browser-engine", "is-custom-browser-engine-app"]);
+ return (_a = appBinaryTraits === null || appBinaryTraits === void 0 ? void 0 : appBinaryTraits.some((trait) => externalBrowserTraits.has(trait))) !== null && _a !== void 0 ? _a : false;
+}
+/**
+ * Determines minimum os version
+ *
+ * @param {mediaDataStructure.Data} data The product data to use.
+ * @param {boolean} isClientHalva Whether the client is halva.
+ * @returns {string} The minimum OS version.
+ */
+export function minimumOSVersionFromData(objectGraph, data, isClientHalva) {
+ const supportsMacOSCompatibleIOSBinary = supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, isClientHalva);
+ const supportsVisionOSCompatibleIOSBinary = supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ if (supportsMacOSCompatibleIOSBinary) {
+ const minimumOSVersion = contentAttributes.contentAttributeAsString(objectGraph, data, "minimumMacOSVersion");
+ if (serverData.isDefinedNonNullNonEmpty(minimumOSVersion)) {
+ return minimumOSVersion;
+ }
+ }
+ else if (supportsVisionOSCompatibleIOSBinary) {
+ const minimumOSVersion = contentAttributes.contentAttributeAsString(objectGraph, data, "minimumXROSVersion");
+ if (serverData.isDefinedNonNullNonEmpty(minimumOSVersion)) {
+ return minimumOSVersion;
+ }
+ }
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
+ return mediaPlatformAttributes.platformAttributeAsString(data, attributePlatform, "minimumOSVersion");
+}
+/**
+ * Determines required capabilities for device.
+ *
+ * @param {mediaDataStructure.Data} data The product data to use.
+ * @param {boolean} isClientHalva Whether the client is halva.
+ * @returns {string} The device capabilities to use.
+ */
+export function requiredCapabilitiesFromData(objectGraph, data, isClientHalva) {
+ const supportsMacOSCompatibleIOSBinary = supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, isClientHalva);
+ const supportsVisionOSCompatibleIOSBinary = supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ if (supportsMacOSCompatibleIOSBinary) {
+ return contentAttributes.contentAttributeAsString(objectGraph, data, "macRequiredCapabilities");
+ }
+ else if (supportsVisionOSCompatibleIOSBinary) {
+ return contentAttributes.contentAttributeAsString(objectGraph, data, "requiredCapabilitiesForRealityDevice");
+ }
+ else {
+ return contentAttributes.contentAttributeAsString(objectGraph, data, "requiredCapabilities");
+ }
+}
+/**
+ * Returns the app platforms you can buy for on the given device.
+ *
+ * @param objectGraph The current object graph
+ * @param data The data for the app in question
+ * @param device The device type to check
+ * @param supportsMacOSCompatibleIOSBinary Whether device and app supports macOS compatible iOS binary
+ * @param supportsVisionOSCompatibleIOSBinary Whether device and app supports visionOS compatible iOS binary
+ * @returns An array of supported app platforms
+ */
+function buyableAppPlatformsForDevice(objectGraph, data, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary) {
+ let systemApps;
+ switch (device) {
+ case "phone":
+ systemApps = sad.systemApps(objectGraph);
+ if (isSome(data) && systemApps.isSystemAppFromData(data)) {
+ return ["phone", "watch", "messages"];
+ }
+ else {
+ return ["phone", "watch", "messages", "tv", "vision"];
+ }
+ case "pad":
+ systemApps = sad.systemApps(objectGraph);
+ if (isSome(data) && systemApps.isSystemAppFromData(data)) {
+ return ["phone", "pad", "messages"];
+ }
+ else {
+ return ["phone", "pad", "messages", "tv", "vision"];
+ }
+ case "tv":
+ return ["tv"];
+ case "watch":
+ return ["watch"];
+ case "mac":
+ if (supportsMacOSCompatibleIOSBinary) {
+ return ["mac", "phone", "pad"];
+ }
+ else {
+ return ["mac"];
+ }
+ case "vision":
+ if (supportsVisionOSCompatibleIOSBinary) {
+ return ["vision", "phone", "pad"];
+ }
+ else {
+ return ["vision"];
+ }
+ default:
+ return [];
+ }
+}
+/**
+ * Returns the app platforms you can preorder on for the given device.
+ *
+ * @param {DeviceType} device The device type to check
+ * @param {boolean} supportsMacOSCompatibleIOSBinary Whether device and app are support macOS compatible iOS binary
+ * @param {boolean} supportsVisionOSCompatibleIOSBinary Whether device and app supports visionOS compatible iOS binary
+ * @returns {models.AppPlatform[]} An array of supported app platforms
+ */
+function preorderableAppPlatformsForDevice(objectGraph, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary) {
+ switch (device) {
+ case "phone":
+ return ["phone", "watch", "messages"];
+ case "pad":
+ return ["phone", "pad", "messages"];
+ case "tv":
+ return ["tv"];
+ case "watch":
+ return ["watch"];
+ case "mac":
+ if (supportsMacOSCompatibleIOSBinary) {
+ return ["mac", "phone", "pad"];
+ }
+ else {
+ return ["mac"];
+ }
+ case "vision":
+ if (supportsVisionOSCompatibleIOSBinary) {
+ return ["vision", "phone", "pad"];
+ }
+ else {
+ return ["vision"];
+ }
+ default:
+ return [];
+ }
+}
+/**
+ * Returns the app platforms you can run on the given device.
+ *
+ * @param {DeviceType} device The device type to check
+ * @param {boolean} supportsMacOSCompatibleIOSBinary Whether device and app are support macOS compatible iOS binary
+ * @param {boolean} supportsVisionOSCompatibleIOSBinary Whether device and app supports visionOS compatible iOS binary
+ * @returns {models.AppPlatform[]} An array of supported app platforms
+ */
+export function runnableAppPlatformsForDevice(objectGraph, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary) {
+ switch (device) {
+ case "phone":
+ return ["phone", "messages"];
+ case "pad":
+ return ["phone", "pad", "messages"];
+ case "tv":
+ return ["tv"];
+ case "watch":
+ return ["watch"];
+ case "mac":
+ if (supportsMacOSCompatibleIOSBinary) {
+ return ["mac", "phone", "pad"];
+ }
+ else {
+ return ["mac"];
+ }
+ case "vision":
+ if (supportsVisionOSCompatibleIOSBinary) {
+ return ["vision", "phone", "pad"];
+ }
+ else {
+ return ["vision"];
+ }
+ default:
+ return [];
+ }
+}
+/**
+ * Determines if a given piece of content supports the provided app platform
+ *
+ * @param {models.AppPlatform[]} appPlatforms The app platforms supported by the content
+ * @param {AppPlatform} platform The platform to check
+ * @returns {boolean} True if the platform is supported, false if not
+ */
+export function supportsPlatform(appPlatforms, platform) {
+ return appPlatforms.indexOf(platform) !== -1;
+}
+/**
+ * Determines if a given piece of content is buyable on the provided device.
+ *
+ * @param {models.AppPlatform[]} appPlatforms The app platforms supported by the content
+ * @param {DeviceType} device The device type to check
+ * @param {boolean} supportsMacOSCompatibleIOSBinary Whether app and device support macOS compatible iOS binary.
+ * @param {boolean} supportsVisionOSCompatibleIOSBinary Whether device and app supports visionOS compatible iOS binary
+ * @param {boolean} isMacOSAppBuyableOnDevice Whether a macOS app is buyable on this device (this enables additional criteria for Apple Silicon).
+ * @returns {boolean} True if any of the app platforms are buyable on the given device, false if not
+ */
+export function buyableOnDevice(objectGraph, data, appPlatforms, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary, isMacOSAppBuyableOnDevice = true) {
+ const platforms = buyableAppPlatformsForDevice(objectGraph, data, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
+ // Do any of the platforms supported by the device match any of the content's app platforms?
+ if (!platforms.some((platform) => supportsPlatform(appPlatforms, platform))) {
+ return false;
+ }
+ if (objectGraph.client.isMac && platforms.includes("mac")) {
+ return isMacOSAppBuyableOnDevice;
+ }
+ return true;
+}
+/**
+ * Determines macOS runnability info for apps and bundles on macOS.
+ */
+function macOSRunnabilityInfoFromData(objectGraph, data) {
+ var _a;
+ const runnabilityInfo = new RunnabilityInfo();
+ // Return most permissible runnability for non-macOS platforms.
+ if (objectGraph.client.deviceType !== "mac") {
+ return runnabilityInfo;
+ }
+ // Use media API attributes for non-bundles.
+ if (data.type !== "app-bundles") {
+ runnabilityInfo.runsOnIntel =
+ (_a = contentAttributes.contentAttributeAsBoolean(objectGraph, data, "runsOnIntel", contentAttributes.defaultAttributePlatform(objectGraph))) !== null && _a !== void 0 ? _a : true;
+ runnabilityInfo.runsOnAppleSilicon = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "runsOnAppleSilicon", contentAttributes.defaultAttributePlatform(objectGraph));
+ runnabilityInfo.requiresRosetta = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "requiresRosetta", contentAttributes.defaultAttributePlatform(objectGraph));
+ return runnabilityInfo;
+ }
+ const bundleAppsData = mediaRelationship.relationshipCollection(data, "apps");
+ // Return most permissible runnability when there no children available
+ if (bundleAppsData.length === 0) {
+ return runnabilityInfo;
+ }
+ // Synthesize runnability info from bundle apps
+ for (const appData of bundleAppsData) {
+ if (serverData.isNull(appData.attributes)) {
+ continue;
+ }
+ const appRunnabilityInfo = macOSRunnabilityInfoFromData(objectGraph, appData);
+ runnabilityInfo.runsOnIntel = runnabilityInfo.runsOnIntel && appRunnabilityInfo.runsOnIntel;
+ runnabilityInfo.runsOnAppleSilicon =
+ runnabilityInfo.runsOnAppleSilicon && appRunnabilityInfo.runsOnAppleSilicon;
+ runnabilityInfo.requiresRosetta = runnabilityInfo.requiresRosetta || appRunnabilityInfo.requiresRosetta;
+ }
+ return runnabilityInfo;
+}
+/**
+ * Determines if a given macOS app is buyable on this device.
+ *
+ */
+export function isMacOSAppBuyableAndRunnableFromData(objectGraph, data, isAppleSiliconSupportEnabled, isRosettaAvailable) {
+ const runnabilityInfo = macOSRunnabilityInfoFromData(objectGraph, data);
+ if (isAppleSiliconSupportEnabled) {
+ return (runnabilityInfo.runsOnAppleSilicon &&
+ (!runnabilityInfo.requiresRosetta || (runnabilityInfo.requiresRosetta && isRosettaAvailable)));
+ }
+ else {
+ return runnabilityInfo.runsOnIntel;
+ }
+}
+/**
+ * Determines if a given piece of content is preorderable on the provided device.
+ *
+ * @param {models.AppPlatform[]} appPlatforms The app platforms supported by the content
+ * @param {DeviceType} device The device type to check
+ * @param {boolean} supportsMacOSCompatibleIOSBinary Whether app and device support macOS compatible iOS binary.
+ * @param {boolean} supportsVisionOSCompatibleIOSBinary Whether app and device support visionOS compatible iOS binary.
+ * @returns {boolean} True if any of the app platforms are buyable on the given device, false if not
+ */
+export function preorderableOnDevice(objectGraph, appPlatforms, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary) {
+ const platforms = preorderableAppPlatformsForDevice(objectGraph, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
+ // Do any of the platforms supported by the device match any of the content's app platforms?
+ return platforms.some((platform) => supportsPlatform(appPlatforms, platform));
+}
+/**
+ * Determines if any of a given array of app platforms can be run on the provided device.
+ *
+ * @param appPlatforms The app platforms supported by a piece of content.
+ * @param device The device type to check.
+ * @param {boolean} supportsMacOSCompatibleIOSBinary Whether app and device support macOS compatible iOS binary.
+ * @param {boolean} supportsVisionOSCompatibleIOSBinary Whether app and device support visionOS compatible iOS binary.
+ * @returns `true` if any of the app platforms can be run on the given device; `false` otherwise.
+ */
+export function runnableOnDevice(objectGraph, appPlatforms, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary, isMacOSAppRunnableOnDevice = true) {
+ const runnablePlatforms = runnableAppPlatformsForDevice(objectGraph, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
+ // Do any of the platforms supported by the device match any of the content's app platforms?
+ if (!runnablePlatforms.some((platform) => supportsPlatform(appPlatforms, platform))) {
+ return false;
+ }
+ if (objectGraph.client.isMac && appPlatforms.includes("mac")) {
+ return isMacOSAppRunnableOnDevice;
+ }
+ return true;
+}
+/**
+ * Determines if a given piece of content is runnable on the provided device.
+ *
+ * @param data The product data to use.
+ * @param device
+ * @param {boolean} doesClientSupportMacOSCompatibleIOSBinary Whether the client supports macOS compatible iOS binaries
+ * @returns {boolean} True if the product can be run on the provided device, false if not
+ */
+export function runnableOnDeviceWithData(objectGraph, data, device, doesClientSupportMacOSCompatibleIOSBinary) {
+ // (1) Required capabilities mismatch
+ if (!lockups.deviceHasCapabilitiesFromData(objectGraph, data)) {
+ return false;
+ }
+ // (2) 32-bit only, unsupported deletable system app, doesn't meet minimum OS requirements, or doesn't support current platform
+ // Note that Filter.UnsupportedPlatform only checks if the product is buyable, not runnable
+ const filter = 2 /* filtering.Filter.ThirtyTwoBit */ |
+ 4 /* filtering.Filter.UnsupportedSystemDeletableApps */ |
+ 512 /* filtering.Filter.MinimumOSRequirement */ |
+ 128 /* filtering.Filter.UnsupportedPlatform */ |
+ 8192 /* filtering.Filter.MacOSRosetta */;
+ if (filtering.shouldFilter(objectGraph, data, filter)) {
+ return false;
+ }
+ // (3) Finally, check if any of the product platforms are supported on this device
+ const supportsMacOSCompatibleIOSBinary = supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, doesClientSupportMacOSCompatibleIOSBinary);
+ const supportsVisionOSCompatibleIOSBinary = supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ const runnableAppPlatforms = runnableAppPlatformsForDevice(objectGraph, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
+ const productAppPlatforms = supportedAppPlatformsFromData(objectGraph, data);
+ return runnableAppPlatforms.some((platform) => supportsPlatform(productAppPlatforms, platform));
+}
+/**
+ * Determines which screenshot keys (MediaType) we need to use to pull the appropriate screenshots
+ * from the server data.
+ *
+ * @param appPlatform The app platform requested
+ * @param screenSize The size of the screen being used to display the screenshots
+ * @returns An array of ScreenshotType strings that can be used on the server data
+ * */
+export function mediaTypesForAppPlatform(objectGraph, appPlatform, screenSize) {
+ switch (appPlatform) {
+ case "mac": {
+ return ["mac"];
+ }
+ case "watch": {
+ if (screenSize.isEqualTo(screenSizeWatchUltra) || screenSize.isEqualTo(screenSizeN230)) {
+ // 2022 is the preferred dropwell for Ultra devices
+ return ["appleWatch_2022", "appleWatch_2024", "appleWatch_2021", "appleWatch_2018", "appleWatch"];
+ }
+ else {
+ return ["appleWatch_2024", "appleWatch_2022", "appleWatch_2021", "appleWatch_2018", "appleWatch"];
+ }
+ }
+ case "tv": {
+ return ["appleTV"];
+ }
+ case "vision": {
+ return ["appleVisionPro"];
+ }
+ case "pad": {
+ const types = [];
+ if ((screenSize.isEqualTo(screenSizeIPadPro2018) ||
+ screenSize.isEqualTo(screenSizeIPadPro2018Landscape) ||
+ screenSize.isEqualTo(screenSizeJ720) ||
+ screenSize.isEqualTo(screenSizeJ720Landscape)) &&
+ objectGraph.client.screenCornerRadius > 0.0) {
+ types.push("ipadPro_2018");
+ types.push("ipad_11");
+ types.push("ipadPro");
+ types.push("ipad_10_5");
+ types.push("ipad");
+ }
+ else if (screenSize.isEqualTo(screenSizeIPadPro)) {
+ types.push("ipadPro");
+ types.push("ipadPro_2018");
+ types.push("ipad_11");
+ types.push("ipad_10_5");
+ types.push("ipad");
+ }
+ else if (screenSize.isEqualTo(screenSizeIPad11) ||
+ screenSize.isEqualTo(screenSizeIPad11Landscape) ||
+ screenSize.isEqualTo(screenSizeIPadJ310) ||
+ screenSize.isEqualTo(screenSizeIPadJ310Landscape) ||
+ screenSize.isEqualTo(screenSizeJ717) ||
+ screenSize.isEqualTo(screenSizeJ717Landscape)) {
+ types.push("ipad_11");
+ types.push("ipadPro_2018");
+ types.push("ipadPro");
+ types.push("ipad_10_5");
+ types.push("ipad");
+ }
+ else if (screenSize.isEqualTo(screenSizeIPad105)) {
+ types.push("ipad_10_5");
+ types.push("ipad");
+ types.push("ipad_11");
+ types.push("ipadPro");
+ types.push("ipadPro_2018");
+ }
+ else if (screenSize.isEqualTo(screenSizeIPadAir2020)) {
+ types.push("ipad_11");
+ types.push("ipadPro");
+ types.push("ipadPro_2018");
+ types.push("ipad_10_5");
+ types.push("ipad");
+ }
+ else if (screenSize.isEqualTo(screenSizeIPad102)) {
+ types.push("ipad");
+ types.push("ipad_10_5");
+ types.push("ipad_11");
+ types.push("ipadPro");
+ types.push("ipadPro_2018");
+ }
+ else {
+ // Regardless of screen size match, we should add on 'some' iPad.
+ types.push("ipadPro_2018");
+ types.push("ipad_11");
+ types.push("ipad");
+ types.push("ipad_10_5");
+ types.push("ipadPro");
+ }
+ return types;
+ }
+ case "phone": {
+ /** Phone Best Match Policy **
+
+ The best match is given by |B| + |L| + |S|, where:
+ B: Exact type match
+ L: All types larger than the exact type, in increasing order
+ S: All types smaller than the exact type, in decreasing order
+
+ Example:
+ Types for iphone6 == [iphone6, iphone6+, iphone_5_8, iphone5, iphone]
+ Types for iphone5 == [iphone5, iphone6, iphone6+, iphone_5_8, iphone]
+
+ ** */
+ // Grab the exact match.
+ let perfectMatch;
+ if (screenSize.isEqualTo(screenSizeIphone65) || screenSize.isEqualTo(screenSizeIPhone134)) {
+ perfectMatch = "iphone_6_5";
+ }
+ else if (screenSize.isEqualTo(screenSizeIPhone58) ||
+ screenSize.isEqualTo(screenSizeIPhone131) ||
+ screenSize.isEqualTo(screenSizeIPhone132)) {
+ perfectMatch = "iphone_5_8";
+ }
+ else if (screenSize.isEqualTo(screenSizeIPhoneOriginal)) {
+ perfectMatch = "iphone";
+ }
+ else if (screenSize.isEqualTo(screenSizeIPhone5)) {
+ perfectMatch = "iphone5";
+ }
+ else if (screenSize.isEqualTo(screenSizeIPhone6)) {
+ perfectMatch = "iphone6";
+ }
+ else if (screenSize.isEqualTo(screenSizeIPhone6Plus)) {
+ perfectMatch = "iphone6+";
+ }
+ else if (screenSize.isEqualTo(screenSizeIPhone61) || screenSize.isEqualTo(screenSizeD93)) {
+ perfectMatch = "iphone_d73";
+ }
+ else if (screenSize.isEqualTo(screenSizeIPhone67) ||
+ screenSize.isEqualTo(screenSizeD94) ||
+ screenSize.isEqualTo(screenSizeD23)) {
+ perfectMatch = "iphone_d74";
+ }
+ else {
+ perfectMatch = "iphone_5_8";
+ }
+ // Append remaining types to our exact match.
+ const perfectMatchIndex = decreasingPhoneTypes.indexOf(perfectMatch);
+ const largerTypes = decreasingPhoneTypes.slice(0, perfectMatchIndex);
+ largerTypes.reverse();
+ const smallerTypes = decreasingPhoneTypes.slice(perfectMatchIndex + 1);
+ const perfectMatchArray = [perfectMatch];
+ return perfectMatchArray.concat(largerTypes, smallerTypes);
+ }
+ default: {
+ return [];
+ }
+ }
+}
+export function combinedFileSizeFromData(objectGraph, data) {
+ var _a;
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ // This background asset information is for the work done in SydneyB
+ const backgroundAssetsInfo = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "backgroundAssetsInfo");
+ // This background asset information is for the work done in SydneyE
+ const backgroundAssetsInfoWithOptional = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "backgroundAssetsInfoWithOptional");
+ const isIOSBinaryCompatibleWithMac = supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, true);
+ const isMacOnly = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "mac");
+ const isWebViewingMac = objectGraph.client.isWeb && ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.platform) === "mac";
+ if ((objectGraph.client.isMac || isWebViewingMac || isMacOnly) && !isIOSBinaryCompatibleWithMac) {
+ const macFileSize = objectGraph.bag.enableProductPageInstallSize
+ ? macInstallSizeInBytesFromData(objectGraph, data)
+ : offers.macFileSizeInBytesFromData(objectGraph, data);
+ if (serverData.isDefinedNonNullNonEmpty(backgroundAssetsInfoWithOptional)) {
+ const maxEssentialInstallSizeInBytes = serverData.asNumber(backgroundAssetsInfoWithOptional, "maxEssentialInstallSizeInBytes");
+ return new modelsBase.CombinedFileSize(macFileSize, null, null, maxEssentialInstallSizeInBytes);
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(backgroundAssetsInfo)) {
+ const maxDownloadSizeInBytes = serverData.asNumber(backgroundAssetsInfo, "maxDownloadSizeInBytes");
+ const maxInstallSizeInBytes = serverData.asNumber(backgroundAssetsInfo, "maxInstallSizeInBytes");
+ return new modelsBase.CombinedFileSize(macFileSize, maxDownloadSizeInBytes, maxInstallSizeInBytes, null);
+ }
+ return new modelsBase.CombinedFileSize(macFileSize, null, null, null);
+ }
+ else {
+ /* File Size: Our policy is to rely on thinned variant, device model, and universal (in that order). */
+ const fileSizeByDevice = mediaAttributes.attributeAsDictionary(data, "fileSizeByDevice");
+ if (fileSizeByDevice) {
+ /* thinnedApplicationVariantIdentifier can contain two device names. The preferred device, and a compatible device. */
+ let fileSizeKeys = [];
+ if (objectGraph.client.thinnedApplicationVariantIdentifier) {
+ fileSizeKeys = objectGraph.client.thinnedApplicationVariantIdentifier.split(" ");
+ }
+ fileSizeKeys = fileSizeKeys.concat([objectGraph.host.deviceModel, "universal"]);
+ for (const key of fileSizeKeys) {
+ const fileSizeValue = serverData.asNumber(fileSizeByDevice[key]);
+ if (fileSizeValue) {
+ if (serverData.isDefinedNonNullNonEmpty(backgroundAssetsInfoWithOptional)) {
+ const maxEssentialInstallSizeInBytes = serverData.asNumber(backgroundAssetsInfoWithOptional, "maxEssentialInstallSizeInBytes");
+ return new modelsBase.CombinedFileSize(fileSizeValue, null, null, maxEssentialInstallSizeInBytes);
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(backgroundAssetsInfo)) {
+ const maxDownloadSizeInBytes = serverData.asNumber(backgroundAssetsInfo, "maxDownloadSizeInBytes");
+ const maxInstallSizeInBytes = serverData.asNumber(backgroundAssetsInfo, "maxInstallSizeInBytes");
+ return new modelsBase.CombinedFileSize(fileSizeValue, maxDownloadSizeInBytes, maxInstallSizeInBytes, null);
+ }
+ else {
+ return new modelsBase.CombinedFileSize(fileSizeValue, null, null, null);
+ }
+ }
+ }
+ }
+ }
+ return null;
+}
+/**
+ * Extract the file size and unit from a CombinedFileSize object.
+ * @param objectGraph Current object graph
+ * @param combinedFileSize The combined file size object
+ * @returns A FileSizeAndUnit object
+ */
+export function fileSizeAndUnitFromCombinedFileSize(objectGraph, combinedFileSize) {
+ let totalFileSize;
+ if (isSome(combinedFileSize.maxEssentialInstallSizeInBytes)) {
+ totalFileSize = combinedFileSize.fileSizeByDevice + combinedFileSize.maxEssentialInstallSizeInBytes;
+ }
+ else if (isSome(combinedFileSize.maxInstallSizeInBytes)) {
+ totalFileSize = combinedFileSize.fileSizeByDevice + combinedFileSize.maxInstallSizeInBytes;
+ }
+ else {
+ totalFileSize = combinedFileSize.fileSizeByDevice;
+ }
+ if (totalFileSize <= 0) {
+ return null;
+ }
+ // We split using all whitespace characters because in some locs a non-breaking space is used.
+ const parts = objectGraph.loc.fileSize(totalFileSize).trim().split(/\s+/);
+ if (parts.length !== 2) {
+ return null;
+ }
+ return {
+ size: parts[0],
+ unit: parts[1],
+ };
+}
+/**
+ * Extracts the install size for a macOS app.
+ * @param objectGraph Current object graph
+ * @param data Product page data
+ * @returns The install size for the Mac binary, in bytes
+ */
+function macInstallSizeInBytesFromData(objectGraph, data) {
+ const deviceData = mediaPlatformAttributes.platformAttributeAsDictionary(data, "osx", "installSizeByDeviceInBytes");
+ if (isNothing(deviceData)) {
+ return null;
+ }
+ // macOS does not support app thinning, so there is only ever one macOS device in this list. Unfortunately
+ // there is no known API that gives us this device name, so we resort to hard-coding for now.
+ const installSizeInBytes = deviceData["Mac"];
+ if (isNothing(installSizeInBytes)) {
+ return null;
+ }
+ return serverData.asNumber(installSizeInBytes);
+}
+/**
+ * Determines the primary langauge locale, from a given list of locales.
+ * @param objectGraph Current object graph
+ * @param locales The list of locales
+ * @returns A single LanguageLocale object, or null
+ */
+export function primaryLanguageLocaleFromLocales(objectGraph, locales) {
+ const languageCount = locales.length;
+ if (languageCount <= 0) {
+ return null;
+ }
+ return {
+ tag: serverData.asString(serverData.traverse(locales, "0.tag")).split("-")[0].toUpperCase(),
+ name: serverData.asString(serverData.traverse(locales, "0.name")),
+ };
+}
+/**
+ * Determines the uber artwork for the product, if there is any.
+ * @param {Data} The data for the product.
+ * @returns {models.Artwork} The artwork for the uber, or `null` if there is none.
+ * null.
+ */
+export function productUberFromData(objectGraph, data, options) {
+ let uberArtworkData;
+ let uberArtworkPath = null;
+ let fallbackUberArtworkPath = null;
+ let cropCode = null;
+ let fallbackCropCode = null;
+ switch (objectGraph.client.deviceType) {
+ case "mac":
+ if (options.supportsArcade) {
+ uberArtworkPath = "editorialArtwork.splashFullScreen";
+ cropCode = "sr";
+ }
+ else {
+ uberArtworkPath = "editorialArtwork.centeredFullscreenBackground";
+ cropCode = "ep";
+ }
+ break;
+ case "tv":
+ if (options.presentedInTopShelf) {
+ uberArtworkPath = "editorialArtwork.topShelf";
+ cropCode = "sr";
+ }
+ else {
+ uberArtworkPath = "editorialArtwork.splashFullScreen";
+ cropCode = "ta";
+ fallbackUberArtworkPath = "editorialArtwork.fullscreenBackground";
+ fallbackCropCode = "sr";
+ }
+ break;
+ case "vision":
+ uberArtworkPath = "editorialArtwork.productUberStatic16x9";
+ cropCode = "sr";
+ break;
+ default:
+ if (options.supportsArcade) {
+ if (options.prefersCompactVariant || objectGraph.client.isPhone) {
+ uberArtworkPath = "editorialArtwork.splashTall";
+ cropCode = "oc";
+ }
+ else {
+ uberArtworkPath = "editorialArtwork.splashFullScreen";
+ cropCode = "oh";
+ }
+ }
+ else {
+ uberArtworkPath = "editorialArtwork.bannerUber";
+ cropCode = "sr";
+ }
+ break;
+ }
+ uberArtworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, uberArtworkPath);
+ // If we don't have the desired artwork, we sometimes attempt to use other artwork as a fallback.
+ if (fallbackUberArtworkPath !== null && serverData.isNullOrEmpty(uberArtworkData)) {
+ uberArtworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, fallbackUberArtworkPath);
+ // Use the fallback crop if it's available.
+ if (fallbackCropCode !== null) {
+ cropCode = fallbackCropCode;
+ }
+ }
+ if (serverData.isDefinedNonNull(uberArtworkData) && serverData.isDefinedNonNull(cropCode)) {
+ return artworkFromApiArtwork(objectGraph, uberArtworkData, {
+ cropCode,
+ useCase: 21 /* ArtworkUseCase.Uber */,
+ withJoeColorPlaceholder: true,
+ overrideHeight: null,
+ overrideWidth: null,
+ });
+ }
+ return null;
+}
+/**
+ * Determines the logo artwork for the product, if there is any.
+ * @param {Data} The data for the product.
+ * @returns {models.Artwork} The artwork for the uber, or `null` if there is none.
+ * null.
+ */
+export function productLogoArtworkFromData(objectGraph, data) {
+ let artworkPath = null;
+ let cropCode = null;
+ switch (objectGraph.client.deviceType) {
+ case "tv":
+ artworkPath = "editorialArtwork.contentLogoTrimmed";
+ cropCode = "bb";
+ break;
+ default:
+ return null;
+ }
+ const artworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, artworkPath);
+ if (serverData.isDefinedNonNull(artworkData) && serverData.isDefinedNonNull(cropCode)) {
+ return artworkFromApiArtwork(objectGraph, artworkData, {
+ cropCode,
+ useCase: 0 /* ArtworkUseCase.Default */,
+ withJoeColorPlaceholder: true,
+ });
+ }
+ return null;
+}
+/**
+ * Determines the editorial video for the product, if there is any.
+ * @returns {models.Video} The editorial video for the product, or `null` if there is none.
+ * null.
+ * @param data
+ * @param useCase
+ * @param preferredFlavorsOverride
+ */
+export function productEditorialVideoFromData(objectGraph, data, useCase, preferredFlavorsOverride, videoPreviewOverride) {
+ let preferredFlavors = [];
+ if (serverData.isDefinedNonNullNonEmpty(preferredFlavorsOverride)) {
+ preferredFlavors = preferredFlavorsOverride;
+ }
+ else {
+ switch (objectGraph.client.deviceType) {
+ case "mac":
+ case "tv":
+ preferredFlavors = ["splashVideo16x9"];
+ break;
+ case "pad":
+ preferredFlavors = ["splashVideo4x3"];
+ break;
+ case "vision":
+ preferredFlavors = ["productUberMotion16x9"];
+ break;
+ default:
+ preferredFlavors = ["splashVideo3x4"];
+ }
+ }
+ let uberEditorialVideoData = null;
+ let videoPreviewData = null;
+ for (const videoFlavor of preferredFlavors) {
+ uberEditorialVideoData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, [
+ "editorialVideo",
+ videoFlavor,
+ ]);
+ videoPreviewData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, [
+ "editorialVideo",
+ videoFlavor,
+ "previewFrame",
+ ]);
+ if (serverData.isDefinedNonNullNonEmpty(uberEditorialVideoData)) {
+ break;
+ }
+ }
+ // Video Preview based on data, or externally provided override if any.
+ const videoPreview = videoPreviewOverride !== null && videoPreviewOverride !== void 0 ? videoPreviewOverride : artworkFromApiArtwork(objectGraph, videoPreviewData, {
+ useCase: useCase,
+ withJoeColorPlaceholder: true,
+ cropCode: "sr",
+ });
+ if (serverData.isDefinedNonNull(uberEditorialVideoData)) {
+ const videoUrl = serverData.asString(uberEditorialVideoData, "video");
+ if (serverData.isNull(videoUrl)) {
+ return null;
+ }
+ let playbackControls;
+ let autoplayPlaybackControls;
+ if (objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isTV) {
+ playbackControls = videoDefaults.standardControls(objectGraph);
+ autoplayPlaybackControls = {
+ muteUnmute: true,
+ };
+ }
+ else {
+ playbackControls = {};
+ autoplayPlaybackControls = {};
+ }
+ const configuration = {
+ allowsAutoPlay: true,
+ looping: true,
+ canPlayFullScreen: false,
+ playbackControls: playbackControls,
+ autoPlayPlaybackControls: autoplayPlaybackControls,
+ };
+ return new models.Video(videoUrl, videoPreview, configuration);
+ }
+ return null;
+}
+/**
+ * Determines the video for the poster lockup, if there is any.
+ * @param {Data} The data for the lockup.
+ * @param {useCase} The use case for this artwork.
+ * @returns {models.Video} The video for the poster lockup, or `null` if there is none.
+ * null.
+ */
+export function posterEditorialVideoFromData(objectGraph, data, useCase) {
+ const editorialVideoData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, [
+ "editorialVideo",
+ "posterCardVideo16x9",
+ ]);
+ const videoPreviewData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, [
+ "editorialVideo",
+ "posterCardVideo16x9",
+ "previewFrame",
+ ]);
+ const cropCode = "sr";
+ const videoPreview = artworkFromApiArtwork(objectGraph, videoPreviewData, {
+ useCase: useCase,
+ withJoeColorPlaceholder: true,
+ cropCode: cropCode,
+ });
+ if (serverData.isDefinedNonNull(editorialVideoData)) {
+ const videoUrl = serverData.asString(editorialVideoData, "video");
+ if (serverData.isNull(videoUrl)) {
+ return null;
+ }
+ const configuration = {
+ allowsAutoPlay: true,
+ looping: true,
+ canPlayFullScreen: false,
+ playbackControls: videoDefaults.noControls(objectGraph),
+ autoPlayPlaybackControls: videoDefaults.noControls(objectGraph),
+ };
+ return new models.Video(videoUrl, videoPreview, configuration);
+ }
+ return null;
+}
+/**
+ * Determines the artwork for the poster lockup, if there is any.
+ * @param {Data} The data for the lockup.
+ * @returns {models.Artwork} The artwork for the poster lockup, or `null` if there is none.
+ * null.
+ */
+export function posterArtworkFromData(objectGraph, data) {
+ const artworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialArtwork.postCard");
+ const cropCode = "sr";
+ if (serverData.isDefinedNonNull(artworkData)) {
+ return artworkFromApiArtwork(objectGraph, artworkData, {
+ cropCode,
+ useCase: 0 /* ArtworkUseCase.Default */,
+ withJoeColorPlaceholder: true,
+ });
+ }
+ return null;
+}
+/**
+ * Determines the artwork for the epic heading on a poster lockup, if there is any.
+ * @param {Data} The data for the product.
+ * @returns {models.Artwork} The artwork for the epic heading, or `null` if there is none.
+ * null.
+ */
+export function posterEpicHeadingArtworkFromData(objectGraph, data) {
+ const artworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialArtwork.epicHeading");
+ const cropCode = "bb";
+ if (serverData.isDefinedNonNull(artworkData) && serverData.isDefinedNonNull(cropCode)) {
+ const epicHeadingArtwork = artworkFromApiArtwork(objectGraph, artworkData, {
+ cropCode,
+ useCase: 0 /* ArtworkUseCase.Default */,
+ });
+ if (objectGraph.client.isVision) {
+ epicHeadingArtwork.backgroundColor = color.named("clear");
+ }
+ return epicHeadingArtwork;
+ }
+ return null;
+}
+/**
+ * Fetch the most-landscape media from data. Hoisted from Arcade See All.
+ * Used for:
+ * - Arcade See All Media Lockups
+ * - Continue Playing Lockups
+ */
+export function editorialSplashVideoFromData(objectGraph, data, videoPreviewOverride) {
+ let preferredEditorialVideoFlavors = null;
+ switch (objectGraph.client.deviceType) {
+ case "mac":
+ case "tv":
+ case "phone":
+ case "vision":
+ preferredEditorialVideoFlavors = ["splashVideo16x9", "splashVideo4x3", "splashVideo3x4"];
+ break;
+ default:
+ preferredEditorialVideoFlavors = ["splashVideo4x3", "splashVideo16x9", "splashVideo3x4"];
+ }
+ return productEditorialVideoFromData(objectGraph, data, 21 /* ArtworkUseCase.Uber */, preferredEditorialVideoFlavors, videoPreviewOverride);
+}
+/**
+ * Determines the URL to use for the developer page.
+ * @param {Data} developerData The data for the "developer" relationship.
+ * @returns {string} The string form of the URL for the developer page, or `null` if the developer data is undefined or
+ * null.
+ */
+export function developerUrlFromDeveloperData(objectGraph, developerData) {
+ if (!serverData.isDefinedNonNull(developerData)) {
+ return null;
+ }
+ if (objectGraph.client.isWeb) {
+ return mediaAttributes.attributeAsString(developerData, "url");
+ }
+ return `${Protocol.internal}:/${Path.developer}/${Path.href}?${Parameters.href}=${developerData.href}`;
+}
+/**
+ * Determines the URL to use for the Charts page.
+ * @param {Data} data The data for the product.
+ * @returns {string} The string form of the URL for the charts page, or `null` if the data is undefined or
+ * null.
+ */
+export function chartUrlFromData(objectGraph, genre, chart) {
+ const request = new mediaDataFetching.Request(objectGraph)
+ .forType("charts")
+ .addingQuery("types", "apps")
+ .addingQuery("chart", chart)
+ .addingQuery("genre", genre)
+ .includingMacOSCompatibleIOSAppsWhenSupported(true);
+ return mediaUrlBuilder.buildURLFromRequest(objectGraph, request).toString();
+}
+/**
+ * Returns the key into the chart-position badge data for the given client name.
+ * @param clientIdentifier Identifier of the current client.
+ * @returns {string} The relevant key in the chart-position badge JSON data.
+ */
+export function badgeChartKeyForClientIdentifier(objectGraph, clientIdentifier) {
+ switch (clientIdentifier) {
+ case client.appStoreIdentifier:
+ case client.productPageExtensionIdentifier:
+ return "appStore";
+ case client.watchIdentifier:
+ return "watch";
+ case client.messagesIdentifier:
+ return "messages";
+ case client.tvIdentifier:
+ return "appletv";
+ default:
+ return null;
+ }
+}
+/**
+ * Internal function returning the name and asset name representing the
+ * storefront content rating for the provided rank.
+ * @param objectGraph The App Store object graph.
+ * @param rank A content rating rank from CX.
+ * @returns A tuple containing the name and asset name representing the rank,
+ * or `undefined` if rank is unknown/invalid.
+ */
+function storefrontContentRatingInfoForRank(objectGraph, rank) {
+ switch (rank) {
+ // Brazil Self-Rated
+ case 6:
+ return ["L", "br.l"];
+ case 7:
+ return ["10", "br.10"];
+ case 8:
+ return ["12", "br.12"];
+ case 9:
+ return ["14", "br.14"];
+ case 10:
+ return ["16", "br.16"];
+ case 11:
+ return ["18", "br.18"];
+ // Brazil Official
+ case 12:
+ return ["AL", "br.l.official"];
+ case 13:
+ return ["A10", "br.10.official"];
+ case 14:
+ return ["A12", "br.12.official"];
+ case 15:
+ return ["A14", "br.14.official"];
+ case 16:
+ return ["A16", "br.16.official"];
+ case 17:
+ return ["A18", "br.18.official"];
+ // Korea
+ case 20:
+ return ["All", "kr.all"];
+ case 21:
+ return ["12", "kr.12"];
+ case 22:
+ return ["15", "kr.15"];
+ // Australia
+ case 31:
+ return ["15+", "AgeRating-AU-15"];
+ case 32:
+ return ["R 18+", "AgeRating-AU-18"];
+ // France
+ case 47:
+ return ["18+", "AgeRating-FR-18"];
+ default:
+ return undefined;
+ }
+}
+/// Returns a localized title for the given app platform.
+export function appPlatformTitle(objectGraph, appPlatform) {
+ switch (appPlatform) {
+ case "phone":
+ return objectGraph.loc.string("AppPlatform.Phone");
+ case "pad":
+ return objectGraph.loc.string("AppPlatform.Pad");
+ case "vision":
+ return objectGraph.loc.string("AppPlatform.Vision");
+ case "tv":
+ return objectGraph.loc.string("AppPlatform.TV");
+ case "watch":
+ return objectGraph.loc.string("AppPlatform.Watch");
+ case "messages":
+ return objectGraph.loc.string("AppPlatform.Messages");
+ case "mac":
+ return objectGraph.loc.string("AppPlatform.Mac");
+ default:
+ return "";
+ }
+}
+/**
+ * Provides the name of the asset representing the storefront content rating
+ * for the provided `rank`.
+ * @param objectGraph The App Store object graph.
+ * @param rank A content rating rank from CX.
+ * @returns The asset name representing the `rank`, corresponding to a file on
+ * device, or `undefined` if rank is unknown/invalid.
+ */
+export function storefrontContentRatingResourceForRank(objectGraph, rank) {
+ var _a;
+ return (_a = storefrontContentRatingInfoForRank(objectGraph, rank)) === null || _a === void 0 ? void 0 : _a[1];
+}
+/**
+ * Provides a textual representation of the storefront content rating for the
+ * provided `rank`, e.g. "18+". This should match the main text displayed in
+ * the content rating pictogram from `storefrontContentRatingResourceForRank`.
+ * @param objectGraph The App Store object graph.
+ * @param rank A content rating rank from CX.
+ * @returns The textual version of the storefront content rating representing
+ * the `rank`, or `undefined` if rank is unknown/invalid.
+ */
+export function storefrontContentRatingNameForRank(objectGraph, rank) {
+ var _a;
+ return (_a = storefrontContentRatingInfoForRank(objectGraph, rank)) === null || _a === void 0 ? void 0 : _a[0];
+}
+export function promotionalTextFromData(objectGraph, data, productVariantData) {
+ return contentAttributes.customAttributeAsString(objectGraph, data, productVariantData, "promotionalText");
+}
+export function hasMessagesExtensionFromData(objectGraph, data, attributePlatform) {
+ return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "hasMessagesExtension", attributePlatform);
+}
+export function supportsFunCameraFromData(objectGraph, data, attributePlatform) {
+ return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "supportsFunCamera", attributePlatform);
+}
+export function isHiddenFromSpringboardFromData(objectGraph, data) {
+ return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isHiddenFromSpringboard");
+}
+function isAppleWatchSupportedFromData(objectGraph, data) {
+ return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isAppleWatchSupported");
+}
+function messagesScreenshotsFromData(objectGraph, data, attributePlatform) {
+ return contentAttributes.contentAttributeAsDictionary(objectGraph, data, "messagesScreenshots", attributePlatform);
+}
+function screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd, attributePlatform) {
+ const attributeKey = isAd ? "customScreenshotsByTypeForAd" : "screenshotsByType";
+ return contentAttributes.customAttributeAsDictionary(objectGraph, data, productVariantData, attributeKey, attributePlatform);
+}
+function videoPreviewsByTypeFromData(objectGraph, data, productVariantData, isAd, attributePlatform) {
+ const attributeKey = isAd ? "customVideoPreviewsByTypeForAd" : "videoPreviewsByType";
+ return contentAttributes.customAttributeAsDictionary(objectGraph, data, productVariantData, attributeKey, attributePlatform);
+}
+/**
+ * Whether Arcade is supported, based on the provided data.
+ * @param objectGraph The App Store object graph.
+ * @param data The data blob to check for Arcade support.
+ * @param attributePlatformOverride An override platform, from which to fetch the attribute.
+ * @returns A boolean indicating if Arcade is supported.
+ */
+export function isArcadeSupported(objectGraph, data, attributePlatformOverride = undefined) {
+ return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "supportsArcade", attributePlatformOverride);
+}
+/**
+ * Try to get notes for some piece of content, giving preference to the enrichedEditorialnotes, falling back to editorialNotes,
+ * then finally to itunesNotes. For some data the notes are stored in the attributes not the platformAttributes.
+ * @param {Data} data
+ * @param {string} key
+ * @param {boolean} enableEditorialCardOverrides This means we will also check for editorial-cards as well before chcking the default notes locations
+ * * @param attributePlatformOverride An override platform, from which to fetch the attribute.
+ * @returns {string}
+ */
+export function notesFromData(objectGraph, data, key, enableEditorialCardOverrides = false, attributePlatformOverride = undefined) {
+ var _a, _b;
+ if (isNothing(data)) {
+ return null;
+ }
+ let note;
+ if (enableEditorialCardOverrides) {
+ const editorialCard = editorialCardFromData(data);
+ if (mediaAttributes.hasAttributes(editorialCard)) {
+ note = contentAttributes.contentAttributeAsString(objectGraph, editorialCard, ["editorialNotes", key], attributePlatformOverride);
+ }
+ }
+ note =
+ (_b = (_a = note !== null && note !== void 0 ? note : contentAttributes.contentAttributeAsString(objectGraph, data, ["enrichedEditorialNotes", key], attributePlatformOverride)) !== null && _a !== void 0 ? _a : contentAttributes.contentAttributeAsString(objectGraph, data, ["editorialNotes", key], attributePlatformOverride)) !== null && _b !== void 0 ? _b : contentAttributes.contentAttributeAsString(objectGraph, data, ["itunesNotes", key], attributePlatformOverride);
+ return note;
+}
+/**
+ * Try and get notes for some piece of content editorialNotes
+ * @param {Data} data
+ * @param {string} key
+ * @param {boolean} enableEditorialCardOverrides This means we will also check for editorial-cards as well before chcking the default notes locations
+ * @returns {string}
+ */
+export function editorialNotesFromData(objectGraph, data, key, enableEditorialCardOverrides = false) {
+ var _a;
+ let note;
+ if (enableEditorialCardOverrides) {
+ const editorialCard = editorialCardFromData(data);
+ if (mediaAttributes.hasAttributes(editorialCard)) {
+ note = contentAttributes.contentAttributeAsString(objectGraph, editorialCard, ["editorialNotes", key]);
+ }
+ }
+ note =
+ (_a = note !== null && note !== void 0 ? note : contentAttributes.contentAttributeAsString(objectGraph, data, ["enrichedEditorialNotes", key])) !== null && _a !== void 0 ? _a : contentAttributes.contentAttributeAsString(objectGraph, data, ["editorialNotes", key]);
+ return note;
+}
+/**
+ * Determines whether the provided data is for a macOS installer.
+ * @param data The data against which to check for a macOS installer.
+ */
+export function isMacOSInstaller(objectGraph, data) {
+ return derivedData.value(data, "isMacOSInstaller", () => {
+ const isMac = objectGraph.client.isMac;
+ if (!isMac) {
+ return false;
+ }
+ const bundleId = contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId");
+ if (!serverData.isDefinedNonNull(bundleId)) {
+ return false;
+ }
+ return bundleId.startsWith("com.apple.InstallAssistant");
+ });
+}
+/**
+ * Check whether an app is unsupported by the current companion configuration.
+ * @param data The data representing an app listing.
+ */
+export function isUnsupportedByCurrentCompanion(objectGraph, data) {
+ const deletableApps = sad.systemApps(objectGraph);
+ if (objectGraph.host.isWatch) {
+ // AppConduit will handle determining if SAD apps are supported
+ if (deletableApps.isUnsupportedDeletableSystemAppFromData(data)) {
+ return true;
+ }
+ else if (objectGraph.client.isTinkerWatch) {
+ return (!contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneWithCompanionForWatchOS") && !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS"));
+ }
+ return false;
+ }
+ else {
+ if (isUnsupportedDeletableSystemAppFromData(objectGraph, data, objectGraph.client.isTinkerWatch)) {
+ return true;
+ }
+ else if (objectGraph.client.isTinkerWatch) {
+ if (deletableApps.isSystemAppFromData(data)) {
+ // We don't consider whether an app is marked as standalone with companion
+ // when running in standalone mode. We always want SAD apps to be installable.
+ return (!contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isDeliveredInIOSAppForWatchOS") &&
+ !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS"));
+ }
+ else {
+ return (!contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneWithCompanionForWatchOS") &&
+ !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS"));
+ }
+ }
+ else {
+ // We only allow standalone system apps to be installed when the watch
+ // is not running in standalone mode. This simplifies things for other teams.
+ return (objectGraph.client.isWatch &&
+ deletableApps.isSystemAppFromData(data) &&
+ !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS"));
+ }
+ }
+}
+/**
+ * @deprecated Use sad.isUnsupportedDeletableSystemAppFromData instead.
+ * Check whether a SAD app is unsupported by the current companion configuration.
+ *
+ * @param data The data representing an app listing.
+ * @param isTinkerWatch Whether the current device is a tinker watch
+ */
+export function isUnsupportedDeletableSystemAppFromData(objectGraph, data, isTinkerWatch) {
+ if (isTinkerWatch && sad.systemApps(objectGraph).isSystemAppFromData(data)) {
+ const watchBundleId = mediaAttributes.attributeAsString(data, "watchBundleId");
+ if (serverData.isDefinedNonNullNonEmpty(watchBundleId)) {
+ switch (watchBundleId) {
+ // rdar://63111354 (On Tinker device, able to attempt to download non-Tinker 1st and 3rd party app)
+ // These apps should prevented from installing on a Tinker device
+ case "com.apple.mobilemail.watchkitapp":
+ case "com.apple.news.watchkitapp":
+ case "com.apple.iBooks.watchkitapp":
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+ return false;
+}
+/**
+ * Device Sizes
+ *
+ * Please do not use these constants for anything but screenshots.
+ * Our code should not depend on absolute screen sizes for anything
+ * not related to selecting the correct screenshots to display. -km
+ */
+/// The screen size of iPhone 6.5" devices.
+export const screenSizeIphone65 = new modelsBase.Size(414.0, 896.0);
+/// The screen size of iPhone 5.8" devices.
+export const screenSizeIPhone58 = new modelsBase.Size(375.0, 812.0);
+/// The screen size of iPhone 6+ like devices.
+export const screenSizeIPhone6Plus = new modelsBase.Size(414.0, 736.0);
+/// The screen size of iPhone 6 like devices.
+export const screenSizeIPhone6 = new modelsBase.Size(375.0, 667.0);
+/// The screen size of iPhone 5 like devices.
+export const screenSizeIPhone5 = new modelsBase.Size(320.0, 568.0);
+/// The screen size of original iPhone like devices.
+export const screenSizeIPhoneOriginal = new modelsBase.Size(320.0, 480.0);
+/// The screen size of iPad and iPad mini devices.
+export const screenSizeIPad = new modelsBase.Size(768.0, 1024.0);
+/// The screen size of 7th and 8th gen 10.2" iPads.
+export const screenSizeIPad102 = new modelsBase.Size(810.0, 1080.0);
+/// The screen size of iPad pro 10.5" devices.
+export const screenSizeIPad105 = new modelsBase.Size(834.0, 1112.0);
+/// The screen size of iPad pro 11" devices.
+export const screenSizeIPad11 = new modelsBase.Size(834.0, 1194.0);
+/// The screen size of iPad Pro 11" devices in landscape orientation.
+/// See `screenSizeIPadJ310Landscape` for info about the landscape orientation.
+export const screenSizeIPad11Landscape = new modelsBase.Size(1194.0, 834.0);
+/// The screen size of iPad pro 12.9" devices.
+export const screenSizeIPadPro = new modelsBase.Size(1024.0, 1366.0);
+/// The screen size of iPad pro 12.9" devices, with rounded corners.
+export const screenSizeIPadPro2018 = new modelsBase.Size(1024.0, 1366.0);
+/// The screen size of iPad pro 12.9" devices, with rounded corners, in landscape orientation.
+/// See `screenSizeIPadJ310Landscape` for info about the landscape orientation.
+export const screenSizeIPadPro2018Landscape = new modelsBase.Size(1366.0, 1024.0);
+// The screen size of the J310 iPad device.
+export const screenSizeIPadJ310 = new modelsBase.Size(744.0, 1133.0);
+// The screen size of the J310 iPad device, in landscape orientation.
+// rdar: //83176176 (J310: kMGQMainScreenCanvasSizes reports width as largest dimension, contrary to all other iPads and UIKit)
+export const screenSizeIPadJ310Landscape = new modelsBase.Size(1133.0, 744.0);
+/// The screen size of J720/J721 devices.
+export const screenSizeJ720 = new modelsBase.Size(1032.0, 1376.0);
+/// The screen size of J720/J721, in landscape orientation.
+/// See `screenSizeIPadJ310Landscape` for info about the landscape orientation.
+export const screenSizeJ720Landscape = new modelsBase.Size(1376.0, 1032.0);
+/// The screen size of J717/J718 devices.
+export const screenSizeJ717 = new modelsBase.Size(834.0, 1210.0);
+/// The screen size of J717/J718, in landscape orientation.
+/// See `screenSizeIPadJ310Landscape` for info about the landscape orientation.
+export const screenSizeJ717Landscape = new modelsBase.Size(1210.0, 834.0);
+/// The screen size of the 42mm Apple Watch devices.
+export const screenSizeWatch = new modelsBase.Size(312.0, 390.0);
+/// The screen size of large 2018 Apple Watch devices.
+export const screenSizeWatch2018 = new modelsBase.Size(368.0, 448.0);
+// The screen size of the large 2021 Apple Watch devices.
+export const screenSizeWatch2021 = new modelsBase.Size(396.0, 484.0);
+// The screen size for the 2022 Apple Watch devices.
+export const screenSizeWatch2022 = new modelsBase.Size(410.0, 502.0);
+// The screen size for the 2024 Apple Watch devices.
+export const screenSizeWatch2024 = new modelsBase.Size(416.0, 496.0);
+// The screen size for the Apple Watch Ultra / Ultra 2 devices.
+export const screenSizeWatchUltra = new modelsBase.Size(410.0, 502.0);
+/// The screen size for iPad device.
+export const screenSizeIPadAir2020 = new modelsBase.Size(820.0, 1180.0);
+/// The screen size for iPhone devices.
+export const screenSizeIPhone131 = new modelsBase.Size(360.0, 780.0);
+export const screenSizeIPhone132 = new modelsBase.Size(390.0, 844.0);
+export const screenSizeIPhone134 = new modelsBase.Size(428.0, 926.0);
+// The screen size for a 6.1" D73-style device.
+export const screenSizeIPhone61 = new modelsBase.Size(393.0, 852.0);
+// The screen size for a 6.7" D74-style device.
+export const screenSizeIPhone67 = new modelsBase.Size(430.0, 932.0);
+/// The screen size for a D93 device.
+export const screenSizeD93 = new modelsBase.Size(402.0, 874.0);
+/// The screen size for a D94 device.
+export const screenSizeD94 = new modelsBase.Size(440.0, 956.0);
+/// The screen size for a D23 device.
+export const screenSizeD23 = new modelsBase.Size(420.0, 912.0);
+/// The screen size for a N230 device.
+export const screenSizeN230 = new modelsBase.Size(422.0, 514.0);
+/// All phone types, in order of decreasing size.
+const decreasingPhoneTypes = [
+ "iphone_d74",
+ "iphone_6_5",
+ "iphone_d73",
+ "iphone_5_8",
+ "iphone6+",
+ "iphone6",
+ "iphone5",
+ "iphone",
+];
+// region Device Corner Radius
+/**
+ * The reason we need to hardcode these is because we may want to display screenshots with rounding for a device that is
+ * not the one the user is currently browsing the store with. For example, imagine that the user is browsing the store
+ * with an iPhone 8 but ends up looking at an app that only has D22 screenshots. They should see the D22 screenshots
+ * according to the D22 corner rounding, and using the current client's `screenCornerRadius` would not give us the
+ * proper value.
+ */
+/// The device corner radius of iPad pro 12.9" devices from 2018.
+const deviceCornerRadiusIpadPro2018 = 18.0;
+/// The device corner radius of iPad pro 11" devices.
+const deviceCornerRadiusIpad11 = 18.0;
+/// The device corner radius of iPhone 6.5" devices.
+const deviceCornerRadiusIphone65 = 41.5;
+/// The device corner radius of iPhone 5.8" devices.
+const deviceCornerRadiusIphone58 = 39.0;
+/// The device corner radius of iPhone 6.1" devices.
+const deviceCornerRadiusIphone61 = 55.0;
+/// The device corner radius of iPhone 6.7" devices.
+const deviceCornerRadiusIphone67 = 55.0;
+/// The device corner radius of large 2018 Apple Watch devices.
+const deviceCornerRadiusWatch2018 = 34.0;
+/// The outer device corner radius of large Apple Watch devices.
+const outerDeviceCornerRadiusWatch = 30.0;
+/// The device border thickness for Apple Watch.
+const deviceBorderThicknessWatch = 13.0;
+/// The device border thickness for 2018 Apple Watch.
+const deviceBorderThicknessWatch2018 = 11.0;
+/// The device corner radius for 2021 Apple Watch.
+const deviceCornerRadiusWatch2021 = 55;
+/// The device border thickness for 2021 Apple Watch.
+const deviceBorderThicknessWatch2021 = 5.5;
+/// The device corner radius for 2022/2024 Apple Watch.
+const deviceCornerRadiusWatch2022 = 108;
+/// The outer device corner radius for 2022/2024 Apple Watch.
+const deviceOuterCornerRadiusWatch2022 = 112.5;
+/// The device border thickness for 2022/2024 Apple Watch.
+const deviceBorderThicknessWatch2022 = 4.5;
+export function currentAppPlatform(objectGraph) {
+ var _a;
+ switch (objectGraph.client.deviceType) {
+ case "web":
+ return unwrap((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.appPlatform);
+ default:
+ return objectGraph.client.deviceType;
+ }
+}
+/// The SF Symbol name that represents the media type.
+export function systemImageNameForAppPlatform(appPlatform) {
+ switch (appPlatform) {
+ case "phone":
+ return "iphone";
+ case "pad":
+ return "ipad";
+ case "tv":
+ return "tv";
+ case "watch":
+ return "applewatch";
+ case "mac":
+ return "macbook";
+ case "messages":
+ return "message";
+ case "vision":
+ return "visionpro";
+ default:
+ unreachable(appPlatform);
+ }
+}
+/**
+ * Returns the factor by which to multiply an artwork's portrait-equivalent width, in order to compute the artwork's
+ * device-rounded corner radius. This is useful because we want to display screenshots for device-rounded screenshots
+ * with a corner radius that is scaled to that of the device screen.
+ *
+ * r = w * r'
+ * r' = R / W
+ *
+ * Where:
+ * r: scaled radius
+ * r': The return value of this function.
+ * R: device corner radius
+ * w: width at which the screenshot will be displayed
+ * W: device width
+ *
+ * We need to have this value here in the JS because we may need to display device-rounded screenshots on a device that
+ * does not have a device corner radius; there is no native API for querying the corner radius of various devices and,
+ * even if there were, we want to avoid specific screen-size checks in the native code.
+ * @param type The screenshot type vended by the server.
+ * @returns {number} The device corner radius factor, or null if the device does not have a corner radius.
+ */
+function deviceCornerRadiusFactorForMediaType(objectGraph, type) {
+ // Let's only bridge over and access client's properties if we need to.
+ switch (type) {
+ case "ipadPro_2018":
+ return deviceCornerRadiusIpadPro2018 / screenSizeIPadPro2018.width;
+ case "ipad_11":
+ return deviceCornerRadiusIpad11 / screenSizeIPad11.width;
+ case "iphone_6_5":
+ return deviceCornerRadiusIphone65 / screenSizeIphone65.width;
+ case "iphone_5_8":
+ return deviceCornerRadiusIphone58 / screenSizeIPhone58.width;
+ case "iphone_d73":
+ return deviceCornerRadiusIphone61 / screenSizeIPhone61.width;
+ case "iphone_d74":
+ return deviceCornerRadiusIphone67 / screenSizeIPhone67.width;
+ case "appleWatch_2018":
+ return deviceCornerRadiusWatch2018 / screenSizeWatch2018.width;
+ case "appleWatch_2021":
+ return deviceCornerRadiusWatch2021 / screenSizeWatch2021.width;
+ case "appleWatch_2022":
+ return deviceCornerRadiusWatch2022 / screenSizeWatch2022.width;
+ case "appleWatch_2024":
+ return deviceCornerRadiusWatch2022 / screenSizeWatch2024.width;
+ default:
+ return null;
+ }
+}
+function deviceOuterCornerRadiusFactorForMediaType(objectGraph, type) {
+ switch (type) {
+ case "appleWatch":
+ return outerDeviceCornerRadiusWatch / screenSizeWatch.width;
+ case "appleWatch_2022":
+ return deviceOuterCornerRadiusWatch2022 / screenSizeWatch2022.width;
+ case "appleWatch_2024":
+ return deviceOuterCornerRadiusWatch2022 / screenSizeWatch2024.width;
+ default:
+ return deviceCornerRadiusFactorForMediaType(objectGraph, type);
+ }
+}
+function deviceBorderThicknessForMediaType(objectGraph, type) {
+ switch (type) {
+ case "appleWatch":
+ return deviceBorderThicknessWatch / screenSizeWatch.width;
+ case "appleWatch_2018":
+ return deviceBorderThicknessWatch2018 / screenSizeWatch2018.width;
+ case "appleWatch_2021":
+ return deviceBorderThicknessWatch2021 / screenSizeWatch2021.width;
+ case "appleWatch_2022":
+ return deviceBorderThicknessWatch2022 / screenSizeWatch2022.width;
+ case "appleWatch_2024":
+ return deviceBorderThicknessWatch2022 / screenSizeWatch2024.width;
+ default:
+ return null;
+ }
+}
+// endregion
+/**
+ Returns a boolean indicating if the client's operating
+ system is the same or later than the specified version.
+
+ @param version The full version number to check against
+ @returns true if the operating system is the same or newer than the specified version; false otherwise.
+ */
+export function isOSAtLeastVersion(objectGraph, version) {
+ if (serverData.isNull(version) || version.length === 0) {
+ return true;
+ }
+ const versionComponents = version.split(".");
+ const majorVersion = serverData.asNumber(versionComponents[0]) || 0;
+ const minorVersion = serverData.asNumber(versionComponents[1]) || 0;
+ const patchVersion = serverData.asNumber(versionComponents[2]) || 0;
+ return objectGraph.host.isOSAtLeast(majorVersion, minorVersion, patchVersion);
+}
+/**
+ Returns a boolean indicating if the system version of the active, paired watch (if any) is
+ at least the provided version number.
+
+ @param version The full version number to check against
+ @returns true if an active, paired watch exists, and its operating system version is the same or newer than the specified version; false otherwise.
+ */
+export function isActivePairedWatchOSAtLeastVersion(objectGraph, version) {
+ if (serverData.isNull(version) || version.length === 0) {
+ return true;
+ }
+ const versionComponents = version.split(".");
+ const majorVersion = serverData.asNumber(versionComponents[0]) || 0;
+ const minorVersion = serverData.asNumber(versionComponents[1]) || 0;
+ const patchVersion = serverData.asNumber(versionComponents[2]) || 0;
+ return objectGraph.client.isActivePairedWatchSystemVersionAtLeastMajorVersionMinorVersionPatchVersion(majorVersion, minorVersion, patchVersion);
+}
+/**
+ * Check whether the active paired device's OS is the same or greater than a given version.
+ */
+export function isActivePairedDeviceAtLeastVersion(objectGraph, version) {
+ if (serverData.isNull(version) || version.length === 0) {
+ return true;
+ }
+ return objectGraph.client.isPairedSystemVersionAtLeast(version);
+}
+/**
+ * Check whether the active paired device's OS is below a given version.
+ */
+export function isActivePairedWatchOSBelowVersion(objectGraph, version) {
+ if (serverData.isNull(version) || version.length === 0) {
+ return false;
+ }
+ return objectGraph.client.isActivePairedWatchSystemVersionBelow(version);
+}
+export function shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfStyle) {
+ if (objectGraph.client.isTV) {
+ switch (shelfStyle) {
+ case "upsellBreakout":
+ return true;
+ default:
+ return false;
+ }
+ }
+ else {
+ switch (shelfStyle) {
+ case "smallLockup":
+ case "mediumLockup":
+ case "appTrailerLockup":
+ case "screenshotsLockup":
+ case "mixedMediaLockup":
+ case "upsellBreakout":
+ case "arcadeShowcase":
+ return true;
+ default:
+ return false;
+ }
+ }
+}
+export function shelfDisplayStyleCanDisplayArcadeOfferButtons(objectGraph, displayStyle) {
+ switch (displayStyle) {
+ case "LockupSmall":
+ case "LockupLarge":
+ case "BreakoutLarge":
+ case "Hero":
+ case "EditorialLockupLarge":
+ case "EditorialLockupLargeVariant":
+ case "EditorialLockupMedium":
+ case "EditorialLockupMediumVariant":
+ case "StoryMedium":
+ return true;
+ default:
+ return false;
+ }
+}
+/**
+ * The dynamic date string used by apps coming soon.
+ */
+export function dynamicPreorderDateFromData(objectGraph, data, fallbackLabel) {
+ const preorderOffer = offers.offerDataFromData(objectGraph, data);
+ const isPreorder = serverData.asString(preorderOffer, "type") === "preorder";
+ if (isPreorder) {
+ const releaseDateRaw = serverData.asString(preorderOffer, "expectedReleaseDate");
+ const dateDisplayFormat = contentAttributes.contentAttributeAsString(objectGraph, data, "expectedReleaseDateDisplayFormat");
+ if (serverData.isDefinedNonNullNonEmpty(dateDisplayFormat)) {
+ if (serverData.isDefinedNonNullNonEmpty(releaseDateRaw)) {
+ const releaseDate = dateUtil.parseDateOmittingTimeFromString(releaseDateRaw);
+ const tokenFormatMap = {
+ "@@expectedDateMY@@": objectGraph.loc.string("PreOrder.Date.MonthYear"),
+ "@@expectedDateMDY@@": objectGraph.loc.string("PreOrder.Date.MonthDayYear"),
+ };
+ for (const [serverToken, dateFormat] of Object.entries(tokenFormatMap)) {
+ if (dateDisplayFormat.includes(serverToken)) {
+ let formattedDate = objectGraph.loc.formatDateWithContext(dateFormat, releaseDate, "middleOfSentence");
+ if (objectGraph.client.isTV) {
+ formattedDate = formattedDate.replace(/ /g, "\u00a0");
+ }
+ return dateDisplayFormat.replace(serverToken, formattedDate);
+ }
+ }
+ }
+ return dateDisplayFormat;
+ }
+ }
+ // There was no dynamic date to display
+ return fallbackLabel;
+}
+/**
+ * The primary content for an editorial item.
+ * @param data The data from which to derive the primary content.
+ */
+export function primaryContentForData(objectGraph, data) {
+ const primaryContent = mediaRelationship.relationshipData(objectGraph, data, "primary-content");
+ if (serverData.isDefinedNonNullNonEmpty(primaryContent)) {
+ return primaryContent;
+ }
+ // If an EI has canvasData, then in MAPI response its "primary-content" relationship will not include the
+ // primary content meta data. Instead, the primary content data will be included in the "card-contents" relationship.
+ if (contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isCanvasAvailable")) {
+ return mediaRelationship.relationshipData(objectGraph, data, "card-contents");
+ }
+ return null;
+}
+const grayColorHex = "#9BA9BD";
+export const grayColorCriteria = {
+ colorHex: grayColorHex,
+ maxSaturation: 4,
+ maxBrightness: 9,
+};
+/**
+ * The list of tag background colors we can use to match an app icon background color against, based on saturation and brightness
+ */
+const saturationBrightnessBasedTagColorBuckets = [
+ grayColorCriteria, // Gray
+];
+/**
+ * The list of tag background colors we can use to match an app icon background color against
+ */
+const hueBasedTagColorBuckets = [
+ { colorHex: "#F7816F", minHue: 0, maxHue: 16 },
+ { colorHex: "#FF9034", minHue: 17, maxHue: 33 },
+ { colorHex: "#E3B059", minHue: 34, maxHue: 59 },
+ { colorHex: "#74BD66", minHue: 60, maxHue: 129 },
+ { colorHex: "#72C792", minHue: 130, maxHue: 169 },
+ { colorHex: "#61BFE2", minHue: 170, maxHue: 209 },
+ { colorHex: "#6EA3E9", minHue: 210, maxHue: 239 },
+ { colorHex: "#7D69FA", minHue: 240, maxHue: 259 },
+ { colorHex: "#B363F7", minHue: 260, maxHue: 289 },
+ { colorHex: "#EE7CBD", minHue: 290, maxHue: 360 }, // Pink
+];
+/**
+ * Find the matching tag color for an icon's background color
+ *
+ * @param iconBackgroundColor The bgColor from the icon artwork data
+ * @returns The closest matching color from the HI provided set of tag background colors for this icon color
+ */
+export function closestTagBackgroundColorForIcon(iconBackgroundColor) {
+ var _a;
+ return ((_a = color.findColorBucketForColor(iconBackgroundColor, saturationBrightnessBasedTagColorBuckets, hueBasedTagColorBuckets)) !== null && _a !== void 0 ? _a : color.fromHex(grayColorHex));
+}
+/**
+ * Parses a JoeColorSet out of a MAPI EditorialArtwork JSON object
+ * @param data An EditorialArtwork JSON object from MAPI
+ * @returns A JoeColorSet from parsing the input RGB values
+ */
+export function joeColorHexSetFromData(data) {
+ var _a, _b, _c, _d, _e;
+ const textGradient = [];
+ for (const gradientColorHex of serverData.asArrayOrEmpty(data, "textGradient")) {
+ if (isSome(gradientColorHex) && serverData.isString(gradientColorHex)) {
+ textGradient.push(gradientColorHex);
+ }
+ }
+ return {
+ bgColor: (_a = serverData.asString(data, "bgColor")) !== null && _a !== void 0 ? _a : undefined,
+ textColor1: (_b = serverData.asString(data, "textColor1")) !== null && _b !== void 0 ? _b : undefined,
+ textColor2: (_c = serverData.asString(data, "textColor2")) !== null && _c !== void 0 ? _c : undefined,
+ textColor3: (_d = serverData.asString(data, "textColor3")) !== null && _d !== void 0 ? _d : undefined,
+ textColor4: (_e = serverData.asString(data, "textColor4")) !== null && _e !== void 0 ? _e : undefined,
+ textGradient: isDefinedNonNullNonEmpty(textGradient) ? textGradient : undefined,
+ };
+}
+/**
+ * Parses a JoeColorSet out of a MAPI EditorialArtwork JSON object
+ * @param data An EditorialArtwork JSON object from MAPI
+ * @returns A JoeColorSet from parsing the input RGB values
+ */
+export function joeColorSetFromData(data) {
+ var _a, _b, _c, _d, _e, _f;
+ const joeColorHexSet = joeColorHexSetFromData(data);
+ const textGradient = [];
+ for (const gradientColorHex of (_a = joeColorHexSet.textGradient) !== null && _a !== void 0 ? _a : []) {
+ const gradientColor = color.fromHex(gradientColorHex);
+ if (isSome(gradientColor)) {
+ textGradient.push(gradientColor);
+ }
+ }
+ return {
+ bgColor: (_b = color.fromHex(joeColorHexSet.bgColor)) !== null && _b !== void 0 ? _b : undefined,
+ textColor1: (_c = color.fromHex(joeColorHexSet.textColor1)) !== null && _c !== void 0 ? _c : undefined,
+ textColor2: (_d = color.fromHex(joeColorHexSet.textColor2)) !== null && _d !== void 0 ? _d : undefined,
+ textColor3: (_e = color.fromHex(joeColorHexSet.textColor3)) !== null && _e !== void 0 ? _e : undefined,
+ textColor4: (_f = color.fromHex(joeColorHexSet.textColor4)) !== null && _f !== void 0 ? _f : undefined,
+ textGradient: isDefinedNonNullNonEmpty(textGradient) ? textGradient : undefined,
+ };
+}
+/**
+ * Attempt to find the first non-gray placeholder color
+ * @param joeColorSet The joe color set for a given icon
+ * @returns The color hext value to use for the joe color placeholder
+ */
+export function bestJoeColorPlaceholderSelectionLogic(joeColorSet) {
+ const joeColorKeys = [
+ "bgColor",
+ "textColor1",
+ "textColor2",
+ "textColor3",
+ "textColor4",
+ ];
+ for (const joeColorKey of joeColorKeys) {
+ const joeColorHex = joeColorSet[joeColorKey];
+ if (!serverData.isString(joeColorHex)) {
+ continue;
+ }
+ const isGrayColor = color.doesColorMeetCriteria(color.fromHex(joeColorHex), grayColorCriteria);
+ if (!isGrayColor) {
+ return joeColorHex;
+ }
+ }
+ return null;
+}
+//# sourceMappingURL=content.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/content/device-family.js b/node_modules/@jet-app/app-store/tmp/src/common/content/device-family.js
new file mode 100644
index 0000000..8c8c925
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/content/device-family.js
@@ -0,0 +1,91 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as derivedData from "../../foundation/json-parsing/derived-data";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+export function dataHasDeviceFamily(objectGraph, data, deviceFamily, recurse = false) {
+ return derivedData.value(data, `dataHasDeviceFamily.${deviceFamily}`, () => {
+ if (!data || !deviceFamily) {
+ return false;
+ }
+ const deviceFamilies = deviceFamiliesFromData(objectGraph, data, recurse);
+ return deviceFamilies.indexOf(deviceFamily) !== -1;
+ });
+}
+export function dataOnlyHasDeviceFamily(objectGraph, data, deviceFamily, recurse = false) {
+ var _a;
+ return (((_a = dataHasDeviceFamily(objectGraph, data, deviceFamily, recurse)) !== null && _a !== void 0 ? _a : false) &&
+ deviceFamiliesFromData(objectGraph, data, recurse).length === 1);
+}
+export function dataHasAnyDeviceFamilies(objectGraph, data, deviceFamilies, recurse = false) {
+ const dataDeviceFamilies = new Set(deviceFamiliesFromData(objectGraph, data, recurse));
+ for (const deviceFamily of deviceFamilies) {
+ if (dataDeviceFamilies.has(deviceFamily)) {
+ return true;
+ }
+ }
+ return false;
+}
+export function dataOnlyHasDeviceFamilies(objectGraph, data, deviceFamilies, recurse = false) {
+ const dataDeviceFamilies = new Set(deviceFamiliesFromData(objectGraph, data, recurse));
+ // Ensure length matches
+ if (deviceFamilies.length !== dataDeviceFamilies.size) {
+ return false;
+ }
+ // Ensure content matches
+ return deviceFamilies.every((deviceFamily) => dataDeviceFamilies.has(deviceFamily));
+}
+export function deviceFamiliesFromData(objectGraph, data, recurse = false) {
+ const deviceFamilies = mediaAttributes.attributeAsArrayOrEmpty(data, "deviceFamilies");
+ // If there isn't a device families attribute...
+ if (deviceFamilies.length === 0 && recurse) {
+ // Use its related apps (if it has them)
+ const apps = mediaRelationship.relationshipCollection(data, "apps");
+ if (isSome(apps)) {
+ for (const app of apps) {
+ // Get the device families from the first app that has one (if any)
+ const appDeviceFamilies = deviceFamiliesFromData(objectGraph, app, recurse);
+ if (serverData.isDefinedNonNullNonEmpty(appDeviceFamilies)) {
+ return appDeviceFamilies;
+ }
+ }
+ }
+ }
+ return deviceFamilies;
+}
+/**
+ * Returns the DeviceFamily for a given device type and model
+ * @param {base.DeviceType} deviceType The device type to check
+ * @param {objectGraph.host.deviceModel} deviceModel The device model to check, currently used to identify iPods
+ * @returns {DeviceFamily} DeviceFamily associated with the input parameters
+ */
+export function deviceFamilyFromDeviceType(objectGraph, deviceType, deviceModel) {
+ let deviceFamily = null;
+ switch (deviceType) {
+ case "phone":
+ deviceFamily = "iphone";
+ break;
+ case "pad":
+ deviceFamily = "ipad";
+ break;
+ case "mac":
+ deviceFamily = "mac";
+ break;
+ case "tv":
+ deviceFamily = "tvos";
+ break;
+ case "watch":
+ deviceFamily = "watch";
+ break;
+ case "vision":
+ deviceFamily = "realityDevice";
+ break;
+ default:
+ break;
+ }
+ if (serverData.isDefinedNonNull(deviceModel) && deviceModel === "ipod") {
+ deviceFamily = "ipod";
+ }
+ return deviceFamily;
+}
+//# sourceMappingURL=device-family.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/content/flow-preview.js b/node_modules/@jet-app/app-store/tmp/src/common/content/flow-preview.js
new file mode 100644
index 0000000..1cc3e7d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/content/flow-preview.js
@@ -0,0 +1,485 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import * as base from "../../api/models/base";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as http from "../../foundation/network/http";
+import * as urls from "../../foundation/network/urls";
+import * as externalDeepLink from "../linking/external-deep-link";
+import * as lockups from "../lockups/lockups";
+import * as metricsUtil from "../metrics/helpers/util";
+import * as offers from "../offers/offers";
+import * as reviews from "../product-page/reviews";
+import * as sharing from "../sharing";
+import * as contentArtwork from "./artwork/artwork";
+import * as content from "./content";
+import * as contentDeviceFamily from "./device-family";
+import { isSome } from "@jet/environment";
+import { Request } from "../../foundation/media/data-fetching";
+import { buildURLFromRequest } from "../../foundation/media/url-builder";
+/**
+ * Create and return the flow preview actions configuration for a product.
+ * @param objectGraph
+ * @param data The response data to read from.
+ * @param includeOfferAction Whether to include an offer action, where possible
+ * @param clientIdentifierOverride The suggested client identifier for the given product
+ * @param clickAction The flow action for viewing the product
+ * @param metricsOptions The metrics options to use for reporting metrics actions.
+ * @param metricsClickOptions TThe metrics click options for the product
+ * @param externalDeepLinkUrl
+ * @returns A configuration object for flow preview actions that can be taken on the product
+ */
+export function flowPreviewActionsConfigurationForProductFromData(objectGraph, data, includeOfferAction, clientIdentifierOverride, clickAction, metricsOptions, metricsClickOptions, externalDeepLinkUrl, lockupSubtitle, lockupTitle) {
+ return validation.context("flowPreviewActionsConfigurationForProductFromData", () => {
+ // Flow preview is only supported on iOS
+ if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") {
+ return null;
+ }
+ const productData = productDataFromData(objectGraph, data);
+ if (!serverData.isDefinedNonNullNonEmpty(productData)) {
+ return null;
+ }
+ const actions = [];
+ // Offer
+ let offerActionIndex = null;
+ let offerDisplayProperties = null;
+ const isPreorder = mediaAttributes.attributeAsBooleanOrFalse(productData, "isPreorder");
+ if (includeOfferAction) {
+ const isArcade = content.isArcadeSupported(objectGraph, productData);
+ const offerType = lockups.offerTypeForMediaType(objectGraph, productData.type, isArcade);
+ const offerAction = offerActionFromData(objectGraph, productData, isPreorder, isArcade, offerType, clientIdentifierOverride, metricsClickOptions);
+ offerDisplayProperties = offers.displayPropertiesFromOfferAction(objectGraph, offerAction, offerType, productData, isPreorder, false, null, null, null, null, "flowPreview");
+ const wrappedOfferAction = wrappedOfferActionFromData(objectGraph, productData, offerAction, isPreorder, clientIdentifierOverride, metricsClickOptions, metricsOptions, externalDeepLinkUrl);
+ if (serverData.isDefinedNonNull(wrappedOfferAction) &&
+ serverData.isDefinedNonNull(offerDisplayProperties)) {
+ offerActionIndex = actions.length;
+ wrappedOfferAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://arrow.down.circle");
+ actions.push(wrappedOfferAction);
+ }
+ }
+ // Share
+ const shareAction = shareActionFromData(objectGraph, productData, metricsOptions);
+ if (serverData.isDefinedNonNull(shareAction)) {
+ actions.push(shareAction);
+ }
+ // Reviews
+ const shouldSuppressReviews = reviews.shouldSuppressReviews(objectGraph, productData);
+ const shouldShowRatingsAndReviews = !isPreorder && !shouldSuppressReviews;
+ if (shouldShowRatingsAndReviews) {
+ // See ratings & reviews
+ if (serverData.isDefinedNonNull(clickAction) &&
+ clickAction instanceof models.FlowAction &&
+ (clickAction.pageData instanceof models.ProductPage ||
+ clickAction.pageData instanceof models.ShelfBasedProductPage)) {
+ const seeRatingsAndReviewsAction = seeRatingsAndReviewsActionFromData(objectGraph, productData, clickAction);
+ if (serverData.isDefinedNonNull(seeRatingsAndReviewsAction)) {
+ actions.push(seeRatingsAndReviewsAction);
+ }
+ }
+ // Write a review
+ const isTvOnlyApp = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "tvos");
+ if (!isTvOnlyApp) {
+ const writeReviewAction = writeReviewActionFromData(objectGraph, productData, lockupSubtitle, lockupTitle);
+ if (serverData.isDefinedNonNull(writeReviewAction)) {
+ actions.push(writeReviewAction);
+ }
+ }
+ }
+ return new models.FlowPreviewActionsConfiguration(actions, offerDisplayProperties, offerActionIndex);
+ });
+}
+/**
+ * Create and return the flow preview configuration for a review object
+ * @param data The response data to read from.
+ * @param deviceId The UUID for the user's device.
+ * @param adamId The adam id of the product associated with the review
+ * @returns A configuration object for flow preview actions that can be taken on the review
+ */
+export function flowPreviewActionsConfigurationForReviewFromData(objectGraph, data, deviceId, adamId, reviewText) {
+ var _a;
+ if (serverData.isNullOrEmpty(data) ||
+ (objectGraph.client.deviceType !== "phone" &&
+ objectGraph.client.deviceType !== "pad" &&
+ objectGraph.client.deviceType !== "mac")) {
+ return null;
+ }
+ const actions = [
+ voteActionFromData(objectGraph, data, deviceId, true),
+ voteActionFromData(objectGraph, data, deviceId, false),
+ ];
+ // Workaround for missing report concern url in bag
+ if (((_a = objectGraph.bag.reportConcernUrl) === null || _a === void 0 ? void 0 : _a.length) > 0) {
+ actions.push(reportConcernActionFromData(objectGraph, data, deviceId));
+ }
+ if ((reviewText === null || reviewText === void 0 ? void 0 : reviewText.length) > 0) {
+ actions.push(copyReviewAction(objectGraph, reviewText));
+ }
+ return new models.FlowPreviewActionsConfiguration(actions);
+}
+/**
+ * Create and return the flow preview configuration for a review summary object
+ * @param data The response data to read from.
+ * @param adamId The adam ID of the app
+ * @param appName The name of the app
+ * @param reviewSummaryId The ID of the review summary
+ * @param reviewSummaryText The text of the review summary
+ * @param deviceId The UUID for the customer's device.
+ * @returns A configuration object for flow preview actions that can be taken on the review summary
+ */
+export function flowPreviewActionsConfigurationForReviewSummaryFromData(objectGraph, data, adamId, appName, reviewSummaryId, reviewSummaryText, deviceId) {
+ if (!objectGraph.client.isiOS) {
+ return null;
+ }
+ const actions = [];
+ const reportConcernAction = reviewSummaryReportConcernActionFromData(objectGraph, data, reviewSummaryId, deviceId);
+ if (isSome(reportConcernAction)) {
+ actions.push(reportConcernAction);
+ }
+ const learnMoreAction = reviews.reviewSummaryLearnMoreAction(objectGraph);
+ if (isSome(learnMoreAction)) {
+ actions.push(learnMoreAction);
+ }
+ const fileRadarAction = fileReviewSummaryRadarAction(objectGraph, adamId, appName, reviewSummaryId, reviewSummaryText);
+ if (isSome(fileRadarAction)) {
+ actions.push(fileRadarAction);
+ }
+ return new models.FlowPreviewActionsConfiguration(actions);
+}
+/**
+ * Creates a report a concern action for a given review
+ * @param data The response data to read from.
+ * @param deviceId The UUID for the user's device.
+ * @returns A report concern action
+ */
+export function reviewSummaryReportConcernActionFromData(objectGraph, data, reviewSummaryId, deviceId) {
+ return validation.context("reviewSummaryReportConcernActionFromApiRow", () => {
+ const isEnabled = serverData.asBoolean(data, "enabled");
+ if (!isEnabled) {
+ return null;
+ }
+ const sendAction = createReviewSummaryReportConcernHtmlTemplateAction(objectGraph, reviewSummaryId);
+ const concerns = serverData.asArrayOrEmpty(data, "concerns");
+ const reviewSummaryConcerns = concerns.map((concernData) => {
+ let name;
+ let uppercaseName;
+ const concernKind = serverData.asString(concernData, "kind");
+ switch (concernKind) {
+ case "OFFENSIVE":
+ case "OFFENSIVE_OR_HARMFUL":
+ name = objectGraph.loc.string("ProductPage.ReviewSummary.ReportAConcern.Offensive.Name");
+ uppercaseName = objectGraph.loc.string("ProductPage.ReviewSummary.ReportAConcern.Offensive.UppercaseName");
+ break;
+ case "MISREPRESENTING_THE_APP":
+ case "MISREPRESENT":
+ name = objectGraph.loc.string("ProductPage.ReviewSummary.ReportAConcern.Misrepresent.Name");
+ uppercaseName = objectGraph.loc.string("ProductPage.ReviewSummary.ReportAConcern.Misrepresent.UppercaseName");
+ break;
+ case "SOMETHING_ELSE":
+ name = objectGraph.loc.string("ProductPage.ReviewSummary.ReportAConcern.SomethingElse.Name");
+ uppercaseName = objectGraph.loc.string("ProductPage.ReviewSummary.ReportAConcern.SomethingElse.UppercaseName");
+ break;
+ default:
+ break;
+ }
+ return new base.ReportConcernReason(concernKind, name, uppercaseName);
+ });
+ if (reviewSummaryConcerns.length === 0) {
+ return null;
+ }
+ const title = objectGraph.loc.string("ACTION_REVIEW_REPORT");
+ const explanation = objectGraph.loc.string("ProductPage.ReviewSummary.ReportAConcern.Explanation");
+ const action = new models.ReviewSummaryReportConcernAction(reviewSummaryConcerns, title, explanation, sendAction);
+ action.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://exclamationmark.circle");
+ return action;
+ });
+}
+function createReviewSummaryReportConcernHtmlTemplateAction(objectGraph, reviewSummaryId) {
+ const request = new Request(objectGraph);
+ request.includeAppBinaryTraitsAttribute = false;
+ request.resourceType = "concerns";
+ const url = buildURLFromRequest(objectGraph, request);
+ const sendAction = new models.HttpTemplateAction(url.toString());
+ sendAction.method = "POST";
+ sendAction.disableCache = true;
+ sendAction.needsMediaToken = true;
+ sendAction.headers = { "Content-Type": "application/json" };
+ sendAction.bodyDictionary = {
+ report: { contentId: reviewSummaryId, contentKind: "review-summaries", concerns: null },
+ };
+ const successToast = new models.AlertAction("toast");
+ successToast.title = objectGraph.loc.string("TOAST_CONCERN_REPORTED_TITLE");
+ successToast.message = objectGraph.loc.string("TOAST_CONCERN_REPORTED_DESCRIPTION");
+ successToast.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://exclamationmark.circle");
+ sendAction.successAction = successToast;
+ const failureAlert = new models.AlertAction("default");
+ failureAlert.title = objectGraph.loc.string("Alert.GenericError.Title");
+ failureAlert.message = objectGraph.loc.string("Alert.GenericError.Message");
+ failureAlert.isCancelable = true;
+ sendAction.failureAction = failureAlert;
+ return sendAction;
+}
+/**
+ * Walks through the given data to extract the data blob for a product
+ * @param data The response data to read from.
+ * @returns Response data for a product
+ */
+function productDataFromData(objectGraph, data) {
+ return validation.context(`productDataFromData: ${data.type}`, () => {
+ switch (data.type) {
+ case "apps":
+ case "app-bundles": {
+ return data;
+ }
+ case "editorial-items": {
+ const cardContents = mediaRelationship.relationshipCollection(data, "card-contents");
+ if (serverData.isDefinedNonNullNonEmpty(cardContents) && cardContents.length === 1) {
+ const cardContentsData = cardContents[0];
+ return productDataFromData(objectGraph, cardContentsData);
+ }
+ break;
+ }
+ case "editorial-elements": {
+ const contents = mediaRelationship.relationshipCollection(data, "contents");
+ if (serverData.isDefinedNonNullNonEmpty(contents) && contents.length === 1) {
+ const contentData = contents[0];
+ return productDataFromData(objectGraph, contentData);
+ }
+ break;
+ }
+ default: {
+ return null;
+ }
+ }
+ return null;
+ });
+}
+/**
+ * Create an offer action for the provided product data
+ * @param data The response data to read from.
+ * @param isPreorder Whether the product is a pre-order
+ * @param isArcade Whether the product is an Arcade product
+ * @param offerType The type of offer for the product
+ * @param clientIdentifierOverride The suggested client identifier for the given product
+ * @param metricsClickOptions TThe metrics click options for the product
+ * @returns An offer action
+ */
+function offerActionFromData(objectGraph, data, isPreorder, isArcade, offerType, clientIdentifierOverride, metricsClickOptions) {
+ if (serverData.isNull(data) || data.type !== "apps") {
+ return null;
+ }
+ const offerData = offers.offerDataFromData(objectGraph, data);
+ const appIcon = content.iconFromData(objectGraph, data, null, clientIdentifierOverride);
+ const metricsPlatformDisplayStyle = metricsUtil.metricsPlatformDisplayStyleFromData(objectGraph, data, appIcon, clientIdentifierOverride);
+ const offerAction = offers.offerActionFromOfferData(objectGraph, offerData, data, isPreorder, false, metricsPlatformDisplayStyle, metricsClickOptions, "flowPreview");
+ return offerAction;
+}
+/**
+ * Wraps an offer action if required
+ * @param data The response data to read from.
+ * @param offerAction The provided offer action to wrap
+ * @param clientIdentifierOverride The suggested client identifier for the given product
+ * @param metricsClickOptions TThe metrics click options for the product
+ * @param metricsOptions The metrics options to use for reporting metrics actions.
+ * @param externalDeepLink The promotional deep link url to use on the product's offer.
+ * @returns A wrapped offer action
+ */
+function wrappedOfferActionFromData(objectGraph, data, offerAction, isPreorder, clientIdentifierOverride, metricsClickOptions, metricsOptions, externalDeepLinkUrl) {
+ if (serverData.isNull(offerAction)) {
+ return null;
+ }
+ let wrappedOfferAction = offers.wrapOfferActionIfNeeded(objectGraph, offerAction, data, isPreorder, metricsClickOptions, "flowPreview", clientIdentifierOverride);
+ if ((externalDeepLinkUrl === null || externalDeepLinkUrl === void 0 ? void 0 : externalDeepLinkUrl.length) > 0) {
+ // Configure cross link as well as deep link action.
+ wrappedOfferAction = externalDeepLink.deepLinkActionWrappingAction(objectGraph, wrappedOfferAction, offerAction.adamId, null, externalDeepLinkUrl, false, metricsClickOptions);
+ }
+ return wrappedOfferAction;
+}
+/**
+ * Creates and returns an action for sharing a product
+ * @param data The response data to read from.
+ * @param metricsOptions The metrics options to use for reporting metrics actions.
+ * @returns A share action or null
+ */
+function shareActionFromData(objectGraph, data, metricsOptions) {
+ const shareAction = sharing.shareProductActionFromData(objectGraph, data, metricsOptions.pageInformation, metricsOptions.locationTracker);
+ if (serverData.isDefinedNonNull(shareAction)) {
+ shareAction.title = objectGraph.loc.string("FLOW_PREVIEW_ACTION_SHARE");
+ shareAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://square.and.arrow.up");
+ return shareAction;
+ }
+ return shareAction;
+}
+/**
+ * Creates and returns an action for navigating to a product's ratings & reviews
+ * @param data The response data to read from.
+ * @param clickAction The flow action for viewing the product
+ * @returns An action or null
+ */
+function seeRatingsAndReviewsActionFromData(objectGraph, data, clickAction) {
+ const seeRatingsAndReviewsAction = reviews.seeRatingsAndReviewsActionFromClickAction(objectGraph, data.id, clickAction);
+ if (serverData.isDefinedNonNull(seeRatingsAndReviewsAction)) {
+ seeRatingsAndReviewsAction.title = objectGraph.loc.string("FLOW_PREVIEW_ACTION_SEE_RATINGS_AND_REVIEWS");
+ seeRatingsAndReviewsAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://star");
+ seeRatingsAndReviewsAction.animationBehavior = "never";
+ }
+ return seeRatingsAndReviewsAction;
+}
+/**
+ * Creates and returns an action for writing a product review
+ * @param data The response data to read from.
+ * @returns An action or null
+ */
+function writeReviewActionFromData(objectGraph, data, lockupSubtitle, lockupTitle) {
+ const writeReviewAction = reviews.writeReviewActionFromData(objectGraph, data, lockupSubtitle, lockupTitle);
+ if (serverData.isDefinedNonNull(writeReviewAction)) {
+ writeReviewAction.title = objectGraph.loc.string("FLOW_PREVIEW_ACTION_WRITE_REVIEW");
+ writeReviewAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://square.and.pencil");
+ }
+ return writeReviewAction;
+}
+/**
+ * Create and returns an action for voting whether a review was helpful or not
+ * @param data The response data to read from.
+ * @param deviceId The UUID for the user's device.
+ * @param helpful Whether or not the vote is indicating the review was helpful, or not helpful.
+ * @returns A new vote action.
+ */
+export function voteActionFromData(objectGraph, data, deviceId, helpful) {
+ const baseVoteUrl = objectGraph.bag.voteUrl;
+ const reviewId = serverData.asString(data, "id", "coercible");
+ const voteUrl = new urls.URL(baseVoteUrl).param("userReviewId", reviewId);
+ if (objectGraph.client.isVision) {
+ // We only attach this parameter for visionOS, as the other platforms have legacy handling in place
+ // that we do not want to interrupt.
+ voteUrl.param("version", "2");
+ }
+ const action = new models.HttpAction(voteUrl.build());
+ const successToast = new models.AlertAction("toast");
+ if (helpful) {
+ action.title = objectGraph.loc.string("ACTION_REVIEW_HELPFUL");
+ successToast.title = objectGraph.loc.string("TOAST_HELPFUL_TITLE");
+ successToast.message = objectGraph.loc.string("TOAST_HELPFUL_DESCRIPTION");
+ action.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://hand.thumbsup");
+ successToast.artwork = contentArtwork.createArtworkForResource(objectGraph, objectGraph.client.isVision ? "systemimage://hand.thumbsup.fill" : "systemimage://hand.thumbsup");
+ }
+ else {
+ action.title = objectGraph.loc.string("ACTION_REVIEW_NOT_HELPFUL");
+ successToast.title = objectGraph.loc.string("TOAST_NOT_HELPFUL_TITLE");
+ successToast.message = objectGraph.loc.string("TOAST_NOT_HELPFUL_DESCRIPTION");
+ action.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://hand.thumbsdown");
+ successToast.artwork = contentArtwork.createArtworkForResource(objectGraph, objectGraph.client.isVision ? "systemimage://hand.thumbsdown.fill" : "systemimage://hand.thumbsdown");
+ }
+ action.method = "POST";
+ action.isStoreRequest = true;
+ action.disableCache = true;
+ action.headers = { "Content-Type": http.FormBuilder.contentType };
+ action.body = new http.FormBuilder()
+ .param("vote", helpful ? "1" : "0")
+ .param("guid", deviceId)
+ .build();
+ action.successAction = successToast;
+ return action;
+}
+/**
+ * Creates a report a concern action for a given review
+ * @param data The response data to read from.
+ * @param deviceId The UUID for the user's device.
+ * @returns A report concern action
+ */
+export function reportConcernActionFromData(objectGraph, data, deviceId) {
+ return validation.context("reportConcernActionFromApiRow", () => {
+ const reviewId = serverData.asString(data, "id", "coercible");
+ const reportConcernUrl = objectGraph.bag.reportConcernUrl;
+ const sendAction = new models.HttpTemplateAction(reportConcernUrl);
+ sendAction.method = "POST";
+ sendAction.isStoreRequest = true;
+ sendAction.disableCache = true;
+ sendAction.needsAuthentication = true;
+ sendAction.headers = { "Content-Type": http.FormBuilder.contentType };
+ sendAction.body = new http.FormBuilder().param("userReviewId", reviewId).param("guid", deviceId).build();
+ const reasonParameter = new models.HttpTemplateParameter("selectedReason", "formBody", "decimalPad");
+ const explanationParameter = new models.HttpTemplateParameter("explanation", "formBody", "text");
+ sendAction.parameters = [reasonParameter, explanationParameter];
+ if (!objectGraph.client.isVision) {
+ const successToast = new models.AlertAction("toast");
+ successToast.title = objectGraph.loc.string("TOAST_CONCERN_REPORTED_TITLE");
+ successToast.message = objectGraph.loc.string("TOAST_CONCERN_REPORTED_DESCRIPTION");
+ successToast.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://exclamationmark.circle");
+ sendAction.successAction = successToast;
+ }
+ const failureAlert = new models.AlertAction("default");
+ failureAlert.title = objectGraph.loc.string("Alert.GenericError.Title");
+ failureAlert.message = objectGraph.loc.string("Alert.GenericError.Message");
+ failureAlert.isCancelable = true;
+ sendAction.failureAction = failureAlert;
+ let concernReasons = objectGraph.bag.reportConcernReasons;
+ if (serverData.isNullOrEmpty(concernReasons)) {
+ // Not available until 18G
+ concernReasons = [
+ {
+ reasonId: "1",
+ name: "It contains offensive material",
+ upperCaseName: "IT CONTAINS OFFENSIVE MATERIAL",
+ },
+ {
+ reasonId: "8",
+ name: "It's off-topic",
+ upperCaseName: "IT\u2019S OFF-TOPIC",
+ },
+ {
+ reasonId: "111003",
+ name: "It looks like spam",
+ upperCaseName: "IT LOOKS LIKE SPAM",
+ },
+ {
+ reasonId: "7",
+ name: "Something else",
+ upperCaseName: "SOMETHING ELSE",
+ },
+ ];
+ }
+ const reasons = concernReasons.map((reasonData) => {
+ return new base.ReportConcernReason(serverData.asString(reasonData, "reasonId"), serverData.asString(reasonData, "name"), serverData.asString(reasonData, "upperCaseName"));
+ });
+ const action = new models.ReportConcernAction(reasons);
+ action.title = objectGraph.loc.string("ACTION_REVIEW_REPORT");
+ action.explanation = objectGraph.bag.reportConcernExplanation;
+ if (serverData.isNullOrEmpty(action.explanation)) {
+ // Not available until 18G
+ action.explanation = "Tell us a little more (Optional)";
+ }
+ action.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://exclamationmark.circle");
+ action.sendAction = sendAction;
+ return action;
+ });
+}
+export function copyReviewAction(objectGraph, reviewText) {
+ const copyTextAction = new models.CopyTextAction(reviewText);
+ copyTextAction.title = objectGraph.loc.string("ACTION_REVIEW_COPY");
+ copyTextAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://doc.on.doc");
+ return copyTextAction;
+}
+/**
+ * Creates an action for filing a radar against a Review Summary
+ * @param objectGraph Current object graph
+ * @param adamId The ID of the app
+ * @param appName The name of the app
+ * @param reviewSummaryId The ID of the review summary
+ * @param reviewSummaryText The text of the review summary
+ * @returns An action that will deep link to Radar
+ */
+function fileReviewSummaryRadarAction(objectGraph, adamId, appName, reviewSummaryId, reviewSummaryText) {
+ if (!["debug", "internal"].includes(objectGraph.client.buildType)) {
+ return null;
+ }
+ const componentId = "999915"; // ASE App Store | Recommendations
+ const title = `Review Summary Feedback: ${appName}`;
+ const description = `App ID: ${adamId}\nApp name: ${appName}\nReview summary ID: ${reviewSummaryId}\nReview summary: ${reviewSummaryText}\n\nFeedback: `;
+ const url = `tap-to-radar://new/problem?componentid=${componentId}&title=${title}&description=${description}`;
+ const action = new models.ExternalUrlAction(url, true);
+ action.title = objectGraph.loc.string("Action.ProvideFeedback");
+ action.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://ant.circle");
+ return action;
+}
+//# sourceMappingURL=flow-preview.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/content/game-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/content/game-controller.js
new file mode 100644
index 0000000..7071edd
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/content/game-controller.js
@@ -0,0 +1,77 @@
+import { isFeatureEnabledForCurrentUser } from "../../common/util/lottery";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as platformAttributes from "../../foundation/media/platform-attributes";
+import * as contentAttributes from "./attributes";
+export function isGameControllerSupported(objectGraph, data) {
+ switch (controllerRequirementFromData(objectGraph, data)) {
+ case "CONTROLLER_OPTIONAL":
+ case "CONTROLLER_REQUIRED":
+ case "SIRI_REMOTE_REQUIRED":
+ case "SIRI_REMOTE_OR_CONTROLLER_REQUIRED":
+ case "CONTROLLER_RECOMMENDED":
+ return true;
+ default:
+ return false;
+ }
+}
+export function isGameControllerRecommended(objectGraph, data) {
+ return (objectGraph.client.isiOS &&
+ isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.gameControllerRecommendedRolloutRate) &&
+ controllerRequirementFromData(objectGraph, data) === "CONTROLLER_RECOMMENDED");
+}
+export function isGameControllerRequired(objectGraph, data) {
+ switch (controllerRequirementFromData(objectGraph, data)) {
+ case "CONTROLLER_REQUIRED":
+ case "SIRI_REMOTE_REQUIRED":
+ case "SIRI_REMOTE_OR_CONTROLLER_REQUIRED":
+ return true;
+ default:
+ return false;
+ }
+}
+export function isSpatialControllerRequired(objectGraph, data) {
+ return spatialControllerRequirementFromData(objectGraph, data) === "REQUIRED";
+}
+export function isSpatialControllerSupported(objectGraph, data) {
+ return (spatialControllerRequirementFromData(objectGraph, data) === "SUPPORTED" ||
+ spatialControllerRequirementFromData(objectGraph, data) === "REQUIRED");
+}
+export function controllerRequirementFromData(objectGraph, data) {
+ // If the data does not contain the current device's OS, we need to use the best alternative that it is compatible with.
+ const platformAttributeForClient = contentAttributes.defaultAttributePlatform(objectGraph);
+ if (platformAttributeForClient === null) {
+ return "NO_BADGE";
+ }
+ const requiresBinaryCompatibilityMode = !platformAttributes.hasPlatformAttribute(data, platformAttributeForClient);
+ if (requiresBinaryCompatibilityMode) {
+ const compatibilityControllerRequirementData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "compatibilityControllerRequirement");
+ if (compatibilityControllerRequirementData === null ||
+ serverData.isNullOrEmpty(compatibilityControllerRequirementData)) {
+ return "NO_BADGE";
+ }
+ const compatibilityControllerRequirement = compatibilityControllerRequirementData[platformAttributeForClient];
+ if (serverData.isNullOrEmpty(compatibilityControllerRequirement)) {
+ return "NO_BADGE";
+ }
+ return compatibilityControllerRequirement;
+ }
+ const remoteControllerRequirement = contentAttributes.contentAttributeAsString(objectGraph, data, "remoteControllerRequirement");
+ if (serverData.isDefinedNonNullNonEmpty(remoteControllerRequirement)) {
+ return remoteControllerRequirement;
+ }
+ return "NO_BADGE";
+}
+export function spatialControllerRequirementFromData(objectGraph, data) {
+ const platformAttributeForClient = contentAttributes.defaultAttributePlatform(objectGraph);
+ if (!objectGraph.client.isVision ||
+ platformAttributeForClient === null ||
+ !objectGraph.featureFlags.isEnabled("spatial_controllers_2025A")) {
+ return "NOT_SUPPORTED";
+ }
+ const spatialRequirement = platformAttributes.platformAttributeAsString(data, platformAttributeForClient, "spatialControllerRequirement");
+ if (serverData.isDefinedNonNullNonEmpty(spatialRequirement)) {
+ return spatialRequirement;
+ }
+ return "NOT_SUPPORTED";
+}
+//# sourceMappingURL=game-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/content/product-capabilities.js b/node_modules/@jet-app/app-store/tmp/src/common/content/product-capabilities.js
new file mode 100644
index 0000000..7c449c8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/content/product-capabilities.js
@@ -0,0 +1,322 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import { isFeatureEnabledForCurrentUser } from "../../common/util/lottery";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as color from "../../foundation/util/color-util";
+import { createArtworkForResource } from "./artwork/artwork";
+import * as contentAttributes from "./attributes";
+import * as gameController from "./game-controller";
+import { isSome } from "@jet/environment";
+import { supportedGameCenterFeaturesFromData } from "./content";
+import { makeRoutableArticlePageIntent } from "../../api/intents/routable-article-page-intent";
+import { getLocale } from "../locale";
+import { makeRoutableArticlePageCanonicalUrl } from "../today/routable-article-page-url-utils";
+import { openGamesUIAction } from "../arcade/arcade-common";
+import { getPlatform } from "../preview-platform";
+/**
+ * Creates a list of product capabilities for a given product.
+ *
+ * @param objectGraph The object graph
+ * @param productData The data for the product
+ * @param isFreeProduct Whether the offer is for a free product
+ * @returns An array of product capabilities
+ */
+export function productCapabilitiesFromData(objectGraph, productData, isFreeProduct) {
+ return validation.context("capabilitiesFromData", () => {
+ return [
+ gameCenterCapabilityFromData(objectGraph, productData),
+ siriCapabilityFromData(objectGraph, productData),
+ sharePlayCapabilityFromData(objectGraph, productData),
+ walletCapabilityFromData(objectGraph, productData),
+ controllersCapabilityFromData(objectGraph, productData),
+ familySharingCapabilityFromData(objectGraph, productData, isFreeProduct),
+ spatialControlsCapabilityFromData(objectGraph, productData),
+ safariExtensionCapabilityFromData(objectGraph, productData),
+ ].filter((capability) => capability);
+ });
+}
+/**
+ * Creates the Game Center capability.
+ * @param objectGraph The object graph
+ * @param productData The data for the product
+ * @param gameInfoSummary The game info summary for the product
+ * @returns A product capability, or null
+ */
+export function gameCenterCapabilityFromData(objectGraph, productData) {
+ const isGameCenterEnabled = isSome(productData) &&
+ contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "isGameCenterEnabled");
+ if (!isGameCenterEnabled) {
+ return null;
+ }
+ if (objectGraph.bag.gameCenterExtendSupportedFeatures) {
+ const supportedGameCenterFeatures = supportedGameCenterFeaturesFromData(productData);
+ const supportsLeaderboards = supportedGameCenterFeatures === null || supportedGameCenterFeatures === void 0 ? void 0 : supportedGameCenterFeatures.includes("leaderboards");
+ const supportsAchievements = supportedGameCenterFeatures === null || supportedGameCenterFeatures === void 0 ? void 0 : supportedGameCenterFeatures.includes("achievements");
+ let captionText;
+ if (supportsLeaderboards && supportsAchievements) {
+ captionText = objectGraph.loc.string("CAPABILITY_GAME_CENTER_EXPLANATION_ALL_FEATURES");
+ }
+ else if (supportsLeaderboards) {
+ captionText = objectGraph.loc.string("CAPABILITY_GAME_CENTER_EXPLANATION_ONLY_LEADERBOARDS");
+ }
+ else if (supportsAchievements) {
+ captionText = objectGraph.loc.string("CAPABILITY_GAME_CENTER_EXPLANATION_ONLY_ACHIEVEMENTS");
+ }
+ else {
+ captionText = objectGraph.loc.string("CAPABILITY_GAME_CENTER_EXPLANATION_NO_FEATURES");
+ }
+ const title = objectGraph.loc.string("CAPABILITY_GAME_CENTER_TITLE");
+ const artwork = createArtworkForResource(objectGraph, "resource://ProductCapabilityGameCenter", 46, 45);
+ if (objectGraph.featureFlags.isGSEUIEnabled("de7bbd8e")) {
+ let linkAction;
+ if (isSome(productData) && objectGraph.props.enabled("gameProductIdOpenGamesUIAction")) {
+ linkAction = openGamesUIAction(objectGraph, {
+ gamePage: { productID: productData.id },
+ });
+ }
+ else {
+ linkAction = openGamesUIAction(objectGraph);
+ }
+ const linkActionTitle = objectGraph.loc.string("ProductPage.Capability.GameCenter.ActionTitle");
+ captionText = `${captionText}\n${linkActionTitle}`;
+ const styledText = new models.StyledText(captionText);
+ const linkedSubstrings = {};
+ linkedSubstrings[linkActionTitle] = linkAction;
+ const linkableCaption = new models.LinkableText(styledText, linkedSubstrings);
+ const captionTrailingArtwork = createArtworkForResource(objectGraph, "systemimage://arrow.up.forward.square.fill", 16, 16);
+ return new models.ProductCapability("gameCenter", title, linkableCaption, captionTrailingArtwork, undefined, artwork);
+ }
+ else {
+ const styledText = new models.StyledText(captionText);
+ const linkableCaption = new models.LinkableText(styledText);
+ return new models.ProductCapability("gameCenter", title, linkableCaption, null, null, artwork);
+ }
+ }
+ else {
+ const title = objectGraph.loc.string("CAPABILITY_GAME_CENTER_TITLE");
+ const styledText = new models.StyledText(objectGraph.loc.string("CAPABILITY_GAME_CENTER_EXPLANATION"));
+ const caption = new models.LinkableText(styledText);
+ const artwork = createArtworkForResource(objectGraph, "resource://ProductCapabilityGameCenter", 46, 45);
+ return new models.ProductCapability("gameCenter", title, caption, undefined, null, artwork);
+ }
+}
+/**
+ * Creates the Siri capability.
+ * @param objectGraph The object graph
+ * @param productData The data for the product
+ * @returns A product capability, or null
+ */
+function siriCapabilityFromData(objectGraph, productData) {
+ if (!contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "isSiriSupported")) {
+ return null;
+ }
+ const title = objectGraph.loc.string("CAPABILITY_SIRI_TITLE");
+ const styledText = new models.StyledText(objectGraph.loc.string("CAPABILITY_SIRI_EXPLANATION"));
+ const caption = new models.LinkableText(styledText);
+ const artwork = createArtworkForResource(objectGraph, "resource://ProductCapabilitySiri", 46, 45);
+ return new models.ProductCapability("siri", title, caption, undefined, null, artwork);
+}
+/**
+ * Creates the Wallet capability.
+ * @param objectGraph The object graph
+ * @param productData The data for the product
+ * @returns A product capability, or null
+ */
+function walletCapabilityFromData(objectGraph, productData) {
+ if (!contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "supportsPassbook")) {
+ return null;
+ }
+ const title = objectGraph.loc.string("CAPABILITY_WALLET_TITLE");
+ const styledText = new models.StyledText(objectGraph.loc.string("CAPABILITY_WALLET_EXPLANATION"));
+ const caption = new models.LinkableText(styledText);
+ const artwork = createArtworkForResource(objectGraph, "resource://ProductCapabilityWallet", 46, 45);
+ return new models.ProductCapability("wallet", title, caption, undefined, null, artwork);
+}
+/**
+ * Creates the Controllers capability.
+ * @param objectGraph The object graph
+ * @param productData The data for the product
+ * @returns A product capability, or null
+ */
+function controllersCapabilityFromData(objectGraph, productData) {
+ if (!gameController.isGameControllerSupported(objectGraph, productData)) {
+ return null;
+ }
+ const title = objectGraph.loc.string("CAPABILITY_MFI_CONTROLLERS_TITLE"); // Game Controllers
+ // Create the Learn More link
+ let linkAction = null;
+ const storyId = objectGraph.bag.gameControllerLearnMoreEditorialItemId;
+ if (isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.gameControllerRecommendedRolloutRate) &&
+ (objectGraph.client.isiOS || objectGraph.client.isVision || objectGraph.client.isWeb) &&
+ isSome(storyId) &&
+ (storyId === null || storyId === void 0 ? void 0 : storyId.length) > 0) {
+ const routableArticlePageIntent = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: storyId,
+ });
+ linkAction = new models.FlowAction("article");
+ linkAction.title = objectGraph.loc.string("ProductPage.Capability.GameController.ActionTitle"); // Learn More
+ linkAction.pageUrl = makeRoutableArticlePageCanonicalUrl(objectGraph, routableArticlePageIntent);
+ if (objectGraph.client.isWeb) {
+ linkAction.destination = routableArticlePageIntent;
+ }
+ }
+ let captionText = linkAction
+ ? objectGraph.loc.string("ProductPage.Capability.GameController.Explanation.v2.WithNewlineActionTemplate")
+ : objectGraph.loc.string("ProductPage.Capability.GameController.Explanation.v2");
+ const linkedSubstrings = {};
+ if (linkAction === null || linkAction === void 0 ? void 0 : linkAction.title) {
+ captionText = captionText.replace("{learnMoreLink}", linkAction.title); // Learn More
+ linkedSubstrings[linkAction.title] = linkAction;
+ }
+ const styledText = new models.StyledText(captionText);
+ const linkableCaption = new models.LinkableText(styledText, linkedSubstrings);
+ const artwork = createArtworkForResource(objectGraph, "resource://ProductCapabilityController", 46, 45);
+ return new models.ProductCapability("controllers", title, linkableCaption, undefined, null, artwork);
+}
+/**
+ * Creates the Spatial Controls capability.
+ * @param objectGraph The object graph
+ * @param productData The data for the product
+ * @returns A product capability, or null
+ */
+function spatialControlsCapabilityFromData(objectGraph, productData) {
+ if (!objectGraph.client.isVision || !gameController.isSpatialControllerSupported(objectGraph, productData)) {
+ return null;
+ }
+ const title = objectGraph.loc.string("ProductPage.Badge.SpatialController.Heading");
+ // Create the Learn More link
+ let linkAction = null;
+ const storyId = objectGraph.bag.spatialControlsLearnMoreEditorialItemId;
+ if (isSome(storyId) && (storyId === null || storyId === void 0 ? void 0 : storyId.length) > 0) {
+ const routableArticlePageIntent = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: storyId,
+ });
+ linkAction = new models.FlowAction("article");
+ linkAction.title = objectGraph.loc.string("Action.LearnMore");
+ linkAction.pageUrl = makeRoutableArticlePageCanonicalUrl(objectGraph, routableArticlePageIntent);
+ if (objectGraph.client.isWeb) {
+ linkAction.destination = routableArticlePageIntent;
+ }
+ }
+ let captionText = linkAction !== null
+ ? objectGraph.loc.string("ProductPage.Capability.SpatialController.Explanation.WithActionTemplate")
+ : objectGraph.loc.string("ProductPage.Capability.SpatialController.Explanation");
+ const linkedSubstrings = {};
+ if (isSome(linkAction === null || linkAction === void 0 ? void 0 : linkAction.title)) {
+ captionText = captionText.replace("{learnMoreLink}", linkAction.title);
+ linkedSubstrings[linkAction.title] = linkAction;
+ }
+ const styledText = new models.StyledText(captionText);
+ const linkableCaption = new models.LinkableText(styledText, linkedSubstrings);
+ const artwork = createArtworkForResource(objectGraph, "resource://ProductCapabilitySpatialControllers");
+ return new models.ProductCapability("spatialControllers", title, linkableCaption, undefined, null, artwork);
+}
+/**
+ * Creates a family sharing capability for the given product, if any. This is driven off whether family sharing
+ * is enabled, whether there are any IAPs, and whther any of those IAPs are shareable.
+ *
+ * @param objectGraph The object graph
+ * @param productData The data for the product
+ * @param isFreeProduct Whether the offer is for a free product
+ * @returns A product capability, or null
+ */
+function familySharingCapabilityFromData(objectGraph, productData, isFreeProduct) {
+ // Check if family sharing is enabled, or if this is a SAD app
+ const familyShareEnabledDateString = mediaAttributes.attributeAsString(productData, "familyShareEnabledDate");
+ if (!familyShareEnabledDateString ||
+ mediaAttributes.attributeAsBooleanOrFalse(productData, "isFirstPartyHideableApp")) {
+ return null;
+ }
+ // Check family sharing was enabled in the past
+ const familyShareEnabledDate = new Date(familyShareEnabledDateString);
+ const now = new Date();
+ if (!familyShareEnabledDate || familyShareEnabledDate > now) {
+ return null;
+ }
+ const hasInAppPurchases = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "hasInAppPurchases");
+ const hasFamilyShareableInAppPurchases = hasInAppPurchases &&
+ contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "hasFamilyShareableInAppPurchases");
+ // Create the Learn More link
+ let linkAction = null;
+ const storyId = objectGraph.bag.familySubscriptionsLearnMoreEditorialItemId;
+ const platformSupportsLink = objectGraph.client.isiOS || objectGraph.client.isMac || objectGraph.client.isVision || objectGraph.client.isWeb;
+ if (isSome(storyId) && (storyId === null || storyId === void 0 ? void 0 : storyId.length) > 0 && platformSupportsLink && hasFamilyShareableInAppPurchases) {
+ const routableArticlePageIntent = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: storyId,
+ });
+ linkAction = new models.FlowAction("article");
+ linkAction.title = objectGraph.loc.string("CAPABILITY_FAMILY_SHARING_ACTION_TITLE");
+ linkAction.pageUrl = makeRoutableArticlePageCanonicalUrl(objectGraph, routableArticlePageIntent);
+ if (objectGraph.client.isWeb) {
+ linkAction.destination = routableArticlePageIntent;
+ }
+ }
+ // Generate the caption based on whether the product has IAPs, whether it has shareable IAPs, and whether it is free
+ const title = objectGraph.loc.string("CAPABILITY_FAMILY_SHARING_TITLE");
+ let captionKey;
+ if (hasFamilyShareableInAppPurchases) {
+ captionKey = linkAction
+ ? "CAPABILITY_FAMILY_SHARING_SOME_SHAREABLE_IAPS_EXPLANATION_WITH_ACTION_TEMPLATE"
+ : "CAPABILITY_FAMILY_SHARING_SOME_SHAREABLE_IAPS_EXPLANATION";
+ }
+ else if (!hasInAppPurchases && !isFreeProduct) {
+ captionKey = linkAction
+ ? "CAPABILITY_FAMILY_SHARING_PAID_APP_NO_IAPS_EXPLANATION_WITH_ACTION_TEMPLATE"
+ : "CAPABILITY_FAMILY_SHARING_PAID_APP_NO_IAPS_EXPLANATION";
+ }
+ if (!captionKey) {
+ return null;
+ }
+ let captionText = objectGraph.loc.string(captionKey);
+ const linkedSubstrings = {};
+ if (linkAction === null || linkAction === void 0 ? void 0 : linkAction.title) {
+ captionText = captionText.replace("{learnMoreLink}", linkAction.title);
+ linkedSubstrings[linkAction.title] = linkAction;
+ }
+ const styledText = new models.StyledText(captionText);
+ const linkableCaption = new models.LinkableText(styledText, linkedSubstrings);
+ const artwork = createArtworkForResource(objectGraph, "resource://ProductCapabilityFamilySharing", 46, 45);
+ return new models.ProductCapability("familySharing", title, linkableCaption, undefined, linkAction, artwork);
+}
+/**
+ * Creates the Safari Extension capability.
+ * @param objectGraph The object graph
+ * @param productData The data for the product
+ * @returns A product capability, or null
+ */
+function safariExtensionCapabilityFromData(objectGraph, productData) {
+ const productHasExtension = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "hasSafariExtension");
+ const platformSupports = objectGraph.client.isMac || objectGraph.client.isiOS;
+ if (!productHasExtension || !platformSupports) {
+ return null;
+ }
+ const title = objectGraph.loc.string("CAPABILITY_SAFARI_EXTENSION_TITLE");
+ const styledText = new models.StyledText(objectGraph.loc.string("CAPABILITY_SAFARI_EXTENSION_EXPLANATION"));
+ const caption = new models.LinkableText(styledText);
+ const artwork = createArtworkForResource(objectGraph, "resource://ProductCapabilitySafariExtension", 129, 129);
+ return new models.ProductCapability("safariExtensions", title, caption, undefined, null, artwork);
+}
+/**
+ * Creates the SharePlay capability
+ * @param objectGraph The object graph
+ * @param productData The data for the product
+ * @returns A product capability, or null
+ */
+function sharePlayCapabilityFromData(objectGraph, productData) {
+ if (!contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "supportsSharePlay")) {
+ return null;
+ }
+ const title = objectGraph.loc.string("CAPABILITY_SHAREPLAY_TITLE");
+ const styledText = new models.StyledText(objectGraph.loc.string("CAPABILITY_SHAREPLAY_EXPLANATION"));
+ const caption = new models.LinkableText(styledText);
+ const artwork = createArtworkForResource(objectGraph, "systemimage://shareplay");
+ const artworkTintColor = color.named("systemGreen");
+ return new models.ProductCapability("sharePlay", title, caption, undefined, null, artwork, artworkTintColor);
+}
+//# sourceMappingURL=product-capabilities.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/content/sad.js b/node_modules/@jet-app/app-store/tmp/src/common/content/sad.js
new file mode 100644
index 0000000..20fde05
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/content/sad.js
@@ -0,0 +1,93 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as contentAttributes from "./attributes";
+let implementation = null;
+/**
+ * Creates an implementation of the `SystemApps` interface
+ * which is appropriate for the current platform.
+ */
+export function systemApps(objectGraph) {
+ if (implementation !== null) {
+ return implementation;
+ }
+ // Because watchOS is not a proper platform from the back end's perspective,
+ // we must map adam IDs to bundle IDs using a bag property.
+ if (objectGraph.client.isWatch) {
+ const systemAppsMap = new Map();
+ const nonDeletableSystemAppsMap = new Map();
+ for (const systemApp of objectGraph.bag.systemApps) {
+ const bundleId = serverData.asString(systemApp, "bundle-id", "coercible");
+ const adamId = serverData.asString(systemApp, "id", "coercible");
+ if (isSome(bundleId) && isSome(adamId)) {
+ systemAppsMap.set(adamId, bundleId);
+ }
+ }
+ for (const nonDeletableSystemApp of objectGraph.bag.nonDeletableSystemApps) {
+ const bundleId = serverData.asString(nonDeletableSystemApp, "bundle-id", "coercible");
+ const adamId = serverData.asString(nonDeletableSystemApp, "id", "coercible");
+ if (isSome(bundleId) && isSome(adamId)) {
+ nonDeletableSystemAppsMap.set(adamId, bundleId);
+ }
+ }
+ const unsupportedSystemApps = new Map();
+ implementation = {
+ bundleIdFromData(data) {
+ const mappedBundleId = systemAppsMap.get(data.id);
+ if (!serverData.isNull(mappedBundleId)) {
+ return mappedBundleId;
+ }
+ else {
+ return contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId");
+ }
+ },
+ isSystemAppFromData(data) {
+ return systemAppsMap.has(data.id);
+ },
+ isUnsupportedDeletableSystemAppFromData(data) {
+ if (systemAppsMap.has(data.id) && !nonDeletableSystemAppsMap.has(data.id)) {
+ // We must use the bundleID from the bag here, rather than a bundleID from `data`,
+ // as AppConduit is expecting the bundleIDs which are provided in the bag
+ const bundleId = systemAppsMap.get(data.id);
+ if (serverData.isDefinedNonNullNonEmpty(bundleId)) {
+ const valueFromMap = unsupportedSystemApps.get(bundleId);
+ if (isSome(valueFromMap)) {
+ return valueFromMap;
+ }
+ const isUnsupported = !objectGraph.client.deletableSystemAppCanBeInstalledOnWatchWithBundleID(bundleId);
+ unsupportedSystemApps.set(bundleId, isUnsupported);
+ return isUnsupported;
+ }
+ }
+ return false;
+ },
+ adamIdFromSystemBundleId(bundleId) {
+ for (const [key, value] of systemAppsMap) {
+ if (value === bundleId) {
+ return key;
+ }
+ }
+ return undefined;
+ },
+ };
+ }
+ else {
+ implementation = {
+ bundleIdFromData(data) {
+ return contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId");
+ },
+ isSystemAppFromData(data) {
+ return mediaAttributes.attributeAsBooleanOrFalse(data, "isFirstPartyHideableApp");
+ },
+ isUnsupportedDeletableSystemAppFromData(data) {
+ return false;
+ },
+ adamIdFromSystemBundleId(bundleId) {
+ // This path shouldn't run on any other platforms.
+ return undefined;
+ },
+ };
+ }
+ return implementation;
+}
+//# sourceMappingURL=sad.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/developer/developer-common.js b/node_modules/@jet-app/app-store/tmp/src/common/developer/developer-common.js
new file mode 100644
index 0000000..881c01d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/developer/developer-common.js
@@ -0,0 +1,611 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import { Path, Protocol } from "../../foundation/network/url-constants";
+import * as client from "../../foundation/wrappers/client";
+import { watchosDeveloperRelationshipKey } from "./developer-request";
+import * as content from "../content/content";
+import * as lockups from "../lockups/lockups";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import * as room from "../room/room-common";
+export class DeveloperRoomToken extends room.RoomPageToken {
+}
+/// Ordering of shelves for macOS developer page per Relationship key.
+/// @seealso mediaUrlMapping.macOSDeveloperRelationshipKeys
+const macosDeveloperRelationshipOrder = [
+ "latest-release-app",
+ "arcade-apps",
+ "app-bundles",
+ "mac-apps",
+ "mac-os-compatible-ios-apps",
+];
+/// Ordering of shelves for iOS developer page per Relationship key.
+/// @seealso mediaUrlMapping.iosDeveloperRelationshipKeys
+const iosDeveloperRelationshipOrder = [
+ "latest-release-app",
+ "arcade-apps",
+ "system-apps",
+ "app-bundles",
+ "ios-apps",
+ "imessage-apps",
+ "watch-apps",
+ "atv-apps",
+];
+/// The threshold for when to show the see all button.
+const seeAllThreshold = 8;
+/// Ordering of shelves for macOS developer page per Relationship key.
+/// @seealso mediaUrlMapping.macOSDeveloperRelationshipKeys
+const visionOSDeveloperRelationshipOrder = [
+ "latest-release-app",
+ "xros-apps",
+ "arcade-apps",
+ "ios-apps",
+];
+/// Ordering of shelves for the web developer page per Relationship key.
+/// @seealso mediaUrlMapping.macOSDeveloperRelationshipKeys
+const webDeveloperRelationshipOrder = [
+ "latest-release-app",
+ "system-apps",
+ "app-bundles",
+ "ios-apps",
+ "mac-apps",
+ "arcade-apps",
+ "xros-apps",
+ "atv-apps",
+ "watch-apps",
+ "imessage-apps",
+];
+export class DeveloperPageShelfToken {
+}
+export function developerPageFromResponse(objectGraph, response) {
+ return validation.context("developerPageFromResponse", () => {
+ const developerData = response.data.length ? response.data[0] : null;
+ if (!developerData) {
+ return null;
+ }
+ const metricsPageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, "Artist", developerData.id, response);
+ const locationTracker = metricsHelpersLocation.newLocationTracker();
+ const shelves = shelvesForDeveloperData(objectGraph, developerData, metricsPageInformation, locationTracker);
+ // A single shelf should be vertical
+ if (shelves.length === 1) {
+ shelves[0].isHorizontal = false;
+ }
+ // Add developer description
+ const itunesNotes = content.notesFromData(objectGraph, developerData, "standard");
+ if (itunesNotes) {
+ const paragraph = new models.Paragraph(itunesNotes, "text/x-apple-as3-nqml");
+ const shelf = new models.Shelf("paragraph");
+ shelf.items = [paragraph];
+ shelves.unshift(shelf);
+ }
+ // Create the page
+ const page = new models.GenericPage(shelves);
+ page.title = mediaAttributes.attributeAsString(developerData, "name");
+ if (objectGraph.client.deviceType !== "watch") {
+ page.presentationOptions = ["prefersLargeTitle"];
+ }
+ page.canonicalURL = mediaAttributes.attributeAsString(developerData, "url");
+ // Setup metrics
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, metricsPageInformation);
+ // Add the uber
+ const uber = mediaAttributes.attributeAsDictionary(developerData, "editorialArtwork.bannerUber");
+ if (uber && !objectGraph.client.isVision) {
+ const uberArtwork = content.artworkFromApiArtwork(objectGraph, uber, {
+ cropCode: "sr",
+ useCase: 21 /* content.ArtworkUseCase.Uber */,
+ });
+ page.uber = uberArtwork;
+ if (objectGraph.client.isiOS || objectGraph.client.isWeb) {
+ const uberShelf = new models.Shelf("uber");
+ const uberModel = new models.Uber("above");
+ uberModel.artwork = uberArtwork;
+ uberShelf.items = [uberModel];
+ uberModel.title = page.title;
+ shelves.unshift(uberShelf);
+ page.presentationOptions.push("prefersNonStandardBackButton");
+ if (!objectGraph.client.isWeb) {
+ page.presentationOptions.push("prefersOverlayedPageHeader");
+ }
+ }
+ }
+ return page;
+ });
+}
+function shelvesForDeveloperData(objectGraph, data, metricsPageInformation, locationTracker) {
+ switch (objectGraph.client.deviceType) {
+ case "mac":
+ return macosShelvesForDeveloperData(objectGraph, data, metricsPageInformation, locationTracker);
+ case "watch":
+ return flatShelvesForDeveloperData(objectGraph, "smallLockup", data, watchosDeveloperRelationshipKey, objectGraph.loc.string("DEVELOPER_WATCH"), metricsPageInformation, locationTracker);
+ case "vision":
+ return orderedShelvesForDeveloperData(objectGraph, data, visionOSDeveloperRelationshipOrder, metricsPageInformation, locationTracker);
+ case "web":
+ return orderedShelvesForDeveloperData(objectGraph, data, webDeveloperRelationshipOrder, metricsPageInformation, locationTracker);
+ default:
+ return orderedShelvesForDeveloperData(objectGraph, data, iosDeveloperRelationshipOrder, metricsPageInformation, locationTracker);
+ }
+}
+/**
+ * Returns the iOS shelf title for a given relationship.
+ * @param relationshipKey The relationship key.
+ * @param developerData Media API data for the developer page.
+ * @returns The localized shelf title.
+ */
+function iosShelfTitle(objectGraph, relationshipKey, developerData) {
+ switch (relationshipKey) {
+ case "latest-release-app":
+ return objectGraph.loc.string("DEVELOPER_LATEST_RELEASE");
+ case "system-apps":
+ return objectGraph.loc.string("DEVELOPER_SYSTEM_APPS");
+ case "imessage-apps":
+ return objectGraph.loc.string("DEVELOPER_IMESSAGE");
+ case "watch-apps":
+ return objectGraph.loc.string("DEVELOPER_WATCH");
+ case "atv-apps":
+ return objectGraph.loc.string("DEVELOPER_TV");
+ case "app-bundles":
+ return objectGraph.loc.string("DEVELOPER_BUNDLES");
+ case "xros-apps":
+ return objectGraph.loc.string("DEVELOPER_VISION");
+ case "ios-apps":
+ const hasApps = mediaAttributes.attributeAsBooleanOrFalse(developerData, "hasApps");
+ const hasGames = mediaAttributes.attributeAsBooleanOrFalse(developerData, "hasGames");
+ if (hasApps && hasGames) {
+ return objectGraph.loc.string("DEVELOPER_APPS_AND_GAMES");
+ }
+ else if (hasGames) {
+ return objectGraph.loc.string("DEVELOPER_GAMES");
+ }
+ else {
+ return objectGraph.loc.string("DEVELOPER_APPS");
+ }
+ case "arcade-apps":
+ return objectGraph.loc.string("DEVELOPER_APPLE_ARCADE");
+ default:
+ return null;
+ }
+}
+/**
+ * Returns the web shelf title for a given relationship.
+ * @param relationshipKey The relationship key.
+ * @param developerData Media API data for the developer page.
+ * @returns The localized shelf title.
+ */
+function webShelfTitle(objectGraph, relationshipKey, developerData) {
+ switch (relationshipKey) {
+ case "latest-release-app":
+ return objectGraph.loc.string("DEVELOPER_LATEST_RELEASE");
+ case "system-apps":
+ return objectGraph.loc.string("DEVELOPER_SYSTEM_APPS");
+ case "imessage-apps":
+ return objectGraph.loc.string("DEVELOPER_IMESSAGE");
+ case "watch-apps":
+ return objectGraph.loc.string("DEVELOPER_WATCH");
+ case "atv-apps":
+ return objectGraph.loc.string("DEVELOPER_TV");
+ case "app-bundles":
+ return objectGraph.loc.string("DEVELOPER_BUNDLES");
+ case "xros-apps":
+ return objectGraph.loc.string("DEVELOPER_VISION");
+ case "ios-apps":
+ return objectGraph.loc.string("DEVELOPER_PHONE_PAD_APPS");
+ case "arcade-apps":
+ return objectGraph.loc.string("DEVELOPER_APPLE_ARCADE");
+ case "mac-apps":
+ return objectGraph.loc.string("DEVELOPER_MAC_APPS");
+ case "mac-os-compatible-ios-apps":
+ return objectGraph.loc.string("DEVELOPER_PHONE_PAD_APPS");
+ default:
+ return null;
+ }
+}
+function orderedShelvesForDeveloperData(objectGraph, developerData, developerRelationshipOrdering, metricsPageInformation, locationTracker) {
+ var _a, _b;
+ if (objectGraph.host.isiOS) {
+ // Filter duplicate Arcade apps on iOS
+ filterDuplicateApps(developerData, "arcade-apps", ["ios-apps", "atv-apps"]);
+ }
+ let shelfContentType;
+ let artworkUseCase;
+ switch (objectGraph.client.deviceType) {
+ case "tv":
+ shelfContentType = "mediumLockup";
+ artworkUseCase = 2 /* content.ArtworkUseCase.LockupIconMedium */;
+ break;
+ case "web":
+ shelfContentType = "mediumLockup";
+ artworkUseCase = 2 /* content.ArtworkUseCase.LockupIconMedium */;
+ break;
+ default:
+ shelfContentType = "smallLockup";
+ artworkUseCase = 1 /* content.ArtworkUseCase.LockupIconSmall */;
+ break;
+ }
+ let shelfCount = 0;
+ const shelves = [];
+ for (const relationship of developerRelationshipOrdering) {
+ const dataContainer = mediaRelationship.relationship(developerData, relationship);
+ const sectionData = serverData.asArrayOrEmpty(dataContainer, "data");
+ const contentCount = sectionData.length;
+ if (contentCount === 0) {
+ continue;
+ }
+ // Skip the latest release shelf if there are no items
+ // Note: This typically happens on the Apple developer page
+ // if the latest release is a system app that you're not eligible for
+ if (relationship === "latest-release-app" && contentCount === 0) {
+ continue;
+ }
+ // Setup some content specific options
+ let clientIdentifier;
+ if (relationship === "imessage-apps") {
+ clientIdentifier = client.messagesIdentifier;
+ }
+ else if (relationship === "watch-apps") {
+ clientIdentifier = client.watchIdentifier;
+ }
+ else if (relationship === "atv-apps") {
+ clientIdentifier = client.tvIdentifier;
+ }
+ else {
+ clientIdentifier = client.appStoreIdentifier;
+ }
+ // Determine the title
+ let shelfTitle;
+ if (objectGraph.client.isWeb) {
+ shelfTitle = webShelfTitle(objectGraph, relationship, developerData);
+ }
+ else {
+ shelfTitle = iosShelfTitle(objectGraph, relationship, developerData);
+ }
+ // Create a metrics context
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: metricsPageInformation,
+ locationTracker: locationTracker,
+ idType: "sequential",
+ id: `${shelfCount}`,
+ targetType: "swoosh",
+ }, shelfTitle);
+ // Create the shelf
+ const listOptions = {
+ lockupOptions: {
+ metricsOptions: {
+ pageInformation: metricsPageInformation,
+ locationTracker: locationTracker,
+ },
+ clientIdentifierOverride: clientIdentifier,
+ artworkUseCase: artworkUseCase,
+ },
+ filter: 76532 /* filtering.Filter.DeveloperPage */,
+ };
+ // Determine the ids we need to load
+ const remainingData = sectionData.filter((data) => {
+ return serverData.isNullOrEmpty(data.attributes);
+ });
+ // Vision doesn't support See All on developer page shelves currently.
+ const shouldShowSeeAll = (((_a = dataContainer.next) === null || _a === void 0 ? void 0 : _a.length) > 0 || contentCount >= seeAllThreshold) &&
+ !(objectGraph.client.isVision || objectGraph.client.isWeb);
+ const shelf = shelfForData(objectGraph, shelfTitle, developerData.id, sectionData, relationship, shelfContentType, listOptions, metricsPageInformation, locationTracker, dataContainer.href, shouldShowSeeAll);
+ // Ensure we're not too high
+ const itemCount = shelf.items.length + remainingData.length;
+ if (objectGraph.client.isVision) {
+ if (itemCount < 5) {
+ shelf.rowsPerColumn = 1;
+ }
+ else if (itemCount < 10) {
+ shelf.rowsPerColumn = 2;
+ }
+ else {
+ shelf.rowsPerColumn = 3;
+ }
+ }
+ else if (objectGraph.client.isWeb) {
+ shelf.rowsPerColumn = itemCount > 3 ? 2 : 1;
+ }
+ else if (itemCount < 3) {
+ shelf.rowsPerColumn = itemCount;
+ }
+ // Add metrics before serializing token for url
+ const shelfMetricsOptions = {
+ id: null,
+ kind: null,
+ softwareType: null,
+ targetType: "swoosh",
+ title: shelf.title,
+ pageInformation: metricsPageInformation,
+ locationTracker: locationTracker,
+ idType: null,
+ };
+ metricsHelpersLocation.popLocation(locationTracker);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ metricsHelpersLocation.nextPosition(locationTracker);
+ if (remainingData.length) {
+ const token = new DeveloperPageShelfToken();
+ token.title = shelfTitle;
+ token.developerId = developerData.id;
+ token.contentType = shelfContentType;
+ token.remainingData = remainingData;
+ token.lockupListOptions = listOptions;
+ token.relationship = relationship;
+ token.roomUrl = dataContainer.href;
+ token.shouldShowSeeAll = shouldShowSeeAll;
+ token.hasExistingContent = serverData.isDefinedNonNullNonEmpty(shelf.items);
+ shelf.url =
+ `${Protocol.internal}:/${Path.developer}/${Path.shelf}/` + encodeURIComponent(JSON.stringify(token));
+ }
+ // Don't add the shelf if there are no items in it, and there is no more content to fetch.
+ if (shelf.items.length > 0 || ((_b = shelf.url) === null || _b === void 0 ? void 0 : _b.length) > 0) {
+ shelves.push(shelf);
+ shelfCount++;
+ }
+ }
+ return shelves;
+}
+/**
+ * Returns the macOS shelf title for a given relationship.
+ * @param relationshipKey The relationship key.
+ * @param developerData Media API data for the developer page.
+ * @returns The localized shelf title.
+ */
+function macosShelfTitle(objectGraph, relationshipKey, developerData) {
+ switch (relationshipKey) {
+ case "latest-release-app":
+ return objectGraph.loc.string("DEVELOPER_LATEST_RELEASE");
+ case "app-bundles":
+ return objectGraph.loc.string("DEVELOPER_BUNDLES");
+ case "mac-apps":
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ return objectGraph.loc.string("DEVELOPER_MAC_APPS");
+ }
+ else {
+ const hasApps = mediaAttributes.attributeAsBooleanOrFalse(developerData, "hasApps");
+ const hasGames = mediaAttributes.attributeAsBooleanOrFalse(developerData, "hasGames");
+ if (hasApps && hasGames) {
+ return objectGraph.loc.string("DEVELOPER_APPS_AND_GAMES");
+ }
+ else if (hasGames) {
+ return objectGraph.loc.string("DEVELOPER_GAMES");
+ }
+ else {
+ return objectGraph.loc.string("DEVELOPER_APPS");
+ }
+ }
+ case "mac-os-compatible-ios-apps":
+ return objectGraph.loc.string("DEVELOPER_PHONE_PAD_APPS");
+ case "arcade-apps":
+ return objectGraph.loc.string("DEVELOPER_APPLE_ARCADE");
+ default:
+ return null;
+ }
+}
+function macosShelvesForDeveloperData(objectGraph, developerData, metricsPageInformation, locationTracker) {
+ var _a;
+ // Filter duplicate apps
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ filterDuplicateApps(developerData, "arcade-apps", ["mac-apps", "mac-os-compatible-ios-apps"]);
+ filterDuplicateApps(developerData, "mac-apps", ["mac-os-compatible-ios-apps"]);
+ }
+ else {
+ filterDuplicateApps(developerData, "arcade-apps", ["mac-apps"]);
+ }
+ const shelfContentType = "smallLockup";
+ const artworkUseCase = 1 /* content.ArtworkUseCase.LockupIconSmall */;
+ let shelfCount = 0;
+ const shelves = [];
+ for (const relationship of macosDeveloperRelationshipOrder) {
+ const dataContainer = mediaRelationship.relationship(developerData, relationship);
+ const sectionData = serverData.asArrayOrEmpty(dataContainer, "data");
+ const contentCount = sectionData.length;
+ if (contentCount === 0) {
+ continue;
+ }
+ // Skip the latest release shelf if there are no items
+ // Note: This typically happens on the Apple developer page
+ // if the latest release is a system app that you're not eligible for
+ if (relationship === "latest-release-app" && contentCount === 0) {
+ continue;
+ }
+ // Determine the title
+ const shelfTitle = macosShelfTitle(objectGraph, relationship, developerData);
+ // Create a metrics context
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: metricsPageInformation,
+ locationTracker: locationTracker,
+ idType: "sequential",
+ id: `${shelfCount}`,
+ targetType: "swoosh",
+ }, shelfTitle);
+ // Create the shelf
+ const listOptions = {
+ lockupOptions: {
+ metricsOptions: {
+ pageInformation: metricsPageInformation,
+ locationTracker: locationTracker,
+ },
+ artworkUseCase: artworkUseCase,
+ },
+ filter: 76532 /* filtering.Filter.DeveloperPage */,
+ };
+ // Determine the ids we need to load
+ const remainingData = sectionData.filter((data) => {
+ return serverData.isNullOrEmpty(data.attributes);
+ });
+ const shouldShowSeeAll = ((_a = dataContainer.next) === null || _a === void 0 ? void 0 : _a.length) > 0 || contentCount >= seeAllThreshold;
+ const shelf = shelfForData(objectGraph, shelfTitle, developerData.id, sectionData, relationship, shelfContentType, listOptions, metricsPageInformation, locationTracker, dataContainer.href, shouldShowSeeAll);
+ // Ensure we're not too high
+ const itemCount = shelf.items.length + remainingData.length;
+ if (itemCount < 3) {
+ shelf.rowsPerColumn = itemCount;
+ }
+ // Add metrics before serializing token for url
+ const shelfMetricsOptions = {
+ id: null,
+ kind: null,
+ softwareType: null,
+ targetType: "swoosh",
+ title: shelf.title,
+ pageInformation: metricsPageInformation,
+ locationTracker: locationTracker,
+ idType: null,
+ };
+ metricsHelpersLocation.popLocation(locationTracker);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ metricsHelpersLocation.nextPosition(locationTracker);
+ if (remainingData.length) {
+ const token = new DeveloperPageShelfToken();
+ token.title = shelfTitle;
+ token.developerId = developerData.id;
+ token.contentType = "smallLockup";
+ token.remainingData = remainingData;
+ token.lockupListOptions = listOptions;
+ token.relationship = relationship;
+ token.roomUrl = dataContainer.href;
+ token.shouldShowSeeAll = shouldShowSeeAll;
+ token.hasExistingContent = serverData.isDefinedNonNullNonEmpty(shelf.items);
+ shelf.url =
+ `${Protocol.internal}:/${Path.developer}/${Path.shelf}/` + encodeURIComponent(JSON.stringify(token));
+ }
+ // Don't add the shelf if there are no items in it
+ if (shelf.items.length > 0) {
+ shelves.push(shelf);
+ shelfCount++;
+ }
+ }
+ return shelves;
+}
+/**
+ * Creates a list of apps for the given relationship type.
+ */
+function flatShelvesForDeveloperData(objectGraph, contentType, developerData, relationshipType, shelfTitle, metricsPageInformation, locationTracker) {
+ const dataCollection = mediaRelationship.relationshipCollection(developerData, relationshipType);
+ const listOptions = {
+ lockupOptions: {
+ metricsOptions: {
+ pageInformation: metricsPageInformation,
+ locationTracker: locationTracker,
+ },
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ },
+ filter: 76532 /* filtering.Filter.DeveloperPage */,
+ };
+ // Create a metrics context
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: metricsPageInformation,
+ locationTracker: locationTracker,
+ idType: "sequential",
+ id: `0`,
+ targetType: "swoosh",
+ }, shelfTitle);
+ const shelf = shelfForData(objectGraph, shelfTitle, developerData.id, dataCollection, relationshipType, contentType, listOptions, metricsPageInformation, locationTracker, null, false);
+ // Determine the ids we need to load
+ const remainingData = dataCollection.filter((data) => {
+ return serverData.isNullOrEmpty(data.attributes);
+ });
+ // Add metrics before serializing token for url
+ const shelfMetricsOptions = {
+ id: null,
+ kind: null,
+ softwareType: null,
+ targetType: "swoosh",
+ title: shelfTitle,
+ pageInformation: metricsPageInformation,
+ locationTracker: locationTracker,
+ idType: null,
+ };
+ metricsHelpersLocation.popLocation(locationTracker);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ metricsHelpersLocation.nextPosition(locationTracker);
+ if (remainingData.length) {
+ const token = new DeveloperPageShelfToken();
+ token.title = shelfTitle;
+ token.developerId = developerData.id;
+ token.contentType = contentType;
+ token.remainingData = remainingData;
+ token.lockupListOptions = listOptions;
+ token.hasExistingContent = serverData.isDefinedNonNullNonEmpty(shelf.items);
+ shelf.url =
+ `${Protocol.internal}:/${Path.developer}/${Path.shelf}/` + encodeURIComponent(JSON.stringify(token));
+ }
+ return [shelf];
+}
+export function shelfForData(objectGraph, title, developerId, dataArray, developerRelationship, contentType, listOptions, pageInformation, locationTracker, roomUrl, includeSeeAll) {
+ const shelf = new models.Shelf(contentType);
+ shelf.title = title;
+ switch (contentType) {
+ case "screenshotsLockup":
+ shelf.items = lockups.screenshotsLockupsFromData(objectGraph, dataArray, listOptions);
+ shelf.isHorizontal = false;
+ shelf.presentationHints = { showSupplementaryText: false };
+ break;
+ case "smallLockup":
+ default:
+ shelf.items = lockups.lockupsFromData(objectGraph, dataArray, listOptions);
+ shelf.isHorizontal = objectGraph.client.deviceType !== "watch";
+ break;
+ }
+ if (includeSeeAll) {
+ // Create token for the see all room
+ const roomToken = new DeveloperRoomToken();
+ roomToken.title = title;
+ roomToken.url = roomUrl;
+ roomToken.developerId = developerId;
+ roomToken.relationshipId = developerRelationship;
+ roomToken.clientIdentifierOverride = listOptions.lockupOptions.clientIdentifierOverride;
+ const seeAllAction = new models.FlowAction("page");
+ seeAllAction.pageUrl = developerRoomUrlWithToken(objectGraph, roomToken);
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ seeAllAction.pageData = room.seeAllPage(objectGraph, title);
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, seeAllAction.pageUrl, {
+ pageInformation,
+ locationTracker,
+ });
+ shelf.seeAllAction = seeAllAction;
+ }
+ return shelf;
+}
+/**
+ * Determines the URL to use for the developer room page.
+ * @param {string} token The token to use.
+ * @returns {string} The string to use for the developer room page.
+ */
+function developerRoomUrlWithToken(objectGraph, token) {
+ if (!serverData.isDefinedNonNull(token)) {
+ return null;
+ }
+ return `${Protocol.internal}:/${Path.developer}/${Path.room}/` + encodeURIComponent(JSON.stringify(token));
+}
+/**
+ * Prune duplicate entries for apps that can appear in other shelves by modifying `developerData` in place.
+ * For example if `arcade-apps` relation contains id `12345`, we want to prune `12345` from shelves like "Games" and "Apple TV"
+ */
+function filterDuplicateApps(developerData, authoritativeRelationshipKey, filteredRelationshipKeys) {
+ const arcadeRelationship = mediaRelationship.relationship(developerData, authoritativeRelationshipKey);
+ if (serverData.isNull(arcadeRelationship)) {
+ return; // No arcade relationship
+ }
+ const arcadeApps = mediaDataStructure.dataCollectionFromDataContainer(arcadeRelationship);
+ if (serverData.isNull(arcadeApps)) {
+ return; // No arcade relationship data
+ }
+ // IDs to filter from other shelves since they are in arcade shelf
+ const arcadeAppIds = arcadeApps.map((app) => app.id);
+ for (const relationshipKeyToFilter of filteredRelationshipKeys) {
+ const relationshipToFilter = mediaRelationship.relationship(developerData, relationshipKeyToFilter);
+ if (serverData.isNull(relationshipToFilter)) {
+ continue; // Skip if relationship didn't exist
+ }
+ const relationshipDataToFilter = mediaDataStructure.dataCollectionFromDataContainer(relationshipToFilter);
+ if (serverData.isNull(relationshipToFilter)) {
+ continue; // Skip if relationship had no `data`
+ }
+ // Overwrite with filtered data
+ developerData.relationships[relationshipKeyToFilter].data = relationshipDataToFilter.filter((data) => !arcadeAppIds.includes(data.id));
+ }
+}
+//# sourceMappingURL=developer-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/developer/developer-request.js b/node_modules/@jet-app/app-store/tmp/src/common/developer/developer-request.js
new file mode 100644
index 0000000..88c7e74
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/developer/developer-request.js
@@ -0,0 +1,115 @@
+import { Request, defaultAdditionalPlatformsForClient } from "../../foundation/media/data-fetching";
+import { shouldFetchCustomAttributes } from "../product-page/product-page-variants";
+import { shouldUsePrerenderedIconArtwork } from "../content/content";
+/**
+ * Returns the attributes to use for a developer media API request.
+ */
+export function developerAttributes(objectGraph) {
+ const attributes = ["editorialArtwork", "editorialVideo", "requiredCapabilities", "minimumOSVersion"];
+ if (objectGraph.client.isMac) {
+ attributes.push("screenshotsByType");
+ }
+ else {
+ attributes.push("isAppleWatchSupported");
+ }
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.client.isVision) {
+ attributes.push("compatibilityControllerRequirement");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ return attributes;
+}
+export const iosDeveloperRelationshipKeys = [
+ "latest-release-app",
+ "system-apps",
+ "arcade-apps",
+ "app-bundles",
+ "ios-apps",
+ "imessage-apps",
+ "watch-apps",
+ "atv-apps",
+];
+export const visionOSDeveloperRelationshipKeys = [
+ "latest-release-app",
+ "xros-apps",
+ "arcade-apps",
+ "ios-apps",
+];
+export const macosDeveloperRelationshipKeys = [
+ "latest-release-app",
+ "arcade-apps",
+ "app-bundles",
+ "mac-apps",
+];
+export const webDeveloperRelationshipKeys = [
+ "latest-release-app",
+ "system-apps",
+ "arcade-apps",
+ "app-bundles",
+ "ios-apps",
+ "imessage-apps",
+ "watch-apps",
+ "atv-apps",
+ "xros-apps",
+ "mac-apps",
+];
+export const watchosDeveloperRelationshipKey = "watch-apps";
+function developerRelationships(objectGraph) {
+ let relationships = [];
+ switch (objectGraph.client.deviceType) {
+ case "mac":
+ relationships = relationships.concat(macosDeveloperRelationshipKeys);
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ relationships.push("mac-os-compatible-ios-apps");
+ }
+ break;
+ case "watch":
+ relationships.push(watchosDeveloperRelationshipKey);
+ break;
+ case "vision":
+ relationships = relationships.concat(visionOSDeveloperRelationshipKeys);
+ break;
+ case "web":
+ relationships = relationships.concat(webDeveloperRelationshipKeys);
+ break;
+ default:
+ relationships = relationships.concat(iosDeveloperRelationshipKeys);
+ break;
+ }
+ return relationships;
+}
+/**
+ * Creates a {@linkcode Request} for a "developer" page with the given {@linkcode id}
+ */
+export function makeDeveloperRequest(objectGraph, id) {
+ const request = new Request(objectGraph).withIdOfType(id, "developers");
+ return addDeveloperRequestProperties(objectGraph, request);
+}
+/**
+ * Add the expected request attributes (relationships, platforms, etc) to a request
+ * for a "developer" resource
+ */
+export function addDeveloperRequestProperties(objectGraph, request) {
+ request
+ .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph))
+ .includingRelationships(developerRelationships(objectGraph))
+ .includingAttributes(developerAttributes(objectGraph))
+ .includingMacOSCompatibleIOSAppsWhenSupported()
+ .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ if (objectGraph.client.isWeb) {
+ // The "web" client needs to load *all* of the data for SEO purposes
+ request.addingQuery("sparseLimit[developers:ios-apps]", "40");
+ }
+ return request;
+}
+//# sourceMappingURL=developer-request.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-action-util.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-action-util.js
new file mode 100644
index 0000000..9c50926
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-action-util.js
@@ -0,0 +1,58 @@
+import * as lockups from "../lockups/lockups";
+import { CollectionShelfDisplayStyle } from "./editorial-page-types";
+/**
+ * For a given piece of MAPI data create the primary clickAction for the component with this data
+ * @param objectGraph
+ * @param data
+ * @param shelfToken
+ */
+export function createPrimaryActionForComponentFromData(objectGraph, data, shelfToken, baseClickOptions) {
+ const clickOptions = createClickOptionsFromData(objectGraph, data, shelfToken, baseClickOptions);
+ const primaryAction = lockups.actionFromData(objectGraph, data, clickOptions, shelfToken.clientIdentifierOverride);
+ return primaryAction;
+}
+function createClickOptionsFromData(objectGraph, data, shelfToken, baseClickOptions) {
+ const targetType = clickTargetForCollectionDisplayStyle(objectGraph, shelfToken.collectionDisplayStyle);
+ const clickOptions = {
+ ...shelfToken.metricsImpressionOptions,
+ ...baseClickOptions,
+ id: data.id,
+ targetType: targetType,
+ };
+ return clickOptions;
+}
+export function clickTargetForCollectionDisplayStyle(objectGraph, displayStyle) {
+ switch (displayStyle) {
+ case CollectionShelfDisplayStyle.Hero:
+ return "hero";
+ case CollectionShelfDisplayStyle.TextOnly:
+ return "textOnly";
+ case CollectionShelfDisplayStyle.TextWithArtwork:
+ return "textWithArtwork";
+ case CollectionShelfDisplayStyle.BrickSmall:
+ return "brickSmall";
+ case CollectionShelfDisplayStyle.BrickMedium:
+ return "brickMedium";
+ case CollectionShelfDisplayStyle.BrickLarge:
+ return "brickLarge";
+ case CollectionShelfDisplayStyle.EditorialLockupMedium:
+ case CollectionShelfDisplayStyle.EditorialLockupMediumVariant:
+ return "editorialLockupMedium";
+ case CollectionShelfDisplayStyle.EditorialLockupLarge:
+ case CollectionShelfDisplayStyle.EditorialLockupLargeVariant:
+ return "editorialLockupLarge";
+ case CollectionShelfDisplayStyle.LockupSmall:
+ return "lockupSmall";
+ case CollectionShelfDisplayStyle.LockupLarge:
+ return "lockupLarge";
+ case CollectionShelfDisplayStyle.StorySmall:
+ return "storySmall";
+ case CollectionShelfDisplayStyle.StoryMedium:
+ return "storyMedium";
+ case CollectionShelfDisplayStyle.BreakoutLarge:
+ return "largeBreakout";
+ default:
+ return "lockup";
+ }
+}
+//# sourceMappingURL=editorial-action-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-data-util.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-data-util.js
new file mode 100644
index 0000000..041bde4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-data-util.js
@@ -0,0 +1,173 @@
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as content from "../content/content";
+import { EditorialShelfType } from "./editorial-page-types";
+/**
+ * Take the remaining content fromt he shelfToken if this is not the first render, otherwise
+ * use the initial data.
+ *
+ * @param objectGraph
+ * @param shelfToken
+ */
+export function extractRemainingItemsOrInitialShelfContents(objectGraph, shelfToken) {
+ if (!shelfToken.isFirstRender) {
+ return serverData.isDefinedNonNullNonEmpty(shelfToken.remainingItems) ? shelfToken.remainingItems : [];
+ }
+ const initialShelfContent = mediaRelationship.relationshipCollection(shelfToken.data, "contents");
+ return serverData.isDefinedNonNullNonEmpty(initialShelfContent) ? initialShelfContent : [];
+}
+/**
+ * Find the related product data from some MAPI data that was programmed on an editorial page.
+ *
+ * @param objectGraph
+ * @param data
+ * @returns the product MAPI data from the correct relationship or the data itself if the supplied data is already a product
+ */
+export function extractProductData(objectGraph, data) {
+ let productData = null;
+ switch (data.type) {
+ case "editorial-items":
+ case "editorial-pages":
+ case "editorial-shelves-header":
+ productData =
+ mediaRelationship.relationshipData(objectGraph, data, "primary-content") ||
+ mediaRelationship.relationshipData(objectGraph, data, "card-contents") ||
+ mediaRelationship.relationshipData(objectGraph, data, "contents") ||
+ mediaRelationship.relationshipData(objectGraph, data, "app");
+ break;
+ case "apps":
+ case "in-apps":
+ case "app-bundles":
+ productData = data;
+ break;
+ default:
+ break;
+ }
+ return productData;
+}
+/**
+ * Find the related collection of product data from some MAPI data that was programmed on an editorial page.
+ *
+ * @param objectGraph
+ * @param data
+ * @returns collection of related content for in the relationship of the supplied MAPI data
+ */
+export function extractCollectionData(objectGraph, data) {
+ let collectionData = null;
+ switch (data.type) {
+ case "editorial-items":
+ case "editorial-pages":
+ case "editorial-shelves-header":
+ collectionData =
+ mediaRelationship.relationshipCollection(data, "primary-contents", true) ||
+ mediaRelationship.relationshipCollection(data, "card-contents", true) ||
+ mediaRelationship.relationshipCollection(data, "contents", true);
+ break;
+ case "tags":
+ collectionData = serverData.asArray(data.meta, "associations.apps.data");
+ break;
+ default:
+ collectionData = null;
+ }
+ return collectionData;
+}
+/**
+ * Find the related editorial card used for determining override information
+ *
+ * @param objectGraph
+ * @param data
+ * @returns The editorial-card data object to use for overrides
+ */
+export function extractEditorialCardData(objectGraph, data) {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ const editorialCardsData = serverData.asArrayOrEmpty(data.meta, "associations.editorial-cards.data");
+ if (serverData.isNullOrEmpty(editorialCardsData)) {
+ return null;
+ }
+ return editorialCardsData[0];
+}
+/**
+ * Find the related editorial client params from this data object
+ *
+ * @param objectGraph
+ * @param data
+ * @returns The EditorialClientParams data object to use for this component
+ */
+export function extractEditorialClientParams(objectGraph, data) {
+ const baseEditorialClientParams = mediaAttributes.attributeAsDictionary(data, "editorialClientParams", {});
+ const editorialCardData = extractEditorialCardData(objectGraph, data);
+ const cardEditorialClientParams = mediaAttributes.attributeAsDictionary(editorialCardData, "editorialClientParams", {});
+ return serverData.asInterface({
+ ...baseEditorialClientParams,
+ ...cardEditorialClientParams,
+ });
+}
+/**
+ * Use the client params to create the display options for this component
+ *
+ * @param clientParams
+ * @returns The editorialDisplayOptions data object to use for this component
+ */
+export function editorialDisplayOptionsFromClientParams(clientParams) {
+ return {
+ showAppIcon: serverData.asBooleanWithDefault(clientParams.showAppIcon, false),
+ suppressLockup: serverData.asBooleanWithDefault(clientParams.suppressLockup, false),
+ useGeneratedBackground: serverData.asBooleanWithDefault(clientParams.useGeneratedBackground, false),
+ useMaterialBlur: serverData.asBooleanWithDefault(clientParams.useMaterialBlur, true),
+ useTextProtectionColor: serverData.asBooleanWithDefault(clientParams.useTextProtectionColor, false),
+ };
+}
+/**
+ * Get the primary content for this MAPI data object, depending on the type it is. If the data
+ * does not have any attributes this method will return null, since we'll need to fetch it.
+ * @param objectGraph
+ * @param data
+ */
+export function extractHydratedPrimaryContentData(objectGraph, data) {
+ if (!mediaAttributes.hasAttributes(data)) {
+ return null;
+ }
+ let primaryData;
+ switch (data.type) {
+ case "editorial-items":
+ primaryData = content.primaryContentForData(objectGraph, data);
+ break;
+ default:
+ primaryData = data;
+ break;
+ }
+ if (!mediaAttributes.hasAttributes(primaryData)) {
+ return null;
+ }
+ return primaryData;
+}
+/**
+ * Method to check whether we need the primary content relationship for a piece of content
+ * to be rendered
+ *
+ * @param objectGraph
+ * @param data The MAPI date we're checking for
+ */
+export function requiresPrimaryContent(objectGraph, data) {
+ const linkData = mediaAttributes.attributeAsDictionary(data, "link");
+ const isLinkAction = serverData.isDefinedNonNullNonEmpty(linkData);
+ const isStoryAction = mediaAttributes.attributeAsBooleanOrFalse(data, "isCanvasAvailable");
+ return !isLinkAction && !isStoryAction;
+}
+/**
+ * Extracts the collection shelf display style, for any collection / recommendation shelf
+ * @param objectGraph Current object graph
+ * @param shelfData The input shelf data
+ * @returns The collection shelf display style, if any
+ */
+export function collectionShelfDisplayStyleFromShelfData(objectGraph, shelfData) {
+ const shelfType = shelfData.type;
+ if (shelfType !== EditorialShelfType.Collection && shelfType !== EditorialShelfType.Recommendations) {
+ return null;
+ }
+ return mediaAttributes.attributeAsString(shelfData, "displayStyle");
+}
+//# sourceMappingURL=editorial-data-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-media-util.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-media-util.js
new file mode 100644
index 0000000..218f74f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-media-util.js
@@ -0,0 +1,511 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaPlatformAttributes from "../../foundation/media/platform-attributes";
+import * as videoDefaults from "../constants/video-constants";
+import * as contentAttributes from "../content/attributes";
+import * as content from "../content/content";
+import * as editorialDataUtil from "./editorial-data-util";
+import * as color from "../../foundation/util/color-util";
+/**
+ * The flavors for editorial art that can be used
+ */
+export var EditorialMediaFlavor;
+(function (EditorialMediaFlavor) {
+ EditorialMediaFlavor["StoryCenteredMotion16x9"] = "storyCenteredMotion16x9";
+ EditorialMediaFlavor["StoryCenteredStatic16x9"] = "storyCenteredStatic16x9";
+ EditorialMediaFlavor["StoryDetailMotion3x4"] = "storyDetailMotion3x4";
+ EditorialMediaFlavor["StoryDetailStatic3x4"] = "storyDetailStatic3x4";
+ EditorialMediaFlavor["HeroMotion16x9"] = "heroMotion16x9";
+ EditorialMediaFlavor["HeroStatic16x9"] = "heroStatic16x9";
+ EditorialMediaFlavor["HeroMotionRTL16x9"] = "heroMotionRTL16x9";
+ EditorialMediaFlavor["HeroStaticRTL16x9"] = "heroStaticRTL16x9";
+ EditorialMediaFlavor["LargeBreakoutMotion16x9"] = "largeBreakoutMotion16x9";
+ EditorialMediaFlavor["LargeBreakoutStatic16x9"] = "largeBreakoutStatic16x9";
+ EditorialMediaFlavor["LargeBreakoutRTLMotion16x9"] = "largeBreakoutRTLMotion16x9";
+ EditorialMediaFlavor["LargeBreakoutRTLStatic16x9"] = "largeBreakoutRTLStatic16x9";
+ EditorialMediaFlavor["StoryCardMotion3x4"] = "storyCardMotion3x4";
+ EditorialMediaFlavor["StoryCardStatic3x4"] = "storyCardStatic3x4";
+ EditorialMediaFlavor["StorySearchStatic16x9"] = "storySearchStatic16x9";
+ EditorialMediaFlavor["SubscriptionHero"] = "subscriptionHero";
+ EditorialMediaFlavor["UniversalAMotion16x9"] = "universalAMotion16x9";
+ EditorialMediaFlavor["UniversalAStatic16x9"] = "universalAStatic16x9";
+ EditorialMediaFlavor["BrickStatic16x9"] = "brickStatic16x9";
+ EditorialMediaFlavor["BrickStaticRTL16x9"] = "brickStaticRTL16x9";
+ EditorialMediaFlavor["SearchCategoryBrick"] = "searchCategoryBrick";
+})(EditorialMediaFlavor || (EditorialMediaFlavor = {}));
+/**
+ * The different locations for where editorial art is displayed
+ */
+export var EditorialMediaPlacement;
+(function (EditorialMediaPlacement) {
+ EditorialMediaPlacement["Hero"] = "hero";
+ EditorialMediaPlacement["LargeBreakout"] = "largeBreakout";
+ EditorialMediaPlacement["StoryCard"] = "storyCard";
+ EditorialMediaPlacement["StoryDetail"] = "storyDetail";
+ EditorialMediaPlacement["StoryDetailLandscape"] = "storyDetailLandscape";
+ EditorialMediaPlacement["Search"] = "search";
+ EditorialMediaPlacement["Brick"] = "brick";
+ EditorialMediaPlacement["EditorialLockup"] = "editorialLockup";
+ EditorialMediaPlacement["EditorialPage"] = "editorialPage";
+})(EditorialMediaPlacement || (EditorialMediaPlacement = {}));
+/**
+ * The mapping between the various placements and the preferred flavor for that location
+ */
+const preferredEditorialMediaFlavorMap = {
+ hero: [
+ EditorialMediaFlavor.HeroMotion16x9,
+ EditorialMediaFlavor.HeroStatic16x9,
+ EditorialMediaFlavor.StoryCenteredMotion16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAMotion16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ ],
+ largeBreakout: [
+ EditorialMediaFlavor.LargeBreakoutMotion16x9,
+ EditorialMediaFlavor.LargeBreakoutStatic16x9,
+ EditorialMediaFlavor.StoryCenteredMotion16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAMotion16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ ],
+ storyCard: [
+ EditorialMediaFlavor.StoryCardMotion3x4,
+ EditorialMediaFlavor.StoryCardStatic3x4,
+ EditorialMediaFlavor.StoryCenteredMotion16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAMotion16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ ],
+ storyDetail: [
+ EditorialMediaFlavor.StoryDetailMotion3x4,
+ EditorialMediaFlavor.StoryDetailStatic3x4,
+ EditorialMediaFlavor.StoryCenteredMotion16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAMotion16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ ],
+ storyDetailLandscape: [
+ EditorialMediaFlavor.StoryCenteredMotion16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAMotion16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ EditorialMediaFlavor.StoryDetailMotion3x4,
+ EditorialMediaFlavor.StoryDetailStatic3x4,
+ ],
+ search: [
+ EditorialMediaFlavor.StorySearchStatic16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ ],
+ brick: [
+ EditorialMediaFlavor.BrickStatic16x9,
+ EditorialMediaFlavor.HeroStatic16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ EditorialMediaFlavor.SearchCategoryBrick,
+ ],
+ editorialLockup: [EditorialMediaFlavor.SubscriptionHero],
+ editorialPage: [
+ EditorialMediaFlavor.StoryCardStatic3x4,
+ EditorialMediaFlavor.HeroStatic16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.SubscriptionHero,
+ ],
+};
+/**
+ * The mapping between the various placements and the preferred flavor for that location, for RTL flavors
+ */
+const preferredEditorialMediaRTLFlavorMap = {
+ hero: [
+ EditorialMediaFlavor.HeroMotionRTL16x9,
+ EditorialMediaFlavor.HeroStaticRTL16x9,
+ EditorialMediaFlavor.StoryCenteredMotion16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAMotion16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ ],
+ largeBreakout: [
+ EditorialMediaFlavor.LargeBreakoutRTLMotion16x9,
+ EditorialMediaFlavor.LargeBreakoutRTLStatic16x9,
+ EditorialMediaFlavor.StoryCenteredMotion16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAMotion16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ ],
+ storyCard: [
+ EditorialMediaFlavor.StoryCardMotion3x4,
+ EditorialMediaFlavor.StoryCardStatic3x4,
+ EditorialMediaFlavor.StoryCenteredMotion16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAMotion16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ ],
+ storyDetail: [
+ EditorialMediaFlavor.StoryDetailMotion3x4,
+ EditorialMediaFlavor.StoryDetailStatic3x4,
+ EditorialMediaFlavor.StoryCenteredMotion16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAMotion16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ ],
+ storyDetailLandscape: [
+ EditorialMediaFlavor.StoryCenteredMotion16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAMotion16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ EditorialMediaFlavor.StoryDetailMotion3x4,
+ EditorialMediaFlavor.StoryDetailStatic3x4,
+ ],
+ search: [
+ EditorialMediaFlavor.StorySearchStatic16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ ],
+ brick: [
+ EditorialMediaFlavor.BrickStaticRTL16x9,
+ EditorialMediaFlavor.HeroStatic16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.UniversalAStatic16x9,
+ EditorialMediaFlavor.SearchCategoryBrick,
+ ],
+ editorialLockup: [EditorialMediaFlavor.SubscriptionHero],
+ editorialPage: [
+ EditorialMediaFlavor.StoryCardStatic3x4,
+ EditorialMediaFlavor.HeroStatic16x9,
+ EditorialMediaFlavor.StoryCenteredStatic16x9,
+ EditorialMediaFlavor.SubscriptionHero,
+ ],
+};
+/**
+ * Create the best suitable editorial artwork / video for the given placement, including the additional data that might be
+ * needed about this artwork
+ *
+ * @param objectGraph
+ * @param data
+ * @param placement
+ */
+export function editorialMediaDataForPlacement(objectGraph, data, placement) {
+ const editorialCardData = editorialDataUtil.extractEditorialCardData(objectGraph, data);
+ const preferredEditorialMediaFlavors = preferredEditorialMediaFlavorMap[placement];
+ const editorialMediaData = editorialMediaDataForPlacementAndFlavors(objectGraph, data, editorialCardData, placement, preferredEditorialMediaFlavors, false);
+ const preferredEditorialMediaRTLFlavors = preferredEditorialMediaRTLFlavorMap[placement];
+ const rtlEditorialMediaData = editorialMediaDataForPlacementAndFlavors(objectGraph, data, editorialCardData, placement, preferredEditorialMediaRTLFlavors, true);
+ editorialMediaData.rtlArtwork = rtlEditorialMediaData.rtlArtwork;
+ editorialMediaData.rtlArtworkData = rtlEditorialMediaData.rtlArtworkData;
+ editorialMediaData.rtlVideo = rtlEditorialMediaData.rtlVideo;
+ editorialMediaData.rtlBackgroundColor = rtlEditorialMediaData.rtlBackgroundColor;
+ editorialMediaData.rtlTextColorOverride = rtlEditorialMediaData.rtlTextColorOverride;
+ return editorialMediaData;
+}
+/**
+ * Creates the `EditorialMediaData` object for a specific placement and flavors.
+ *
+ * Note: This will only populate either the LTR properties or the RTL properties on the `EditorialMediaData`
+ * object, never both. Which ones are populated is based on the `isRTL` flag.
+ *
+ * @param objectGraph Current object graph
+ * @param data The data blob for the main item
+ * @param editorialCardData The data blob for the editorial card
+ * @param placement The placement for the media
+ * @param flavors The flavor candidates for the media
+ * @param isRTL Whether the placement is for RTL media
+ * @returns A built `EditorialMediaData` object
+ */
+function editorialMediaDataForPlacementAndFlavors(objectGraph, data, editorialCardData, placement, flavors, isRTL) {
+ let editorialMediaData = {};
+ for (const flavor of flavors) {
+ // Check the editorial card first for override media
+ if (serverData.isDefinedNonNull(editorialCardData)) {
+ editorialMediaData =
+ editorialVideoFromDataForFlavor(objectGraph, editorialCardData, placement, flavor, isRTL) ||
+ editorialArtworkFromDataForFlavor(objectGraph, editorialCardData, placement, flavor, isRTL);
+ if (serverData.isDefinedNonNullNonEmpty(editorialMediaData)) {
+ break;
+ }
+ }
+ // If no override found, then check the main data
+ editorialMediaData =
+ editorialVideoFromDataForFlavor(objectGraph, data, placement, flavor, isRTL) ||
+ editorialArtworkFromDataForFlavor(objectGraph, data, placement, flavor, isRTL);
+ if (serverData.isDefinedNonNullNonEmpty(editorialMediaData)) {
+ break;
+ }
+ }
+ if (isNothing(editorialMediaData)) {
+ editorialMediaData = {};
+ }
+ return editorialMediaData;
+}
+/**
+ * Create the best suitable editorial artwork for the given flavor, including the additional data that might be
+ * needed about this artwork, returns null if there is no editorial artwork for this flavor
+ *
+ * @param objectGraph
+ * @param data
+ * @param placement
+ * @param flavor
+ * @param isRTL
+ */
+function editorialArtworkFromDataForFlavor(objectGraph, data, placement, flavor, isRTL) {
+ let editorialArtwork = null;
+ let editorialArtworkData = null;
+ let editorialArtworkBackgroundColor = null;
+ const editorialArtworkPath = `editorialArtwork.${flavor}`;
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
+ if (serverData.isNull(attributePlatform)) {
+ return null;
+ }
+ editorialArtworkData =
+ mediaAttributes.attributeAsDictionary(data, editorialArtworkPath) ||
+ mediaPlatformAttributes.platformAttributeAsDictionary(data, attributePlatform, editorialArtworkPath);
+ if (!serverData.isDefinedNonNullNonEmpty(editorialArtworkData)) {
+ return null;
+ }
+ editorialArtwork = content.artworkFromApiArtwork(objectGraph, editorialArtworkData, {
+ withJoeColorPlaceholder: true,
+ useCase: artworkUseCaseForPlacement(placement),
+ cropCode: cropCodeForPlacementAndFlavor(placement, flavor, isRTL),
+ });
+ if (serverData.isNull(editorialArtwork)) {
+ return null;
+ }
+ editorialArtworkBackgroundColor = editorialArtwork.backgroundColor;
+ const textColorOverride = editorialTextColorOverride(objectGraph, editorialArtworkData, editorialArtwork);
+ if (isRTL) {
+ return {
+ rtlArtwork: editorialArtwork,
+ rtlArtworkData: editorialArtworkData,
+ rtlBackgroundColor: editorialArtworkBackgroundColor,
+ rtlTextColorOverride: textColorOverride,
+ };
+ }
+ else {
+ return {
+ artwork: editorialArtwork,
+ artworkData: editorialArtworkData,
+ backgroundColor: editorialArtworkBackgroundColor,
+ textColorOverride: textColorOverride,
+ };
+ }
+}
+/**
+ * Create the best suitable editorial artwork for the given flavor, including the additional data that might be
+ * needed about this artwork, returns null if there is no editorial artwork for this flavor
+ *
+ * @param objectGraph
+ * @param data
+ * @param placement
+ * @param flavor
+ */
+function editorialVideoFromDataForFlavor(objectGraph, data, placement, flavor, isRTL) {
+ let editorialVideo = null;
+ let previewFrameArtwork = null;
+ let editorialVideoData = null;
+ let editorialVideoBackgroundColor = null;
+ const editorialVideoPath = `editorialVideo.${flavor}`;
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
+ if (isNothing(attributePlatform)) {
+ return null;
+ }
+ editorialVideoData =
+ mediaAttributes.attributeAsDictionary(data, editorialVideoPath) ||
+ mediaPlatformAttributes.platformAttributeAsDictionary(data, attributePlatform, editorialVideoPath);
+ if (serverData.isNullOrEmpty(editorialVideoData)) {
+ return null;
+ }
+ const previewFrameData = serverData.asDictionary(editorialVideoData, "previewFrame");
+ if (serverData.isDefinedNonNullNonEmpty(previewFrameData)) {
+ previewFrameArtwork = content.artworkFromApiArtwork(objectGraph, previewFrameData, {
+ withJoeColorPlaceholder: true,
+ useCase: artworkUseCaseForPlacement(placement),
+ cropCode: "sr",
+ });
+ }
+ if (serverData.isDefinedNonNullNonEmpty(previewFrameArtwork)) {
+ previewFrameArtwork.crop = "sr";
+ editorialVideoBackgroundColor = previewFrameArtwork.backgroundColor;
+ }
+ const videoUrl = serverData.asString(editorialVideoData, "video");
+ if (serverData.isDefinedNonNullNonEmpty(previewFrameArtwork) && isSome(videoUrl) && videoUrl.length > 0) {
+ editorialVideo = new models.Video(videoUrl, previewFrameArtwork, {
+ canPlayFullScreen: false,
+ allowsAutoPlay: true,
+ looping: true,
+ playbackControls: videoPlaybackControls(objectGraph, placement),
+ autoPlayPlaybackControls: {},
+ });
+ editorialVideo.editorialMediaFlavor = flavor;
+ editorialVideo.editorialMediaPlacement = placement;
+ }
+ if (isNothing(previewFrameData) || isNothing(previewFrameArtwork)) {
+ return null;
+ }
+ const textColorOverride = editorialTextColorOverride(objectGraph, previewFrameData, previewFrameArtwork);
+ if (isRTL) {
+ return {
+ rtlVideo: editorialVideo,
+ rtlArtwork: previewFrameArtwork,
+ rtlArtworkData: previewFrameData,
+ rtlBackgroundColor: editorialVideoBackgroundColor,
+ rtlTextColorOverride: textColorOverride,
+ };
+ }
+ else {
+ return {
+ video: editorialVideo,
+ artwork: previewFrameArtwork,
+ artworkData: previewFrameData,
+ backgroundColor: editorialVideoBackgroundColor,
+ textColorOverride: textColorOverride,
+ };
+ }
+}
+/**
+ * Determine the correct artwork use case for each placement
+ * @param placement
+ */
+function artworkUseCaseForPlacement(placement) {
+ switch (placement) {
+ case EditorialMediaPlacement.Hero:
+ return 19 /* content.ArtworkUseCase.GroupingHero */;
+ case EditorialMediaPlacement.LargeBreakout:
+ return 6 /* content.ArtworkUseCase.ArcadeLargeBreakout */;
+ case EditorialMediaPlacement.StoryCard:
+ return 15 /* content.ArtworkUseCase.TodayCardMedia */;
+ case EditorialMediaPlacement.StoryDetail:
+ case EditorialMediaPlacement.StoryDetailLandscape:
+ return 13 /* content.ArtworkUseCase.ArticleImage */;
+ case EditorialMediaPlacement.Search:
+ return 9 /* content.ArtworkUseCase.SearchEditorialResult */;
+ default:
+ return 0 /* content.ArtworkUseCase.Default */;
+ }
+}
+/**
+ * Determine the correct crop code to use for artwork at a given flavor
+ * @param placement
+ * @param flavor
+ * @param isRTL
+ */
+function cropCodeForPlacementAndFlavor(placement, flavor, isRTL) {
+ switch (placement) {
+ case EditorialMediaPlacement.Hero:
+ switch (flavor) {
+ case EditorialMediaFlavor.HeroStatic16x9:
+ if (!isRTL) {
+ return "gd";
+ }
+ break;
+ case EditorialMediaFlavor.HeroStaticRTL16x9:
+ if (isRTL) {
+ return "gg";
+ }
+ break;
+ case EditorialMediaFlavor.StoryCenteredStatic16x9:
+ return isRTL ? "gh" : "ge";
+ case EditorialMediaFlavor.UniversalAStatic16x9:
+ return isRTL ? "gj" : "gi";
+ default:
+ break;
+ }
+ break;
+ case EditorialMediaPlacement.LargeBreakout:
+ switch (flavor) {
+ case EditorialMediaFlavor.StoryCenteredStatic16x9:
+ return isRTL ? "gk" : "gf";
+ case EditorialMediaFlavor.UniversalAStatic16x9:
+ return isRTL ? "gl" : "gm";
+ default:
+ break;
+ }
+ break;
+ case EditorialMediaPlacement.StoryCard:
+ case EditorialMediaPlacement.StoryDetail:
+ case EditorialMediaPlacement.StoryDetailLandscape:
+ case EditorialMediaPlacement.Search:
+ switch (flavor) {
+ case EditorialMediaFlavor.UniversalAStatic16x9:
+ return "gn";
+ default:
+ break;
+ }
+ break;
+ case EditorialMediaPlacement.Brick:
+ switch (flavor) {
+ case EditorialMediaFlavor.SearchCategoryBrick:
+ return "SCB.ApSCBL01";
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return "sr";
+}
+/**
+ * Determines whether a given piece of editorial media is considered dark, light, or unknown.
+ * @param objectGraph Current object graph
+ * @param mediaData The data for the editorial media
+ * @param isRTL Whether the media to check is for RTL
+ * @returns True if the media is considered to be dark, false if considered light, or undefined if unknown.
+ */
+export function isMediaDark(objectGraph, editorialMediaData, isRTL = false) {
+ if (isNothing(editorialMediaData)) {
+ return null;
+ }
+ const textColorOverride = isRTL
+ ? editorialMediaData.rtlTextColorOverride
+ : editorialMediaData.textColorOverride;
+ const backgroundColor = isRTL
+ ? editorialMediaData.rtlBackgroundColor
+ : editorialMediaData.backgroundColor;
+ if (isSome(textColorOverride)) {
+ // Custom text colors can either be white or black. If the custom color is white, we set the
+ // media as dark, which will implicitly use white text. The behaviour is reversed for black text.
+ return color.isColorEqualToColor(textColorOverride, color.white);
+ }
+ else if (isSome(backgroundColor)) {
+ // If no custom text color is set, use the media background color to determine the darkness.
+ return color.isDarkColor(backgroundColor);
+ }
+ return null;
+}
+/**
+ * Determines if an editorially set custom text color should be used.
+ * @param objectGraph Current object graph
+ * @param artworkData The data that represents the editorial artwork
+ * @param artwork The parsed editorial artwork
+ * @returns True if the component should respect an editorial color
+ */
+function editorialTextColorOverride(objectGraph, artworkData, artwork) {
+ const textColor = artwork === null || artwork === void 0 ? void 0 : artwork.textColor;
+ if (isNothing(textColor)) {
+ return null;
+ }
+ const useCustomTextColor = serverData.asBooleanOrFalse(artworkData, "useCustomTextColor");
+ const customTextColorIsValid = color.isColorEqualToColor(textColor, color.black) || color.isColorEqualToColor(textColor, color.white);
+ if (useCustomTextColor && customTextColorIsValid) {
+ return textColor;
+ }
+ return null;
+}
+/**
+ * Determines the video playback controls for a given placement.
+ * @param objectGraph Current object graph
+ * @param placement The placement for the view containing the video
+ * @returns The video playback controls
+ */
+function videoPlaybackControls(objectGraph, placement) {
+ switch (placement) {
+ case EditorialMediaPlacement.Hero:
+ case EditorialMediaPlacement.StoryDetail:
+ case EditorialMediaPlacement.StoryDetailLandscape:
+ return videoDefaults.noControls(objectGraph);
+ default:
+ return videoDefaults.standardControls(objectGraph);
+ }
+}
+//# sourceMappingURL=editorial-media-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-component-media-util.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-component-media-util.js
new file mode 100644
index 0000000..9a39901
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-component-media-util.js
@@ -0,0 +1,76 @@
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as lockups from "../lockups/lockups";
+import * as editorialDataUtil from "./editorial-data-util";
+import * as editorialMediaUtil from "./editorial-media-util";
+/**
+ * Get the editorial artwork / video data for a component on an editorial page
+ * @param objectGraph
+ * @param data
+ * @param collectionDisplayStyle
+ */
+export function editorialMediaDataFromData(objectGraph, data, collectionDisplayStyle) {
+ switch (collectionDisplayStyle) {
+ case "EditorialLockupMedium":
+ case "EditorialLockupLarge":
+ case "EditorialLockupMediumVariant":
+ case "EditorialLockupLargeVariant":
+ return editorialMediaUtil.editorialMediaDataForPlacement(objectGraph, data, editorialMediaUtil.EditorialMediaPlacement.EditorialLockup);
+ case "BrickSmall":
+ case "BrickMedium":
+ case "BrickLarge":
+ return editorialMediaUtil.editorialMediaDataForPlacement(objectGraph, data, editorialMediaUtil.EditorialMediaPlacement.Brick);
+ case "StoryMedium":
+ case "StorySmall":
+ return editorialMediaUtil.editorialMediaDataForPlacement(objectGraph, data, editorialMediaUtil.EditorialMediaPlacement.StoryCard);
+ case "Hero":
+ return editorialMediaUtil.editorialMediaDataForPlacement(objectGraph, data, editorialMediaUtil.EditorialMediaPlacement.Hero);
+ case "BreakoutLarge":
+ return editorialMediaUtil.editorialMediaDataForPlacement(objectGraph, data, editorialMediaUtil.EditorialMediaPlacement.LargeBreakout);
+ default:
+ return {};
+ }
+}
+/**
+ * Get the app icons to display when there is no editorial artwork / video
+ * @param objectGraph
+ * @param data
+ * @param lockupOptions
+ */
+export function editorialFallbackAppIconsFromData(objectGraph, data, lockupOptions) {
+ const collectionLockups = editorialCollectionAppsFromData(objectGraph, data, { lockupOptions: lockupOptions });
+ if (serverData.isNullOrEmpty(collectionLockups)) {
+ return undefined;
+ }
+ return collectionLockups === null || collectionLockups === void 0 ? void 0 : collectionLockups.map((lockup) => {
+ return lockup.icon;
+ });
+}
+/**
+ * Get lockup to be displayed on an editorial component
+ * @param objectGraph
+ * @param data
+ * @param lockupOptions
+ */
+export function editorialAppLockupFromData(objectGraph, data, lockupOptions) {
+ const productData = editorialDataUtil.extractProductData(objectGraph, data);
+ if (serverData.isNullOrEmpty(productData)) {
+ return null;
+ }
+ const appLockup = lockups.lockupFromData(objectGraph, productData, lockupOptions);
+ return appLockup;
+}
+/**
+ * Get the lockups from the collection this data is pointing to for a component on an editorial page
+ * @param objectGraph
+ * @param data
+ * @param lockupOptions
+ */
+export function editorialCollectionAppsFromData(objectGraph, data, lockupOptions) {
+ const collectionData = editorialDataUtil.extractCollectionData(objectGraph, data);
+ if (serverData.isNullOrEmpty(collectionData)) {
+ return null;
+ }
+ const collectionApps = lockups.lockupsFromData(objectGraph, collectionData, lockupOptions);
+ return collectionApps;
+}
+//# sourceMappingURL=editorial-page-component-media-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-controller-util.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-controller-util.js
new file mode 100644
index 0000000..db81f0d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-controller-util.js
@@ -0,0 +1,209 @@
+import { isNothing } from "@jet/environment";
+import * as editorialMediaUtil from "./editorial-media-util";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaDataFetching from "../../foundation/media/data-fetching";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as metricsHelpersLocation from "../../common/metrics/helpers/location";
+import * as metricsHelpersPage from "../../common/metrics/helpers/page";
+import * as metricsHelpersUtil from "../../common/metrics/helpers/util";
+import * as models from "../../api/models";
+import * as onDevicePersonalization from "../../common/personalization/on-device-personalization";
+import * as refresh from "../../common/refresh/page-refresh-controller";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as sharing from "../../common/sharing";
+import * as shelfBatching from "../../common/grouping/shelf-batching";
+import { createBaseShelfToken } from "./editorial-page-shelf-token";
+import { buildEditorialShelf } from "./editorial-page-shelf-builder";
+import { buildArcadeLockup } from "./editorial-page-shelf-builder/editorial-page-arcade-lockup-builder";
+import { shelfForTermsAndConditions } from "../grouping/grouping-common";
+import { EditorialShelfType } from "./editorial-page-types";
+/**
+ * Returns the number of shelves to hydrate on first load
+ */
+export function pageSparseCount(objectGraph) {
+ if (objectGraph.client.isWeb) {
+ // Returning `null` will avoid `Request` from setting the count at all
+ return null;
+ }
+ else {
+ return 5;
+ }
+}
+/**
+ * Returns the number of hydrated items in the hydrated shelves
+ */
+export function pageSparseLimit(objectGraph) {
+ return 12;
+}
+/**
+ * Modify request for a items fetched for grouping shelf items. (i.e. individual IDs).
+ * This should be ideally be in grouping builder's `prepareRequest`, but that would change how url-driven requests are configured.
+ * @param objectGraph
+ * @param request Request to modify.
+ */
+export function prepareMAPIRequest(objectGraph, request) {
+ const baseAttributes = ["editorialArtwork", "editorialVideo", "editorialClientParams"];
+ const editorialItemsAttributes = [
+ ...baseAttributes,
+ "showExpectedReleaseDate",
+ "expectedReleaseDateDisplayFormat",
+ "enrichedEditorialNotes",
+ "shortEditorialNotes",
+ ];
+ const editorialPagesAttributes = [...baseAttributes, "enrichedEditorialNotes"];
+ const contentAttributes = [
+ ...baseAttributes,
+ "minimumOSVersion",
+ "screenshotsByType",
+ "videoPreviewsByType",
+ "expectedReleaseDateDisplayFormat",
+ "isAppleWatchSupported",
+ "minimumOSVersion",
+ "compatibilityControllerRequirement",
+ ];
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ contentAttributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ contentAttributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.client.isVision) {
+ request.addingRelationshipLimit("editorial-shelves-collection:contents", 100);
+ }
+ if (objectGraph.client.isWeb) {
+ request.addingRelationshipLimit("editorial-shelves-collection:contents", 20);
+ }
+ const appsAttributes = [...contentAttributes];
+ const features = ["personalization", "supportsCustomTextColor"];
+ request
+ .includingAgeRestrictions()
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .enablingFeatures(features)
+ .includingRelationshipsForUpsell(true)
+ .includingScopedAttributes("editorial-items", editorialItemsAttributes)
+ .includingScopedAttributes("editorial-pages", editorialPagesAttributes)
+ .includingScopedAttributes("apps", appsAttributes);
+}
+export function shareSheetActionFromData(objectGraph, editorialPageData) {
+ var _a;
+ const url = mediaAttributes.attributeAsString(editorialPageData, "url");
+ const title = mediaAttributes.attributeAsString(editorialPageData, "editorialNotes.name");
+ if (serverData.isNull(url) || serverData.isNull(title)) {
+ return null;
+ }
+ const subtitle = objectGraph.loc.string("SHARE_EDITORIAL_PAGE_SUBTITLE");
+ const artwork = (_a = editorialMediaUtil.editorialMediaDataForPlacement(objectGraph, editorialPageData, editorialMediaUtil.EditorialMediaPlacement.EditorialPage)) === null || _a === void 0 ? void 0 : _a.artwork;
+ // Create share sheet model (bail out if unable to do so)
+ const shareData = sharing.shareSheetDataForGenericPage(objectGraph, title, url, subtitle, null, artwork);
+ if (isNothing(shareData)) {
+ return null;
+ }
+ const activities = sharing.shareSheetActivitiesForGenericPage(objectGraph, url);
+ return new models.ShareSheetAction(shareData, activities);
+}
+export async function renderPage(objectGraph, data, parameters) {
+ var _a, _b;
+ const editorialPageData = mediaDataStructure.dataFromDataContainer(objectGraph, data);
+ if (!serverData.isDefinedNonNullNonEmpty(editorialPageData)) {
+ return null;
+ }
+ const canvasData = mediaRelationship.relationshipCollection(editorialPageData, "canvas");
+ if (!serverData.isDefinedNonNullNonEmpty(canvasData)) {
+ return null;
+ }
+ let pageType = "Editorial";
+ const typeFromServer = serverData.asString(editorialPageData, "type");
+ if (typeFromServer === "tags") {
+ pageType = "TagRoom";
+ }
+ const metricsPageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, pageType, editorialPageData.id, data);
+ const metricsLocationTracker = metricsHelpersLocation.newLocationTracker();
+ const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph);
+ metricsPageInformation.recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(metricsPageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData);
+ const pageRefreshController = refresh.newPageRefreshControllerFromResponse(data);
+ const { isArcadePage } = parameters;
+ const shelves = [];
+ let pageData = null;
+ let arcadeLockup = null;
+ if (serverData.isDefinedNonNull(editorialPageData.attributes)) {
+ pageData = {
+ id: editorialPageData.id,
+ type: editorialPageData.type,
+ attributes: editorialPageData.attributes,
+ relationships: editorialPageData.relationships,
+ meta: editorialPageData.meta,
+ };
+ }
+ else {
+ return null;
+ }
+ let hasUpsellbreakout = false;
+ for (const shelfData of canvasData) {
+ const shelfIndex = shelves.length;
+ const shelfToken = createBaseShelfToken(objectGraph, pageData.id, shelfData, isArcadePage, shelfIndex, metricsPageInformation, metricsLocationTracker);
+ const shelf = buildEditorialShelf(objectGraph, pageData, shelfToken);
+ if (serverData.isNull(shelf)) {
+ continue;
+ }
+ if (shelves.length === 0) {
+ const existingPresentationHints = shelf.presentationHints;
+ shelf.presentationHints = {
+ ...existingPresentationHints,
+ isFirstShelf: true,
+ };
+ }
+ if (shelfToken.type === EditorialShelfType.Upsell) {
+ hasUpsellbreakout = true;
+ }
+ if (shelf.contentType === "upsellBreakout") {
+ arcadeLockup = buildArcadeLockup(objectGraph, shelfToken);
+ }
+ shelves.push(shelf);
+ }
+ const page = isArcadePage ? new models.ArcadePage(shelves) : new models.GenericPage(shelves);
+ page.title = hasUpsellbreakout
+ ? null
+ : (_a = mediaAttributes.attributeAsString(editorialPageData, "editorialNotes.name")) !== null && _a !== void 0 ? _a : mediaAttributes.attributeAsString(editorialPageData, "name");
+ page.canonicalURL = mediaAttributes.attributeAsString(editorialPageData, "url");
+ page.shareAction = shareSheetActionFromData(objectGraph, editorialPageData);
+ page.pageRefreshPolicy = refresh.pageRefreshPolicyForController(objectGraph, pageRefreshController);
+ // Create the ArcadeLockup for Arcade's sticky header
+ if (isArcadePage && serverData.isDefinedNonNull(arcadeLockup)) {
+ page.subscriptionLockup = arcadeLockup;
+ }
+ if (isArcadePage) {
+ metricsPageInformation.isCrossfireReferralCandidate = true;
+ }
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, metricsPageInformation);
+ if (objectGraph.client.isVision) {
+ page.presentationOptions.push("prefersRevealingPageTitleWhenNotRoot");
+ }
+ const isLastShelfSeeAllGames = ((_b = shelves[shelves.length - 1]) === null || _b === void 0 ? void 0 : _b.contentType) === "arcadeFooter";
+ if (objectGraph.client.deviceType !== "watch" &&
+ objectGraph.client.deviceType !== "tv" &&
+ !isLastShelfSeeAllGames) {
+ const url = objectGraph.bag.termsAndConditionsURL;
+ if (!serverData.isNull(url)) {
+ const termsAndConditionsShelf = shelfForTermsAndConditions(objectGraph, url);
+ shelves.push(termsAndConditionsShelf);
+ }
+ }
+ if (shelves.length > 0) {
+ const isLargeHeroBreakout = shelves[0].contentType === "largeHeroBreakout";
+ const isUpsell = shelves[0].contentType === "upsellBreakout";
+ const isHeroCarousel = shelves[0].contentType === "heroCarousel";
+ const isMediaPageHeader = shelves[0].contentType === "mediaPageHeader";
+ const shouldOverlayPageHeader = isLargeHeroBreakout || isUpsell || isHeroCarousel || isMediaPageHeader;
+ if (shouldOverlayPageHeader && !preprocessor.GAMES_TARGET) {
+ page.presentationOptions.push("prefersOverlayedPageHeader");
+ }
+ if (isMediaPageHeader) {
+ page.presentationOptions.push("prefersNonStandardBackButton");
+ page.presentationOptions.push("prefersHiddenPageTitle");
+ }
+ }
+ shelfBatching.addBatchGroupsToPage(page);
+ return page;
+}
+//# sourceMappingURL=editorial-page-controller-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-editorial-copy-util.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-editorial-copy-util.js
new file mode 100644
index 0000000..7e8da86
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-editorial-copy-util.js
@@ -0,0 +1,247 @@
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaPlatformAttributes from "../../foundation/media/platform-attributes";
+import * as contentAttributes from "../content/attributes";
+import * as editorialDataUtil from "./editorial-data-util";
+/**
+ * Get the text that should be displayed as the badge (some times referred to as caption) for
+ * an editorial component
+ * @param objectGraph
+ * @param data
+ * @param collectionDisplayStyle
+ */
+export function editorialBadgeFromData(objectGraph, data, collectionDisplayStyle) {
+ let editorialBadge = null;
+ let editorialBadgeOverride = null;
+ let editorialBadgePaths = [];
+ // Check the editorial card first for override content
+ const editorialCardData = editorialDataUtil.extractEditorialCardData(objectGraph, data);
+ switch (data.type) {
+ case "editorial-items":
+ case "editorial-pages":
+ case "editorial-shelves-header":
+ editorialBadgePaths = ["enrichedEditorialNotes.badge", "editorialNotes.badge", "label"];
+ editorialBadge = findFirstMatchingString(objectGraph, data, editorialBadgePaths, false);
+ editorialBadgeOverride = findFirstMatchingString(objectGraph, editorialCardData, editorialBadgePaths, false);
+ break;
+ case "apps":
+ case "in-apps":
+ case "app-bundles":
+ editorialBadgePaths = ["editorialNotes.badge"];
+ editorialBadge = findFirstMatchingString(objectGraph, data, editorialBadgePaths, true);
+ editorialBadgeOverride = findFirstMatchingString(objectGraph, editorialCardData, editorialBadgePaths, true);
+ break;
+ default:
+ break;
+ }
+ const badge = serverData.isDefinedNonNull(editorialBadgeOverride) ? editorialBadgeOverride : editorialBadge;
+ if (serverData.isNullOrEmpty(badge)) {
+ return null;
+ }
+ return badge;
+}
+/**
+ * Get the text that should be displayed as the title for
+ * an editorial component
+ * @param objectGraph
+ * @param data
+ * @param collectionDisplayStyle
+ */
+export function editorialTitleFromData(objectGraph, data, collectionDisplayStyle) {
+ let editorialTitle = null;
+ let editorialTitlePaths = [];
+ switch (collectionDisplayStyle) {
+ case "EditorialLockupMediumVariant":
+ case "EditorialLockupLargeVariant":
+ switch (data.type) {
+ case "editorial-items":
+ case "editorial-pages":
+ case "editorial-shelves-header":
+ editorialTitlePaths = ["enrichedEditorialNotes.name", "editorialNotes.name"];
+ editorialTitle = attributeAsString(data, editorialTitlePaths);
+ break;
+ case "apps":
+ case "in-apps":
+ case "app-bundles":
+ editorialTitlePaths = ["editorialNotes.tagline"];
+ editorialTitle = findFirstMatchingString(objectGraph, data, editorialTitlePaths, true);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ switch (data.type) {
+ case "editorial-items":
+ case "editorial-pages":
+ case "editorial-shelves-header":
+ editorialTitlePaths = ["enrichedEditorialNotes.name", "editorialNotes.name"];
+ editorialTitle = findFirstMatchingString(objectGraph, data, editorialTitlePaths, false);
+ break;
+ case "tags":
+ case "apps":
+ case "in-apps":
+ case "app-bundles":
+ editorialTitlePaths = ["name"];
+ editorialTitle = findFirstMatchingString(objectGraph, data, editorialTitlePaths, false);
+ break;
+ default:
+ break;
+ }
+ }
+ // Check the editorial card first for override content
+ const editorialCardData = editorialDataUtil.extractEditorialCardData(objectGraph, data);
+ const editorialTitleOverride = findFirstMatchingString(objectGraph, editorialCardData, editorialTitlePaths, false);
+ const title = serverData.isDefinedNonNull(editorialTitleOverride) ? editorialTitleOverride : editorialTitle;
+ if (serverData.isNullOrEmpty(title)) {
+ return null;
+ }
+ return title;
+}
+/**
+ * Get the text that should be displayed as the description for
+ * an editorial component
+ * @param objectGraph
+ * @param data
+ */
+export function editorialDescriptionFromData(objectGraph, data, preferHeroDescription = false) {
+ let editorialDescription = null;
+ let editorialDescriptionPaths = [];
+ switch (data.type) {
+ case "editorial-items":
+ if (preferHeroDescription) {
+ editorialDescriptionPaths = ["enrichedEditorialNotes.tagline", "editorialNotes.tagline"];
+ }
+ else {
+ const ignoreShortNotes = mediaAttributes.attributeAsBooleanOrFalse(data, "ignoreITunesShortNotes");
+ if (!ignoreShortNotes) {
+ editorialDescriptionPaths = ["enrichedEditorialNotes.short", "editorialNotes.short"];
+ }
+ }
+ editorialDescription = findFirstMatchingString(objectGraph, data, editorialDescriptionPaths, false);
+ break;
+ case "editorial-pages":
+ case "editorial-shelves-header":
+ editorialDescriptionPaths = ["enrichedEditorialNotes.tagline", "editorialNotes.tagline"];
+ editorialDescription = findFirstMatchingString(objectGraph, data, editorialDescriptionPaths, false);
+ break;
+ case "apps":
+ case "in-apps":
+ case "app-bundles":
+ editorialDescriptionPaths = ["editorialNotes.tagline"];
+ editorialDescription = findFirstMatchingString(objectGraph, data, editorialDescriptionPaths, true);
+ break;
+ default:
+ break;
+ }
+ // Check the editorial card first for override content
+ const editorialCardData = editorialDataUtil.extractEditorialCardData(objectGraph, data);
+ const editorialDescriptionOverride = findFirstMatchingString(objectGraph, editorialCardData, editorialDescriptionPaths, false);
+ const description = serverData.isDefinedNonNull(editorialDescriptionOverride)
+ ? editorialDescriptionOverride
+ : editorialDescription;
+ if (serverData.isNullOrEmpty(description)) {
+ return null;
+ }
+ return description;
+}
+/**
+ * Get the text that should be displayed as the call to action on a button for
+ * an editorial component
+ * @param objectGraph
+ * @param data
+ */
+export function editorialCallToActionFromData(objectGraph, data) {
+ let editorialCallToAction = null;
+ let editorialCallToActionPaths = [];
+ switch (data.type) {
+ case "editorial-items":
+ case "editorial-pages":
+ case "editorial-shelves-header":
+ editorialCallToActionPaths = ["enrichedEditorialNotes.callToAction", "editorialNotes.callToAction"];
+ editorialCallToAction = findFirstMatchingString(objectGraph, data, editorialCallToActionPaths, false);
+ break;
+ case "apps":
+ case "in-apps":
+ case "app-bundles":
+ editorialCallToActionPaths = ["enrichedEditorialNotes.callToAction", "editorialNotes.callToAction"];
+ editorialCallToAction = findFirstMatchingString(objectGraph, data, editorialCallToActionPaths, true);
+ break;
+ default:
+ break;
+ }
+ // Check the editorial card first for override content
+ const editorialCardData = editorialDataUtil.extractEditorialCardData(objectGraph, data);
+ const editorialCallToActionOverride = findFirstMatchingString(objectGraph, editorialCardData, editorialCallToActionPaths, false);
+ let callToAction = null;
+ if ((editorialCallToActionOverride === null || editorialCallToActionOverride === void 0 ? void 0 : editorialCallToActionOverride.length) > 0) {
+ callToAction = editorialCallToActionOverride;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(editorialCallToAction)) {
+ callToAction = editorialCallToAction;
+ }
+ else {
+ if (preprocessor.GAMES_TARGET) {
+ callToAction = objectGraph.loc.string("Card.Collection.ActionButton.Title");
+ }
+ else {
+ callToAction = objectGraph.loc.string("HERO_CAROUSEL_OVERLAY_BUTTON_TITLE_VIEW");
+ }
+ }
+ return callToAction;
+}
+// region Debug Helpers
+/**
+ * Retrieve the specified attribute from the data as a string.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The object path for the attribute.
+ * @returns {string} The attribute value as a string.
+ */
+function attributeAsString(data, attributePath) {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ return mediaAttributes.attributeAsString(data, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as a string. Returning the path if null or missing
+ *
+ * If the attribute lives under the platform-specific attributes, then a platform may be provided to properly call in to
+ * the nested structure.
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The object path for the attribute.
+ * @returns {string} The attribute value as a string.
+ */
+export function platformAttributeAsString(data, platform, attributePath) {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ return mediaPlatformAttributes.platformAttributeAsString(data, platform, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as a string. Trying to find the best match for the given paths.
+ * @param objectGraph The object graph to use lookups
+ * @param mediaApiData The media data to use to query for the string
+ * @param stringPaths The different paths to try to find the string
+ * @param usePlatformAttributes Whether to check the platform attributes or not
+ * @returns The first matching string or null if none found
+ */
+function findFirstMatchingString(objectGraph, mediaApiData, stringPaths, usePlatformAttributes) {
+ if (serverData.isNullOrEmpty(mediaApiData)) {
+ return null;
+ }
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, mediaApiData);
+ for (const path of stringPaths) {
+ const matchingString = usePlatformAttributes
+ ? platformAttributeAsString(mediaApiData, attributePlatform, path)
+ : attributeAsString(mediaApiData, path);
+ if (serverData.isDefinedNonNullNonEmpty(matchingString)) {
+ return matchingString;
+ }
+ }
+ return null;
+}
+// endregion
+//# sourceMappingURL=editorial-page-editorial-copy-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-hero-util.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-hero-util.js
new file mode 100644
index 0000000..7ecca30
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-hero-util.js
@@ -0,0 +1,88 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as content from "../content/content";
+import * as heroCarouselOverlayCommon from "../grouping/hero/hero-carousel-overlay-common";
+import { deepLinkUrlFromData } from "../linking/external-deep-link";
+import * as lockups from "../lockups/lockups";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as editorialComponentMediaUtil from "./editorial-page-component-media-util";
+import * as editorialCopyUtil from "./editorial-page-editorial-copy-util";
+import * as editorialOverlayContentUtil from "./editorial-page-overlay-content-util";
+import { CollectionShelfDisplayStyle } from "./editorial-page-types";
+// region Overlay
+export function heroOverlayFromData(objectGraph, data, shelfToken, editorialClientParams) {
+ const heroCarouselItemOverlay = new models.HeroCarouselItemOverlay();
+ heroCarouselItemOverlay.overlayType = heroCarouselOverlayCommon.overlayTypeFromData(objectGraph, data);
+ heroCarouselItemOverlay.displayOptions = {
+ horizontalPlacement: "leading",
+ textAlignment: "leading",
+ isOverDarkContent: heroCarouselOverlayCommon.overlayIsOverDarkContent(objectGraph, data),
+ };
+ heroCarouselItemOverlay.badgeText = editorialCopyUtil.editorialBadgeFromData(objectGraph, data, CollectionShelfDisplayStyle.Hero);
+ heroCarouselItemOverlay.titleText = editorialCopyUtil.editorialTitleFromData(objectGraph, data, CollectionShelfDisplayStyle.Hero);
+ if (!editorialClientParams.suppressTagline) {
+ heroCarouselItemOverlay.descriptionText = editorialCopyUtil.editorialDescriptionFromData(objectGraph, data, true);
+ }
+ heroCarouselItemOverlay.callToActionText = editorialCopyUtil.editorialCallToActionFromData(objectGraph, data);
+ heroCarouselItemOverlay.buttonTitle = heroCarouselOverlayCommon.overlayButtonTitleFromData(objectGraph, data);
+ const lockupOptions = {
+ metricsOptions: {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ targetType: "lockupSmall",
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ },
+ metricsClickOptions: {
+ id: data.id,
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ actionDetails: {
+ franchise: heroCarouselItemOverlay.badgeText,
+ },
+ },
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ offerEnvironment: "dark",
+ canDisplayArcadeOfferButton: content.shelfDisplayStyleCanDisplayArcadeOfferButtons(objectGraph, shelfToken.collectionDisplayStyle),
+ isContainedInPreorderExclusiveShelf: false,
+ externalDeepLinkUrl: deepLinkUrlFromData(objectGraph, data),
+ };
+ const appLockup = editorialComponentMediaUtil.editorialAppLockupFromData(objectGraph, data, lockupOptions);
+ const overlayContent = editorialOverlayContentUtil.extractOverlayContent(objectGraph, data, lockupOptions);
+ heroCarouselItemOverlay.lockup = overlayContent.lockup;
+ heroCarouselItemOverlay.collectionIcons = overlayContent.collectionIcons;
+ // If this EI can and should show the expected release date of an arcade app, override the badge.
+ const fallbackLabel = mediaAttributes.attributeAsString(data, "label");
+ // For hero items the design is to use editorial labels instead of "Apple Arcade" so we replace that text here
+ if (serverData.isDefinedNonNullNonEmpty(fallbackLabel) && serverData.isDefinedNonNull(appLockup)) {
+ appLockup.heading = fallbackLabel;
+ }
+ const suppressLockup = isSome(editorialClientParams.suppressLockup) && editorialClientParams.suppressLockup;
+ if (serverData.isDefinedNonNullNonEmpty(heroCarouselItemOverlay.lockup) && !suppressLockup) {
+ heroCarouselItemOverlay.clickAction = heroCarouselItemOverlay.lockup.clickAction;
+ heroCarouselItemOverlay.impressionMetrics = heroCarouselItemOverlay.lockup.impressionMetrics;
+ }
+ else {
+ const heroCarouselItemOverlayMetricsOptions = {
+ targetType: "hero",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ };
+ const heroCarouselOverlayItemClickOptions = {
+ ...heroCarouselItemOverlayMetricsOptions,
+ id: data.id,
+ actionDetails: {
+ franchise: heroCarouselItemOverlay.badgeText,
+ },
+ };
+ heroCarouselItemOverlay.clickAction = lockups.editorialItemActionFromData(objectGraph, data, heroCarouselOverlayItemClickOptions, shelfToken.clientIdentifierOverride);
+ const heroCarouselItemOverlayImpressionsOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, heroCarouselItemOverlay.titleText, heroCarouselItemOverlayMetricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, heroCarouselItemOverlay, heroCarouselItemOverlayImpressionsOptions);
+ }
+ return heroCarouselItemOverlay;
+}
+// endregion
+//# sourceMappingURL=editorial-page-hero-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-intent-controller-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-intent-controller-utils.js
new file mode 100644
index 0000000..e3c2690
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-intent-controller-utils.js
@@ -0,0 +1,5 @@
+import { generateRoutes } from "../util/generate-routes";
+import { makeEditorialPageIntentByID } from "../../api/intents/editorial/editorial-page-intent";
+const { routes: editorialPageRoutes, makeCanonicalUrl: makeEditorialPageURL } = generateRoutes(makeEditorialPageIntentByID, "/{platform}/editorial/{id}");
+export { editorialPageRoutes, makeEditorialPageURL };
+//# sourceMappingURL=editorial-page-intent-controller-utils.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-lockup-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-lockup-utils.js
new file mode 100644
index 0000000..35e1cf9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-lockup-utils.js
@@ -0,0 +1,91 @@
+// region Lockup Creation
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as content from "../content/content";
+import * as adLockups from "../lockups/ad-lockups";
+import * as lockups from "../lockups/lockups";
+import { clickTargetForCollectionDisplayStyle } from "./editorial-action-util";
+import { CollectionShelfFilterOverride } from "./editorial-page-types";
+import { isNothing } from "@jet/environment/types/optional";
+// region Lockup Creation
+/**
+ * Create a lockup for shelfContents to display within an editorial page shelf
+ * @param objectGraph
+ * @param lockupData shelfContents to create lockup for.
+ * @param shelfToken Shelf shelfToken
+ * @param itemPosition The position of the item in the shelf
+ * @param collectionDisplayStyle The collection display style
+ */
+export function lockupFromData(objectGraph, lockupData, shelfToken, itemPosition, collectionDisplayStyle) {
+ if (serverData.isNullOrEmpty(lockupData)) {
+ return null;
+ }
+ // Set the ordinal
+ let ordinalString;
+ if (shelfToken.showOrdinals) {
+ ordinalString = objectGraph.loc.decimal(shelfToken.ordinalIndex);
+ }
+ let offerStyle = null;
+ if (serverData.isDefinedNonNull(shelfToken.shelfBackground) &&
+ (shelfToken.shelfBackground.type === "color" || shelfToken.shelfBackground.type === "interactive")) {
+ offerStyle = "white";
+ }
+ let clientIdentifierOverride;
+ if (serverData.isDefinedNonNullNonEmpty(shelfToken)) {
+ clientIdentifierOverride = shelfToken.clientIdentifierOverride;
+ }
+ const contentMetricsOptions = {
+ ...shelfToken.metricsImpressionOptions,
+ id: lockupData.id,
+ idType: "its_id",
+ };
+ // Create the lockup
+ const lockupOptions = {
+ ordinal: ordinalString,
+ metricsOptions: {
+ ...contentMetricsOptions,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(lockupData),
+ isAdvert: adLockups.isAdvert(objectGraph, lockupData),
+ targetType: clickTargetForCollectionDisplayStyle(objectGraph, collectionDisplayStyle),
+ },
+ clientIdentifierOverride: clientIdentifierOverride,
+ artworkUseCase: 0 /* content.ArtworkUseCase.Default */,
+ offerStyle: offerStyle,
+ canDisplayArcadeOfferButton: content.shelfDisplayStyleCanDisplayArcadeOfferButtons(objectGraph, collectionDisplayStyle),
+ isContainedInPreorderExclusiveShelf: shelfToken.filterOverrides.includes(CollectionShelfFilterOverride.ShowOnlyPreorder),
+ };
+ let lockup;
+ switch (collectionDisplayStyle) {
+ case "EditorialLockupMediumVariant":
+ case "EditorialLockupMedium":
+ case "EditorialLockupLargeVariant":
+ case "EditorialLockupLarge":
+ lockupOptions.offerEnvironment = "light";
+ lockup = lockups.imageLockupFromData(objectGraph, lockupData, lockupOptions, collectionDisplayStyle);
+ break;
+ case "Poster":
+ lockup = lockups.posterLockupFromData(objectGraph, lockupData, lockupOptions);
+ break;
+ default:
+ lockup = lockups.lockupFromData(objectGraph, lockupData, lockupOptions);
+ }
+ if (serverData.isNull(lockup) || !lockup.isValid()) {
+ return null;
+ }
+ lockup.id = lockupIdentifier(shelfToken, itemPosition, lockup.adamId);
+ return lockup;
+}
+/**
+ * @param shelfToken The shelf token
+ * @param itemPosition The position of the item in the shelf
+ * @param adamId The adamID associated with the lockup
+ * @returns A string for uniquely identifying the lockup in this shelf
+ */
+function lockupIdentifier(shelfToken, itemPosition, adamId) {
+ if (isNothing(adamId)) {
+ return null;
+ }
+ return `${shelfToken.id}_${itemPosition}_${adamId}`;
+}
+// endregion
+//# sourceMappingURL=editorial-page-lockup-utils.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-media-api-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-media-api-utils.js
new file mode 100644
index 0000000..42e838a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-media-api-utils.js
@@ -0,0 +1,39 @@
+import * as editorialPageControllerUtil from "./editorial-page-controller-util";
+import { Request as MediaAPIRequest, defaultAdditionalPlatformsForClient } from "../../foundation/media/data-fetching";
+import { Parameters } from "../../foundation/network/url-constants";
+import { shouldFetchCustomAttributes } from "../product-page/product-page-variants";
+import { isEditorialPageIntentByName, } from "../../api/intents/editorial/editorial-page-intent";
+import { setPreviewPlatform } from "../preview-platform";
+export function makeEditorialPageRequest(objectGraph, intent) {
+ const mediaApiRequest = makeBaseMediaAPIRequest(objectGraph, intent);
+ if (isEditorialPageIntentByName(intent)) {
+ mediaApiRequest.addingQuery(Parameters.name, intent.name);
+ }
+ else {
+ mediaApiRequest.withIdOfType(intent.id, "editorial-pages");
+ }
+ prepareMediaAPIRequest(objectGraph, mediaApiRequest);
+ setPreviewPlatform(objectGraph, mediaApiRequest);
+ return mediaApiRequest;
+}
+function makeBaseMediaAPIRequest(objectGraph, locale) {
+ return new MediaAPIRequest(objectGraph, `/v1/editorial/${locale.storefront}`).forType("editorial-pages");
+}
+function prepareMediaAPIRequest(objectGraph, mediaApiRequest) {
+ mediaApiRequest.includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph));
+ mediaApiRequest.usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ // <rdar://53420717> For performance reasons, we limit shelves to 3 items on watchOS.
+ if (objectGraph.client.isWatch) {
+ mediaApiRequest.addingRelationshipLimit("contents", 3);
+ }
+ mediaApiRequest
+ .includingAgeRestrictions()
+ .includingScopedRelationships("editorial-shelves", ["contents"])
+ .includingScopedRelationships("editorial-pages", ["primary-contents"])
+ .includingRelationships(["canvas"])
+ .withSparseCount(editorialPageControllerUtil.pageSparseCount(objectGraph))
+ .withSparseLimit(editorialPageControllerUtil.pageSparseLimit(objectGraph))
+ .includingAssociateKeys("editorial-shelves-collection:contents", ["editorial-cards"]);
+ editorialPageControllerUtil.prepareMAPIRequest(objectGraph, mediaApiRequest);
+}
+//# sourceMappingURL=editorial-page-media-api-utils.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-overlay-content-util.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-overlay-content-util.js
new file mode 100644
index 0000000..f76d85a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-overlay-content-util.js
@@ -0,0 +1,20 @@
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { popLocation, pushBasicLocation } from "../metrics/helpers/location";
+import * as editorialComponentMediaUtil from "./editorial-page-component-media-util";
+export function extractOverlayContent(objectGraph, itemData, lockupOptions) {
+ const overlayContent = {};
+ pushBasicLocation(objectGraph, lockupOptions.metricsOptions, "collectionIcons");
+ const collectionIcons = editorialComponentMediaUtil.editorialFallbackAppIconsFromData(objectGraph, itemData, lockupOptions);
+ popLocation(lockupOptions.metricsOptions.locationTracker);
+ const appLockup = editorialComponentMediaUtil.editorialAppLockupFromData(objectGraph, itemData, lockupOptions);
+ if (serverData.isDefinedNonNullNonEmpty(collectionIcons) &&
+ (collectionIcons.length > 1 || serverData.isNullOrEmpty(appLockup))) {
+ overlayContent.collectionIcons = collectionIcons;
+ }
+ else if (serverData.isDefinedNonNull(appLockup)) {
+ overlayContent.lockup = appLockup;
+ overlayContent.collectionIcons = [appLockup.icon];
+ }
+ return overlayContent;
+}
+//# sourceMappingURL=editorial-page-overlay-content-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-arcade-lockup-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-arcade-lockup-builder.js
new file mode 100644
index 0000000..edb539c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-arcade-lockup-builder.js
@@ -0,0 +1,17 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as arcadeCommon from "../../arcade/arcade-common";
+import * as lockups from "../../lockups/lockups";
+export function buildArcadeLockup(objectGraph, upsellBreakoutShelfToken) {
+ const metricsOptions = {
+ pageInformation: upsellBreakoutShelfToken.metricsPageInformation,
+ locationTracker: upsellBreakoutShelfToken.metricsLocationTracker,
+ };
+ const upsellData = arcadeCommon.upsellFromRelationshipOf(objectGraph, upsellBreakoutShelfToken.data);
+ if (serverData.isNullOrEmpty(upsellData)) {
+ return null;
+ }
+ const arcadeLockup = lockups.arcadeLockupFromData(objectGraph, upsellData, metricsOptions, models.marketingItemContextFromString("arcadeTabHeader"), "colored", objectGraph.client.isMac ? "arcade" : "navigationBar");
+ return arcadeLockup;
+}
+//# sourceMappingURL=editorial-page-arcade-lockup-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-arcade-see-all-games-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-arcade-see-all-games-shelf-builder.js
new file mode 100644
index 0000000..a197c19
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-arcade-see-all-games-shelf-builder.js
@@ -0,0 +1,71 @@
+import * as arcadeCommon from "../../arcade/arcade-common";
+import * as color from "../../../foundation/util/color-util";
+import * as content from "../../content/content";
+import * as editorialDataUtil from "../editorial-data-util";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as gamesComponentBuilder from "../../../gameservicesui/src/editorial-page/editorial-component-builder";
+export function buildArcadeSeeAllGamesShelf(objectGraph, shelfToken) {
+ const footer = new models.ArcadeFooter();
+ const shelf = new models.Shelf("arcadeFooter");
+ shelf.items = [footer];
+ const impressionOptions = {
+ targetType: "arcadeSeeAllGamesFooter",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ title: objectGraph.loc.string("Arcade.SeeAllGames.Button.Title"),
+ id: shelfToken.id,
+ kind: "footer",
+ softwareType: "Arcade",
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, footer, impressionOptions);
+ metricsHelpersLocation.pushContentLocation(objectGraph, impressionOptions, impressionOptions.title);
+ if (preprocessor.GAMES_TARGET) {
+ footer.buttonAction = gamesComponentBuilder.makeArcadeSeeAllAction(objectGraph);
+ }
+ else {
+ footer.buttonAction = arcadeCommon.seeAllArcadeGamesPageFlowAction(objectGraph, "releaseDate", shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker);
+ }
+ const buttonImpressionOptions = {
+ targetType: "button",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ title: footer.buttonAction.title,
+ id: "SeeAllGames",
+ kind: "button",
+ softwareType: "Arcade",
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, footer.buttonAction, buttonImpressionOptions);
+ metricsHelpersLocation.popLocation(impressionOptions.locationTracker);
+ const termsAndConditionsUrl = objectGraph.bag.termsAndConditionsURL;
+ const shouldAddTermsAndConditions = !serverData.isNull(termsAndConditionsUrl) && objectGraph.client.deviceType !== "tv";
+ if (shouldAddTermsAndConditions) {
+ const termsAndConditionTitle = objectGraph.loc.string("TermsAndConditions.Title");
+ const urlAction = new models.ExternalUrlAction(termsAndConditionsUrl);
+ const footnote = new models.Footnote(termsAndConditionTitle);
+ footnote.clickAction = urlAction;
+ footnote.presentationStyle = ["hasChevron", "textLightensOnHighlight", "hasSeparator"];
+ footer.footnote = footnote;
+ }
+ shelf.background = {
+ type: "color",
+ color: color.named("placeholderBackground"),
+ };
+ const shelfContents = editorialDataUtil.extractRemainingItemsOrInitialShelfContents(objectGraph, shelfToken);
+ if (serverData.isDefinedNonNullNonEmpty(shelfContents)) {
+ const metricsOptions = {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ };
+ footer.icons = content.impressionableAppIconsFromDataCollection(objectGraph, shelfContents, metricsOptions, {
+ useCase: 2 /* content.ArtworkUseCase.LockupIconMedium */,
+ });
+ }
+ else {
+ footer.icons = [];
+ }
+ return shelf;
+}
+//# sourceMappingURL=editorial-page-arcade-see-all-games-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-chart-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-chart-shelf-builder.js
new file mode 100644
index 0000000..58e6862
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-chart-shelf-builder.js
@@ -0,0 +1,106 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../api/models";
+import { attributeAsString } from "../../../foundation/media/attributes";
+import { chartUrlFromData } from "../../content/content";
+import { addClickEventToSeeAllAction } from "../../metrics/helpers/clicks";
+import { insertPlaceholdersIntoShelfIfRequired } from "../../placeholders/placeholders";
+import { CollectionShelfLayoutDirection } from "../editorial-page-types";
+import { createShelfTokenUrlIfNecessary } from "./editorial-page-collection-shelf-builder/editorial-page-collection-shelf-common";
+import { buildLockupChartShelf, buildLockupSmallShelf, } from "./editorial-page-collection-shelf-builder/editorial-page-lockup-collection-shelf-builder";
+import { viewChartListActionWithGames } from "../../../gameservicesui/src/intent-controllers/page/charts-list-page-intent-controller";
+import * as editorialDataUtil from "../editorial-data-util";
+import { makeGamesFromCollection } from "../../../gameservicesui/src/models/cards/collection-card";
+export function buildChartShelf(objectGraph, shelfToken) {
+ var _a;
+ shelfToken.showOrdinals = true;
+ let shelf;
+ if (preprocessor.GAMES_TARGET) {
+ shelf = buildLockupChartShelf(objectGraph, shelfToken);
+ }
+ else {
+ /// Currently the charts shelf doesn't return a `displayStyle`, we only support LockupSmall.
+ shelf = buildLockupSmallShelf(objectGraph, shelfToken, false, CollectionShelfLayoutDirection.Horizontal);
+ }
+ if (isNothing(shelf)) {
+ return undefined;
+ }
+ shelf.url = createShelfTokenUrlIfNecessary(objectGraph, shelf, shelfToken);
+ insertPlaceholdersIntoShelfIfRequired(objectGraph, shelf, shelfToken);
+ if (preprocessor.GAMES_TARGET) {
+ if (isSome(shelf.header)) {
+ shelf.header.titleAction = (_a = chartPageSeeAllActionForGames(objectGraph, shelfToken)) !== null && _a !== void 0 ? _a : undefined;
+ }
+ else {
+ shelf.seeAllAction = chartPageSeeAllActionForGames(objectGraph, shelfToken);
+ }
+ shelf.id = `shelf_${shelfToken.id}`;
+ }
+ else {
+ replaceShelfSeeAllAction(objectGraph, shelf, chartPageSeeAllAction(objectGraph, shelfToken));
+ }
+ return shelf;
+}
+function chartPageSeeAllActionForGames(objectGraph, shelfToken) {
+ var _a;
+ const gamesData = editorialDataUtil.extractRemainingItemsOrInitialShelfContents(objectGraph, shelfToken);
+ const games = makeGamesFromCollection(objectGraph, gamesData, {}, {});
+ const genre = attributeAsString(shelfToken.data, "genre");
+ const chart = attributeAsString(shelfToken.data, "chart");
+ if (isSome(games) && isSome(genre) && isSome(chart)) {
+ const chartUrl = chartUrlFromData(objectGraph, genre, chart);
+ return viewChartListActionWithGames(games, shelfToken.title, (_a = shelfToken.subtitle) !== null && _a !== void 0 ? _a : "", chartUrl, shelfToken.data.id, // TODO: provide real `reco_id` rdar://133659618 ([Play Now Feed] Use metrics.meta.reco_id from shelf meta in metrics)
+ objectGraph);
+ }
+ return null;
+}
+function chartPageSeeAllAction(objectGraph, shelfToken) {
+ const genre = attributeAsString(shelfToken.data, "genre");
+ const chart = attributeAsString(shelfToken.data, "chart");
+ const action = new models.FlowAction("page");
+ action.pageUrl = chartUrlFromData(objectGraph, genre, chart);
+ action.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ action.referrerUrl = shelfToken.metricsPageInformation.pageUrl;
+ addClickEventToSeeAllAction(objectGraph, action, action.pageUrl, {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ });
+ return action;
+}
+/**
+ * Update the shelf header to use the provided seeAll action.
+ * @param objectGraph The App Store object graph used to check feature flags
+ * @param shelfHeader The shelf header to update
+ * @param seeAllAction The "See All" action to apply
+ */
+export function replaceShelfHeaderSeeAllAction(objectGraph, shelfHeader, seeAllAction) {
+ if (objectGraph.featureFlags.isEnabled("shelf_header")) {
+ // Modern headers make title tappable with a chevron (>).
+ shelfHeader.titleAction = seeAllAction;
+ }
+ else {
+ // Legacy headers show "See All" textual button on trailing edge.
+ shelfHeader.accessoryAction = seeAllAction;
+ }
+}
+/**
+ * Update the shelf header to use the provided seeAll action.
+ * @param objectGraph The App Store object graph used to check feature flags
+ * @param shelf The shelf whose header needs updating
+ * @param seeAllAction The see all action to apply
+ */
+export function replaceShelfSeeAllAction(objectGraph, shelf, seeAllAction) {
+ if (objectGraph.featureFlags.isEnabled("shelf_header")) {
+ if (isSome(shelf.header)) {
+ replaceShelfHeaderSeeAllAction(objectGraph, shelf.header, seeAllAction);
+ }
+ else {
+ shelf.header = {
+ titleAction: seeAllAction,
+ };
+ }
+ }
+ else {
+ shelf.seeAllAction = seeAllAction;
+ }
+}
+//# sourceMappingURL=editorial-page-chart-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-breakout-large-collection-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-breakout-large-collection-shelf-builder.js
new file mode 100644
index 0000000..093336d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-breakout-large-collection-shelf-builder.js
@@ -0,0 +1,94 @@
+import * as models from "../../../../api/models";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../../foundation/media/data-structure";
+import * as metricsHelpersImpressions from "../../../metrics/helpers/impressions";
+import * as editorialActionUtil from "../../editorial-action-util";
+import * as editorialDataUtil from "../../editorial-data-util";
+import * as editorialComponentMediaUtil from "../../editorial-page-component-media-util";
+import * as editorialCopyUtil from "../../editorial-page-editorial-copy-util";
+import * as editorialOverlayContentUtil from "../../editorial-page-overlay-content-util";
+import { createShelfTokenUrlIfNecessary, shouldDefer } from "./editorial-page-collection-shelf-common";
+import { deepLinkUrlFromData } from "../../../linking/external-deep-link";
+import { isMediaDark } from "../../editorial-media-util";
+import { insertPlaceholdersIntoShelfIfRequired } from "../../../placeholders/placeholders";
+export function buildBreakoutLargeShelf(objectGraph, shelfToken) {
+ const items = [];
+ const shelf = new models.Shelf("largeHeroBreakout");
+ shelf.isHorizontal = mediaAttributes.attributeAsString(shelfToken.data, "layoutDirection") === "Horizontal";
+ const shelfContents = editorialDataUtil.extractRemainingItemsOrInitialShelfContents(objectGraph, shelfToken);
+ for (const itemData of shelfContents) {
+ const primaryItemData = editorialDataUtil.extractHydratedPrimaryContentData(objectGraph, itemData);
+ if ((editorialDataUtil.requiresPrimaryContent(objectGraph, itemData) &&
+ !mediaAttributes.hasAttributes(primaryItemData)) ||
+ shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(itemData);
+ continue;
+ }
+ const metricsOptions = {
+ targetType: "largeBreakout",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ };
+ const editorialClientParams = editorialDataUtil.extractEditorialClientParams(objectGraph, itemData);
+ const hideEditorialMedia = serverData.asBooleanOrFalse(editorialClientParams.hideEditorialMedia);
+ const mediaData = !hideEditorialMedia
+ ? editorialComponentMediaUtil.editorialMediaDataFromData(objectGraph, itemData, shelfToken.collectionDisplayStyle)
+ : null;
+ const lockupOptions = {
+ metricsOptions,
+ clientIdentifierOverride: shelfToken.clientIdentifierOverride,
+ artworkUseCase: 0 /* content.ArtworkUseCase.Default */,
+ offerEnvironment: "lightOverArtwork",
+ canDisplayArcadeOfferButton: true,
+ externalDeepLinkUrl: deepLinkUrlFromData(objectGraph, itemData),
+ };
+ const overlayContent = editorialOverlayContentUtil.extractOverlayContent(objectGraph, itemData, lockupOptions);
+ if (serverData.isNullOrEmpty(mediaData) && serverData.isNullOrEmpty(overlayContent.collectionIcons)) {
+ continue;
+ }
+ const badgeText = editorialCopyUtil.editorialBadgeFromData(objectGraph, itemData, shelfToken.collectionDisplayStyle);
+ const badgeType = serverData.isDefinedNonNull(badgeText)
+ ? { type: "text", title: badgeText }
+ : { type: "none", title: null };
+ const largeBreakoutDetails = new models.BreakoutDetails(editorialCopyUtil.editorialTitleFromData(objectGraph, itemData, shelfToken.collectionDisplayStyle), editorialCopyUtil.editorialDescriptionFromData(objectGraph, itemData, true), badgeType, null);
+ const largeBreakout = new models.LargeHeroBreakout(largeBreakoutDetails, { position: "leading", wantsBlur: true }, null, mediaData === null || mediaData === void 0 ? void 0 : mediaData.artwork, mediaData === null || mediaData === void 0 ? void 0 : mediaData.video, overlayContent.collectionIcons, mediaData === null || mediaData === void 0 ? void 0 : mediaData.backgroundColor);
+ largeBreakout.rtlArtwork = mediaData === null || mediaData === void 0 ? void 0 : mediaData.rtlArtwork;
+ largeBreakout.rtlVideo = mediaData === null || mediaData === void 0 ? void 0 : mediaData.rtlVideo;
+ largeBreakout.rtlBackgroundColor = mediaData === null || mediaData === void 0 ? void 0 : mediaData.rtlBackgroundColor;
+ // Configure impressions
+ const contentMetricsOptions = {
+ ...metricsOptions,
+ id: itemData.id,
+ idType: "its_contentId",
+ };
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, itemData, largeBreakout.details.title, contentMetricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, largeBreakout, impressionOptions);
+ const breakoutAction = editorialActionUtil.createPrimaryActionForComponentFromData(objectGraph, itemData, shelfToken);
+ if (serverData.isDefinedNonNull(breakoutAction)) {
+ breakoutAction.title = editorialCopyUtil.editorialCallToActionFromData(objectGraph, itemData);
+ }
+ largeBreakout.details.callToActionButtonAction = breakoutAction;
+ largeBreakout.clickAction = breakoutAction;
+ largeBreakout.editorialDisplayOptions =
+ editorialDataUtil.editorialDisplayOptionsFromClientParams(editorialClientParams);
+ largeBreakout.lockup = overlayContent.lockup;
+ largeBreakout.isMediaDark = isMediaDark(objectGraph, mediaData);
+ largeBreakout.isRTLMediaDark = isMediaDark(objectGraph, mediaData, true);
+ items.push(largeBreakout);
+ }
+ // We don't need this in our incomplete shelf URL, so we'll preemptively remove it.
+ delete shelfToken.maxItemCount;
+ shelf.eyebrow = shelfToken.eyebrow;
+ shelf.eyebrowArtwork = shelfToken.eyebrowArtwork;
+ shelf.title = shelfToken.title;
+ shelf.subtitle = shelfToken.subtitle;
+ shelf.items = items;
+ shelf.rowsPerColumn = mediaAttributes.attributeAsNumber(shelfToken.data, "rowCount");
+ shelf.url = createShelfTokenUrlIfNecessary(objectGraph, shelf, shelfToken);
+ insertPlaceholdersIntoShelfIfRequired(objectGraph, shelf, shelfToken);
+ return shelf;
+}
+//# sourceMappingURL=editorial-page-breakout-large-collection-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-brick-collection-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-brick-collection-shelf-builder.js
new file mode 100644
index 0000000..cefe63e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-brick-collection-shelf-builder.js
@@ -0,0 +1,146 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as models from "../../../../api/models";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../../foundation/media/data-structure";
+import * as content from "../../../content/content";
+import * as metricsHelpersImpressions from "../../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../../metrics/helpers/location";
+import * as editorialActionUtil from "../../editorial-action-util";
+import * as editorialDataUtil from "../../editorial-data-util";
+import * as editorialMediaUtil from "../../editorial-page-component-media-util";
+import * as editorialCopyUtil from "../../editorial-page-editorial-copy-util";
+import { CollectionShelfDisplayStyle } from "../../editorial-page-types";
+import { createShelfTokenUrlIfNecessary, shouldDefer } from "./editorial-page-collection-shelf-common";
+import { deepLinkUrlFromData } from "../../../linking/external-deep-link";
+import { isMediaDark } from "../../editorial-media-util";
+import { insertPlaceholdersIntoShelfIfRequired } from "../../../placeholders/placeholders";
+import { convertToShelfModel, makeShelfId, } from "../../../../gameservicesui/src/editorial-page/editorial-component-builder";
+import { createSmallBrickFromData } from "../../../../gameservicesui/src/models/cards/small-brick";
+export function buildBrickSmallShelf(objectGraph, shelfToken) {
+ return buildBrickShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.BrickSmall, "smallBrick");
+}
+export function buildBrickMediumShelf(objectGraph, shelfToken) {
+ return buildBrickShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.BrickMedium, "brick");
+}
+export function buildBrickLargeShelf(objectGraph, shelfToken) {
+ return buildBrickShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.BrickLarge, "largeBrick");
+}
+// region Brick Creation
+function buildBrickShelf(objectGraph, shelfToken, displayStyle, brickComponentType) {
+ const items = [];
+ const shelf = new models.Shelf(brickComponentType);
+ shelf.isHorizontal = mediaAttributes.attributeAsString(shelfToken.data, "layoutDirection") === "Horizontal";
+ const shelfContents = editorialDataUtil.extractRemainingItemsOrInitialShelfContents(objectGraph, shelfToken);
+ for (const itemData of shelfContents) {
+ const metricsOptions = {
+ ...shelfToken.metricsImpressionOptions,
+ targetType: editorialActionUtil.clickTargetForCollectionDisplayStyle(objectGraph, displayStyle),
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ };
+ if (!itemData.attributes || shouldDefer(shelfToken)) {
+ shelfToken.remainingItems.push(itemData);
+ shelfToken.isDeferring = true;
+ continue;
+ }
+ if (preprocessor.GAMES_TARGET && displayStyle === CollectionShelfDisplayStyle.BrickSmall) {
+ const clickAction = editorialActionUtil.createPrimaryActionForComponentFromData(objectGraph, itemData, shelfToken);
+ const smallBrick = createSmallBrickFromData(objectGraph, itemData, makeShelfId(shelfToken), clickAction, {}, {}, true);
+ if (isNothing(smallBrick)) {
+ continue;
+ }
+ items.push(convertToShelfModel(smallBrick));
+ }
+ else {
+ const brick = buildBrick(objectGraph, itemData, shelfToken.collectionDisplayStyle, metricsOptions, metricsOptions, shelfToken.clientIdentifierOverride);
+ brick.clickAction = editorialActionUtil.createPrimaryActionForComponentFromData(objectGraph, itemData, shelfToken);
+ if (!brick.isValid()) {
+ continue;
+ }
+ items.push(brick);
+ }
+ metricsHelpersLocation.nextPosition(shelfToken.metricsImpressionOptions.locationTracker);
+ }
+ if (serverData.isDefinedNonNull(shelfToken.presentationHints)) {
+ shelf.presentationHints = shelfToken.presentationHints;
+ }
+ // We don't need this in our incomplete shelf URL, so we'll preemptively remove it.
+ delete shelfToken.maxItemCount;
+ shelf.eyebrow = shelfToken.eyebrow;
+ shelf.eyebrowArtwork = shelfToken.eyebrowArtwork;
+ shelf.title = shelfToken.title;
+ shelf.subtitle = shelfToken.subtitle;
+ shelf.items = items;
+ shelf.rowsPerColumn = mediaAttributes.attributeAsNumber(shelfToken.data, "rowCount");
+ shelf.url = createShelfTokenUrlIfNecessary(objectGraph, shelf, shelfToken);
+ insertPlaceholdersIntoShelfIfRequired(objectGraph, shelf, shelfToken);
+ return shelf;
+}
+/**
+ * Creates a Brick from the input data.
+ * Note: the clickAction will need to be assigned post-creation.
+ *
+ * @param objectGraph Current object graph
+ * @param data The data for the Brick
+ * @param collectionDisplayStyle The desired display style for the brick
+ * @param metricsOptions Metrics options for impressions
+ * @param lockupMetricsOptions Metrics options for the collection icon lockups
+ * @param clientIdentifierOverride Any preferred client identifier override
+ * @returns A built Brick object
+ */
+export function buildBrick(objectGraph, data, collectionDisplayStyle, metricsOptions, lockupMetricsOptions, clientIdentifierOverride) {
+ const brick = new models.Brick();
+ const editorialClientParams = editorialDataUtil.extractEditorialClientParams(objectGraph, data);
+ const hideEditorialMedia = serverData.asBooleanOrFalse(editorialClientParams.hideEditorialMedia);
+ if (!hideEditorialMedia) {
+ const editorialMediaData = editorialMediaUtil.editorialMediaDataFromData(objectGraph, data, collectionDisplayStyle);
+ if (isSome(editorialMediaData === null || editorialMediaData === void 0 ? void 0 : editorialMediaData.artwork)) {
+ brick.artworks = [editorialMediaData.artwork];
+ brick.isMediaDark = isMediaDark(objectGraph, editorialMediaData);
+ }
+ if (isSome(editorialMediaData === null || editorialMediaData === void 0 ? void 0 : editorialMediaData.rtlArtwork)) {
+ brick.rtlArtwork = editorialMediaData === null || editorialMediaData === void 0 ? void 0 : editorialMediaData.rtlArtwork;
+ brick.isRTLMediaDark = isMediaDark(objectGraph, editorialMediaData, true);
+ }
+ }
+ const lockupOptions = {
+ metricsOptions: lockupMetricsOptions,
+ clientIdentifierOverride: clientIdentifierOverride,
+ artworkUseCase: 0 /* content.ArtworkUseCase.Default */,
+ canDisplayArcadeOfferButton: true,
+ externalDeepLinkUrl: deepLinkUrlFromData(objectGraph, data),
+ };
+ metricsHelpersLocation.pushBasicLocation(objectGraph, lockupOptions.metricsOptions, "collectionIcons");
+ brick.collectionIcons = editorialMediaUtil.editorialFallbackAppIconsFromData(objectGraph, data, lockupOptions);
+ metricsHelpersLocation.popLocation(lockupOptions.metricsOptions.locationTracker);
+ if (serverData.isNullOrEmpty(brick.collectionIcons)) {
+ const primaryAppIcon = content.iconFromData(objectGraph, data, {
+ useCase: 0 /* content.ArtworkUseCase.Default */,
+ });
+ if (serverData.isDefinedNonNullNonEmpty(primaryAppIcon)) {
+ brick.collectionIcons = [primaryAppIcon];
+ }
+ }
+ brick.caption = editorialCopyUtil.editorialBadgeFromData(objectGraph, data, collectionDisplayStyle);
+ brick.title = editorialCopyUtil.editorialTitleFromData(objectGraph, data, collectionDisplayStyle);
+ brick.subtitle = editorialCopyUtil.editorialDescriptionFromData(objectGraph, data);
+ brick.accessibilityLabel = brick.title;
+ brick.editorialDisplayOptions = editorialDataUtil.editorialDisplayOptionsFromClientParams(editorialClientParams);
+ // Configure impressions
+ const metricsIdType = serverData.isDefinedNonNullNonEmpty(brick.artworks)
+ ? "its_contentId"
+ : "collection_id";
+ const contentMetricsOptions = {
+ ...metricsOptions,
+ id: data.id,
+ idType: metricsIdType,
+ };
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, brick.title, contentMetricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, brick, impressionOptions);
+ // Safe area
+ brick.artworkSafeArea = models.ChartOrCategorySafeArea.defaultTileArtworkSafeArea;
+ brick.textSafeArea = models.ChartOrCategorySafeArea.defaultTileTextSafeArea;
+ return brick;
+}
+// endregion
+//# sourceMappingURL=editorial-page-brick-collection-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-collection-shelf-common.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-collection-shelf-common.js
new file mode 100644
index 0000000..5af39c4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-collection-shelf-common.js
@@ -0,0 +1,248 @@
+import { buildEditorialShelf } from "..";
+import * as models from "../../../../api/models";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import { Parameters, Path, Protocol } from "../../../../foundation/network/url-constants";
+import * as urls from "../../../../foundation/network/urls";
+import * as mediaUrlMapping from "../../../builders/url-mapping";
+import * as metricsHelpersClicks from "../../../metrics/helpers/clicks";
+import * as metricsHelpersLocation from "../../../metrics/helpers/location";
+import { createLocationTrackerCopy, setCurrentPosition } from "../../../metrics/helpers/location";
+import * as placeholders from "../../../placeholders/placeholders";
+import { createBaseShelfToken } from "../../editorial-page-shelf-token";
+import { collectionDisplayStyleOverride, CollectionShelfDisplayStyle, CollectionShelfFilterOverride, EditorialShelfType, } from "../../editorial-page-types";
+import { isNothing, isSome } from "@jet/environment";
+import * as gamesComponentBuilder from "../../../../gameservicesui/src/editorial-page/editorial-component-builder";
+import { makeEditorialShelfCollectionPageIntent } from "../../../../api/intents/editorial/editorial-shelf-collection-page-intent";
+import { makeEditorialShelfCollectionPageURL } from "../../editorial-shelf-collection-page-utils";
+import { getLocale } from "../../../locale";
+import { getPlatform } from "../../../preview-platform";
+import { collectionShelfDisplayStyleFromShelfData } from "../../editorial-data-util";
+// region Shelf Tokens
+/**
+ * Whether we should defer building the rest of a shelf given a shelf token
+ * @param token
+ */
+export function shouldDefer(token) {
+ return token && token.isDeferring && token.isFirstRender;
+}
+// endregion
+// region Shelf Urls
+/**
+ * Configure `url` on a standard editorial page shelf if it needs to fetch more content.
+ * @param objectGraph
+ * @param shelf The shelf we'll be adding the url to
+ * @param shelfToken Token to encode in URL for subsequent fetch.
+ */
+export function createShelfTokenUrlIfNecessary(objectGraph, shelf, shelfToken) {
+ if (serverData.isNullOrEmpty(shelfToken)) {
+ return null;
+ }
+ if (objectGraph.client.isWeb) {
+ return null;
+ }
+ const shouldAddFirstRenderUrl = shelfToken.remainingItems.length || shelfToken.recommendationsHref || shelfToken.onDeviceRecommendationsUseCase;
+ if (shouldAddFirstRenderUrl && shelfToken.isFirstRender) {
+ return editorialPageShelfUrl(shelfToken);
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Updates a shelf URL based on the provided token.
+ * @param objectGraph Current object graph
+ * @param shelf Shelf to add url to.
+ * @param token Token to encode in the url.
+ */
+function updateShelfTokenUrlWithNewShelfToken(objectGraph, shelf, shelfToken) {
+ const originalShelfUrl = urls.URL.from(shelf.url);
+ const updatedUrl = urls.URL.from(createShelfTokenUrlIfNecessary(objectGraph, shelf, shelfToken));
+ // Add missing query params to the updated URL from the original.
+ for (const key of Object.keys(originalShelfUrl.query)) {
+ if (serverData.isNull(updatedUrl.query[key])) {
+ updatedUrl.query[key] = originalShelfUrl.query[key];
+ }
+ }
+ // Finally, update the shelf's URL.
+ shelf.url = updatedUrl.build();
+}
+/**
+ * Stores the impression metrics for a placeholder shelf in the shelf token.
+ * @param objectGraph Current object graph
+ * @param shelf The shelf
+ * @param shelfToken The shelf token
+ */
+export function storePlaceholderShelfImpressionMetrics(objectGraph, shelf, shelfToken) {
+ if (isNothing(shelf.url) || isNothing(shelf.impressionMetrics) || !shelfToken.showingPlaceholders) {
+ return;
+ }
+ const originalShelfUrlString = shelf.url;
+ try {
+ // Extract the token from the URL.
+ // Note: Although we have access to the shelfToken here, we do not know that
+ // the url was constructed from token in its current state. To be safe,
+ // if not efficient, we reverse engineer the URL to get the token.
+ const originalShelfUrl = urls.URL.from(originalShelfUrlString);
+ const encodedToken = originalShelfUrl.query[Parameters.token];
+ const shelfTokenFromUrl = JSON.parse(decodeURIComponent(encodedToken));
+ // Modify the token to include the impressions metrics.
+ shelfTokenFromUrl.originalPlaceholderShelfImpressionMetrics = shelf.impressionMetrics;
+ updateShelfTokenUrlWithNewShelfToken(objectGraph, shelf, shelfTokenFromUrl);
+ }
+ catch {
+ shelf.url = originalShelfUrlString;
+ }
+}
+/**
+ * // Merge the original impression metrics with the newly created impression metrics.
+ * @param objectGraph Current object graph
+ * @param shelf The hydrated shelf
+ * @param shelfToken The shelf token
+ */
+export function mergeShelfImpressionMetricsWithPlaceholders(objectGraph, shelf, shelfToken) {
+ if (serverData.isNullOrEmpty(shelfToken.originalPlaceholderShelfImpressionMetrics)) {
+ return;
+ }
+ // If the `shelf.impressionMetrics` is null, we just defer to the original metrics.
+ if (serverData.isNull(shelf.impressionMetrics)) {
+ shelf.impressionMetrics = shelfToken.originalPlaceholderShelfImpressionMetrics;
+ }
+ else {
+ for (const key in shelfToken.originalPlaceholderShelfImpressionMetrics.fields) {
+ if (Object.prototype.hasOwnProperty.call(shelfToken.originalPlaceholderShelfImpressionMetrics.fields, key)) {
+ shelf.impressionMetrics.fields[key] = shelfToken.originalPlaceholderShelfImpressionMetrics.fields[key];
+ }
+ }
+ }
+}
+/**
+ * Configure `url` on a standard editorial page shelf if it needs to fetch more content.
+ * @param objectGraph
+ * @param shelf The shelf we'll be adding the url to
+ * @param shelfToken Token to encode in URL for subsequent fetch.
+ */
+export function createShelfSeeAllActionIfNecessary(objectGraph, shelf, shelfToken) {
+ if (serverData.isNullOrEmpty(shelfToken) || serverData.isNullOrEmpty(shelf)) {
+ return null;
+ }
+ if (shouldShowSeeAll(shelfToken)) {
+ const action = new models.FlowAction("page");
+ // The web client makes a separate request for See All pages, so we don't sidepack for web.
+ if (!placeholders.isPlaceholderShelf(shelf) && !objectGraph.client.isWeb) {
+ const contentShelfId = `shelf_${shelfToken.id}`;
+ action.pageData = createSeeAllPage(objectGraph, shelfToken, shelf.contentType, contentShelfId, isSome(shelfToken.recommendationsHref));
+ }
+ const additionalPageUrlQueryParams = {};
+ if (shelfToken.type === EditorialShelfType.Recommendations) {
+ additionalPageUrlQueryParams[Parameters.editorialPageId] = shelfToken.pageId;
+ }
+ if (objectGraph.client.isWeb) {
+ const destinationIntent = makeEditorialShelfCollectionPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: shelfToken.id,
+ });
+ action.destination = destinationIntent;
+ action.pageUrl = makeEditorialShelfCollectionPageURL(objectGraph, destinationIntent);
+ }
+ else {
+ action.pageUrl = mediaUrlMapping.hrefToRoutableUrl(objectGraph, shelfToken.data.href, additionalPageUrlQueryParams);
+ }
+ action.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ action.referrerUrl = shelfToken.metricsPageInformation.pageUrl;
+ // This can be called after we've gone through and added items to a shelf, meaning the locationPosition
+ // would have been incremented to the number of shelf items, the see all should always have a position of
+ // 0 so we reset it here and use that, without mucking with the active location tracker.
+ const resetLocationTracker = createLocationTrackerCopy(shelfToken.metricsLocationTracker);
+ setCurrentPosition(resetLocationTracker, 0);
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, action, shelfToken.seeAllUrl, {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: resetLocationTracker,
+ });
+ return action;
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Determine whether lockup shelves should show "See All" on given platforms.
+ *
+ * @param shelfToken Token to encode in URL for subsequent fetch.
+ * @return Whether or not a lockup shelf with given properties should have a "See All" action.
+ */
+export function shouldShowSeeAll(shelfToken) {
+ if (shelfToken.isSeeAll) {
+ return false;
+ }
+ switch (shelfToken.collectionDisplayStyle) {
+ case CollectionShelfDisplayStyle.BreakoutLarge:
+ case CollectionShelfDisplayStyle.Poster:
+ return false;
+ default:
+ break;
+ }
+ // On all other platforms, we always to attach "See All", except AppStore_ComingSoon,
+ // which should not because the see all page won't drop lockups whose pre-order has
+ // been released (which the AppStore_ComingSoon swoosh will).
+ //
+ // The Games app is able to show the see all irrespective of the preorder flag.
+ return (!shelfToken.filterOverrides.includes(CollectionShelfFilterOverride.ShowOnlyPreorder) ||
+ preprocessor.GAMES_TARGET);
+}
+/**
+ * Returns the URL schema for editorial page shelves that may need to fetch additional content.
+ * Editorial-Page-Related builders can extend on this scheme if needed, e.g. query param on Continue Playing shelves.
+ * @param shelfToken Token to encode in URL for subsequent fetch.
+ * @param parseContext
+ */
+export function editorialPageShelfUrl(shelfToken) {
+ const shelfUrl = new urls.URL()
+ .set("protocol", Protocol.internal)
+ .append("pathname", Path.editorialPage)
+ .append("pathname", Path.shelf)
+ .param(Parameters.token, encodeURIComponent(JSON.stringify(shelfToken)));
+ return shelfUrl.build();
+}
+/**
+ * Creates a page that can be used for side-packing see all pages into a room.
+ *
+ * @param objectGraph
+ * @param {EditorialPageShelfToken} shelfToken The shelf token used to build the original shelf
+ * @param {ShelfContentType} preferredShelfContentType The content type to use for the page
+ * @param {string} parentShelfId The ID of the parent shelf that the contains the sidepack items
+ * @returns {GenericPage} A GenericPage which will use the parentShelfItems from the see all to render the initial room
+ */
+export function createSeeAllPage(objectGraph, shelfToken, preferredShelfContentType, parentShelfId, isComplete) {
+ // Create a new shelf token with a new location tracker in order to get accurate metrics for the items in the shelf.
+ const newLocationTracker = metricsHelpersLocation.newLocationTracker();
+ const collectionDisplayStyle = collectionShelfDisplayStyleFromShelfData(objectGraph, shelfToken.data);
+ const newToken = createBaseShelfToken(objectGraph, shelfToken.pageId, shelfToken.data, shelfToken.isArcadePage, shelfToken.shelfIndex, shelfToken.metricsPageInformation, newLocationTracker, true, collectionDisplayStyleOverride(collectionDisplayStyle));
+ const contentShelf = buildEditorialShelf(objectGraph, undefined, newToken);
+ if (contentShelf === null) {
+ return null;
+ }
+ contentShelf.title = null;
+ contentShelf.eyebrow = null;
+ contentShelf.isHorizontal = false;
+ contentShelf.id = parentShelfId;
+ let pageHeader;
+ if (preprocessor.GAMES_TARGET) {
+ pageHeader = gamesComponentBuilder.makeGamesPageHeader(shelfToken, shelfToken.title, contentShelf.items, shelfToken.subtitle);
+ }
+ else {
+ pageHeader = new models.PageHeader(null, shelfToken.title, null);
+ }
+ const headerShelf = new models.Shelf("pageHeader");
+ headerShelf.id = "shelf_page_header";
+ headerShelf.items = [pageHeader];
+ const shelves = [headerShelf, contentShelf];
+ const page = new models.GenericPage(shelves);
+ page.isIncomplete = !(isComplete !== null && isComplete !== void 0 ? isComplete : false);
+ if (isNothing(page.pageMetrics.pageFields)) {
+ page.pageMetrics.pageFields = {};
+ }
+ return page;
+}
+// endregion
+//# sourceMappingURL=editorial-page-collection-shelf-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-hero-collection-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-hero-collection-shelf-builder.js
new file mode 100644
index 0000000..adc7087
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-hero-collection-shelf-builder.js
@@ -0,0 +1,137 @@
+import * as models from "../../../../api/models";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../../foundation/media/data-structure";
+import { shallowCopyOf } from "../../../../foundation/util/objects";
+import * as breakoutsCommon from "../../../arcade/breakouts-common";
+import * as contentAttributes from "../../../content/attributes";
+import * as metricsHelpersImpressions from "../../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../../metrics/helpers/location";
+import * as article from "../../../today/article";
+import * as editorialDataUtil from "../../editorial-data-util";
+import * as editorialMediaUtil from "../../editorial-media-util";
+import { editorialTitleFromData } from "../../editorial-page-editorial-copy-util";
+import { heroOverlayFromData } from "../../editorial-page-hero-util";
+import * as editorialOverlayContentUtil from "../../editorial-page-overlay-content-util";
+import { CollectionShelfDisplayStyle } from "../../editorial-page-types";
+import { createShelfTokenUrlIfNecessary, shouldDefer } from "./editorial-page-collection-shelf-common";
+export function buildHeroShelf(objectGraph, shelfToken) {
+ if (shelfToken.shelfIndex !== 0) {
+ return null;
+ }
+ const shelf = new models.Shelf("heroCarousel");
+ const heroCarousel = new models.HeroCarousel();
+ heroCarousel.autoScrollConfiguration = heroAutoscrollConfiguration(objectGraph);
+ const shelfContents = editorialDataUtil.extractRemainingItemsOrInitialShelfContents(objectGraph, shelfToken);
+ for (const itemData of shelfContents) {
+ const primaryItemData = editorialDataUtil.extractHydratedPrimaryContentData(objectGraph, itemData);
+ if ((editorialDataUtil.requiresPrimaryContent(objectGraph, itemData) &&
+ !mediaAttributes.hasAttributes(primaryItemData)) ||
+ shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(itemData);
+ continue;
+ }
+ const heroCarouselItemMetricsOptions = {
+ ...shelfToken.metricsImpressionOptions,
+ idType: "its_contentId",
+ targetType: "hero",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ };
+ const heroMediaData = editorialMediaUtil.editorialMediaDataForPlacement(objectGraph, itemData, editorialMediaUtil.EditorialMediaPlacement.Hero);
+ const lockupOptions = {
+ metricsOptions: heroCarouselItemMetricsOptions,
+ clientIdentifierOverride: shelfToken.clientIdentifierOverride,
+ artworkUseCase: 0 /* content.ArtworkUseCase.Default */,
+ offerEnvironment: "lightOverArtwork",
+ canDisplayArcadeOfferButton: true,
+ };
+ const overlayContent = editorialOverlayContentUtil.extractOverlayContent(objectGraph, itemData, lockupOptions);
+ const heroCarouselItem = new models.HeroCarouselItem();
+ const productData = article.productDataFromArticle(objectGraph, itemData);
+ const metricsTitle = editorialTitleFromData(objectGraph, itemData, CollectionShelfDisplayStyle.Hero);
+ const heroCarouselItemImpressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, itemData, metricsTitle, heroCarouselItemMetricsOptions);
+ heroCarouselItemImpressionOptions.isPreorder = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "isPreorder");
+ metricsHelpersImpressions.addImpressionFields(objectGraph, heroCarouselItem, heroCarouselItemImpressionOptions);
+ // Push the heroCarouselItem here so that the click action has the item in its location
+ // but we do not want to add it to the overall location tracker, so pop it right after adding it to the button
+ // actiono
+ // <rdar://problem/60883269> Metrics: Arcade: Container values requested in Location field
+ metricsHelpersLocation.pushContentLocation(objectGraph, heroCarouselItemImpressionOptions, metricsTitle);
+ const editorialClientParams = editorialDataUtil.extractEditorialClientParams(objectGraph, itemData);
+ const hideEditorialMedia = serverData.asBooleanOrFalse(editorialClientParams.hideEditorialMedia);
+ heroCarouselItem.overlay = heroOverlayFromData(objectGraph, itemData, shelfToken, editorialClientParams);
+ heroCarouselItem.collectionIcons = overlayContent.collectionIcons;
+ heroCarouselItem.editorialDisplayOptions =
+ editorialDataUtil.editorialDisplayOptionsFromClientParams(editorialClientParams);
+ populateHeroCarouselItemMedia(objectGraph, heroCarouselItem, hideEditorialMedia, heroMediaData, false);
+ if (heroCarouselItem.isValid()) {
+ heroCarousel.items.push(heroCarouselItem);
+ }
+ const rtlHeroCarouselItem = shallowCopyOf(heroCarouselItem);
+ populateHeroCarouselItemMedia(objectGraph, rtlHeroCarouselItem, hideEditorialMedia, heroMediaData, true);
+ if (rtlHeroCarouselItem.isValid()) {
+ heroCarousel.rtlItems.push(rtlHeroCarouselItem);
+ }
+ metricsHelpersLocation.popLocation(heroCarouselItemImpressionOptions.locationTracker);
+ metricsHelpersLocation.nextPosition(heroCarouselItemImpressionOptions.locationTracker);
+ }
+ // We don't need this in our incomplete shelf URL, so we'll preemptively remove it.
+ delete shelfToken.maxItemCount;
+ shelf.isHorizontal = false;
+ shelf.eyebrow = shelfToken.eyebrow;
+ shelf.eyebrowArtwork = shelfToken.eyebrowArtwork;
+ shelf.title = shelfToken.title;
+ shelf.subtitle = shelfToken.subtitle;
+ shelf.items = [heroCarousel];
+ shelf.url = createShelfTokenUrlIfNecessary(objectGraph, shelf, shelfToken);
+ return shelf;
+}
+/**
+ * Populates the media properties of a `HeroCarouselItem`.
+ * @param objectGraph Current object graph
+ * @param heroCarouselItem The item to populate
+ * @param hideEditorialMedia Whether to hide editorial media
+ * @param heroMediaData The `EditorialMediaData` to derive the media properties from
+ * @param isRTL Whether this item will be used in an RTL layout
+ */
+function populateHeroCarouselItemMedia(objectGraph, heroCarouselItem, hideEditorialMedia, heroMediaData, isRTL) {
+ if (!hideEditorialMedia) {
+ const backgroundColor = isRTL ? heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.rtlBackgroundColor : heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.backgroundColor;
+ heroCarouselItem.backgroundColor = backgroundColor;
+ const artworkData = isRTL ? heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.rtlArtworkData : heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.artworkData;
+ if (!objectGraph.client.isMac || objectGraph.props.isNotEnabled("macOSArcadeHeaderUpdates")) {
+ heroCarouselItem.titleEffect = breakoutsCommon.titleEffectFromArtwork(objectGraph, artworkData);
+ }
+ const artwork = isRTL ? heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.rtlArtwork : heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.artwork;
+ heroCarouselItem.artwork = artwork;
+ const video = isRTL ? heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.rtlVideo : heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.video;
+ heroCarouselItem.video = video;
+ }
+ heroCarouselItem.isMediaDark = editorialMediaUtil.isMediaDark(objectGraph, heroMediaData, isRTL);
+}
+/**
+ * Generates the auto scroll configuration for the hero carousel
+ * @param objectGraph Current object graph
+ * @returns Built auto scroll configuration
+ */
+export function heroAutoscrollConfiguration(objectGraph) {
+ return {
+ isAutoScrollEnabled: objectGraph.bag.heroCarouselAutoScrollDuration > 0.0,
+ autoScrollInterval: objectGraph.bag.heroCarouselAutoScrollDuration,
+ };
+}
+/**
+ * Decorates the input shelf metrics options with shelf specific fields.
+ * @param objectGraph Current object graph
+ * @param shelfMetricsOptions Base shelf metrics options
+ * @returns Decorated shelf metrics options
+ */
+export function decorateHeroShelfMetricsOptions(objectGraph, shelfMetricsOptions) {
+ shelfMetricsOptions.title = "heroCarousel";
+ const autoScrollConfiguration = heroAutoscrollConfiguration(objectGraph);
+ shelfMetricsOptions.autoAdvanceInterval = autoScrollConfiguration.autoScrollInterval;
+}
+//# sourceMappingURL=editorial-page-hero-collection-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-lockup-collection-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-lockup-collection-shelf-builder.js
new file mode 100644
index 0000000..12525c4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-lockup-collection-shelf-builder.js
@@ -0,0 +1,193 @@
+import { isSome } from "@jet/environment";
+import * as models from "../../../../api/models";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import * as filtering from "../../../filtering";
+import * as metricsHelpersLocation from "../../../metrics/helpers/location";
+import { insertPlaceholdersIntoShelfIfRequired } from "../../../placeholders/placeholders";
+import * as editorialDataUtil from "../../editorial-data-util";
+import { lockupFromData } from "../../editorial-page-lockup-utils";
+import { CollectionShelfDisplayStyle, CollectionShelfFilterOverride, collectionShelfLayoutDirection, CollectionShelfLayoutDirection, displayLimitForCollectionShelf, } from "../../editorial-page-types";
+import { createShelfTokenUrlIfNecessary, shouldDefer } from "./editorial-page-collection-shelf-common";
+import * as gamesComponentBuilder from "../../../../gameservicesui/src/editorial-page/editorial-component-builder";
+import * as charts from "../../../../gameservicesui/src/utility/charts";
+import { arcadeColor } from "../../../arcade/arcade-common";
+import { artworkTemplateForBundleImage, createArtworkForResource } from "../../../content/artwork/artwork";
+export function buildEditorialLockupHierarchicalPortrait(objectGraph, shelfToken) {
+ return buildLockupShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.EditorialLockupHierarchicalPortrait, "mediumImageLockup", true, CollectionShelfLayoutDirection.Vertical);
+}
+export function buildEditorialLockupHierarchicalRows(objectGraph, shelfToken) {
+ return buildLockupShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.EditorialLockupHierarchicalRows, "mediumImageLockup", true, CollectionShelfLayoutDirection.Vertical);
+}
+export function buildEditorialLockupMediumShelf(objectGraph, shelfToken) {
+ return buildLockupShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.EditorialLockupMedium, "mediumImageLockup");
+}
+export function buildEditorialLockupMediumVariantShelf(objectGraph, shelfToken) {
+ return buildLockupShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.EditorialLockupMediumVariant, "mediumImageLockup");
+}
+export function buildEditorialLockupLargeShelf(objectGraph, shelfToken) {
+ return buildLockupShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.EditorialLockupLarge, "largeImageLockup");
+}
+export function buildEditorialLockupLargeVariantShelf(objectGraph, shelfToken) {
+ return buildLockupShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.EditorialLockupLargeVariant, "largeImageLockup");
+}
+export function buildLockupChartShelf(objectGraph, shelfToken) {
+ return buildLockupShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.Charts, shelfToken.isSeeAll ? "smallLockup" : "mediumImageLockup", true, CollectionShelfLayoutDirection.Vertical);
+}
+export function buildLockupMedium(objectGraph, shelfToken) {
+ shelfToken.metricsImpressionOptions.shouldOmitImpressionIndex = true;
+ return buildLockupShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.LockupMedium, "mediumLockup");
+}
+export function buildLockupSmallShelf(objectGraph, shelfToken, insertPlaceholdersIfRequired = true, overrideShelfLayoutDirection) {
+ if (objectGraph.featureFlags.isEnabled("force_display_lockup_ordinals")) {
+ // For 2023F Charts testing before server is ready. Can be removed in the 2024A timeline.
+ shelfToken.showOrdinals = true;
+ }
+ return buildLockupShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.LockupSmall, "smallLockup", insertPlaceholdersIfRequired, overrideShelfLayoutDirection);
+}
+export function buildLockupLargeShelf(objectGraph, shelfToken, insertPlaceholdersIfRequired = true) {
+ return buildLockupShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.LockupLarge, "largeLockup", insertPlaceholdersIfRequired);
+}
+export function buildEditorialLockupPosterShelf(objectGraph, shelfToken) {
+ const shelf = buildLockupShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.Poster, "posterLockup");
+ if (isSome(shelf)) {
+ shelf.horizontalScrollTargetBehavior = models.ShelfHorizontalScrollTargetBehavior.CenterAligned;
+ }
+ return shelf;
+}
+// region Lockup Creation
+function buildLockupShelf(objectGraph, shelfToken, displayStyle, lockupComponentType, insertPlaceholdersIfRequired = true, overrideShelfLayoutDirection) {
+ const items = [];
+ const shelf = new models.Shelf(lockupComponentType);
+ const layoutDirection = overrideShelfLayoutDirection !== null && overrideShelfLayoutDirection !== void 0 ? overrideShelfLayoutDirection : collectionShelfLayoutDirection(shelfToken);
+ shelf.isHorizontal = layoutDirection === CollectionShelfLayoutDirection.Horizontal;
+ const shelfContents = editorialDataUtil.extractRemainingItemsOrInitialShelfContents(objectGraph, shelfToken);
+ const displayLimit = displayLimitForCollectionShelf(displayStyle, layoutDirection, shelfToken);
+ for (const [index, itemData] of shelfContents.entries()) {
+ // If we encounter a type of app-events, this means they have been incorrectly programmed,
+ // and we should throw the shelf away.
+ if (itemData.type === "app-events") {
+ return null;
+ }
+ if (serverData.isNull(itemData.attributes) || shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(itemData);
+ continue;
+ }
+ // Filter out unwanted content
+ if (filtering.shouldFilter(objectGraph, itemData, 80894 /* filtering.Filter.All */)) {
+ continue;
+ }
+ let lockup = null;
+ if (preprocessor.GAMES_TARGET && gamesComponentBuilder.shouldUseChartCard(displayStyle, shelfToken)) {
+ lockup = gamesComponentBuilder.makeChartCard(objectGraph, itemData, shelfToken, index, "Editorial");
+ }
+ else if (preprocessor.GAMES_TARGET && gamesComponentBuilder.shouldUseMediaCard(displayStyle)) {
+ lockup = gamesComponentBuilder.makeEditorialMediaCard(objectGraph, itemData, "Card", shelfToken, "Editorial");
+ }
+ else if (preprocessor.GAMES_TARGET && gamesComponentBuilder.shouldUseGameLockup(displayStyle)) {
+ lockup = gamesComponentBuilder.makeGameLockupShelfModel(objectGraph, itemData, shelfToken);
+ }
+ else {
+ lockup = lockupFromData(objectGraph, itemData, shelfToken, items.length, displayStyle);
+ }
+ if (lockup) {
+ items.push(lockup);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsImpressionOptions.locationTracker);
+ shelfToken.ordinalIndex++;
+ if (items.length === displayLimit) {
+ break;
+ }
+ }
+ }
+ if (displayStyle === CollectionShelfDisplayStyle.EditorialLockupMediumVariant ||
+ displayStyle === CollectionShelfDisplayStyle.EditorialLockupLargeVariant) {
+ if (serverData.isNull(shelf.presentationHints)) {
+ shelf.presentationHints = { showSupplementaryText: true };
+ }
+ else {
+ shelf.presentationHints = {
+ ...shelf.presentationHints,
+ showSupplementaryText: true,
+ };
+ }
+ }
+ // We don't need this in our incomplete shelf URL, so we'll preemptively remove it.
+ delete shelfToken.maxItemCount;
+ shelf.eyebrow = shelfToken.eyebrow;
+ shelf.eyebrowArtwork = shelfToken.eyebrowArtwork;
+ shelf.title = shelfToken.title;
+ shelf.subtitle = shelfToken.subtitle;
+ // Hydrate Games data if the eyebrow has not already been populated from the shelf token.
+ if (preprocessor.GAMES_TARGET && shelf.eyebrow == null && shelf.eyebrowArtwork == null) {
+ populateGamesHeaderContent(shelf, shelfToken, objectGraph);
+ }
+ if (preprocessor.GAMES_TARGET &&
+ gamesComponentBuilder.shouldUseComponentGrid(displayStyle, layoutDirection, shelfToken)) {
+ const item = gamesComponentBuilder.makeComponentGrid(objectGraph, items, shelfToken, displayStyle);
+ shelf.items = isSome(item) ? [item] : [];
+ }
+ else {
+ shelf.items = items;
+ }
+ shelf.rowsPerColumn = mediaAttributes.attributeAsNumber(shelfToken.data, "rowCount");
+ shelf.shouldFilterApps = !shelfToken.filterOverrides.includes(CollectionShelfFilterOverride.ShowInstalled);
+ shelf.url = createShelfTokenUrlIfNecessary(objectGraph, shelf, shelfToken);
+ if (insertPlaceholdersIfRequired) {
+ let placeholderDisplayLimit = displayLimit;
+ if (isSome(displayLimit) &&
+ displayLimit > 4 &&
+ (displayStyle === CollectionShelfDisplayStyle.Charts ||
+ displayStyle === CollectionShelfDisplayStyle.EditorialLockupMedium)) {
+ // TODO: Remove this override when this radar is complete: rdar://148395979 ([EditorialPage] Placeholders for Charts and 4UP are not rendering as a ComponentGrid)
+ placeholderDisplayLimit = 4;
+ }
+ insertPlaceholdersIntoShelfIfRequired(objectGraph, shelf, shelfToken, placeholderDisplayLimit);
+ }
+ return shelf;
+}
+/**
+ * Hydrates game related header data
+ *
+ * Note that both the header and non header fields should be hydrated, since header
+ * preference can be switched off. In this case we will not have the capacity to color the Arcade eyebrow
+ */
+function populateGamesHeaderContent(shelf, shelfToken, objectGraph) {
+ switch (serverData.asString(shelfToken.data.attributes, "chart")) {
+ case "combined-game-center" /* TopChartType.CombinedGameCenter */:
+ const gameCenterEyebrow = charts.makeChartsTopSubtitle("game-center", objectGraph);
+ const gameCenterEyebrowArtwork = createArtworkForResource(objectGraph, artworkTemplateForBundleImage("ProductCapabilityGameCenter"));
+ shelf.eyebrow = gameCenterEyebrow;
+ shelf.eyebrowArtwork = gameCenterEyebrowArtwork;
+ shelf.header = {
+ eyebrow: gameCenterEyebrow,
+ eyebrowArtwork: gameCenterEyebrowArtwork,
+ eyebrowArtworkSize: 14,
+ title: shelfToken.title,
+ subtitle: shelfToken.subtitle,
+ };
+ break;
+ case "top-arcade" /* TopChartType.TopArcade */:
+ const color = arcadeColor;
+ const arcadeEyebrow = charts.makeChartsTopSubtitle("arcade", objectGraph);
+ shelf.eyebrow = arcadeEyebrow;
+ shelf.header = {
+ eyebrow: arcadeEyebrow,
+ title: shelfToken.title,
+ subtitle: shelfToken.subtitle,
+ configuration: {
+ eyebrowColor: {
+ type: "rgb",
+ red: color.red,
+ green: color.green,
+ blue: color.blue,
+ },
+ },
+ };
+ break;
+ default:
+ break;
+ }
+}
+// endregion
+//# sourceMappingURL=editorial-page-lockup-collection-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-collection-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-collection-shelf-builder.js
new file mode 100644
index 0000000..3e78c25
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-collection-shelf-builder.js
@@ -0,0 +1,70 @@
+import * as models from "../../../../api/models";
+import { isNull } from "../../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../../foundation/media/data-structure";
+import * as metricsHelpersLocation from "../../../metrics/helpers/location";
+import { insertPlaceholdersIntoShelfIfRequired } from "../../../placeholders/placeholders";
+import { clickTargetForCollectionDisplayStyle } from "../../editorial-action-util";
+import * as editorialDataUtil from "../../editorial-data-util";
+import * as editorialMediaUtil from "../../editorial-media-util";
+import { CollectionShelfDisplayStyle, collectionShelfLayoutDirection } from "../../editorial-page-types";
+import { createShelfTokenUrlIfNecessary, shouldDefer } from "./editorial-page-collection-shelf-common";
+import { buildStoryCard } from "./editorial-page-story-card-utils";
+import * as gamesComponentBuilder from "../../../../gameservicesui/src/editorial-page/editorial-component-builder";
+import { isSome } from "@jet/environment/types/optional";
+export function buildSmallStoryCardShelf(objectGraph, shelfToken) {
+ return buildStoryCardShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.StorySmall, "smallStoryCard");
+}
+export function buildMediumStoryCardShelf(objectGraph, shelfToken) {
+ return buildStoryCardShelf(objectGraph, shelfToken, CollectionShelfDisplayStyle.StoryMedium, "mediumStoryCard");
+}
+function buildStoryCardShelf(objectGraph, shelfToken, displayStyle, storyComponentType) {
+ const items = [];
+ const shelf = new models.Shelf(storyComponentType);
+ const layoutDirection = collectionShelfLayoutDirection(shelfToken);
+ shelf.isHorizontal = layoutDirection === "Horizontal";
+ const shelfContents = editorialDataUtil.extractRemainingItemsOrInitialShelfContents(objectGraph, shelfToken);
+ for (const itemData of shelfContents) {
+ const metricsOptions = {
+ ...shelfToken.metricsImpressionOptions,
+ targetType: clickTargetForCollectionDisplayStyle(objectGraph, displayStyle),
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ };
+ if (!mediaAttributes.hasAttributes(itemData) || shouldDefer(shelfToken)) {
+ shelfToken.remainingItems.push(itemData);
+ shelfToken.isDeferring = true;
+ continue;
+ }
+ let storyCard = null;
+ if (preprocessor.GAMES_TARGET && gamesComponentBuilder.shouldUseMediaCard(displayStyle)) {
+ storyCard = gamesComponentBuilder.makeEditorialMediaCard(objectGraph, itemData, "Card", shelfToken, "Editorial");
+ }
+ else {
+ storyCard = buildStoryCard(objectGraph, itemData, editorialMediaUtil.EditorialMediaPlacement.StoryCard, metricsOptions, shelfToken.collectionDisplayStyle, true, shelfToken);
+ }
+ if (isNull(storyCard)) {
+ continue;
+ }
+ items.push(storyCard);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsImpressionOptions.locationTracker);
+ }
+ // We don't need this in our incomplete shelf URL, so we'll preemptively remove it.
+ delete shelfToken.maxItemCount;
+ shelf.eyebrow = shelfToken.eyebrow;
+ shelf.eyebrowArtwork = shelfToken.eyebrowArtwork;
+ shelf.title = shelfToken.title;
+ shelf.subtitle = shelfToken.subtitle;
+ if (preprocessor.GAMES_TARGET &&
+ gamesComponentBuilder.shouldUseComponentGrid(displayStyle, layoutDirection, shelfToken)) {
+ const item = gamesComponentBuilder.makeComponentGrid(objectGraph, items, shelfToken, displayStyle);
+ shelf.items = isSome(item) ? [item] : [];
+ }
+ else {
+ shelf.items = items;
+ }
+ shelf.rowsPerColumn = mediaAttributes.attributeAsNumber(shelfToken.data, "rowCount");
+ shelf.url = createShelfTokenUrlIfNecessary(objectGraph, shelf, shelfToken);
+ insertPlaceholdersIntoShelfIfRequired(objectGraph, shelf, shelfToken);
+ return shelf;
+}
+//# sourceMappingURL=editorial-page-story-card-collection-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-utils.js
new file mode 100644
index 0000000..ff85e3d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-utils.js
@@ -0,0 +1,128 @@
+import * as models from "../../../../api/models";
+import * as content from "../../../content/content";
+import * as mediaDataStructure from "../../../../foundation/media/data-structure";
+import * as metricsHelpersImpressions from "../../../metrics/helpers/impressions";
+import * as editorialCopyUtil from "../../editorial-page-editorial-copy-util";
+import * as editorialOverlayContentUtil from "../../editorial-page-overlay-content-util";
+import { asBooleanOrFalse, isDefinedNonNull, isDefinedNonNullNonEmpty, } from "../../../../foundation/json-parsing/server-data";
+import * as editorialDataUtil from "../../editorial-data-util";
+import * as editorialActionUtil from "../../editorial-action-util";
+import * as editorialMediaUtil from "../../editorial-media-util";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import { deepLinkUrlFromData } from "../../../linking/external-deep-link";
+import { shouldFilter } from "../../../filtering";
+import { mixedMediaItemFromEditorialData } from "../../../../gameservicesui/src/common/mixed-media-item-editorial-artwork";
+/**
+ * Build an `EditorialStoryCard` from the provided data.
+ * @param objectGraph The object graph.
+ * @param itemData Data for the card item being created.
+ * @param mediaPlacement The placement within which the card is being created for media.
+ * @param metricsOptions A set of metrics options.
+ * @param collectionDisplayStyle The collection display style for the shelf this is in.
+ * @param allowSuppressLockup Whether any lockup can be suppressed
+ * @param shelfToken A shelf token, if being presented within a shelf. If not, it's being used in another context.
+ * @returns The built editorial story card.
+ */
+export function buildStoryCard(objectGraph, itemData, mediaPlacement, metricsOptions, collectionDisplayStyle, allowSuppressLockup, shelfToken) {
+ var _a, _b;
+ const editorialClientParams = editorialDataUtil.extractEditorialClientParams(objectGraph, itemData);
+ const storyCard = new models.EditorialStoryCard();
+ storyCard.title = editorialCopyUtil.editorialTitleFromData(objectGraph, itemData, collectionDisplayStyle);
+ if (!editorialClientParams.suppressShort) {
+ storyCard.description = editorialCopyUtil.editorialDescriptionFromData(objectGraph, itemData);
+ }
+ const hideEditorialMedia = asBooleanOrFalse(editorialClientParams.hideEditorialMedia);
+ if (!hideEditorialMedia) {
+ const mediaData = editorialMediaUtil.editorialMediaDataForPlacement(objectGraph, itemData, mediaPlacement);
+ storyCard.artwork = mediaData === null || mediaData === void 0 ? void 0 : mediaData.artwork;
+ storyCard.video = mediaData === null || mediaData === void 0 ? void 0 : mediaData.video;
+ storyCard.isMediaDark = (_a = editorialMediaUtil.isMediaDark(objectGraph, mediaData)) !== null && _a !== void 0 ? _a : undefined;
+ if (mediaPlacement === editorialMediaUtil.EditorialMediaPlacement.StoryDetail) {
+ if (preprocessor.GAMES_TARGET) {
+ storyCard.mixedMediaItem = mixedMediaItemFromEditorialData(objectGraph, itemData, "DetailStory");
+ }
+ const landscapeMediaData = editorialMediaUtil.editorialMediaDataForPlacement(objectGraph, itemData, editorialMediaUtil.EditorialMediaPlacement.StoryDetailLandscape);
+ storyCard.landscapeArtwork = landscapeMediaData === null || landscapeMediaData === void 0 ? void 0 : landscapeMediaData.artwork;
+ storyCard.landscapeVideo = landscapeMediaData === null || landscapeMediaData === void 0 ? void 0 : landscapeMediaData.video;
+ storyCard.isLandscapeMediaDark =
+ (_b = editorialMediaUtil.isMediaDark(objectGraph, landscapeMediaData)) !== null && _b !== void 0 ? _b : undefined;
+ }
+ }
+ const lockupOptions = {
+ metricsOptions: {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ },
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ offerEnvironment: editorialClientParams.useMaterialBlur ? "light" : "lightOverArtwork",
+ canDisplayArcadeOfferButton: content.shelfDisplayStyleCanDisplayArcadeOfferButtons(objectGraph, collectionDisplayStyle),
+ isContainedInPreorderExclusiveShelf: false,
+ externalDeepLinkUrl: deepLinkUrlFromData(objectGraph, itemData),
+ useJoeColorIconPlaceholder: preprocessor.GAMES_TARGET,
+ };
+ const overlayContent = editorialOverlayContentUtil.extractOverlayContent(objectGraph, itemData, lockupOptions);
+ const suppressLockup = allowSuppressLockup && asBooleanOrFalse(editorialClientParams.suppressLockup);
+ if (!suppressLockup) {
+ storyCard.lockup = overlayContent.lockup;
+ }
+ // For Arcade app that is preorder and bincompat, we should override the badge to always display "COMING SOON" in upper cased.
+ const lockupData = editorialDataUtil.extractProductData(objectGraph, itemData);
+ const isPreorder = mediaAttributes.attributeAsBooleanOrFalse(lockupData, "isPreorder");
+ const isArcadeLockup = content.isArcadeSupported(objectGraph, lockupData);
+ const supportsVisionOSCompatibleIOSBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, lockupData);
+ // If there is lockup data, and we're not suppressing the lockup, check the data is valid for this platform.
+ // If this is for a story detail page, ignore these checks so that the artwork card is still presented as expected.
+ if (isDefinedNonNullNonEmpty(lockupData) &&
+ !suppressLockup &&
+ shouldFilter(objectGraph, lockupData, 80894 /* Filter.All */) &&
+ mediaPlacement !== editorialMediaUtil.EditorialMediaPlacement.StoryDetail) {
+ return null;
+ }
+ let badgeText;
+ if (isPreorder && isArcadeLockup && supportsVisionOSCompatibleIOSBinary) {
+ badgeText = objectGraph.loc.string("ARCADE_PREORDER_COMING_SOON");
+ }
+ else {
+ badgeText = editorialCopyUtil.editorialBadgeFromData(objectGraph, itemData, collectionDisplayStyle);
+ }
+ if (objectGraph.client.isVision && badgeText === objectGraph.loc.string("APPLE_ARCADE")) {
+ storyCard.badge = {
+ type: "arcadeWordmark",
+ };
+ }
+ else {
+ storyCard.badge = {
+ type: "text",
+ title: badgeText,
+ };
+ }
+ storyCard.collectionIcons = overlayContent.collectionIcons;
+ if (isDefinedNonNull(shelfToken)) {
+ const baseClickOptions = {
+ id: itemData.id,
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ actionDetails: {
+ franchise: storyCard.badge.title,
+ },
+ idType: "editorial_id",
+ };
+ storyCard.clickAction = editorialActionUtil.createPrimaryActionForComponentFromData(objectGraph, itemData, shelfToken, baseClickOptions);
+ }
+ storyCard.editorialDisplayOptions =
+ editorialDataUtil.editorialDisplayOptionsFromClientParams(editorialClientParams);
+ // Configure impressions
+ const contentMetricsOptions = {
+ ...metricsOptions,
+ id: itemData.id,
+ idType: "editorial_id",
+ };
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, itemData, storyCard.title, contentMetricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, storyCard, impressionOptions);
+ if (!storyCard.isValid()) {
+ return null;
+ }
+ return storyCard;
+}
+//# sourceMappingURL=editorial-page-story-card-utils.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-text-only-collection-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-text-only-collection-shelf-builder.js
new file mode 100644
index 0000000..3f9f2b7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-text-only-collection-shelf-builder.js
@@ -0,0 +1,54 @@
+import * as models from "../../../../api/models";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import * as metricsHelpersLocation from "../../../metrics/helpers/location";
+import * as editorialActionUtil from "../../editorial-action-util";
+import * as editorialDataUtil from "../../editorial-data-util";
+import * as editorialCopyUtil from "../../editorial-page-editorial-copy-util";
+import { CollectionShelfLayoutDirection } from "../../editorial-page-types";
+import { createShelfTokenUrlIfNecessary, shouldDefer } from "./editorial-page-collection-shelf-common";
+import * as metricsHelpersImpressions from "../../../metrics/helpers/impressions";
+import { insertPlaceholdersIntoShelfIfRequired } from "../../../placeholders/placeholders";
+export function buildTextOnlyShelf(objectGraph, shelfToken) {
+ var _a;
+ const items = [];
+ const shelf = new models.Shelf("action");
+ shelf.isHorizontal =
+ mediaAttributes.attributeAsString(shelfToken.data, "layoutDirection") ===
+ CollectionShelfLayoutDirection.Horizontal;
+ const shelfContents = editorialDataUtil.extractRemainingItemsOrInitialShelfContents(objectGraph, shelfToken);
+ for (const itemData of shelfContents) {
+ if (serverData.isNull(itemData.attributes) || shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(itemData);
+ continue;
+ }
+ const action = editorialActionUtil.createPrimaryActionForComponentFromData(objectGraph, itemData, shelfToken);
+ action.title = editorialCopyUtil.editorialTitleFromData(objectGraph, itemData, shelfToken.collectionDisplayStyle);
+ // Configure impressions
+ const contentMetricsOptions = {
+ ...shelfToken.metricsImpressionOptions,
+ id: itemData.id,
+ idType: "editorial_id",
+ targetType: "textOnly",
+ };
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, itemData, action.title, contentMetricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, action, impressionOptions);
+ if (action.isValid()) {
+ items.push(action);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsImpressionOptions.locationTracker);
+ }
+ }
+ // We don't need this in our incomplete shelf URL, so we'll preemptively remove it.
+ delete shelfToken.maxItemCount;
+ shelf.eyebrow = shelfToken.eyebrow;
+ shelf.eyebrowArtwork = shelfToken.eyebrowArtwork;
+ shelf.title = shelfToken.title;
+ shelf.subtitle = shelfToken.subtitle;
+ shelf.items = items;
+ shelf.rowsPerColumn = (_a = mediaAttributes.attributeAsNumber(shelfToken.data, "rowCount")) !== null && _a !== void 0 ? _a : 1;
+ shelf.url = createShelfTokenUrlIfNecessary(objectGraph, shelf, shelfToken);
+ insertPlaceholdersIntoShelfIfRequired(objectGraph, shelf, shelfToken);
+ return shelf;
+}
+//# sourceMappingURL=editorial-page-text-only-collection-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-text-with-artwork-collection-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-text-with-artwork-collection-shelf-builder.js
new file mode 100644
index 0000000..7587b49
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-text-with-artwork-collection-shelf-builder.js
@@ -0,0 +1,66 @@
+import * as models from "../../../../api/models";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import * as content from "../../../content/content";
+import * as metricsHelpersLocation from "../../../metrics/helpers/location";
+import { insertPlaceholdersIntoShelfIfRequired } from "../../../placeholders/placeholders";
+import * as editorialActionUtil from "../../editorial-action-util";
+import * as editorialDataUtil from "../../editorial-data-util";
+import * as editorialCopyUtil from "../../editorial-page-editorial-copy-util";
+import { CollectionShelfLayoutDirection } from "../../editorial-page-types";
+import { createShelfTokenUrlIfNecessary, shouldDefer } from "./editorial-page-collection-shelf-common";
+import * as gamesComponentBuilder from "../../../../gameservicesui/src/editorial-page/editorial-component-builder";
+export function buildTextWithArtworkShelf(objectGraph, shelfToken) {
+ var _a;
+ const items = [];
+ const shelf = new models.Shelf("action");
+ shelf.isHorizontal =
+ mediaAttributes.attributeAsString(shelfToken.data, "layoutDirection") ===
+ CollectionShelfLayoutDirection.Horizontal;
+ const shelfContents = editorialDataUtil.extractRemainingItemsOrInitialShelfContents(objectGraph, shelfToken);
+ for (const itemData of shelfContents) {
+ if (serverData.isNull(itemData.attributes) || shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(itemData);
+ continue;
+ }
+ const action = editorialActionUtil.createPrimaryActionForComponentFromData(objectGraph, itemData, shelfToken);
+ action.title = editorialCopyUtil.editorialTitleFromData(objectGraph, itemData, shelfToken.collectionDisplayStyle);
+ let artwork;
+ if (preprocessor.GAMES_TARGET) {
+ const editorialArtwork = mediaAttributes.attributeAsDictionary(itemData, "editorialArtwork");
+ artwork = serverData.asDictionary(editorialArtwork, "contentIconTrimmed");
+ }
+ else {
+ artwork = mediaAttributes.attributeAsDictionary(itemData, "artwork");
+ }
+ if (serverData.isDefinedNonNullNonEmpty(artwork)) {
+ action.artwork = content.artworkFromApiArtwork(objectGraph, artwork, {
+ allowingTransparency: true,
+ useCase: 20 /* content.ArtworkUseCase.CategoryIcon */,
+ });
+ }
+ if (action.isValid()) {
+ items.push(action);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsImpressionOptions.locationTracker);
+ }
+ }
+ // We don't need this in our incomplete shelf URL, so we'll preemptively remove it.
+ delete shelfToken.maxItemCount;
+ shelf.eyebrow = shelfToken.eyebrow;
+ shelf.eyebrowArtwork = shelfToken.eyebrowArtwork;
+ shelf.title = shelfToken.title;
+ shelf.subtitle = shelfToken.subtitle;
+ if (preprocessor.GAMES_TARGET) {
+ shelf.items = [gamesComponentBuilder.makeRibbonBar(objectGraph, items)];
+ shelf.contentType = "ribbonBar";
+ }
+ else {
+ shelf.items = items;
+ }
+ shelf.rowsPerColumn = (_a = mediaAttributes.attributeAsNumber(shelfToken.data, "rowCount")) !== null && _a !== void 0 ? _a : 1;
+ shelf.url = createShelfTokenUrlIfNecessary(objectGraph, shelf, shelfToken);
+ insertPlaceholdersIntoShelfIfRequired(objectGraph, shelf, shelfToken);
+ return shelf;
+}
+//# sourceMappingURL=editorial-page-text-with-artwork-collection-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/index.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/index.js
new file mode 100644
index 0000000..ae02877
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/index.js
@@ -0,0 +1,113 @@
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import { attributeAsString } from "../../../../foundation/media/attributes";
+import { CollectionShelfDisplayStyle } from "../../editorial-page-types";
+import { buildBreakoutLargeShelf } from "./editorial-page-breakout-large-collection-shelf-builder";
+import { buildBrickLargeShelf, buildBrickMediumShelf, buildBrickSmallShelf, } from "./editorial-page-brick-collection-shelf-builder";
+import { createShelfSeeAllActionIfNecessary } from "./editorial-page-collection-shelf-common";
+import { buildHeroShelf } from "./editorial-page-hero-collection-shelf-builder";
+import { buildEditorialLockupLargeShelf, buildEditorialLockupLargeVariantShelf, buildEditorialLockupMediumShelf, buildEditorialLockupMediumVariantShelf, buildLockupLargeShelf, buildLockupSmallShelf, buildEditorialLockupPosterShelf, buildEditorialLockupHierarchicalRows, buildEditorialLockupHierarchicalPortrait, buildLockupMedium, } from "./editorial-page-lockup-collection-shelf-builder";
+import { buildMediumStoryCardShelf, buildSmallStoryCardShelf, } from "./editorial-page-story-card-collection-shelf-builder";
+import { buildTextOnlyShelf } from "./editorial-page-text-only-collection-shelf-builder";
+import { buildTextWithArtworkShelf } from "./editorial-page-text-with-artwork-collection-shelf-builder";
+import { collectionShelfDisplayStyleFromShelfData } from "../../editorial-data-util";
+import * as gamesHeroShelfBuilder from "../../../../gameservicesui/src/editorial-page/editorial-page-hero-collection-shelf-builder";
+import { isSome } from "@jet/environment";
+export function buildCollectionShelf(objectGraph, shelfToken) {
+ let collectionDisplayStyle = attributeAsString(shelfToken.data, "displayStyle");
+ if (preprocessor.GAMES_TARGET) {
+ // A way to override display style as design wants to see large cards in "See all" rooms.
+ const shelfTokenStyle = shelfToken.collectionDisplayStyle;
+ if (isSome(shelfTokenStyle) && shelfTokenStyle !== collectionDisplayStyle) {
+ collectionDisplayStyle = shelfToken.collectionDisplayStyle;
+ }
+ }
+ if (serverData.isNullOrEmpty(collectionDisplayStyle)) {
+ return null;
+ }
+ let shelf = null;
+ switch (collectionDisplayStyle) {
+ case CollectionShelfDisplayStyle.Hero:
+ if (preprocessor.GAMES_TARGET) {
+ shelf = gamesHeroShelfBuilder.buildHeroShelf(objectGraph, shelfToken);
+ }
+ else {
+ shelf = buildHeroShelf(objectGraph, shelfToken);
+ }
+ break;
+ case CollectionShelfDisplayStyle.TextOnly:
+ shelf = buildTextOnlyShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.TextWithArtwork:
+ shelf = buildTextWithArtworkShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.BrickSmall:
+ shelf = buildBrickSmallShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.BrickMedium:
+ shelf = buildBrickMediumShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.BrickLarge:
+ shelf = buildBrickLargeShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.EditorialLockupHierarchicalPortrait:
+ shelf = buildEditorialLockupHierarchicalPortrait(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.EditorialLockupHierarchicalRows:
+ shelf = buildEditorialLockupHierarchicalRows(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.EditorialLockupMedium:
+ shelf = buildEditorialLockupMediumShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.EditorialLockupMediumVariant:
+ shelf = buildEditorialLockupMediumVariantShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.EditorialLockupLarge:
+ shelf = buildEditorialLockupLargeShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.EditorialLockupLargeVariant:
+ shelf = buildEditorialLockupLargeVariantShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.LockupMedium:
+ shelf = buildLockupMedium(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.Poster:
+ shelf = buildEditorialLockupPosterShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.StorySmall:
+ shelf = buildSmallStoryCardShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.StoryMedium:
+ shelf = buildMediumStoryCardShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.LockupSmall:
+ shelf = buildLockupSmallShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.LockupLarge:
+ shelf = buildLockupLargeShelf(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.BreakoutLarge:
+ shelf = buildBreakoutLargeShelf(objectGraph, shelfToken);
+ break;
+ default:
+ break;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(shelf)) {
+ shelf.seeAllAction = createShelfSeeAllActionIfNecessary(objectGraph, shelf, shelfToken);
+ shelf.id = `shelf_${shelfToken.id}`;
+ }
+ return shelf;
+}
+/**
+ * Decorates the input shelf metrics options with shelf specific fields.
+ * @param objectGraph Current object graph
+ * @param shelfMetricsOptions Base shelf metrics options
+ * @returns Decorated shelf metrics options
+ */
+export function decorateCollectionShelfMetricsOptions(objectGraph, shelfData, shelfMetricsOptions) {
+ const collectionDisplayStyle = collectionShelfDisplayStyleFromShelfData(objectGraph, shelfData);
+ if (serverData.isNullOrEmpty(collectionDisplayStyle)) {
+ return;
+ }
+ shelfMetricsOptions.displayStyle = collectionDisplayStyle;
+}
+//# sourceMappingURL=index.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-engagement-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-engagement-shelf-builder.js
new file mode 100644
index 0000000..76756a7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-engagement-shelf-builder.js
@@ -0,0 +1,4 @@
+export function buildEngagementShelf(objectGraph, shelfToken) {
+ return null;
+}
+//# sourceMappingURL=editorial-page-engagement-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-game-center-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-game-center-shelf-builder.js
new file mode 100644
index 0000000..7de4145
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-game-center-shelf-builder.js
@@ -0,0 +1,4 @@
+export function buildGameCenterShelf(objectGraph, shelfToken) {
+ return null;
+}
+//# sourceMappingURL=editorial-page-game-center-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-header-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-header-shelf-builder.js
new file mode 100644
index 0000000..0f3a98f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-header-shelf-builder.js
@@ -0,0 +1,157 @@
+import { attributeAsString } from "@apple-media-services/media-api";
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import { asBooleanOrFalse, isDefinedNonNullNonEmpty } from "../../../foundation/json-parsing/server-data";
+import { attributeAsDictionary } from "../../../foundation/media/attributes";
+import { metricsFromMediaApiObject } from "../../../foundation/media/data-structure";
+import * as color from "../../../foundation/util/color-util";
+import { shallowCopyOf } from "../../../foundation/util/objects";
+import * as gamesComponentBuilder from "../../../gameservicesui/src/editorial-page/editorial-component-builder";
+import * as content from "../../content/content";
+import { deepLinkUrlFromData } from "../../linking/external-deep-link";
+import { popLocation, pushBasicLocation } from "../../metrics/helpers/location";
+import { areAppTagsEnabled } from "../../util/app-tags-util";
+import { editorialDisplayOptionsFromClientParams, extractEditorialClientParams } from "../editorial-data-util";
+import { editorialMediaDataForPlacement, EditorialMediaPlacement, isMediaDark, } from "../editorial-media-util";
+import * as editorialComponentMediaUtil from "../editorial-page-component-media-util";
+import { heroOverlayFromData } from "../editorial-page-hero-util";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+/**
+ * Build a header shelf for the editorial page, if present.
+ * @param objectGraph The App Store object graph.
+ * @param pageData The data for the editorial page.
+ * @param shelfToken The shelf token.
+ * @returns A Shelf, if the data is available.
+ */
+export function buildHeaderShelf(objectGraph, pageData, shelfToken) {
+ var _a, _b, _c;
+ const shouldUseShelfData = isDefinedNonNullNonEmpty(attributeAsDictionary(shelfToken.data, "editorialNotes"));
+ const headerData = shouldUseShelfData ? shelfToken.data : pageData;
+ if (!headerData) {
+ return null;
+ }
+ if (preprocessor.GAMES_TARGET) {
+ return gamesComponentBuilder.makeGamesPageHeaderFromData(objectGraph, headerData, shelfToken);
+ }
+ const editorialArtworkData = isDefinedNonNullNonEmpty(attributeAsDictionary(headerData, "editorialArtwork"));
+ if (editorialArtworkData) {
+ const pageHeader = new models.HeroCarousel();
+ const pageHeaderCarouselItem = new models.HeroCarouselItem();
+ const editorialClientParams = extractEditorialClientParams(objectGraph, headerData);
+ const pageHeaderCarouselItemOverlay = heroOverlayFromData(objectGraph, headerData, shelfToken, editorialClientParams);
+ pageHeaderCarouselItemOverlay.overlayType = "text";
+ pageHeaderCarouselItemOverlay.callToActionText = null;
+ pageHeaderCarouselItem.clickAction = null;
+ pageHeaderCarouselItemOverlay.collectionIcons = null;
+ pageHeaderCarouselItemOverlay.lockup = null;
+ pageHeaderCarouselItemOverlay.callToActionText = null;
+ pageHeaderCarouselItem.overlay = pageHeaderCarouselItemOverlay;
+ const lockupOptions = {
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ offerStyle: "transparent",
+ offerEnvironment: "dark",
+ canDisplayArcadeOfferButton: content.shelfDisplayStyleCanDisplayArcadeOfferButtons(objectGraph, shelfToken.collectionDisplayStyle),
+ isContainedInPreorderExclusiveShelf: false,
+ metricsOptions: {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: metricsFromMediaApiObject(headerData),
+ },
+ externalDeepLinkUrl: deepLinkUrlFromData(objectGraph, headerData),
+ };
+ pushBasicLocation(objectGraph, lockupOptions.metricsOptions, "collectionIcons");
+ const collectionIcons = editorialComponentMediaUtil.editorialFallbackAppIconsFromData(objectGraph, headerData, lockupOptions);
+ popLocation(lockupOptions.metricsOptions.locationTracker);
+ pageHeaderCarouselItem.collectionIcons = collectionIcons;
+ const heroMediaData = editorialMediaDataForPlacement(objectGraph, headerData, EditorialMediaPlacement.Hero);
+ const hideEditorialMedia = asBooleanOrFalse(editorialClientParams.hideEditorialMedia);
+ populatePageHeaderCarouselItemMedia(objectGraph, pageHeaderCarouselItem, hideEditorialMedia, heroMediaData, false);
+ pageHeaderCarouselItem.editorialDisplayOptions = editorialDisplayOptionsFromClientParams(editorialClientParams);
+ pageHeader.items = [pageHeaderCarouselItem];
+ const rtlPageHeaderCarouselItem = shallowCopyOf(pageHeaderCarouselItem);
+ populatePageHeaderCarouselItemMedia(objectGraph, rtlPageHeaderCarouselItem, hideEditorialMedia, heroMediaData, true);
+ pageHeader.rtlItems = [rtlPageHeaderCarouselItem];
+ const shelf = new models.Shelf("heroCarousel");
+ shelf.items = [pageHeader];
+ return shelf;
+ }
+ else {
+ const editorialName = attributeAsString(headerData, "name");
+ if (isDefinedNonNullNonEmpty(editorialName) && areAppTagsEnabled(objectGraph, null)) {
+ const shelf = new models.Shelf("mediaPageHeader");
+ shelf.id = "mediaPageHeader";
+ const editorialBadge = (_a = attributeAsString(headerData, "editorialNotes.badge")) !== null && _a !== void 0 ? _a : null;
+ const lockupOptions = {
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ offerStyle: "transparent",
+ offerEnvironment: "dark",
+ canDisplayArcadeOfferButton: content.shelfDisplayStyleCanDisplayArcadeOfferButtons(objectGraph, shelfToken.collectionDisplayStyle),
+ isContainedInPreorderExclusiveShelf: false,
+ metricsOptions: {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: metricsFromMediaApiObject(headerData),
+ },
+ externalDeepLinkUrl: deepLinkUrlFromData(objectGraph, headerData),
+ useJoeColorIconPlaceholder: true,
+ joeColorPlaceholderSelectionLogic: content.bestJoeColorPlaceholderSelectionLogic,
+ };
+ let collectionIcons = (_b = editorialComponentMediaUtil.editorialFallbackAppIconsFromData(objectGraph, shelfToken.data, lockupOptions)) !== null && _b !== void 0 ? _b : undefined;
+ let backgroundColor;
+ if (isSome(collectionIcons) && (collectionIcons === null || collectionIcons === void 0 ? void 0 : collectionIcons.length) >= 3) {
+ collectionIcons = collectionIcons.slice(0, 3);
+ const collectionIconBackgroundColor = collectionIcons[0].backgroundColor;
+ if (isSome(collectionIconBackgroundColor) && (collectionIconBackgroundColor === null || collectionIconBackgroundColor === void 0 ? void 0 : collectionIconBackgroundColor.type) === "rgb") {
+ backgroundColor = content.closestTagBackgroundColorForIcon(collectionIconBackgroundColor);
+ }
+ const isArtworkDark = color.isDarkColor(backgroundColor);
+ const editorialClientParams = extractEditorialClientParams(objectGraph, shelfToken.data);
+ const mediaPageHeader = new models.MediaPageHeader(editorialBadge, editorialName, null, undefined, undefined, collectionIcons, false, backgroundColor !== null && backgroundColor !== void 0 ? backgroundColor : undefined, convertHintToIconCollectionStyle((_c = editorialClientParams.fallbackDisplayStyleHint) !== null && _c !== void 0 ? _c : "one", collectionIcons === null || collectionIcons === void 0 ? void 0 : collectionIcons.length), isArtworkDark ? "dark" : "light");
+ // Configure impressions
+ const metricsOptions = {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: metricsFromMediaApiObject(headerData),
+ };
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForTagHeader(objectGraph, headerData, editorialName, metricsOptions);
+ metricsHelpersImpressions.addImpressionFieldsToTagRoomHeader(objectGraph, mediaPageHeader, impressionOptions);
+ shelf.items = [mediaPageHeader];
+ return shelf;
+ }
+ else {
+ return null;
+ }
+ }
+ else {
+ return null;
+ }
+ }
+}
+function convertHintToIconCollectionStyle(hint, artworkCount) {
+ if (artworkCount === 2) {
+ return "TwoUp";
+ }
+ else {
+ switch (hint) {
+ case "one":
+ return "ThreeUp";
+ case "two":
+ return "Fan";
+ case "three":
+ return "Asymmetrical";
+ default:
+ return "ThreeUp";
+ }
+ }
+}
+function populatePageHeaderCarouselItemMedia(objectGraph, pageHeaderCarouselItem, hideEditorialMedia, heroMediaData, isRTL) {
+ if (!hideEditorialMedia) {
+ pageHeaderCarouselItem.backgroundColor = isRTL
+ ? heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.rtlBackgroundColor
+ : heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.backgroundColor;
+ pageHeaderCarouselItem.artwork = isRTL ? heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.rtlArtwork : heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.artwork;
+ pageHeaderCarouselItem.video = isRTL ? heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.rtlVideo : heroMediaData === null || heroMediaData === void 0 ? void 0 : heroMediaData.video;
+ }
+ pageHeaderCarouselItem.isMediaDark = isMediaDark(objectGraph, heroMediaData, isRTL);
+}
+//# sourceMappingURL=editorial-page-header-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-image-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-image-shelf-builder.js
new file mode 100644
index 0000000..44ce98e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-image-shelf-builder.js
@@ -0,0 +1,4 @@
+export function buildImageShelf(objectGraph, shelfToken) {
+ return null;
+}
+//# sourceMappingURL=editorial-page-image-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-marker-shelf-builder/editorial-page-quick-links-marker-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-marker-shelf-builder/editorial-page-quick-links-marker-shelf-builder.js
new file mode 100644
index 0000000..0f3b482
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-marker-shelf-builder/editorial-page-quick-links-marker-shelf-builder.js
@@ -0,0 +1,71 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../../api/models";
+import * as artworkBuilder from "../../../content/artwork/artwork";
+import * as metricsHelpersClicks from "../../../metrics/helpers/clicks";
+export function buildQuickLinksShelf(objectGraph, shelfToken) {
+ if (objectGraph.user && objectGraph.user.isManagedAppleID) {
+ return null;
+ }
+ // Watch App Store does not support titled button stacks,
+ // and has different footer buttons.
+ if (objectGraph.client.isWatch) {
+ const shelf = new models.Shelf("action");
+ const items = [];
+ const accountFlowAction = new models.FlowAction("account");
+ accountFlowAction.title = objectGraph.loc.string("ACCOUNT", "Account");
+ accountFlowAction.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://person.crop.circle");
+ const accountFlowClickOptions = {
+ targetType: "button",
+ id: "account",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, accountFlowAction, accountFlowClickOptions);
+ items.push(accountFlowAction);
+ shelf.items = items;
+ return shelf;
+ }
+ else {
+ const items = [];
+ if (isSome(objectGraph.bag.aboutAppStoreUrl)) {
+ const aboutAppStoreAction = new models.ExternalUrlAction(objectGraph.bag.aboutAppStoreUrl);
+ aboutAppStoreAction.title = objectGraph.loc.string("QuickLinks.AboutTheAppStore.Title");
+ items.push(aboutAppStoreAction);
+ }
+ if (isSome(objectGraph.bag.aboutInAppPurchasesEditorialItemId)) {
+ const aboutIAPsFlowAction = new models.FlowAction("article");
+ aboutIAPsFlowAction.title = objectGraph.loc.string("QuickLinks.AboutInAppPurchases.Title");
+ aboutIAPsFlowAction.pageUrl = `https://apps.apple.com/story/id${objectGraph.bag.aboutInAppPurchasesEditorialItemId}`;
+ items.push(aboutIAPsFlowAction);
+ }
+ if (isSome(objectGraph.bag.reportProblemUrl)) {
+ const reportProblemAction = new models.ExternalUrlAction(objectGraph.bag.reportProblemUrl);
+ reportProblemAction.title = objectGraph.loc.string("REPORT_A_PROBLEM", "Report a Problem");
+ items.push(reportProblemAction);
+ }
+ if (isSome(objectGraph.bag.requestARefundUrl)) {
+ const requestRefundAction = new models.ExternalUrlAction(objectGraph.bag.requestARefundUrl);
+ requestRefundAction.title = objectGraph.loc.string("QuickLinks.RequestARefund.Title");
+ items.push(requestRefundAction);
+ }
+ if (isSome(objectGraph.bag.changePaymentMethodUrl)) {
+ const changePaymentMethodAction = new models.ExternalUrlAction(objectGraph.bag.changePaymentMethodUrl);
+ changePaymentMethodAction.title = objectGraph.loc.string("QuickLinks.ChangePaymentMethod.Title");
+ items.push(changePaymentMethodAction);
+ }
+ if (isSome(objectGraph.bag.aboutFrenchAppStoreEditorialItemId)) {
+ const aboutFrenchAppStoreFlowAction = new models.FlowAction("article");
+ aboutFrenchAppStoreFlowAction.title = objectGraph.loc.string("QuickLinks.AboutFrenchAppStore.Title");
+ aboutFrenchAppStoreFlowAction.pageUrl = `https://apps.apple.com/story/id${objectGraph.bag.aboutFrenchAppStoreEditorialItemId}`;
+ items.push(aboutFrenchAppStoreFlowAction);
+ }
+ const shelf = new models.Shelf("action");
+ shelf.title = objectGraph.loc.string("QuickLinks.Title");
+ shelf.items = items;
+ shelf.isHorizontal = true;
+ shelf.rowsPerColumn = 1;
+ shelfToken.metricsImpressionOptions.title = shelf.title;
+ return shelf;
+ }
+}
+//# sourceMappingURL=editorial-page-quick-links-marker-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-marker-shelf-builder/index.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-marker-shelf-builder/index.js
new file mode 100644
index 0000000..6392f23
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-marker-shelf-builder/index.js
@@ -0,0 +1,20 @@
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import { attributeAsString } from "../../../../foundation/media/attributes";
+import { MarkerShelfKind } from "../../editorial-page-types";
+import { buildQuickLinksShelf } from "./editorial-page-quick-links-marker-shelf-builder";
+export function buildMarkerShelf(objectGraph, shelfToken) {
+ const markerKind = attributeAsString(shelfToken.data, "kind");
+ if (serverData.isNullOrEmpty(markerKind)) {
+ return null;
+ }
+ let shelf = null;
+ switch (markerKind) {
+ case MarkerShelfKind.QuickLinks:
+ shelf = buildQuickLinksShelf(objectGraph, shelfToken);
+ break;
+ default:
+ break;
+ }
+ return shelf;
+}
+//# sourceMappingURL=index.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-recommendations-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-recommendations-shelf-builder.js
new file mode 100644
index 0000000..828d652
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-recommendations-shelf-builder.js
@@ -0,0 +1,77 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../api/models";
+import { isDefinedNonNullNonEmpty } from "../../../foundation/json-parsing/server-data";
+import { attributeAsBooleanOrFalse, attributeAsString } from "../../../foundation/media/attributes";
+import { Request } from "../../../foundation/media/data-fetching";
+import { relationshipCollection } from "../../../foundation/media/relationships";
+import { buildURLFromRequest } from "../../../foundation/media/url-builder";
+import { Parameters } from "../../../foundation/network/url-constants";
+import { insertPlaceholdersIntoShelfIfRequired } from "../../placeholders/placeholders";
+import { CollectionShelfDisplayStyle } from "../editorial-page-types";
+import { createShelfSeeAllActionIfNecessary, createShelfTokenUrlIfNecessary, } from "./editorial-page-collection-shelf-builder/editorial-page-collection-shelf-common";
+import { buildEditorialLockupHierarchicalPortrait, buildEditorialLockupLargeShelf, buildLockupLargeShelf, buildLockupSmallShelf, } from "./editorial-page-collection-shelf-builder/editorial-page-lockup-collection-shelf-builder";
+export function buildRecommendationsShelf(objectGraph, shelfToken) {
+ let collectionDisplayStyle = attributeAsString(shelfToken.data, "displayStyle");
+ if (preprocessor.GAMES_TARGET) {
+ // A way to override display style as design wants to see large cards in "See all" rooms.
+ const shelfTokenStyle = shelfToken.collectionDisplayStyle;
+ if (isSome(shelfTokenStyle) && shelfTokenStyle !== collectionDisplayStyle) {
+ collectionDisplayStyle = shelfToken.collectionDisplayStyle;
+ }
+ }
+ let shelf = null;
+ switch (collectionDisplayStyle) {
+ case CollectionShelfDisplayStyle.LockupSmall:
+ shelf = buildLockupSmallShelf(objectGraph, shelfToken, false);
+ break;
+ case CollectionShelfDisplayStyle.LockupLarge:
+ shelf = buildLockupLargeShelf(objectGraph, shelfToken, false);
+ break;
+ case CollectionShelfDisplayStyle.EditorialLockupHierarchicalPortrait:
+ shelf = buildEditorialLockupHierarchicalPortrait(objectGraph, shelfToken);
+ break;
+ case CollectionShelfDisplayStyle.EditorialLockupLarge:
+ shelf = buildEditorialLockupLargeShelf(objectGraph, shelfToken);
+ break;
+ default:
+ shelf = shelfToken.isFirstRender ? makeHiddenShelf() : null;
+ break;
+ }
+ if (isNothing(shelf)) {
+ return null;
+ }
+ shelf.id = `shelf_${shelfToken.id}`;
+ addRecommendationsHrefIfNecessary(objectGraph, shelfToken);
+ shelf.url = createShelfTokenUrlIfNecessary(objectGraph, shelf, shelfToken);
+ insertPlaceholdersIntoShelfIfRequired(objectGraph, shelf, shelfToken);
+ shelf.seeAllAction = createShelfSeeAllActionIfNecessary(objectGraph, shelf, shelfToken);
+ return shelf;
+}
+/**
+ * Add the recommendations href for out of band reqeusts if necessary
+ * @param shelfToken The shelf token to add the href to
+ */
+function addRecommendationsHrefIfNecessary(objectGraph, shelfToken) {
+ const hasRecommendedContents = isDefinedNonNullNonEmpty(relationshipCollection(shelfToken.data, "contents")) ||
+ isDefinedNonNullNonEmpty(shelfToken.remainingItems);
+ const isPersonalizationAvailable = attributeAsBooleanOrFalse(shelfToken.data, "isPersonalizationAvailable");
+ const baseHref = shelfToken.data.href;
+ const shouldAddHref = isPersonalizationAvailable && !hasRecommendedContents && isDefinedNonNullNonEmpty(baseHref);
+ if (!shouldAddHref) {
+ return;
+ }
+ const recommendationsRequest = new Request(objectGraph, shelfToken.data.href)
+ .addingQuery(Parameters.editorialPageId, shelfToken.pageId)
+ .includingRelationships(["contents"]);
+ const recommendationsUrl = buildURLFromRequest(objectGraph, recommendationsRequest);
+ shelfToken.recommendationsHref = recommendationsUrl.toString();
+}
+/**
+ * Creates a hidden shelf for when we're performing a secondary fetch and need to hide a shelf that returns null
+ */
+function makeHiddenShelf() {
+ const hiddenShelf = new models.Shelf("placeholder");
+ hiddenShelf.isHidden = true;
+ return hiddenShelf;
+}
+//# sourceMappingURL=editorial-page-recommendations-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-tag-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-tag-shelf-builder.js
new file mode 100644
index 0000000..9378611
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-tag-shelf-builder.js
@@ -0,0 +1,4 @@
+export function buildTagShelf(objectGraph, shelfToken) {
+ return null;
+}
+//# sourceMappingURL=editorial-page-tag-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-text-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-text-shelf-builder.js
new file mode 100644
index 0000000..3b8fd26
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-text-shelf-builder.js
@@ -0,0 +1,19 @@
+import * as models from "../../../api/models";
+import { isNothing } from "@jet/environment";
+import { makeShelfId } from "../../../gameservicesui/src/editorial-page/editorial-component-builder";
+import * as mediaAPI from "@apple-media-services/media-api";
+export function buildTextShelf(objectGraph, shelfToken) {
+ if (!preprocessor.GAMES_TARGET) {
+ return null;
+ }
+ const shelf = new models.Shelf("paragraph");
+ shelf.id = makeShelfId(shelfToken).toString();
+ const editorialCopy = mediaAPI.attributeAsString(shelfToken.data, "editorialCopy");
+ if (isNothing(editorialCopy)) {
+ return null;
+ }
+ const paragraph = new models.Paragraph(editorialCopy, "text/x-apple-as3-nqml", "article");
+ shelf.items = [paragraph];
+ return shelf;
+}
+//# sourceMappingURL=editorial-page-text-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-upsell-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-upsell-shelf-builder.js
new file mode 100644
index 0000000..deb0ac9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-upsell-shelf-builder.js
@@ -0,0 +1,31 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as arcadeCommon from "../../arcade/arcade-common";
+import * as arcadeUpsell from "../../arcade/arcade-upsell";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as metricsHelpersUtil from "../../metrics/helpers/util";
+import { makeGamesPageUpsellHeaderFromData } from "../../../gameservicesui/src/editorial-page/editorial-component-builder";
+export function buildUpsellShelf(objectGraph, shelfToken) {
+ if (preprocessor.GAMES_TARGET) {
+ return makeGamesPageUpsellHeaderFromData(objectGraph, shelfToken);
+ }
+ const upsellData = arcadeCommon.upsellFromRelationshipOf(objectGraph, shelfToken.data);
+ if (serverData.isNullOrEmpty(upsellData)) {
+ return null;
+ }
+ const marketingItemData = upsellData.marketingItemData;
+ if (serverData.isNullOrEmpty(marketingItemData) || serverData.isNullOrEmpty(marketingItemData.attributes)) {
+ return null;
+ }
+ const metricsOptions = {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ mercuryMetricsData: metricsHelpersUtil.marketingItemTopLevelBaseFieldsFromData(objectGraph, marketingItemData),
+ };
+ const upsellBreakout = arcadeUpsell.createUpsellBreakout(objectGraph, marketingItemData, metricsOptions);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker);
+ const shelf = new models.Shelf("upsellBreakout");
+ shelf.items = [upsellBreakout];
+ return shelf;
+}
+//# sourceMappingURL=editorial-page-upsell-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-video-clip-shelf-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-video-clip-shelf-builder.js
new file mode 100644
index 0000000..b5548db
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/editorial-page-video-clip-shelf-builder.js
@@ -0,0 +1,4 @@
+export function buildVideoClipShelf(objectGraph, shelfToken) {
+ return null;
+}
+//# sourceMappingURL=editorial-page-video-clip-shelf-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/index.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/index.js
new file mode 100644
index 0000000..59dbabe
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-builder/index.js
@@ -0,0 +1,83 @@
+import { isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { EditorialShelfType } from "../editorial-page-types";
+import { buildChartShelf } from "./editorial-page-chart-shelf-builder";
+import { buildCollectionShelf } from "./editorial-page-collection-shelf-builder";
+import { buildEngagementShelf } from "./editorial-page-engagement-shelf-builder";
+import { buildGameCenterShelf } from "./editorial-page-game-center-shelf-builder";
+import { buildHeaderShelf } from "./editorial-page-header-shelf-builder";
+import { buildImageShelf } from "./editorial-page-image-shelf-builder";
+import { buildMarkerShelf } from "./editorial-page-marker-shelf-builder";
+import { buildArcadeSeeAllGamesShelf } from "./editorial-page-arcade-see-all-games-shelf-builder";
+import { buildRecommendationsShelf } from "./editorial-page-recommendations-shelf-builder";
+import { buildTagShelf } from "./editorial-page-tag-shelf-builder";
+import { buildTextShelf } from "./editorial-page-text-shelf-builder";
+import { buildUpsellShelf } from "./editorial-page-upsell-shelf-builder";
+import { buildVideoClipShelf } from "./editorial-page-video-clip-shelf-builder";
+import { nextPosition, popLocation, pushContentLocation } from "../../metrics/helpers/location";
+import { addImpressionFields } from "../../metrics/helpers/impressions";
+import { storePlaceholderShelfImpressionMetrics } from "./editorial-page-collection-shelf-builder/editorial-page-collection-shelf-common";
+import { isSome } from "@jet/environment/types/optional";
+export function buildEditorialShelf(objectGraph, pageData, shelfToken) {
+ let shelf = null;
+ if (shelfToken.isFirstRender) {
+ pushContentLocation(objectGraph, shelfToken.metricsImpressionOptions, shelfToken.title);
+ }
+ switch (shelfToken.type) {
+ case EditorialShelfType.ArcadeSeeAllGames:
+ shelf = buildArcadeSeeAllGamesShelf(objectGraph, shelfToken);
+ break;
+ case EditorialShelfType.Collection:
+ shelf = buildCollectionShelf(objectGraph, shelfToken);
+ break;
+ case EditorialShelfType.Chart:
+ shelf = buildChartShelf(objectGraph, shelfToken);
+ break;
+ case EditorialShelfType.Tag:
+ shelf = buildTagShelf(objectGraph, shelfToken);
+ break;
+ case EditorialShelfType.Engagement:
+ shelf = buildEngagementShelf(objectGraph, shelfToken);
+ break;
+ case EditorialShelfType.Text:
+ shelf = buildTextShelf(objectGraph, shelfToken);
+ break;
+ case EditorialShelfType.Image:
+ shelf = buildImageShelf(objectGraph, shelfToken);
+ break;
+ case EditorialShelfType.VideoClip:
+ shelf = buildVideoClipShelf(objectGraph, shelfToken);
+ break;
+ case EditorialShelfType.Header:
+ shelf = buildHeaderShelf(objectGraph, pageData, shelfToken);
+ break;
+ case EditorialShelfType.Recommendations:
+ shelf = buildRecommendationsShelf(objectGraph, shelfToken);
+ break;
+ case EditorialShelfType.GameCenter:
+ shelf = buildGameCenterShelf(objectGraph, shelfToken);
+ break;
+ case EditorialShelfType.Upsell:
+ shelf = buildUpsellShelf(objectGraph, shelfToken);
+ break;
+ case EditorialShelfType.Marker:
+ shelf = buildMarkerShelf(objectGraph, shelfToken);
+ break;
+ default:
+ break;
+ }
+ if (isNullOrEmpty(shelf === null || shelf === void 0 ? void 0 : shelf.items) && isNullOrEmpty(shelf === null || shelf === void 0 ? void 0 : shelf.url)) {
+ shelf = null;
+ }
+ if (shelfToken.isFirstRender) {
+ popLocation(shelfToken.metricsImpressionOptions.locationTracker);
+ }
+ addImpressionFields(objectGraph, shelf, shelfToken.metricsImpressionOptions);
+ if (shelfToken.isFirstRender) {
+ nextPosition(shelfToken.metricsImpressionOptions.locationTracker);
+ }
+ if (isSome(shelf)) {
+ storePlaceholderShelfImpressionMetrics(objectGraph, shelf, shelfToken);
+ }
+ return shelf;
+}
+//# sourceMappingURL=index.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-metrics.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-metrics.js
new file mode 100644
index 0000000..e1f18ed
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-metrics.js
@@ -0,0 +1,51 @@
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import { collectionShelfDisplayStyleFromShelfData } from "./editorial-data-util";
+import { decorateCollectionShelfMetricsOptions } from "./editorial-page-shelf-builder/editorial-page-collection-shelf-builder";
+import { decorateHeroShelfMetricsOptions } from "./editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-hero-collection-shelf-builder";
+import { CollectionShelfDisplayStyle, EditorialShelfType } from "./editorial-page-types";
+/**
+ * Creates the shelf metrics options for a given shelf data item.
+ * @param objectGraph Current object graph
+ * @param shelfData The data for the shelf
+ * @param title The metrics title for the shelf
+ * @param metricsPageInformation Current metrics page information
+ * @param metricsLocationTracker Current metrics location tracker
+ * @returns Built shelf metrics options
+ */
+export function buildShelfMetricsOptions(objectGraph, shelfData, title, metricsPageInformation, metricsLocationTracker) {
+ const shelfType = shelfData.type;
+ const collectionDisplayStyle = collectionShelfDisplayStyleFromShelfData(objectGraph, shelfData);
+ const shelfMetricsOptions = {
+ id: serverData.asString(shelfData, "id"),
+ kind: null,
+ softwareType: null,
+ targetType: "swoosh",
+ title: title,
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ idType: "shelf_id",
+ shelfType: shelfType,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(shelfData),
+ canonicalId: serverData.asString(shelfData.meta, "canonicalId"),
+ };
+ switch (shelfType) {
+ case EditorialShelfType.Collection:
+ decorateCollectionShelfMetricsOptions(objectGraph, shelfData, shelfMetricsOptions);
+ switch (collectionDisplayStyle) {
+ case CollectionShelfDisplayStyle.Hero:
+ decorateHeroShelfMetricsOptions(objectGraph, shelfMetricsOptions);
+ break;
+ default:
+ break;
+ }
+ break;
+ case EditorialShelfType.Recommendations:
+ decorateCollectionShelfMetricsOptions(objectGraph, shelfData, shelfMetricsOptions);
+ break;
+ default:
+ break;
+ }
+ return shelfMetricsOptions;
+}
+//# sourceMappingURL=editorial-page-shelf-metrics.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-token.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-token.js
new file mode 100644
index 0000000..6857cdb
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-shelf-token.js
@@ -0,0 +1,120 @@
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as content from "../content/content";
+import { recoMetricsDataForFCData } from "../grouping/shelf-controllers/grouping-shelf-controller-common";
+import { collectionShelfDisplayStyleFromShelfData, extractEditorialClientParams } from "./editorial-data-util";
+import { buildShelfMetricsOptions } from "./editorial-page-shelf-metrics";
+import { GameCenterShelfClientFilter, } from "./editorial-page-types";
+import { isSome } from "@jet/environment";
+/**
+ * Create the base shelf token to start parsing an editorial page shelf. This will create a new shelf token with the
+ * base metrics options
+ *
+ * @param objectGraph The App Store dependency graph for making native calls and viewing properties
+ * @param pageId The id of the editorial page this shelf is on.
+ * @param shelfData The media api data for this specific shelf
+ * @param isArcadePage Whether this shelf is on the arcade page or not
+ * @param shelfIndex The index of this shelf on the page, at the time of building
+ * @param metricsPageInformation The metrics page information for the editorial page
+ * @param metricsLocationTracker The location tracker for building metrics location information
+ * @param isSeeAll Whether the shelf is being built as a shelf for a sidepacked See All page.
+ * @param collectionDisplayStyleOverride A way to override collection display style that is coming from mediaAPI.
+ */
+export function createBaseShelfToken(objectGraph, pageId, shelfData, isArcadePage, shelfIndex, metricsPageInformation, metricsLocationTracker, isSeeAll = false, collectionDisplayStyle = null) {
+ const shelfToken = {
+ id: serverData.asString(shelfData, "id"),
+ type: shelfData.type,
+ collectionDisplayStyle: collectionDisplayStyle !== null && collectionDisplayStyle !== void 0 ? collectionDisplayStyle : collectionShelfDisplayStyleFromShelfData(objectGraph, shelfData),
+ filterOverrides: mediaAttributes.attributeAsArrayOrEmpty(shelfData, "filterOverrides"),
+ pageId: pageId,
+ data: shelfData,
+ presentationHints: {},
+ clientIdentifierOverride: null,
+ isFirstRender: true,
+ shouldFilter: true,
+ gamesFilter: gamesFilterFromShelfData(shelfData),
+ hasExistingContent: false,
+ title: null,
+ subtitle: null,
+ eyebrow: null,
+ titleArtwork: null,
+ remainingItems: [],
+ metricsImpressionOptions: null,
+ metricsPageInformation: metricsPageInformation,
+ metricsLocationTracker: metricsLocationTracker,
+ recoMetricsData: recoMetricsDataForFCData(objectGraph, shelfData),
+ isDeferring: false,
+ showOrdinals: false,
+ ordinalIndex: 1,
+ isSearchLandingPage: false,
+ isArcadePage: isArcadePage,
+ shelfIndex: shelfIndex,
+ isSeeAll: isSeeAll,
+ };
+ applyBaseShelfTokenTitleValues(objectGraph, shelfData, shelfToken);
+ return shelfToken;
+}
+/**
+ * Apply the values for the title, subtitle, and eyebrow to the shelf token, as well as the impressionOptions
+ *
+ * @param objectGraph The App Store dependency graph for making native calls and viewing properties
+ * @param shelfData The media api data for this specific shelf
+ * @param shelfToken The shelf token to apply the title values to
+ */
+export function applyBaseShelfTokenTitleValues(objectGraph, shelfData, shelfToken) {
+ let title = mediaAttributes.attributeAsString(shelfData, "editorialNotes.name");
+ let subtitle = mediaAttributes.attributeAsString(shelfData, "editorialNotes.tagline");
+ let eyebrow = mediaAttributes.attributeAsString(shelfData, "editorialNotes.badge");
+ let titleArtwork = null;
+ // 'Similar To' Personalised Shelf
+ // Check if personalised shelf has a badge-content, if it's got valid content, and the clients support eyebrows and title artwork:
+ // - Reco shelf title becomes the eyebrow
+ // - Featured App in badge-content becomes the shelf title, and we add the icon as title artwork
+ // - No subtitle should be shown
+ // - Flag to use the eyebrown name for metrics
+ let wantsEyebrowNameForMetrics = false;
+ const badgeContent = mediaRelationship.relationshipData(objectGraph, shelfData, "badge-content");
+ if (serverData.isDefinedNonNullNonEmpty(badgeContent)) {
+ eyebrow = objectGraph.loc.uppercased(mediaAttributes.attributeAsString(shelfData, "editorialNotes.name"));
+ title = mediaAttributes.attributeAsString(badgeContent, "editorialNotes.name");
+ titleArtwork = content.iconFromData(objectGraph, badgeContent, {
+ useCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ });
+ subtitle = null;
+ wantsEyebrowNameForMetrics = true;
+ }
+ if (preprocessor.GAMES_TARGET) {
+ const editorialClientParams = extractEditorialClientParams(objectGraph, shelfData);
+ // This flag hides the whole shelf header content altogether
+ if (isSome(editorialClientParams) && editorialClientParams.suppressName) {
+ title = null;
+ subtitle = null;
+ eyebrow = null;
+ titleArtwork = null;
+ }
+ }
+ const shelfMetricsOptionsTitle = wantsEyebrowNameForMetrics ? eyebrow : title;
+ const shelfMetricsOptions = buildShelfMetricsOptions(objectGraph, shelfData, shelfMetricsOptionsTitle, shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker);
+ shelfToken.title = title;
+ shelfToken.subtitle = subtitle;
+ shelfToken.eyebrow = eyebrow;
+ shelfToken.titleArtwork = titleArtwork;
+ shelfToken.metricsImpressionOptions = shelfMetricsOptions;
+}
+function gamesFilterFromShelfData(shelfData) {
+ const clientFilter = mediaAttributes.attributeAsString(shelfData, "clientFilter");
+ let gamesFilter = null;
+ switch (clientFilter) {
+ case GameCenterShelfClientFilter.ArcadeGames:
+ gamesFilter = "arcade";
+ break;
+ case GameCenterShelfClientFilter.AllGames:
+ gamesFilter = "all";
+ break;
+ default:
+ break;
+ }
+ return gamesFilter;
+}
+//# sourceMappingURL=editorial-page-shelf-token.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-types.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-types.js
new file mode 100644
index 0000000..22daf5f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-page-types.js
@@ -0,0 +1,139 @@
+import { isNothing } from "@jet/environment";
+import * as mediaAttributes from "../../foundation/media/attributes";
+export var EditorialShelfType;
+(function (EditorialShelfType) {
+ EditorialShelfType["ArcadeSeeAllGames"] = "editorial-shelves-arcade-see-all-games";
+ EditorialShelfType["Collection"] = "editorial-shelves-collection";
+ EditorialShelfType["Chart"] = "editorial-shelves-chart";
+ EditorialShelfType["Tag"] = "editorial-shelves-tag";
+ EditorialShelfType["Engagement"] = "editorial-shelves-engagement";
+ EditorialShelfType["Text"] = "editorial-shelves-text";
+ EditorialShelfType["Image"] = "editorial-shelves-image";
+ EditorialShelfType["VideoClip"] = "editorial-shelves-video-clip";
+ EditorialShelfType["Header"] = "editorial-shelves-header";
+ EditorialShelfType["Recommendations"] = "editorial-shelves-recommendation";
+ EditorialShelfType["GameCenter"] = "editorial-shelves-game-center";
+ EditorialShelfType["Upsell"] = "editorial-shelves-upsell";
+ EditorialShelfType["Marker"] = "editorial-shelves-marker";
+})(EditorialShelfType || (EditorialShelfType = {}));
+export var CollectionShelfDisplayStyle;
+(function (CollectionShelfDisplayStyle) {
+ CollectionShelfDisplayStyle["Hero"] = "Hero";
+ CollectionShelfDisplayStyle["TextOnly"] = "TextOnly";
+ CollectionShelfDisplayStyle["TextWithArtwork"] = "TextWithArtwork";
+ CollectionShelfDisplayStyle["BrickSmall"] = "BrickSmall";
+ CollectionShelfDisplayStyle["BrickMedium"] = "BrickMedium";
+ CollectionShelfDisplayStyle["BrickLarge"] = "BrickLarge";
+ CollectionShelfDisplayStyle["Charts"] = "Charts";
+ CollectionShelfDisplayStyle["EditorialLockupHierarchicalPortrait"] = "EditorialLockupHierarchicalPortrait";
+ CollectionShelfDisplayStyle["EditorialLockupHierarchicalRows"] = "EditorialLockupHierarchicalRows";
+ CollectionShelfDisplayStyle["EditorialLockupMedium"] = "EditorialLockupMedium";
+ CollectionShelfDisplayStyle["EditorialLockupMediumVariant"] = "EditorialLockupMediumVariant";
+ CollectionShelfDisplayStyle["EditorialLockupLarge"] = "EditorialLockupLarge";
+ CollectionShelfDisplayStyle["EditorialLockupLargeVariant"] = "EditorialLockupLargeVariant";
+ CollectionShelfDisplayStyle["LockupMedium"] = "LockupMedium";
+ CollectionShelfDisplayStyle["Lockup4Up"] = "Lockup4Up";
+ CollectionShelfDisplayStyle["Poster"] = "Poster";
+ CollectionShelfDisplayStyle["StorySmall"] = "StorySmall";
+ CollectionShelfDisplayStyle["StoryMedium"] = "StoryMedium";
+ CollectionShelfDisplayStyle["LockupSmall"] = "LockupSmall";
+ CollectionShelfDisplayStyle["LockupLarge"] = "LockupLarge";
+ CollectionShelfDisplayStyle["BreakoutLarge"] = "BreakoutLarge";
+})(CollectionShelfDisplayStyle || (CollectionShelfDisplayStyle = {}));
+/**
+ * Returns the limit for a collection shelf to display in a non-See All page.
+ * @param {CollectionShelfDisplayStyle} displayStyle Collection shelf display style.
+ * @returns {number | undefined} Number of items to display.
+ */
+export function displayLimitForCollectionShelf(displayStyle, layoutDirection, shelfToken) {
+ if (!preprocessor.GAMES_TARGET || shelfToken.isSeeAll) {
+ return undefined;
+ }
+ switch (displayStyle) {
+ case CollectionShelfDisplayStyle.EditorialLockupLarge:
+ // "2 Cards" style https://quip-apple.com/qjjZAy4x977f
+ return layoutDirection === "Condensed" ? 2 : undefined;
+ case CollectionShelfDisplayStyle.EditorialLockupHierarchicalPortrait:
+ // "3 Cards" style https://quip-apple.com/qjjZAy4x977f
+ return 3;
+ case CollectionShelfDisplayStyle.EditorialLockupMedium:
+ // "4 Cards (4up)" style https://quip-apple.com/qjjZAy4x977f
+ return layoutDirection === "Condensed" ? 4 : undefined;
+ case CollectionShelfDisplayStyle.EditorialLockupHierarchicalRows:
+ // "4 Cards (Hierarchical)" style https://quip-apple.com/qjjZAy4x977f
+ return 4;
+ case CollectionShelfDisplayStyle.Charts:
+ // "4 Cards (Charts)" style https://quip-apple.com/qjjZAy4x977f
+ return 6;
+ case CollectionShelfDisplayStyle.LockupSmall:
+ // "6 Rows" style https://quip-apple.com/qjjZAy4x977f
+ return layoutDirection === "Condensed" ? 9 : undefined;
+ default:
+ return undefined;
+ }
+}
+/**
+ * Returns the display style override for See All pages.
+ * @param {CollectionShelfDisplayStyle} displayStyle Collection shelf display style.
+ * @returns {number | undefined} Number of items to display.
+ */
+export function collectionDisplayStyleOverride(displayStyle) {
+ if (!preprocessor.GAMES_TARGET || isNothing(displayStyle)) {
+ return null;
+ }
+ switch (displayStyle) {
+ // These shelves should maintain their display style in See All pages.
+ case CollectionShelfDisplayStyle.BrickSmall:
+ case CollectionShelfDisplayStyle.LockupMedium:
+ return displayStyle;
+ // All other shelves will display in EditorialLockupLarge display style in See All pages.
+ default:
+ return CollectionShelfDisplayStyle.EditorialLockupLarge;
+ }
+}
+export var MarkerShelfKind;
+(function (MarkerShelfKind) {
+ MarkerShelfKind["QuickLinks"] = "QuickLinks";
+})(MarkerShelfKind || (MarkerShelfKind = {}));
+export var CollectionShelfLayoutDirection;
+(function (CollectionShelfLayoutDirection) {
+ CollectionShelfLayoutDirection["Vertical"] = "Vertical";
+ CollectionShelfLayoutDirection["Horizontal"] = "Horizontal";
+ CollectionShelfLayoutDirection["Condensed"] = "Condensed";
+})(CollectionShelfLayoutDirection || (CollectionShelfLayoutDirection = {}));
+export function collectionShelfLayoutDirection(shelfToken) {
+ const layoutDirection = mediaAttributes.attributeAsString(shelfToken.data, "layoutDirection");
+ if (layoutDirection === CollectionShelfLayoutDirection.Horizontal) {
+ return CollectionShelfLayoutDirection.Horizontal;
+ }
+ if (layoutDirection === CollectionShelfLayoutDirection.Condensed) {
+ return CollectionShelfLayoutDirection.Condensed;
+ }
+ // Make default one as vertical for backward compatibility when code used to check `=== "Horizontal"`.
+ return CollectionShelfLayoutDirection.Vertical;
+}
+export var CollectionShelfFilterOverride;
+(function (CollectionShelfFilterOverride) {
+ CollectionShelfFilterOverride["ShowInstalled"] = "ShowInstalled";
+ CollectionShelfFilterOverride["ShowAllPlatforms"] = "ShowAllPlatforms";
+ CollectionShelfFilterOverride["ShowOnlyPreorder"] = "ShowOnlyPreorder";
+})(CollectionShelfFilterOverride || (CollectionShelfFilterOverride = {}));
+export var GameCenterShelfClientFilter;
+(function (GameCenterShelfClientFilter) {
+ GameCenterShelfClientFilter["AllGames"] = "AllGames";
+ GameCenterShelfClientFilter["ArcadeGames"] = "ArcadeGames";
+})(GameCenterShelfClientFilter || (GameCenterShelfClientFilter = {}));
+export var GameCenterShelfKind;
+(function (GameCenterShelfKind) {
+ GameCenterShelfKind["FriendsArePlaying"] = "FriendsArePlaying";
+ GameCenterShelfKind["ContinuePlaying"] = "ContinuePlaying";
+})(GameCenterShelfKind || (GameCenterShelfKind = {}));
+export var UpsellShelfPlacement;
+(function (UpsellShelfPlacement) {
+ UpsellShelfPlacement["ArcadeTabHeader"] = "ArcadeTabHeader";
+})(UpsellShelfPlacement || (UpsellShelfPlacement = {}));
+export var UpsellShelfServiceType;
+(function (UpsellShelfServiceType) {
+ UpsellShelfServiceType["Arcade"] = "Arcade";
+})(UpsellShelfServiceType || (UpsellShelfServiceType = {}));
+//# sourceMappingURL=editorial-page-types.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-shelf-collection-page-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-shelf-collection-page-utils.js
new file mode 100644
index 0000000..6544691
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/editorial-pages/editorial-shelf-collection-page-utils.js
@@ -0,0 +1,120 @@
+import { isSome, unwrapOptional as unwrap } from "@jet/environment/types/optional";
+import { GenericPage, Shelf, PageHeader } from "../../api/models";
+import { makeEditorialShelfCollectionPageIntent, } from "../../api/intents/editorial/editorial-shelf-collection-page-intent";
+import { Request, defaultAdditionalPlatformsForClient } from "../../foundation/media/data-fetching";
+import { dataFromDataContainer } from "../../foundation/media/data-structure";
+import { attributeAsString } from "../../foundation/media/attributes";
+import { isDefinedNonNullNonEmpty } from "../../foundation/json-parsing/server-data";
+import { metricsPageInformationFromMediaApiResponse, addMetricsEventsToPageWithInformation, } from "../../common/metrics/helpers/page";
+import { newLocationTracker } from "../../common/metrics/helpers/location";
+import { createBaseShelfToken } from "../../common/editorial-pages/editorial-page-shelf-token";
+import { buildEditorialShelf } from "../../common/editorial-pages/editorial-page-shelf-builder";
+import { newPageRefreshControllerFromResponse, pageRefreshPolicyForController, } from "../../common/refresh/page-refresh-controller";
+import { shouldFetchCustomAttributes } from "../product-page/product-page-variants";
+import { prepareMAPIRequest } from "./editorial-page-controller-util";
+import { generateRoutes } from "../util/generate-routes";
+import { setPreviewPlatform } from "../preview-platform";
+import { makeGamesPageHeader } from "../../gameservicesui/src/editorial-page/editorial-component-builder";
+import { collectionShelfDisplayStyleFromShelfData } from "./editorial-data-util";
+import { collectionDisplayStyleOverride } from "./editorial-page-types";
+import { shouldUsePrerenderedIconArtwork } from "../content/content";
+// MARK: - `EditorialShelfCollectionPageIntentController` URL Building
+const { routes: routesWithoutPlatformSegment, makeCanonicalUrl: makeCanonicalUrlWithoutPlatformSegment } = generateRoutes(makeEditorialShelfCollectionPageIntent, "/collections/{id}");
+const { routes: routesWithPlatformSegment, makeCanonicalUrl: makeCanonicalUrlWithPlatformSegment } = generateRoutes(makeEditorialShelfCollectionPageIntent, "/{platform}/collections/{id}");
+export function editorialShelfCollectionPageRoutes(objectGraph) {
+ return [...routesWithoutPlatformSegment(objectGraph), ...routesWithPlatformSegment(objectGraph)];
+}
+export function makeEditorialShelfCollectionPageURL(objectGraph, intent) {
+ if (intent.platform) {
+ return makeCanonicalUrlWithPlatformSegment(objectGraph, intent);
+ }
+ else {
+ return makeCanonicalUrlWithoutPlatformSegment(objectGraph, intent);
+ }
+}
+// MARK: - Media API Request Building
+export function defaultRequestAttributes(objectGraph) {
+ const attributes = [
+ "editorialArtwork",
+ "editorialVideo",
+ "isAppleWatchSupported",
+ "requiredCapabilities",
+ "expectedReleaseDateDisplayFormat",
+ "showExpectedReleaseDate",
+ "badge-content",
+ "compatibilityControllerRequirement",
+ ];
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ return attributes;
+}
+/**
+ * Creates a {@linkcode Request} to fetch a single `editorial-shelves-collection` item by {@linkcode id}
+ */
+export function makeEditorialShelfCollectionPageRequest(objectGraph, intent) {
+ const request = new Request(objectGraph, `/v1/editorial/${intent.storefront}`).withIdOfType(intent.id, "editorial-shelves-collection");
+ request
+ .includingAgeRestrictions()
+ .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph))
+ .includingAttributes(defaultRequestAttributes(objectGraph))
+ .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ if (objectGraph.client.isWatch) {
+ request.addingRelationshipLimit("contents", 3);
+ }
+ request
+ .includingRelationships(["contents"])
+ .includingScopedRelationships("editorial-pages", ["primary-contents"])
+ .includingAssociateKeys("editorial-shelves-collection:contents", ["editorial-cards"]);
+ prepareMAPIRequest(objectGraph, request);
+ setPreviewPlatform(objectGraph, request);
+ return request;
+}
+// MARK: - Editorial Shelves Collection Page Rendering
+export function renderEditorialShelfCollectionPage(objectGraph, data) {
+ var _a;
+ const shelfData = dataFromDataContainer(objectGraph, data);
+ if (!isDefinedNonNullNonEmpty(shelfData)) {
+ return null;
+ }
+ const metricsPageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "Room", shelfData.id, data);
+ const metricsPageLocationTracker = newLocationTracker();
+ const pageRefreshController = newPageRefreshControllerFromResponse(data);
+ const collectionDisplayStyle = collectionShelfDisplayStyleFromShelfData(objectGraph, shelfData);
+ const shelfToken = createBaseShelfToken(objectGraph, undefined, shelfData, false, 0, metricsPageInformation, metricsPageLocationTracker, preprocessor.GAMES_TARGET, collectionDisplayStyleOverride(collectionDisplayStyle));
+ const shelves = [];
+ const shelf = unwrap(buildEditorialShelf(objectGraph, undefined, shelfToken));
+ shelf.title = null;
+ shelf.eyebrow = null;
+ shelf.isHorizontal = false;
+ const headerTitle = attributeAsString(shelfData, "editorialNotes.name");
+ if (isSome(headerTitle)) {
+ // There must be a title to create a pageHeader.
+ const headerShelf = new Shelf("pageHeader");
+ headerShelf.id = "shelf_page_header";
+ let pageHeader;
+ if (preprocessor.GAMES_TARGET) {
+ pageHeader = makeGamesPageHeader(shelfToken, headerTitle, shelf.items, (_a = attributeAsString(shelfData, "editorialNotes.tagline")) !== null && _a !== void 0 ? _a : undefined);
+ }
+ else {
+ pageHeader = new PageHeader(null, headerTitle, attributeAsString(shelfData, "editorialNotes.tagline"));
+ }
+ headerShelf.items = [pageHeader];
+ shelves.push(headerShelf);
+ }
+ shelves.push(shelf);
+ const page = new GenericPage(shelves);
+ page.pageRefreshPolicy = pageRefreshPolicyForController(objectGraph, pageRefreshController);
+ addMetricsEventsToPageWithInformation(objectGraph, page, metricsPageInformation);
+ return page;
+}
+//# sourceMappingURL=editorial-shelf-collection-page-utils.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/filtering.js b/node_modules/@jet-app/app-store/tmp/src/common/filtering.js
new file mode 100644
index 0000000..a313802
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/filtering.js
@@ -0,0 +1,446 @@
+/**
+ * Created by keithpk on 4/25/17.
+ */
+import * as derivedData from "../foundation/json-parsing/derived-data";
+import * as serverData from "../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../foundation/media/attributes";
+import * as mediaPlatformAttributes from "../foundation/media/platform-attributes";
+import * as client from "../foundation/wrappers/client";
+import * as contentAttributes from "./content/attributes";
+import * as content from "./content/content";
+import * as sad from "./content/sad";
+export function shouldFilter(objectGraph, data, type = 80894 /* Filter.All */) {
+ let filter = false;
+ if (type & 2 /* Filter.ThirtyTwoBit */) {
+ filter = filter || shouldFilter32Bit(objectGraph, data);
+ }
+ if (type & 4 /* Filter.UnsupportedSystemDeletableApps */) {
+ filter = filter || shouldFilterDeletableSystemApp(objectGraph, data);
+ }
+ if (type & 8 /* Filter.UnsupportedWatchAppOnWatchClient */) {
+ filter = filter || shouldFilterUnsupportedWatchAppOnWatchClient(objectGraph, data);
+ }
+ if (type & 16 /* Filter.DisabledMerchandisableIAPs */) {
+ filter = filter || shouldFilterMerchandisableInAppPurchase(objectGraph, data);
+ }
+ if (type & 32 /* Filter.LegacyApps */) {
+ filter = filter || shouldFilterLegacyApps(objectGraph, data);
+ }
+ if (type & 64 /* Filter.LegacyProductionMacOSInstaller */) {
+ filter = filter || isLegacyProductionMacOSInstaller(objectGraph, data);
+ }
+ if (type & 128 /* Filter.UnsupportedPlatform */) {
+ filter = filter || shouldFilterUnsupportedPlatform(objectGraph, data);
+ }
+ if (type & 16384 /* Filter.UnsupportedPlatformForMacCharts */) {
+ filter = filter || shouldFilterUnsupportedPlatformForMacCharts(objectGraph, data);
+ }
+ if (type & 256 /* Filter.UnsupportedPreorders */) {
+ filter = filter || shouldFilterUnsupportedPreorders(objectGraph, data);
+ }
+ if (type & 512 /* Filter.MinimumOSRequirement */) {
+ filter = filter || shouldFilterForMinimumOSRequirement(objectGraph, data);
+ }
+ if (type & 1024 /* Filter.SADWatchApps */) {
+ filter = filter || shouldFilterSADWatchApps(objectGraph, data);
+ }
+ if (type & 2048 /* Filter.MinimumCompanionOSRequirement */) {
+ filter = filter || shouldFilterForMinimumCompanionOSRequirement(objectGraph, data);
+ }
+ if (type & 4096 /* Filter.MacOSCompatibleIOSBinary */) {
+ filter = filter || shouldFilterForMacOSCompatibleIOSBinary(objectGraph, data);
+ }
+ if (type & 8192 /* Filter.MacOSRosetta */) {
+ filter = filter || shouldFilterRosetta(objectGraph, data);
+ }
+ if (type & 32768 /* Filter.VisionOSCompatibility */) {
+ filter = filter || shouldFilterForVisionOSCompatibility(objectGraph, data);
+ }
+ if (type & 65536 /* Filter.SADMinimumOSRequirement */) {
+ filter = filter || shouldFilterForSADMinimumOSRequirement(objectGraph, data);
+ }
+ return filter;
+}
+function shouldFilter32Bit(objectGraph, data) {
+ if (objectGraph.client.isWatch) {
+ // We still ship 32 bit watches and software.
+ return false;
+ }
+ return (contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "is32bitOnly") ||
+ // I don't get why we need a different field for macOS, but so is life
+ contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "requires32bit"));
+}
+function shouldFilterUnsupportedWatchAppOnWatchClient(objectGraph, data) {
+ return derivedData.value(data, "shouldFilterUnsupportedWatchAppOnWatchClient", () => {
+ const isWatchSupported = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isAppleWatchSupported");
+ const isBundle = data.type === "app-bundles";
+ if (objectGraph.host.clientIdentifier === client.watchIdentifier && !isWatchSupported && !isBundle) {
+ return true;
+ }
+ return false;
+ });
+}
+function shouldFilterLegacyApps(objectGraph, data) {
+ return derivedData.value(data, "shouldFilterLegacyApps", () => {
+ const bundleId = contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId");
+ if (objectGraph.client.isiOS || objectGraph.client.isVision) {
+ switch (bundleId) {
+ // "Find My iPhone" has been replaced by "Find My" in Yukon
+ case "com.apple.mobileme.fmip1":
+ return true;
+ // Old 2nd party Shortcuts is no longer available on iOS 13
+ // <rdar://problem/51756700> In Search results user will see 2 Shortcuts apps
+ case "is.workflow.my.app":
+ return true;
+ default:
+ break;
+ }
+ }
+ if (objectGraph.client.isVision) {
+ switch (bundleId) {
+ // iTunes Store is not available on visionOS
+ case "com.apple.MobileStore":
+ return true;
+ default:
+ break;
+ }
+ }
+ if (objectGraph.client.isWatch) {
+ switch (bundleId) {
+ // "Find My Friends" has been replaced by "Find My" in Grace
+ // rdar://62885564 (Find my app changes)
+ case "com.apple.mobileme.fmf1":
+ return true;
+ case "com.apple.findmy":
+ // "Find My" has been replaced by "Find People" in JupiterC.
+ // rdar://85616308 (2021 JS: Find People (watch app) - Filter Search and Disable Offer Button for watchOS 8.3 and above)
+ return content.isOSAtLeastVersion(objectGraph, "8.3");
+ case "com.apple.findmy.findpeople":
+ // "Find My" has been replaced by "Find People" in JupiterC.
+ // Older clients should filter the new "Find People" app.
+ // rdar://85616308 (2021 JS: Find People (watch app) - Filter Search and Disable Offer Button for watchOS 8.3 and above)
+ return !content.isOSAtLeastVersion(objectGraph, "8.3");
+ default:
+ break;
+ }
+ }
+ if (objectGraph.client.isMac) {
+ switch (bundleId) {
+ // Facetime is now bundled into macOS
+ case "com.apple.FaceTime":
+ return true;
+ // These are betas of old macOS releases
+ case "com.apple.InstallAssistant.Seed.macOS1013Seed1":
+ case "com.apple.InstallAssistant.OSX12PublicSeed2":
+ case "com.apple.InstallAssistant.OSX12CustomerSeed1":
+ case "com.apple.InstallAssistant.OSX12Seed2":
+ return true;
+ // These are old version of macOS server that are no longer relevant
+ case "com.apple.Server.v3":
+ case "com.apple.Server.v2":
+ case "com.apple.Server.v1":
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+ });
+}
+function isLegacyProductionMacOSInstaller(objectGraph, data) {
+ return derivedData.value(data, "isLegacyMacOSInstaller", () => {
+ const bundleId = contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId");
+ const isMac = objectGraph.client.isMac;
+ if (!isMac) {
+ return false;
+ }
+ switch (bundleId) {
+ // These are production releases of old OSes
+ case "com.apple.InstallAssistant.ElCapitan2":
+ case "com.apple.InstallAssistant.ElCapitan":
+ case "com.apple.InstallAssistant.Yosemite":
+ case "com.apple.InstallAssistant.Mavericks":
+ case "com.apple.InstallAssistant.MountainLion":
+ case "com.apple.InstallAssistant.Lion":
+ case "com.apple.InstallAssistant.Sierra":
+ case "com.apple.InstallAssistant.HighSierra":
+ case "com.apple.InstallAssistant.Mojave":
+ return true;
+ default:
+ break;
+ }
+ return false;
+ });
+}
+/**
+ * For Mac charts, we want to show Almond apps regardless of AS/Intel configuration
+ * @param objectGraph The object graph reference
+ * @param data The data blob representing a single application
+ * @returns Whether the application should be filtered out for charts on Mac based on its available platforms
+ */
+function shouldFilterUnsupportedPlatformForMacCharts(objectGraph, data) {
+ return derivedData.value(data, "shouldFilterUnsupportedPlatformForCharts", () => {
+ if (data.type !== "apps" && data.type !== "app-bundles") {
+ return false;
+ }
+ // All apps should be considered "supported" by the "web" client
+ if (objectGraph.client.isWeb) {
+ return false;
+ }
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ const supportsMacOSCompatibleIOSBinary = content.supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, true);
+ const supportsVisionOSCompatibleIOSBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ // This check purposely omits passing additional parameters to consider Rosetta for compatibility
+ // because doing so would result in the app being filtered from Charts on macOS.
+ return !content.buyableOnDevice(objectGraph, data, appPlatforms, objectGraph.client.deviceType, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
+ });
+}
+function shouldFilterUnsupportedPlatform(objectGraph, data) {
+ return derivedData.value(data, "shouldFilterUnsupportedPlatform", () => {
+ if (data.type !== "apps" && data.type !== "app-bundles") {
+ return false;
+ }
+ // All apps should be considered "supported" by the "web" client
+ if (objectGraph.client.isWeb) {
+ return false;
+ }
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ // rdar://83688780 (Top chart - several categories pages are missing apps)
+ // For now, we will support showing almond apps for intel and AS mac while charts backend looks to enable server-side filtering.
+ const supportsMacOSCompatibleIOSBinary = content.supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, objectGraph.appleSilicon.isSupportEnabled);
+ const supportsVisionOSCompatibleIOSBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ // This check purposely omits passing additional parameters to consider Rosetta for compatibility
+ // because doing so would result in the app being filtered from Charts on macOS.
+ return !content.buyableOnDevice(objectGraph, data, appPlatforms, objectGraph.client.deviceType, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
+ });
+}
+function shouldFilterUnsupportedPreorders(objectGraph, data) {
+ return derivedData.value(data, "shouldFilterUnsupportedPreordersOnPlatform", () => {
+ const isPreorder = mediaAttributes.attributeAsBooleanOrFalse(data, "isPreorder");
+ if (!isPreorder) {
+ return false;
+ }
+ if (data.type !== "apps") {
+ return false;
+ }
+ // On the web, we show preoderable apps across all device types for the browsing experience
+ if (objectGraph.client.isWeb) {
+ return false;
+ }
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ const supportsMacOSCompatibleIOSBinary = content.supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, objectGraph.appleSilicon.isSupportEnabled);
+ const supportsVisionOSCompatibleIOSBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ return !content.preorderableOnDevice(objectGraph, appPlatforms, objectGraph.client.deviceType, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
+ });
+}
+function shouldFilterDeletableSystemApp(objectGraph, data) {
+ return derivedData.value(data, "shouldFilterDeletableSystemApp", () => {
+ const systemApps = sad.systemApps(objectGraph);
+ if (!systemApps.isSystemAppFromData(data)) {
+ return false;
+ }
+ const bundleId = systemApps.bundleIdFromData(data);
+ const model = objectGraph.host.deviceModel;
+ if (objectGraph.client.isWeb) {
+ return false;
+ }
+ const isPhone = model.startsWith("iPhone");
+ const isPad = model.startsWith("iPad");
+ let countrySupportsTVApp;
+ if (content.isOSAtLeastVersion(objectGraph, "12.3") || objectGraph.client.isVision) {
+ // All countries support the tv app for PeaceF+ clients.
+ countrySupportsTVApp = true;
+ }
+ else {
+ countrySupportsTVApp = objectGraph.bag.isTVAppEnabled;
+ }
+ switch (bundleId) {
+ // These apps are only available on iPhone
+ case "com.apple.Bridge":
+ case "com.apple.Passbook":
+ case "com.apple.visionproapp":
+ return !isPhone;
+ // These apps are only avaliable on iPad
+ case "com.apple.Photo-Booth":
+ return !isPad;
+ // TV.app is only enabled in the US storefront
+ case "com.apple.tv":
+ return !countrySupportsTVApp;
+ // Videos.app everywhere else
+ case "com.apple.videos":
+ return countrySupportsTVApp;
+ // iCloud Drive is replaced by Files on Tigris clients
+ case "com.apple.iCloudDriveApp":
+ return true;
+ // FaceTime & Phone are not available in some Arab countries; defer to MobileGestalt
+ case "com.apple.facetime":
+ case "com.apple.mobilephone":
+ return (!deviceHasCapability(objectGraph, "any-telephony", data) ||
+ !deviceHasCapability(objectGraph, "venice", data));
+ case "com.apple.Fitness":
+ if (isPad || isPhone) {
+ let isFitnessAppInstallationAllowed = false;
+ if (serverData.isDefinedNonNull(objectGraph.user.isFitnessAppInstallationAllowed)) {
+ isFitnessAppInstallationAllowed = objectGraph.user.isFitnessAppInstallationAllowed;
+ }
+ return !isFitnessAppInstallationAllowed;
+ }
+ else {
+ /// Should be safe to filter out on all other platforms since tvOS has it bundled and watchOS has it as Activity.
+ return true;
+ }
+ case "com.apple.measure":
+ return !deviceHasCapability(objectGraph, "arkit", data);
+ case "com.apple.Jellyfish":
+ return !deviceHasCapability(objectGraph, "front-depth-camera", data);
+ // Old 2nd party Shortcuts is no longer available on iOS 13
+ // <rdar://problem/49929227> SAD: Filter our Shortcuts 2nd party app in App Store on iOS 13
+ case "is.workflow.my.app":
+ return true;
+ // Find My apps are being combined in iOS 13. Filter out the existing ones
+ // <rdar://problem/48297097> SAD: Filter FM apps from app store results
+ case "com.apple.mobileme.fmf1":
+ return true;
+ case "com.apple.NanoHeartRhythm":
+ return !objectGraph.client.isElectrocardiogramInstallationAllowed;
+ // This app has hardware requirements we must enforce.
+ // <rdar://problem/48789916> WAS: SAD: Ursa App - HW exclusion
+ case "com.apple.NanoCompass.watchkitapp":
+ return !deviceHasCapability(objectGraph, "magnetometer", data);
+ case "com.apple.NanoOxygenSaturation.watchkitapp":
+ return !objectGraph.client.isScandiumInstallationAllowed;
+ case "com.apple.DeepBreathing":
+ return true;
+ case "com.apple.NanoRadio":
+ return true;
+ case "com.apple.Depth":
+ return !objectGraph.client.isCharonSupported;
+ case "com.apple.Mandrake":
+ return !objectGraph.client.isMandrakeSupported;
+ case "com.apple.GenerativePlaygroundApp":
+ return !deviceHasCapability(objectGraph, "generative-model-systems", data);
+ default:
+ break;
+ }
+ return false;
+ });
+}
+function shouldFilterMerchandisableInAppPurchase(objectGraph, data) {
+ if (data.type !== "in-apps") {
+ return false;
+ }
+ // If a contingent offer exists than do not filter
+ if (serverData.isDefinedNonNullNonEmpty(serverData.asDictionary(data, "meta.contingentItemOffer"))) {
+ return false;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(serverData.asDictionary(data, "meta.discountOffer"))) {
+ return false;
+ }
+ return !mediaAttributes.attributeAsBooleanOrFalse(data, "isMerchandisedEnabled");
+}
+/**
+ * Returns whether an app supports at least the current version of the OS
+ * @param {JSONData} data
+ * @returns {boolean}
+ */
+function shouldFilterForMinimumOSRequirement(objectGraph, data) {
+ // If the device is a phone but the app is watch-only, check that the watch version is supported.
+ if (objectGraph.client.isPhone &&
+ contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS")) {
+ const minimumWatchOSVersionString = contentAttributes.contentAttributeAsString(objectGraph, data, "minimumWatchOSVersion");
+ if (!content.isActivePairedWatchOSAtLeastVersion(objectGraph, minimumWatchOSVersionString)) {
+ return true;
+ }
+ }
+ if (objectGraph.client.isWatch) {
+ const minWatchOSVersion = contentAttributes.contentAttributeAsString(objectGraph, data, "minimumWatchOSVersion");
+ return !content.isOSAtLeastVersion(objectGraph, minWatchOSVersion);
+ }
+ const minOSVersionString = content.minimumOSVersionFromData(objectGraph, data, objectGraph.appleSilicon.isSupportEnabled);
+ return !content.isOSAtLeastVersion(objectGraph, minOSVersionString);
+}
+/**
+ * Returns whether an app supports at least the current version of the companion's OS
+ * @param {JSONData} data
+ * @returns {boolean}
+ */
+function shouldFilterForMinimumCompanionOSRequirement(objectGraph, data) {
+ if (objectGraph.client.deviceType !== "watch") {
+ return false;
+ }
+ // We do not need to check the companion OS version when an app is standalone, or can run standalone.
+ if (contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS") ||
+ contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneWithCompanionForWatchOS")) {
+ return false;
+ }
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
+ const minCompanionVersion = mediaPlatformAttributes.platformAttributeAsString(data, attributePlatform, "minimumOSVersion");
+ return !content.isActivePairedDeviceAtLeastVersion(objectGraph, minCompanionVersion);
+}
+function shouldFilterSADWatchApps(objectGraph, data) {
+ if (objectGraph.client.isWatch || objectGraph.client.isWeb) {
+ return false;
+ }
+ const isFirstPartyHideableApp = mediaAttributes.attributeAsBooleanOrFalse(data, "isFirstPartyHideableApp");
+ const isWatchOnlyApp = !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isDeliveredInIOSAppForWatchOS") &&
+ contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS");
+ return isFirstPartyHideableApp && isWatchOnlyApp;
+}
+function shouldFilterForMacOSCompatibleIOSBinary(objectGraph, data) {
+ if (objectGraph.client.deviceType !== "mac") {
+ return false; // mac only filter.
+ }
+ let isIOSBinaryMacOSCompatible = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isIOSBinaryMacOSCompatible", contentAttributes.defaultAttributePlatform(objectGraph));
+ // Override for News in Moltres
+ if (preprocessor.GAMES_TARGET && data.id === "1066498020") {
+ isIOSBinaryMacOSCompatible = true;
+ }
+ return isIOSBinaryMacOSCompatible && !objectGraph.appleSilicon.isSupportEnabled;
+}
+function shouldFilterRosetta(objectGraph, data) {
+ // Only apply Rosetta filtering to macOS apps on macOS devices.
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ if (objectGraph.client.deviceType !== "mac" || !appPlatforms.includes("mac")) {
+ return false;
+ }
+ const isBuyable = content.isMacOSAppBuyableAndRunnableFromData(objectGraph, data, objectGraph.appleSilicon.isSupportEnabled, objectGraph.appleSilicon.isRosettaAvailable);
+ return !isBuyable;
+}
+function shouldFilterForVisionOSCompatibility(objectGraph, data) {
+ // Only apply filtering to visionOS compatible apps when running in the Vision companion app.
+ if (!objectGraph.client.isCompanionVisionApp) {
+ return false;
+ }
+ const isVisionOSCompatibleIOSBinary = mediaPlatformAttributes.platformAttributeAsBooleanOrFalse(data, "ios", "isXROSCompatible");
+ const supportedAppPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ const isVisionOSBinary = supportedAppPlatforms.indexOf("vision") > -1;
+ return !isVisionOSBinary && !isVisionOSCompatibleIOSBinary;
+}
+/**
+ * Checks whether filtering should be applied due to minimum OS, specifically for a SAD app. This check
+ * exists separately to `shouldFilterForMinimumOSRequirement` as offer buttons aren't typically disabled
+ * for apps that don't meet the minOS, but they should be for SAD apps.
+ *
+ * @param objectGraph Current object graph
+ * @param data The product data
+ * @returns True if the product should be filtered
+ */
+function shouldFilterForSADMinimumOSRequirement(objectGraph, data) {
+ const systemApps = sad.systemApps(objectGraph);
+ if (!systemApps.isSystemAppFromData(data)) {
+ return false;
+ }
+ return shouldFilterForMinimumOSRequirement(objectGraph, data);
+}
+/**
+ * Checks whether a device has a given capability.
+ * @param objectGraph Current object graph
+ * @param capability The capability string
+ * @param data The data related to the product
+ * @returns True if the device supports the capability.
+ */
+function deviceHasCapability(objectGraph, capability, data) {
+ const supportsVisionOSCompatibleIOSBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ return objectGraph.client.deviceHasCapabilitiesIncludingCompatibilityCheckIsVisionOSCompatibleIOSApp([capability], supportsVisionOSCompatibleIOSBinary);
+}
+//# sourceMappingURL=filtering.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-common.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-common.js
new file mode 100644
index 0000000..53294d3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-common.js
@@ -0,0 +1,319 @@
+import { isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as artworkBuilder from "../content/artwork/artwork";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as types from "./grouping-types";
+import { GroupingAppEventShelfController } from "./shelf-controllers/grouping-app-event-shelf-controller";
+import { GroupingArcadeFooterShelfController } from "./shelf-controllers/grouping-arcade-footer-shelf-controller";
+import { GroupingBrickShelfController } from "./shelf-controllers/grouping-brick-shelf-controller";
+import { GroupingRibbonBarShelfController } from "./shelf-controllers/grouping-ribbon-bar-shelf-controller";
+import { GroupingCategoryShelfController } from "./shelf-controllers/grouping-category-shelf-controller";
+import { GroupingEditorialCardShelfController } from "./shelf-controllers/grouping-editorial-card-shelf-controller";
+import { GroupingEditorialStoryCardShelfController } from "./shelf-controllers/grouping-editorial-story-card-shelf-controller";
+import { GroupingGameCenterActivityFeedController } from "./shelf-controllers/grouping-game-center-activity-feed-shelf-controller";
+import { GroupingGameCenterContinuePlayingShelfController } from "./shelf-controllers/grouping-game-center-continue-playing-shelf-controller";
+import { GroupingGameCenterPopularWithYourFriendsController } from "./shelf-controllers/grouping-game-center-popular-with-your-friends-shelf-controller";
+import { GroupingGameCenterReengagementShelfController } from "./shelf-controllers/grouping-game-center-reengagement-shelf-controller";
+import { GroupingGameCenterSuggestedFriendsController } from "./shelf-controllers/grouping-game-center-suggested-friends-shelf-controller";
+import { GroupingHeroCarouselShelfController } from "./shelf-controllers/grouping-hero-carousel-shelf-controller";
+import { GroupingHorizontalCardShelfController } from "./shelf-controllers/grouping-horizontal-card-shelf-controller";
+import { GroupingLargeBreakoutShelfController } from "./shelf-controllers/grouping-large-breakout-shelf-controller";
+import { GroupingLinkShelfController } from "./shelf-controllers/grouping-link-shelf-controller";
+import { GroupingLockupShelfController } from "./shelf-controllers/grouping-lockup-shelf-controller";
+import { GroupingPersonalizedLockupShelfController } from "./shelf-controllers/grouping-personalized-lockup-shelf-controller";
+import { createShelfAccessibilityMetadata, } from "./shelf-controllers/grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./shelf-controllers/grouping-shelf-controller-common";
+import { GroupingSmallBreakoutShelfController } from "./shelf-controllers/grouping-small-breakout-shelf-controller";
+import { ArcadeDownloadPackShelfController } from "./shelf-controllers/arcade-download-pack-shelf-controller";
+import { flowActionForAccountURL } from "../account/account-links-regex-parser";
+import { GroupingMediaPageHeaderShelfController } from "./shelf-controllers/grouping-tags-header-shelf-controller";
+import { applySearchAdMissedOpportunityToShelvesIfNeeded } from "../ads/ad-common";
+import { GroupingTagBrickShelfController } from "./shelf-controllers/grouping-tag-brick-shelf-controller";
+// region Constants
+/// The controllers that know how to render specific shelves
+const shelfControllers = [
+ new GroupingAppEventShelfController(),
+ new GroupingArcadeFooterShelfController(),
+ new GroupingBrickShelfController(),
+ new GroupingRibbonBarShelfController(),
+ new GroupingCategoryShelfController(),
+ new GroupingEditorialCardShelfController(),
+ new GroupingEditorialStoryCardShelfController(),
+ new GroupingGameCenterContinuePlayingShelfController(),
+ new GroupingGameCenterPopularWithYourFriendsController(),
+ new GroupingGameCenterReengagementShelfController(),
+ new GroupingGameCenterSuggestedFriendsController(),
+ new GroupingGameCenterActivityFeedController(),
+ new GroupingHeroCarouselShelfController(),
+ new GroupingHorizontalCardShelfController(),
+ new GroupingLargeBreakoutShelfController(),
+ new GroupingLinkShelfController(),
+ new GroupingLockupShelfController(),
+ new GroupingPersonalizedLockupShelfController(),
+ new GroupingSmallBreakoutShelfController(),
+ new ArcadeDownloadPackShelfController(),
+ new GroupingMediaPageHeaderShelfController(),
+ new GroupingTagBrickShelfController(),
+];
+/**
+ * For a given flattened grouping go through and use our controllers to render the associated shelves, this should be used
+ * for the initial page load. The second fetches for these shelves are handled by the routing system and the routed
+ * shelf controller. This method will add all the shelves to the current groupingParseContext.
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param flattenedGrouping The original grouping page response data from MAPI flattened into the list of featured content
+ * @param groupingParseContext The current parse context for this rendering of the grouping.
+ * @protected
+ */
+export function insertInitialShelvesIntoGroupingParseContext(objectGraph, flattenedGrouping, groupingParseContext) {
+ validation.catchingContext(`parseGrouping`, () => {
+ for (const mediaApiData of flattenedGrouping.data) {
+ const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind");
+ if (types.isTopChart(featuredContentId)) {
+ const editorialId = serverData.asString(mediaApiData, "id");
+ groupingParseContext.chartSet = lookupChartSetForEditorialId(editorialId, flattenedGrouping.editorialChartSets);
+ }
+ else {
+ groupingParseContext.chartSet = null;
+ }
+ const baseRequirements = groupingShelfControllerCommon.createBaseShelfRequirements(objectGraph, mediaApiData, groupingParseContext);
+ const token = baseRequirements.shelfToken;
+ const baseMetricsOptions = baseRequirements.metricsOptions;
+ let shelf;
+ for (const shelfController of shelfControllers) {
+ if (shelfController.supports(objectGraph, mediaApiData, featuredContentId)) {
+ shelf = validation.catchingContext(`parseGroupingShelf`, () => {
+ return shelfController.createShelf(objectGraph, mediaApiData, groupingParseContext, token, baseMetricsOptions);
+ });
+ break;
+ }
+ }
+ if (serverData.isDefinedNonNull(shelf)) {
+ groupingParseContext.shelves.push(shelf);
+ applySearchAdMissedOpportunityToShelvesIfNeeded(objectGraph, groupingParseContext.shelves, "searchLanding", baseMetricsOptions.id, groupingParseContext.metricsPageInformation);
+ }
+ }
+ });
+}
+/**
+ * Parse a single editorially programmed chart from media API data.
+ * @returns The chart model or null.
+ */
+function makeEditorialChartFromMediaApiData(chartData) {
+ const fcKind = mediaAttributes.attributeAsNumber(chartData, "editorialElementKind");
+ if (!types.isTopChart(fcKind)) {
+ return null;
+ }
+ return {
+ id: serverData.asString(chartData, "id"),
+ href: mediaAttributes.attributeAsString(chartData, "chartHref"),
+ name: mediaAttributes.attributeAsString(chartData, "name"),
+ type: mediaAttributes.attributeAsString(chartData, "chart"),
+ };
+}
+/**
+ * Parse a collection of charts, programmed by editorial, from media api data.
+ * @returns The chart set model, or null.
+ */
+function makeEditorialChartSetFromMediaApiData(chartSetData) {
+ const fcKind = mediaAttributes.attributeAsNumber(chartSetData, "editorialElementKind");
+ if (fcKind !== 424 /* types.FeaturedContentID.AppStore_ChartSet */) {
+ return null;
+ }
+ return {
+ id: serverData.asString(chartSetData, "id"),
+ name: mediaAttributes.attributeAsString(chartSetData, "name"),
+ editorialCharts: mediaRelationship
+ .relationshipCollection(chartSetData, "children")
+ .map((chartData) => makeEditorialChartFromMediaApiData(chartData))
+ .filter((chart) => isSome(chart)),
+ };
+}
+/**
+ * Given a list of chart sets, find the chart set that has the provided editorial identifier.
+ *
+ * @param editorialId The chart or chart set identifier from editorial.
+ * @param chartSets The list of all chart sets from the grouping.
+ * @returns The charts associated with the editorial item from the grouping.
+ */
+function lookupChartSetForEditorialId(editorialId, editorialChartSets) {
+ for (const chartSet of editorialChartSets) {
+ if (chartSet.id === editorialId) {
+ return chartSet; // editorialId is for a chart set itself
+ }
+ for (const chart of chartSet.editorialCharts) {
+ if (chart.id === editorialId) {
+ return chartSet; // / editorialId is for a specific chart from the chart set.
+ }
+ }
+ }
+ return null;
+}
+/**
+ * Take the media API data for a single grouping, and flatten that tree structure into a single list of media api data objects.
+ *
+ * @param mediaApiGroupingData The original media API data for a single grouping
+ */
+export function flattenMediaApiGroupingData(objectGraph, mediaApiGroupingData) {
+ const groupingData = [];
+ const chartSets = [];
+ function addMediaApiDataToGroupingData(mediaApiData) {
+ const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind");
+ validation.catchingContext(`flattenGroupingTree.addMediaApiDataToGroupingData: ${featuredContentId}`, () => {
+ const groupingTabData = mediaRelationship.relationshipData(objectGraph, mediaApiData, "tabs");
+ if (serverData.isDefinedNonNull(groupingTabData)) {
+ addMediaApiDataToGroupingData(groupingTabData);
+ }
+ else if (types.shouldContinueToWalkChildren(featuredContentId)) {
+ const featuredContentChildren = mediaRelationship.relationshipCollection(mediaApiData, "children");
+ for (const featuredContentChild of featuredContentChildren) {
+ addMediaApiDataToGroupingData(featuredContentChild);
+ }
+ }
+ else if (featuredContentId === 424 /* types.FeaturedContentID.AppStore_ChartSet */) {
+ // Keep track of chartSets programmed by editorial, e.g. "Top Charts" includes "Top Free Apps" and "Top Paid Apps".
+ const editorialChartSet = makeEditorialChartSetFromMediaApiData(mediaApiData);
+ if (isSome(editorialChartSet)) {
+ chartSets.push(editorialChartSet);
+ }
+ // Add charts from the chartSet, as defined by editorial, to grouping page data.
+ const chartSetChildren = mediaRelationship.relationshipCollection(mediaApiData, "children");
+ for (const chartSetChild of chartSetChildren) {
+ addMediaApiDataToGroupingData(chartSetChild);
+ }
+ }
+ else {
+ groupingData.push(mediaApiData);
+ }
+ });
+ }
+ addMediaApiDataToGroupingData(mediaApiGroupingData);
+ return {
+ data: groupingData,
+ editorialChartSets: chartSets,
+ originalGroupingData: mediaApiGroupingData,
+ };
+}
+// endregion
+// region Shelves
+/**
+ * Create a shelf for footer buttons, e.g "Send Gift" and "Redeem".
+ *
+ * @return Shelf configured to display `FooterButtons`s.
+ */
+export function shelfForFooterButtons(objectGraph, metricsPageInformation, metricsLocationTracker) {
+ var _a, _b, _c, _d, _e, _f;
+ if (objectGraph.user && objectGraph.user.isManagedAppleID) {
+ return null;
+ }
+ // Watch App Store does not support titled button stacks,
+ // and has different footer buttons.
+ if (objectGraph.client.isWatch) {
+ const shelf = new models.Shelf("action");
+ const items = [];
+ const accountFlowAction = new models.FlowAction("account");
+ accountFlowAction.title = objectGraph.loc.string("ACCOUNT", "Account");
+ accountFlowAction.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://person.crop.circle");
+ const accountFlowClickOptions = {
+ targetType: "button",
+ id: "account",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, accountFlowAction, accountFlowClickOptions);
+ items.push(accountFlowAction);
+ shelf.items = items;
+ shelf.accessibilityMetadata = createShelfAccessibilityMetadata(objectGraph, shelf);
+ return shelf;
+ }
+ else {
+ const items = [];
+ const redeemTitle = objectGraph.loc.string("FOOTER_REDEEM", "Redeem");
+ const redeemActionUrl = objectGraph.client.isVision
+ ? objectGraph.bag.redeemCodeLanding
+ : objectGraph.bag.redeemUrl;
+ const redeemFlowAction = flowActionForAccountURL(objectGraph, redeemActionUrl);
+ const redeemButton = new models.TitledButton(redeemTitle, redeemFlowAction);
+ redeemButton.id = "redeem";
+ items.push(redeemButton);
+ if (objectGraph.bag.isMonetaryGiftingEnabled) {
+ const giftTitle = objectGraph.loc.string("FOOTER_SEND_GIFT", "Send Gift");
+ const giftActionUrl = "gift";
+ const giftFlowAction = flowActionForAccountURL(objectGraph, giftActionUrl);
+ const giftButton = new models.TitledButton(giftTitle, giftFlowAction);
+ giftButton.id = "gift";
+ items.push(giftButton);
+ }
+ const topUpUrl = objectGraph.bag.accountTopUpURL;
+ if (isSome(topUpUrl)) {
+ const topUpTitle = (_a = objectGraph.bag.accountTopUpTitle) !== null && _a !== void 0 ? _a : objectGraph.loc.string("FOOTER_ADD_MONEY");
+ const topUpFlowAction = flowActionForAccountURL(objectGraph, topUpUrl);
+ const topUpButton = new models.TitledButton(topUpTitle, topUpFlowAction);
+ topUpButton.id = "topUp";
+ items.push(topUpButton);
+ }
+ let shelf;
+ if (objectGraph.client.isVision) {
+ shelf = new models.Shelf("titledButton");
+ shelf.items = items;
+ }
+ else {
+ shelf = new models.Shelf("titledButtonStack");
+ const stack = new models.TitledButtonStack(items);
+ // On compact width, we want to place a line break after each button.
+ stack.compactLineBreaks = stack.buttons.map((value, index) => index);
+ shelf.items = [stack];
+ const accessibilityLabel = objectGraph.loc.string("Shelves.Accessibility.Label");
+ const roleDescription = objectGraph.loc.string("Shelves.Accessibility.RoleDescription");
+ shelf.accessibilityMetadata = {
+ label: (_d = (_b = shelf.title) !== null && _b !== void 0 ? _b : (_c = shelf.header) === null || _c === void 0 ? void 0 : _c.title) !== null && _d !== void 0 ? _d : accessibilityLabel,
+ roleDescription: roleDescription.replace("%d", `${(_f = (_e = stack.buttons) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0}`),
+ };
+ }
+ return shelf;
+ }
+}
+/**
+ * Create a T&C shelf that on click opens an external url.
+ *
+ * @param objectGraph
+ * @param destinationUrl Url to open when terms and conditions is tapped.
+ * @param hasSeparator Whether the footnote has a separator above it.
+ * @return Shelf configured to display Terms and Conditions.
+ */
+export function shelfForTermsAndConditions(objectGraph, destinationUrl, hasSeparator = true) {
+ const urlAction = new models.ExternalUrlAction(destinationUrl);
+ const termsAndConditionTitle = objectGraph.loc.string("TermsAndConditions.Title");
+ const footnote = new models.Footnote(termsAndConditionTitle);
+ footnote.clickAction = urlAction;
+ footnote.presentationStyle = ["hasChevron", "textLightensOnHighlight"];
+ if (hasSeparator) {
+ footnote.presentationStyle.push("hasSeparator");
+ }
+ const shelf = new models.Shelf("footnote");
+ shelf.items = [footnote];
+ if (objectGraph.bag.emailSupportLinkURL) {
+ const emailSupportAction = new models.ExternalUrlAction(objectGraph.bag.emailSupportLinkURL);
+ const emailSupport = new models.Footnote("Email Support");
+ emailSupport.clickAction = emailSupportAction;
+ emailSupport.presentationStyle = ["hasChevron", "textLightensOnHighlight"];
+ if (hasSeparator) {
+ emailSupport.presentationStyle.push("hasSeparator");
+ }
+ shelf.items.push(emailSupport);
+ }
+ return shelf;
+}
+export function shelfForUnifiedMessage(objectGraph, placement, context, deliveryMethod) {
+ const shelf = new models.Shelf("unifiedMessage");
+ shelf.id = placement;
+ shelf.items = [new models.UnifiedMessage(placement, context, deliveryMethod)];
+ shelf.isHidden = true;
+ return shelf;
+}
+// endregion
+//# sourceMappingURL=grouping-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-page-url.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-page-url.js
new file mode 100644
index 0000000..4e4c608
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-page-url.js
@@ -0,0 +1,8 @@
+import { makeGroupingPageIntentByID } from "../../api/intents/grouping-page-intent";
+import { generateRoutes } from "../util/generate-routes";
+const { routes: groupingPageRoutes, makeCanonicalUrl } = generateRoutes(makeGroupingPageIntentByID, "/{platform}/grouping/{id}");
+export { groupingPageRoutes };
+export function makeGroupingPageCanonicalURL(objectGraph, intent) {
+ return makeCanonicalUrl(objectGraph, intent);
+}
+//# sourceMappingURL=grouping-page-url.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-request.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-request.js
new file mode 100644
index 0000000..2ec9021
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-request.js
@@ -0,0 +1,107 @@
+import { Request, defaultAdditionalPlatformsForClient, defaultSparseLimitForClient, } from "../../foundation/media/data-fetching";
+import { configureContingentItemsForGroupingRequest, configureOfferItemsForMediaRequest, configureTagsForMediaRequest, } from "../builders/url-mapping-utils";
+import { shouldFetchCustomAttributes } from "../product-page/product-page-variants";
+import { appContingentItemsAreEnabled, appEventsAreEnabled, appOfferItemsAreEnabled, } from "../app-promotions/app-promotions-common";
+import { areAppTagsEnabled } from "../util/app-tags-util";
+import * as mediaRequestUtils from "../../common/builders/url-mapping-utils";
+import { shouldUsePrerenderedIconArtwork } from "../content/content";
+import { AppEventsAttributes } from "../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types";
+/**
+ * Create a "base" Media API {@linkcode Request} object for a "Grouping" page
+ *
+ * This is used to provision an initial {@linkcode Request} object that will be further
+ * modified later to include the specific page that should be fetched
+ *
+ * @see {@linkcode prepareGroupingPageRequest} to complete configuration of the `Request` object
+ */
+export function makeBaseGroupingPageRequest(objectGraph) {
+ // TODO: Update this code to work with genreId when this radar is complete
+ // <rdar://problem/37676122> Media Api: Need ability to lookup grouping resource by genreId
+ // TODO: Remove isAppleWatchSupported, once the following radar is resolved
+ // <rdar://problem/38923866> Media API: Include isAppleWatchSupported in secondary lookups
+ // Add this back in 18E when grouping supports additional platforms
+ // fixed with <rdar://problem/38899888> App Store Personalization: grouping: remove tabs query param for Apps realm
+ // .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient())
+ const attributes = [
+ "editorialArtwork",
+ "editorialVideo",
+ "isAppleWatchSupported",
+ "requiredCapabilities",
+ "expectedReleaseDateDisplayFormat",
+ "showExpectedReleaseDate",
+ "badge-content",
+ ];
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ const mediaApiRequest = new Request(objectGraph)
+ .forType("groupings")
+ .includingAgeRestrictions()
+ .includingAttributes(attributes)
+ .includingRelationshipsForUpsell(true);
+ if (areAppTagsEnabled(objectGraph, "grouping")) {
+ mediaRequestUtils.configureTagsForMediaRequest(mediaApiRequest);
+ }
+ return mediaApiRequest;
+}
+/**
+ * Complete the configuration of a "Grouping Page" {@linkcode Request}
+ */
+export function prepareGroupingPageRequest(objectGraph, request) {
+ request.includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph));
+ request.includingAgeRestrictions();
+ request.usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ // <rdar://53420717> For performance reasons, we limit shelves to 3 items on watchOS
+ if (objectGraph.client.isWatch) {
+ request.addingRelationshipLimit("contents", 3);
+ }
+ if (objectGraph.client.isWeb) {
+ // The "web" client needs to load *all* of the data for SEO purposes
+ // Without this, shelves will render with some missing items
+ request.withSparseCount(40);
+ const sparseLimit = defaultSparseLimitForClient(objectGraph);
+ if (sparseLimit) {
+ request.includingScopedSparseLimit("editorial-elements:contents", sparseLimit);
+ }
+ }
+ request.includingMacOSCompatibleIOSAppsWhenSupported();
+ if (appEventsAreEnabled(objectGraph)) {
+ request.enablingFeature("appEvents");
+ request.includingMetaKeys("editorial-elements:contents", ["personalizationData", "cppData"]);
+ request.includingScopedAttributes("app-events", AppEventsAttributes);
+ request.includingScopedRelationships("app-events", ["app"]);
+ }
+ if (areAppTagsEnabled(objectGraph, "grouping")) {
+ configureTagsForMediaRequest(request);
+ }
+ if (appContingentItemsAreEnabled(objectGraph)) {
+ request.enablingFeature("contingentItems");
+ configureContingentItemsForGroupingRequest(request);
+ }
+ if (appOfferItemsAreEnabled(objectGraph)) {
+ request.enablingFeature("offerItems");
+ configureOfferItemsForMediaRequest(request);
+ }
+ if (objectGraph.client.isiOS) {
+ request.enablingFeature("categoryHeaders");
+ request.enablingFeature("onDevicePersonalization");
+ }
+ // Enable category features
+ if (objectGraph.bag.enableFeaturedCategoriesOnGroupings) {
+ request.enablingFeature("featuredCategories");
+ }
+ // Enable category bricks
+ if (objectGraph.bag.enableCategoryBricksOnGroupings) {
+ request.enablingFeature("categoryBricks");
+ }
+}
+//# sourceMappingURL=grouping-request.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-types.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-types.js
new file mode 100644
index 0000000..33e01b7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/grouping-types.js
@@ -0,0 +1,128 @@
+export const topChartFeaturedContentIds = new Set([
+ 117 /* FeaturedContentID.Common_iPhoneTopChart */,
+ 122 /* FeaturedContentID.Common_iPhonePaidTopChart */,
+ 118 /* FeaturedContentID.Common_iPhoneFreeTopChart */,
+ 195 /* FeaturedContentID.Common_iPadTopChart */,
+ 194 /* FeaturedContentID.Common_iPadFreeTopChart */,
+ 197 /* FeaturedContentID.Common_iPadPaidTopChart */,
+ 369 /* FeaturedContentID.AppStore_iPhoneFreeTopChartV2 */,
+ 370 /* FeaturedContentID.AppStore_iPhoneGrossingTopCharV2t */,
+ 371 /* FeaturedContentID.AppStore_iPadFreeTopChartV2 */,
+ 372 /* FeaturedContentID.AppStore_iPadGrossingTopChartV2 */,
+ 373 /* FeaturedContentID.AppStore_TVFreeTopChartV2 */,
+ 374 /* FeaturedContentID.AppStore_TVGrossingTopChartV2 */,
+ 375 /* FeaturedContentID.AppStore_WatchFreeTopChartV2 */,
+ 376 /* FeaturedContentID.AppStore_WatchGrossingTopChartV2 */,
+ 377 /* FeaturedContentID.AppStore_iPhonePaidTopChartV2 */,
+ 378 /* FeaturedContentID.AppStore_iPadPaidTopChartV2 */,
+ 379 /* FeaturedContentID.AppStore_TVPaidTopChartV2 */,
+ 380 /* FeaturedContentID.AppStore_WatchPaidTopChartV2 */,
+ 395 /* FeaturedContentID.AppStore_iPhoneiMessageFreeTopChart */,
+ 396 /* FeaturedContentID.AppStore_iPhoneiMessagePaidTopChart */,
+ 397 /* FeaturedContentID.AppStore_iPhoneiMessageGrossingTopChart */,
+ 398 /* FeaturedContentID.AppStore_iPadiMessageFreeTopChart */,
+ 399 /* FeaturedContentID.AppStore_iPadiMessagePaidTopChart */,
+ 400 /* FeaturedContentID.AppStore_iPadiMessageGrossingTopChart */,
+ 491 /* FeaturedContentID.AppStore_MacFreeTopChartV2 */,
+ 492 /* FeaturedContentID.AppStore_MacPaidTopChartV2 */,
+ 493 /* FeaturedContentID.AppStore_MacGrossingTopChartV2 */,
+ 504 /* FeaturedContentID.AppStore_SafariExtensionsFreeTopChartV2 */,
+ 505 /* FeaturedContentID.AppStore_SafariExtensionsPaidTopChartV2 */,
+ 506 /* FeaturedContentID.AppStore_SafariExtensionsGrossingTopChartV2 */,
+ 520 /* FeaturedContentID.AppStore_SafariExtensionsFreeTopChartV3_iPhone */,
+ 521 /* FeaturedContentID.AppStore_SafariExtensionsPaidTopChartV3_iPhone */,
+ 522 /* FeaturedContentID.AppStore_SafariExtensionsGrossingTopChartV3_iPhone */,
+ 523 /* FeaturedContentID.AppStore_SafariExtensionsFreeTopChartV3_iPad */,
+ 524 /* FeaturedContentID.AppStore_SafariExtensionsPaidTopChartV3_iPad */,
+ 525 /* FeaturedContentID.AppStore_SafariExtensionsGrossingTopChartV3_iPad */,
+ 530 /* FeaturedContentID.Arcade_iPadChartsV2 */,
+ 531 /* FeaturedContentID.Arcade_MacChartsV2 */,
+ 532 /* FeaturedContentID.Arcade_TVChartsV2 */,
+ 533 /* FeaturedContentID.Arcade_iPhoneChartsV2 */,
+ 567 /* FeaturedContentID.GameCenter_iPhoneTopCombinedChart */,
+ 571 /* FeaturedContentID.GameCenter_iPadTopCombinedChart */,
+]);
+const macTopChartFeaturedContentIds = new Set([
+ 491 /* FeaturedContentID.AppStore_MacFreeTopChartV2 */,
+ 492 /* FeaturedContentID.AppStore_MacPaidTopChartV2 */,
+ 493 /* FeaturedContentID.AppStore_MacGrossingTopChartV2 */,
+]);
+const arcadeTopChartFeaturedContentIds = new Set([
+ 530 /* FeaturedContentID.Arcade_iPadChartsV2 */,
+ 531 /* FeaturedContentID.Arcade_MacChartsV2 */,
+ 532 /* FeaturedContentID.Arcade_TVChartsV2 */,
+ 533 /* FeaturedContentID.Arcade_iPhoneChartsV2 */,
+]);
+/// Game Center top charts for iPhone and iPad
+export const gameCenterTopChartFeaturedContentIds = new Set([
+ 567 /* FeaturedContentID.GameCenter_iPhoneTopCombinedChart */,
+ 571 /* FeaturedContentID.GameCenter_iPadTopCombinedChart */,
+]);
+export const recommendationsLockupShelfFeaturedContentIds = new Set([
+ 311 /* FeaturedContentID.Sundance_RecommendedAppsShelf */,
+ 312 /* FeaturedContentID.Sundance_RecommendedGamesShelf */,
+ 476 /* FeaturedContentID.AppStore_PersonalizedShelfMarker */,
+]);
+export const recommendationsShelfFeaturedContentIds = new Set([
+ ...recommendationsLockupShelfFeaturedContentIds,
+ 518 /* FeaturedContentID.AppStore_PersonalizedAppEventsMarker */,
+]);
+export const lockupShelfFeaturedContentIds = new Set([
+ 557 /* FeaturedContentID.AppStore_CategoryBreakoutMarker */,
+ 418 /* FeaturedContentID.AppStore_Shelf */,
+ 431 /* FeaturedContentID.AppStore_iAPShelf */,
+ 429 /* FeaturedContentID.AppStore_ScreenShotShelf */,
+ 430 /* FeaturedContentID.AppStore_VideoShelf */,
+ 420 /* FeaturedContentID.AppStore_TrailerShelf */,
+ 304 /* FeaturedContentID.Sundance_iPhoneAppTrailerShelf */,
+ 305 /* FeaturedContentID.Sundance_iPadAppTrailerShelf */,
+ 260 /* FeaturedContentID.Sundance_Shelf */,
+ 497 /* FeaturedContentID.AppStore_ComingSoon */,
+]);
+const featuredContentIdsRequiringAdditionalTraversal = new Set([
+ 413 /* FeaturedContentID.AppStore_Root */,
+ 414 /* FeaturedContentID.AppStore_TabRoot */,
+ 254 /* FeaturedContentID.Sundance_Root */,
+ 255 /* FeaturedContentID.Sundance_TabRoot */,
+ 256 /* FeaturedContentID.Sundance_Stack */,
+ 266 /* FeaturedContentID.Sundance_QuickLinks */,
+ 271 /* FeaturedContentID.Sundance_TabSet */,
+ 436 /* FeaturedContentID.AppStore_DateBlock */,
+]);
+export function shouldContinueToWalkChildren(featuredContentId) {
+ return featuredContentIdsRequiringAdditionalTraversal.has(featuredContentId);
+}
+export function isLockupShelf(featuredContentId) {
+ return (isTopChart(featuredContentId) ||
+ isRecommendationsShelf(featuredContentId) ||
+ lockupShelfFeaturedContentIds.has(featuredContentId) ||
+ featuredContentId === 518 /* FeaturedContentID.AppStore_PersonalizedAppEventsMarker */);
+}
+export function isRecommendationsLockupShelf(featuredContentId) {
+ return recommendationsLockupShelfFeaturedContentIds.has(featuredContentId);
+}
+export function isRecommendationsShelf(featuredContentId) {
+ return recommendationsShelfFeaturedContentIds.has(featuredContentId);
+}
+export function isMacTopChart(featuredContentId) {
+ return macTopChartFeaturedContentIds.has(featuredContentId);
+}
+export function isTopChart(featuredContentId) {
+ return topChartFeaturedContentIds.has(featuredContentId);
+}
+/// Game Center top charts are handled differently in some cases,
+/// e.g. showing Game Center eyebrow
+export function isGameCenterTopChart(featuredContentId) {
+ return gameCenterTopChartFeaturedContentIds.has(featuredContentId);
+}
+/**
+ * Arcade top charts are handled differently in some cases, eg. showing
+ * a 'See All' button
+ *
+ * @param featuredContentId The kind of featured content
+ * @returns Whether this is an Arcade Top Chart
+ */
+export function isArcadeTopChart(featuredContentId) {
+ return arcadeTopChartFeaturedContentIds.has(featuredContentId);
+}
+//# sourceMappingURL=grouping-types.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/hero/hero-carousel-overlay-common.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/hero/hero-carousel-overlay-common.js
new file mode 100644
index 0000000..183947f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/hero/hero-carousel-overlay-common.js
@@ -0,0 +1,263 @@
+//
+// hero-carousel-overlay-common.ts
+// AppStoreKit
+//
+// Created by Jonathan Ellenbogen on 12/11/20.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as color from "../../../foundation/util/color-util";
+import * as breakoutsCommon from "../../arcade/breakouts-common";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as heroCommon from "./hero-common";
+import * as lottery from "../../util/lottery";
+export function overlayFromData(objectGraph, data, requirements) {
+ const heroCarouselItemOverlay = new models.HeroCarouselItemOverlay();
+ heroCarouselItemOverlay.overlayType = overlayTypeFromData(objectGraph, data);
+ heroCarouselItemOverlay.displayOptions = {
+ horizontalPlacement: overlayPlacementFromData(objectGraph, data),
+ textAlignment: overlayTextAlignmentFromData(objectGraph, data),
+ isOverDarkContent: overlayIsOverDarkContent(objectGraph, data),
+ };
+ const primaryContent = content.primaryContentForData(objectGraph, data);
+ // If this EI can and should show the expected release date of an app, override the badge.
+ const fallbackLabel = mediaAttributes.attributeAsString(data, "label");
+ if (contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "showExpectedReleaseDate")) {
+ heroCarouselItemOverlay.badgeText = objectGraph.loc.uppercased(content.dynamicPreorderDateFromData(objectGraph, primaryContent, fallbackLabel));
+ }
+ else {
+ heroCarouselItemOverlay.badgeText = fallbackLabel;
+ }
+ heroCarouselItemOverlay.titleText =
+ content.editorialNotesFromData(objectGraph, data, "name") ||
+ contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "name");
+ if (heroCarouselItemOverlay.overlayType === "singleModule" ||
+ heroCarouselItemOverlay.overlayType === "collectionModule") {
+ heroCarouselItemOverlay.descriptionText = content.editorialNotesFromData(objectGraph, data, "tagline");
+ }
+ else {
+ heroCarouselItemOverlay.descriptionText =
+ content.editorialNotesFromData(objectGraph, data, "short") ||
+ contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "tagline");
+ }
+ heroCarouselItemOverlay.callToActionText = mediaAttributes.attributeAsString(data, "breakoutCallToActionLabel");
+ heroCarouselItemOverlay.buttonTitle = overlayButtonTitleFromData(objectGraph, data);
+ if (heroCarouselItemOverlay.overlayType === "lockup" || heroCarouselItemOverlay.overlayType === "singleModule") {
+ heroCarouselItemOverlay.lockup = overlayLockupFromData(objectGraph, data, requirements);
+ }
+ // For hero items the design is to use editorial labels instead of "Apple Arcade" so we replace that text here
+ if ((fallbackLabel === null || fallbackLabel === void 0 ? void 0 : fallbackLabel.length) > 0 && serverData.isDefinedNonNull(heroCarouselItemOverlay.lockup)) {
+ heroCarouselItemOverlay.lockup.heading = fallbackLabel;
+ }
+ heroCarouselItemOverlay.collectionIcons = overlayCollectionIconsFromData(objectGraph, data);
+ if (serverData.isDefinedNonNullNonEmpty(heroCarouselItemOverlay.lockup)) {
+ heroCarouselItemOverlay.clickAction = heroCarouselItemOverlay.lockup.clickAction;
+ heroCarouselItemOverlay.impressionMetrics = heroCarouselItemOverlay.lockup.impressionMetrics;
+ }
+ else {
+ const heroCarouselItemOverlayMetricsOptions = {
+ targetType: "lockup",
+ pageInformation: requirements.metricsPageInformation,
+ locationTracker: requirements.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ };
+ const overlayMetricsTitle = heroCarouselItemTitleFromData(objectGraph, data);
+ const heroCarouselItemOverlayAction = breakoutsCommon.actionFromData(objectGraph, data);
+ heroCarouselItemOverlayAction.title = overlayMetricsTitle;
+ const heroCarouselItemOverlayClickOptions = {
+ pageInformation: requirements.metricsPageInformation,
+ locationTracker: requirements.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ targetType: "lockup",
+ id: data.id,
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, heroCarouselItemOverlayAction, heroCarouselItemOverlayClickOptions);
+ heroCarouselItemOverlay.clickAction = heroCarouselItemOverlayAction;
+ const heroCarouselItemOverlayImpressionsOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, overlayMetricsTitle, heroCarouselItemOverlayMetricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, heroCarouselItemOverlay, heroCarouselItemOverlayImpressionsOptions);
+ }
+ return heroCarouselItemOverlay;
+}
+export function overlayTypeFromData(objectGraph, data) {
+ const displayMaterial = mediaAttributes.attributeAsBooleanOrFalse(data, "displayBreakoutMaterial");
+ const editorialItemKind = mediaAttributes.attributeAsString(data, "kind");
+ const relatedCollection = mediaRelationship.relationshipCollection(data, "card-contents");
+ const tagline = content.editorialNotesFromData(objectGraph, data, "tagline");
+ const canPresentHeroModuleStyle = allowHeroModuleStylePresentation(objectGraph) && (tagline === null || tagline === void 0 ? void 0 : tagline.length) > 0;
+ let relatedApp = mediaRelationship.relationshipData(objectGraph, data, "primary-content");
+ if (serverData.isNullOrEmpty(relatedApp) && serverData.isDefinedNonNullNonEmpty(relatedCollection)) {
+ relatedApp = relatedCollection[0];
+ }
+ if (serverData.isDefinedNonNull(relatedApp) && editorialItemKind === "App") {
+ return canPresentHeroModuleStyle ? "singleModule" : "lockup";
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(relatedCollection) && editorialItemKind === "Collection") {
+ return canPresentHeroModuleStyle ? "collectionModule" : "collectionLockup";
+ }
+ else if (displayMaterial) {
+ return "materialText";
+ }
+ else {
+ return objectGraph.client.isTV ? "materialText" : "text";
+ }
+}
+export function heroCarouselItemTitleFromData(objectGraph, data) {
+ const overlayType = overlayTypeFromData(objectGraph, data);
+ const primaryContent = mediaRelationship.relationshipData(objectGraph, data, "primary-content");
+ switch (overlayType) {
+ case "lockup":
+ case "singleModule":
+ return mediaAttributes.attributeAsString(primaryContent, "name");
+ case "materialText":
+ case "text":
+ case "collectionLockup":
+ case "collectionModule":
+ return (content.editorialNotesFromData(objectGraph, data, "name") ||
+ contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "name"));
+ default:
+ return null;
+ }
+}
+export function overlayLockupFromData(objectGraph, data, requirements) {
+ let relatedApp = mediaRelationship.relationshipData(objectGraph, data, "primary-content");
+ const relatedCollection = mediaRelationship.relationshipCollection(data, "card-contents");
+ if (serverData.isNullOrEmpty(relatedApp) && serverData.isDefinedNonNullNonEmpty(relatedCollection)) {
+ relatedApp = relatedCollection[0];
+ }
+ if (serverData.isNullOrEmpty(relatedApp)) {
+ return null;
+ }
+ // Create the lockup
+ const lockupOptions = {
+ metricsOptions: {
+ pageInformation: requirements.metricsPageInformation,
+ locationTracker: requirements.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ },
+ artworkUseCase: requirements.lockupArtworkUseCase,
+ offerStyle: "transparent",
+ offerEnvironment: "dark",
+ canDisplayArcadeOfferButton: requirements.canDisplayArcadeOfferButton,
+ shouldHideArcadeHeader: objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab"),
+ isContainedInPreorderExclusiveShelf: requirements.isContainedInPreorderExclusiveShelf,
+ };
+ const lockup = lockups.lockupFromData(objectGraph, relatedApp, lockupOptions);
+ const primaryContent = content.primaryContentForData(objectGraph, data);
+ const editorialTagline = content.editorialNotesFromData(objectGraph, data, "short") ||
+ contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "tagline");
+ if ((editorialTagline === null || editorialTagline === void 0 ? void 0 : editorialTagline.length) > 0) {
+ lockup.subtitle = editorialTagline;
+ }
+ return lockup;
+}
+export function overlayCollectionIconsFromData(objectGraph, data) {
+ const relatedCollection = mediaRelationship.relationshipCollection(data, "card-contents");
+ if (serverData.isNullOrEmpty(relatedCollection)) {
+ return null;
+ }
+ const icons = [];
+ for (const item of relatedCollection) {
+ const icon = content.iconFromData(objectGraph, item, {
+ useCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ withJoeColorPlaceholder: true,
+ });
+ if (serverData.isDefinedNonNullNonEmpty(icon)) {
+ icons.push(icon);
+ }
+ }
+ return serverData.isDefinedNonNullNonEmpty(icons) ? icons : null;
+}
+export function overlayButtonTitleFromData(objectGraph, data) {
+ if (objectGraph.client.deviceType !== "tv") {
+ return null;
+ }
+ const overlayType = overlayTypeFromData(objectGraph, data);
+ switch (overlayType) {
+ case "lockup":
+ return objectGraph.loc.string("HERO_CAROUSEL_OVERLAY_BUTTON_TITLE_GAME");
+ case "materialText":
+ case "text":
+ let buttonTitle = objectGraph.loc.string("HERO_CAROUSEL_OVERLAY_BUTTON_TITLE_VIEW");
+ if (buttonTitle === "HERO_CAROUSEL_OVERLAY_BUTTON_TITLE_VIEW") {
+ buttonTitle = objectGraph.loc.string("HERO_CAROUSEL_OVERLAY_BUTTON_TITLE_GAME");
+ }
+ return buttonTitle;
+ case "collectionLockup":
+ return objectGraph.loc.string("HERO_CAROUSEL_OVERLAY_BUTTON_TITLE_COLLECTION");
+ default:
+ return null;
+ }
+}
+export function overlayIsOverDarkContent(objectGraph, data) {
+ const heroVideoData = heroCommon.heroVideoFromData(objectGraph, data);
+ const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, data);
+ const backgroundColor = heroVideoData.backgroundColor || heroArtworkData.backgroundColor;
+ if (!backgroundColor) {
+ return true;
+ }
+ return color.isDarkColor(backgroundColor, 50);
+}
+export function overlayPlacementFromData(objectGraph, data) {
+ if (objectGraph.client.isPhone) {
+ return "center";
+ }
+ if (objectGraph.client.isTV) {
+ return "leading";
+ }
+ const heroCarouselItemPlacementString = mediaAttributes.attributeAsString(data, "breakoutTextAlignment");
+ if (!serverData.isDefinedNonNullNonEmpty(heroCarouselItemPlacementString)) {
+ return objectGraph.client.isMac ? "center" : "leading";
+ }
+ switch (heroCarouselItemPlacementString.toLowerCase()) {
+ case "left":
+ return "leading";
+ case "center":
+ return "center";
+ case "right":
+ return "trailing";
+ default:
+ return "leading";
+ }
+}
+/**
+ * The text alignment to use for a particular detail position on large breakouts
+ * @param data
+ */
+function overlayTextAlignmentFromData(objectGraph, data) {
+ const placement = overlayPlacementFromData(objectGraph, data);
+ const isTV = objectGraph.client.isTV;
+ switch (placement) {
+ case "leading":
+ return "leading";
+ case "trailing":
+ return "leading";
+ case "center":
+ if (isTV) {
+ return "leading";
+ }
+ else {
+ return objectGraph.client.isMac ? "center" : "leading";
+ }
+ default:
+ return "leading";
+ }
+}
+/**
+ * Decides whether to account for the Hero 3.0 styles.
+ *
+ * @param objectGraph The object graph.
+ */
+function allowHeroModuleStylePresentation(objectGraph) {
+ const isNotTV = !objectGraph.client.isTV;
+ const isFeatureEnabledForCurrentUser = lottery.isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.hero3RolloutRate);
+ return isNotTV && isFeatureEnabledForCurrentUser;
+}
+//# sourceMappingURL=hero-carousel-overlay-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/hero/hero-common.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/hero/hero-common.js
new file mode 100644
index 0000000..4a67b72
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/hero/hero-common.js
@@ -0,0 +1,90 @@
+//
+// hero-carousel-overlay-common.ts
+// AppStoreKit
+//
+// Created by Jonathan Ellenbogen on 12/11/20.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { artworkDictionaryFromData, videoDictionaryFromData } from "../../arcade/breakouts-common";
+import * as content from "../../content/content";
+export function heroArtworkFromData(objectGraph, data, presentedInTopShelf = false) {
+ const artworkDictionary = artworkDictionaryFromData(objectGraph, data);
+ const isPhone = objectGraph.client.isPhone;
+ const isTV = objectGraph.client.isTV;
+ let heroArtworkKey = isPhone ? "breakoutTall" : "breakoutFullScreen";
+ if (presentedInTopShelf) {
+ heroArtworkKey = "topShelf";
+ }
+ const heroArtworkData = serverData.asDictionary(artworkDictionary, heroArtworkKey);
+ let heroArtwork = null;
+ let backgroundColor = null;
+ if (serverData.isDefinedNonNullNonEmpty(heroArtworkData)) {
+ heroArtwork = content.artworkFromApiArtwork(objectGraph, heroArtworkData, {
+ withJoeColorPlaceholder: true,
+ useCase: 6 /* content.ArtworkUseCase.ArcadeLargeBreakout */,
+ });
+ if (serverData.isDefinedNonNullNonEmpty(heroArtwork)) {
+ if (isPhone) {
+ heroArtwork.crop = "oa";
+ }
+ else if (presentedInTopShelf) {
+ heroArtwork.crop = "ta";
+ }
+ else if (isTV) {
+ heroArtwork.crop = "od";
+ }
+ else {
+ heroArtwork.crop = "ob";
+ }
+ backgroundColor = heroArtwork.backgroundColor;
+ }
+ }
+ return {
+ artwork: heroArtwork,
+ artworkData: heroArtworkData,
+ backgroundColor: backgroundColor,
+ };
+}
+export function heroVideoFromData(objectGraph, data, isUpsell = false, wants9x16 = false) {
+ const videoDictionary = videoDictionaryFromData(objectGraph, data);
+ const use9x16 = objectGraph.client.isPhone || wants9x16;
+ const heroVideoKey = use9x16 ? "sizzleVideo9x16" : "sizzleVideo16x9";
+ const heroVideoFallbackKey = use9x16 ? "breakoutVideo9x16" : "breakoutVideo16x9";
+ const heroVideoData = serverData.asDictionary(videoDictionary, heroVideoKey) ||
+ serverData.asDictionary(videoDictionary, heroVideoFallbackKey);
+ let heroVideo = null;
+ let backgroundColor = null;
+ let artworkData = null;
+ if (serverData.isDefinedNonNullNonEmpty(heroVideoData)) {
+ artworkData = serverData.asDictionary(heroVideoData, "previewFrame");
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ withJoeColorPlaceholder: true,
+ useCase: 6 /* content.ArtworkUseCase.ArcadeLargeBreakout */,
+ });
+ if (serverData.isDefinedNonNullNonEmpty(artwork)) {
+ artwork.crop = "sr";
+ backgroundColor = artwork.backgroundColor;
+ }
+ // Get the video URL
+ const videoUrl = serverData.asString(heroVideoData, "video");
+ if (serverData.isDefinedNonNullNonEmpty(artwork) && (videoUrl === null || videoUrl === void 0 ? void 0 : videoUrl.length) > 0) {
+ heroVideo = new models.Video(videoUrl, artwork, {
+ canPlayFullScreen: false,
+ allowsAutoPlay: true,
+ looping: true,
+ playbackControls: {
+ prominentPlay: isUpsell,
+ },
+ autoPlayPlaybackControls: {},
+ });
+ }
+ }
+ return {
+ video: heroVideo,
+ artworkData: artworkData,
+ backgroundColor: backgroundColor,
+ };
+}
+//# sourceMappingURL=hero-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/render-grouping-page.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/render-grouping-page.js
new file mode 100644
index 0000000..8640079
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/render-grouping-page.js
@@ -0,0 +1,246 @@
+import { isSome, isNothing } from "@jet/environment/types/optional";
+import { FlowAction, GenericPage, Shelf } from "../../api/models";
+import { attributeAsNumber, attributeAsString } from "../../foundation/media/attributes";
+import { dataFromDataContainer } from "../../foundation/media/data-structure";
+import { asNumber, isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, } from "../../foundation/json-parsing/server-data";
+import { hasRelationship, relationship } from "../../foundation/media/relationships";
+import { URL } from "../../foundation/network/urls";
+import { appStoreIdentifier } from "../../foundation/wrappers/client";
+import { createArtworkForResource } from "../content/artwork/artwork";
+import { addMetricsEventsToPageWithInformation, metricsPageInformationFromMediaApiResponse, } from "../metrics/helpers/page";
+import { newPageRefreshControllerFromResponse, pageRefreshPolicyForController, } from "../refresh/page-refresh-controller";
+import { metricsData } from "../personalization/on-device-personalization";
+import { impressionEventsFromData } from "../personalization/on-device-impression-demotion";
+import { combinedRecoMetricsDataFromMetricsData } from "../metrics/helpers/util";
+import { newLocationTracker } from "../metrics/helpers/location";
+import { flattenMediaApiGroupingData, insertInitialShelvesIntoGroupingParseContext, shelfForFooterButtons, shelfForTermsAndConditions, shelfForUnifiedMessage, } from "./grouping-common";
+import { addBatchGroupsToPage } from "./shelf-batching";
+import { nameFromGroupingData } from "../lockups/lockups";
+// The key that contains the impression data for the grouping page fetched as an additional request.
+export const GroupingImpressionAdditionalDataKey = "amdImpressionData";
+export function groupingParseContextFromDataContainer(objectGraph, data, additionalPageRequirements) {
+ const mediaApiGroupingData = dataFromDataContainer(objectGraph, data);
+ if (isNothing(mediaApiGroupingData)) {
+ return null;
+ }
+ const metricsPageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "Genre", mediaApiGroupingData.id, data);
+ const onDevicePersonalizationMetricsData = metricsData(objectGraph);
+ metricsPageInformation.recoMetricsData = combinedRecoMetricsDataFromMetricsData(metricsPageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData);
+ return {
+ shelves: [],
+ metricsPageInformation: metricsPageInformation,
+ metricsLocationTracker: newLocationTracker(),
+ pageGenreAdamId: attributeAsString(mediaApiGroupingData, "id"),
+ pageGenreId: null,
+ hasAuthenticatedUser: isDefinedNonNull(objectGraph.user.dsid),
+ refreshController: newPageRefreshControllerFromResponse(data),
+ recoImpressionData: impressionEventsFromData(objectGraph, additionalPageRequirements === null || additionalPageRequirements === void 0 ? void 0 : additionalPageRequirements[GroupingImpressionAdditionalDataKey]),
+ };
+}
+export function flattenedGroupingFromDataContainer(objectGraph, data) {
+ const mediaApiGroupingData = dataFromDataContainer(objectGraph, data);
+ if (!mediaApiGroupingData) {
+ return null;
+ }
+ if (!hasRelationship(mediaApiGroupingData, "tabs")) {
+ return null;
+ }
+ return flattenMediaApiGroupingData(objectGraph, mediaApiGroupingData);
+}
+// TODO: Once 18E is in we need to get the genreId from the chart URL
+export function genreIdFromChartURL(chartURLString) {
+ if (isNull(chartURLString)) {
+ return null;
+ }
+ const chartURL = URL.from(chartURLString);
+ return asNumber(chartURL.query, "genre");
+}
+function groupingPageTitleForData(objectGraph, groupingData, genreId) {
+ var _a;
+ let pageTitle = null;
+ switch (genreId) {
+ case 36 /* GenreIds.Apps */:
+ if (objectGraph.host.clientIdentifier === appStoreIdentifier && !objectGraph.client.isWatch) {
+ if (objectGraph.client.isMac) {
+ pageTitle = macDiscoverPageTitleForData(objectGraph, groupingData);
+ }
+ else {
+ pageTitle = objectGraph.loc.string("GROUPING_APPS");
+ }
+ }
+ else {
+ if (objectGraph.client.isTV) {
+ pageTitle = objectGraph.loc.string("GROUPING_APPS");
+ }
+ else if (objectGraph.client.isWeb) {
+ pageTitle =
+ ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.previewPlatform) === "mac"
+ ? macDiscoverPageTitleForData(objectGraph, groupingData)
+ : objectGraph.loc.string("GROUPING_APPS");
+ }
+ else {
+ pageTitle = objectGraph.loc.string("GROUPING_APP_STORE", "App Store");
+ }
+ }
+ break;
+ case 39 /* GenreIds.Discover */:
+ pageTitle = objectGraph.loc.string("GROUPING_DISCOVER");
+ break;
+ default:
+ pageTitle = nameFromGroupingData(objectGraph, groupingData);
+ break;
+ }
+ return pageTitle;
+}
+/**
+ * Create a shelf for header buttons, e.g. "Search".
+ *
+ * @return Shelf configured to display header buttons.
+ */
+function shelfForHeaderButtons(objectGraph) {
+ const shelf = new Shelf("action");
+ const items = [];
+ const searchFlowAction = new FlowAction("search");
+ searchFlowAction.title = objectGraph.loc.string("SEARCH", "Search");
+ searchFlowAction.artwork = createArtworkForResource(objectGraph, "systemimage://magnifyingglass");
+ items.push(searchFlowAction);
+ shelf.items = items;
+ return shelf;
+}
+/**
+ * Returns the page title for the Discover tab on macOS
+ *
+ * @param objectGraph
+ * @param {Data} groupingData MAPI data blob describing the grouping
+ * @return Page title string
+ */
+function macDiscoverPageTitleForData(objectGraph, groupingData) {
+ const tabs = relationship(groupingData, "tabs");
+ if (isDefinedNonNull(tabs) && isDefinedNonNull(tabs.data)) {
+ for (const tabData of tabs.data) {
+ const tabName = attributeAsString(tabData, "name");
+ const featuredContentId = attributeAsNumber(tabData, "editorialElementKind");
+ const token = attributeAsString(tabData, "token");
+ if (isSome(tabName) &&
+ tabName.length > 0 &&
+ isDefinedNonNull(featuredContentId) &&
+ isDefinedNonNullNonEmpty(token) &&
+ token === "macOS" &&
+ featuredContentId === 414 /* FeaturedContentID.AppStore_TabRoot */) {
+ return tabName;
+ }
+ }
+ }
+ return objectGraph.loc.string("GROUPING_DISCOVER");
+}
+/**
+ * Insert the unified message shelves into the arcade page
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param page The generic page to insert shelves into
+ * @param genreId The genre Id of the generic page, e.g. Apps or Games
+ */
+function insertUnifiedMessageShelves(objectGraph, page, genreId) {
+ if (page.shelves.length <= 0) {
+ return;
+ }
+ // Insert Unified message shelves. These are hidden by default and act as 'placeholder' locations
+ // for the marketing teams to target with inline banners or bubble tip messages
+ switch (genreId) {
+ case 36 /* GenreIds.Apps */:
+ page.shelves.splice(0, 0, shelfForUnifiedMessage(objectGraph, "appsPageHeader"));
+ page.shelves.splice(6, 0, shelfForUnifiedMessage(objectGraph, "appsPageMid"));
+ page.shelves.push(shelfForUnifiedMessage(objectGraph, "appsPageFooter"));
+ break;
+ case 6014 /* GenreIds.Games */:
+ page.shelves.splice(0, 0, shelfForUnifiedMessage(objectGraph, "gamesPageHeader"));
+ page.shelves.splice(6, 0, shelfForUnifiedMessage(objectGraph, "gamesPageMid"));
+ page.shelves.push(shelfForUnifiedMessage(objectGraph, "gamesPageFooter"));
+ break;
+ default:
+ break;
+ }
+}
+export function groupingPageFromFlattenedGrouping(objectGraph, flattenedGrouping, groupingParseContext) {
+ groupingParseContext.pageGenreId =
+ attributeAsNumber(flattenedGrouping.originalGroupingData, "genre") ||
+ genreIdFromChartURL(attributeAsString(flattenedGrouping.originalGroupingData, "chartUrl"));
+ // Construct the shelves
+ insertInitialShelvesIntoGroupingParseContext(objectGraph, flattenedGrouping, groupingParseContext);
+ if (objectGraph.client.isWatch) {
+ // Remove title of first shelf with cards if needed.
+ if (groupingParseContext.shelves.length > 0 && groupingParseContext.shelves[0].contentType === "todayCard") {
+ groupingParseContext.shelves[0].title = undefined;
+ }
+ // Prepend Header Buttons
+ if (objectGraph.client.deviceType !== "watch") {
+ const headerButtonShelf = shelfForHeaderButtons(objectGraph);
+ groupingParseContext.shelves.unshift(headerButtonShelf);
+ }
+ }
+ // TODO: Add for ATV
+ // Append Footer Buttons
+ if (objectGraph.client.deviceType !== "tv" && objectGraph.client.deviceType !== "web") {
+ const footerButtonShelf = shelfForFooterButtons(objectGraph, groupingParseContext.metricsPageInformation, groupingParseContext.metricsLocationTracker);
+ if (footerButtonShelf) {
+ groupingParseContext.shelves.push(footerButtonShelf);
+ }
+ }
+ // TODO: Add for ATV
+ // Append T&C
+ if (objectGraph.client.deviceType !== "watch" && objectGraph.client.deviceType !== "tv") {
+ const url = objectGraph.bag.termsAndConditionsURL;
+ if (!isNull(url)) {
+ const termsAndConditionsShelf = shelfForTermsAndConditions(objectGraph, url);
+ groupingParseContext.shelves.push(termsAndConditionsShelf);
+ }
+ }
+ // Determine the title
+ const pageTitle = groupingPageTitleForData(objectGraph, flattenedGrouping.originalGroupingData, groupingParseContext.pageGenreId);
+ // Build the page
+ const page = new GenericPage(groupingParseContext.shelves);
+ switch (objectGraph.client.deviceType) {
+ case "tv":
+ page.title = pageTitle;
+ break;
+ case "watch":
+ page.title = pageTitle;
+ page.presentationOptions = ["prefersLargeTitleWhenRoot"];
+ break;
+ case "web":
+ page.title = pageTitle;
+ page.presentationOptions = ["prefersLargeTitleWhenRoot"];
+ const firstShelfType = page.shelves[0].contentType;
+ if (["heroCarousel", "editorialCard", "largeStoryCard"].includes(firstShelfType)) {
+ page.presentationOptions.push("prefersOverlayedPageHeader");
+ }
+ break;
+ default:
+ page.title = pageTitle;
+ if ((groupingParseContext.pageGenreId === 39 /* GenreIds.Discover */ ||
+ (groupingParseContext.pageGenreId === 36 /* GenreIds.Apps */ && objectGraph.client.isMac)) &&
+ !objectGraph.client.isIconArtworkCapable) {
+ page.presentationOptions = ["prefersRevealNavigationBarOnMouseOver"];
+ }
+ else {
+ page.presentationOptions = ["prefersLargeTitleWhenRoot"];
+ }
+ break;
+ }
+ if (objectGraph.client.isiOS) {
+ if (groupingParseContext.shelves[0].contentType === "mediaPageHeader") {
+ page.presentationOptions.push("prefersNonStandardBackButton");
+ page.presentationOptions.push("prefersHiddenPageTitle");
+ page.presentationOptions.push("prefersOverlayedPageHeader");
+ }
+ }
+ page.pageRefreshPolicy = pageRefreshPolicyForController(objectGraph, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.refreshController);
+ addMetricsEventsToPageWithInformation(objectGraph, page, groupingParseContext.metricsPageInformation);
+ // Add unified message shelves
+ if (objectGraph.client.isiOS) {
+ insertUnifiedMessageShelves(objectGraph, page, groupingParseContext.pageGenreId);
+ }
+ addBatchGroupsToPage(page);
+ return page;
+}
+//# sourceMappingURL=render-grouping-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-batching.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-batching.js
new file mode 100644
index 0000000..3746b11
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-batching.js
@@ -0,0 +1,27 @@
+import { isSome } from "@jet/environment";
+import * as models from "../../api/models";
+/// The number of shelves allowed in a single shelf batch.
+const shelfBatchGroupMaxCount = 5;
+/**
+ * For a given generic page group the shelves in this page into batch groups
+ * allowing us to separate our calls for incomplete shelves into groups based on where
+ * the shelves are on the page. This will not muck with any pre-existing shelfBatchGroups
+ *
+ * @param page: The GenericPage to add shelf batches to.
+ */
+export function addBatchGroupsToPage(page) {
+ let currentShelfBatchGroup = 0;
+ let currentShelfBatchGroupCount = 0;
+ for (const shelf of page.shelves) {
+ if (isSome(shelf.batchGroup) && shelf.batchGroup.length > 0) {
+ continue;
+ }
+ shelf.batchGroup = `${models.genericShelfBatchGroupBase}${currentShelfBatchGroup}`;
+ currentShelfBatchGroupCount++;
+ if (currentShelfBatchGroupCount === shelfBatchGroupMaxCount) {
+ currentShelfBatchGroupCount = 0;
+ currentShelfBatchGroup++;
+ }
+ }
+}
+//# sourceMappingURL=shelf-batching.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/arcade-download-pack-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/arcade-download-pack-shelf-controller.js
new file mode 100644
index 0000000..0e89233
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/arcade-download-pack-shelf-controller.js
@@ -0,0 +1,295 @@
+import * as models from "../../../api/models";
+import * as mediaDataFetching from "../../../foundation/media/data-fetching";
+import * as mediaNetwork from "../../../foundation/media/network";
+import * as urls from "../../../foundation/network/urls";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as validation from "@jet/environment/json/validation";
+import { ResponseMetadata } from "../../../foundation/network/network";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as content from "../../content/content";
+import { developerAttributes } from "../../../common/developer/developer-request";
+import { lockupShelfTokenFromBaseTokenAndMediaApiData } from "./grouping-lockup-shelf-controller-common";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import { arcadeOnboardingSubscriptionStatusFromString } from "../../../api/models";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export class ArcadeDownloadPackShelfController extends GroupingShelfController {
+ constructor() {
+ super("ArcadeDownloadPackShelfController");
+ // Stable identifier of the shelf to match in native.
+ // Used to make a shelf refresh request after user finishes onboarding.
+ this.shelfId = "arcadeDownloadPackShelf";
+ this.supportedFeaturedContentIds = new Set([566 /* groupingTypes.FeaturedContentID.AppStore_ArcadeDownloadPackMarker */]);
+ }
+ shelfRoute(objectGraph) {
+ return [
+ ...super.shelfRoute(objectGraph),
+ {
+ protocol: Protocol.internal,
+ path: `/${Path.grouping}/${Path.shelf}/{token}`,
+ query: [Parameters.isArcadeDownloadPackShelfPlaceholder],
+ },
+ ];
+ }
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return lockupShelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext);
+ }
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return {
+ shelfContents: [],
+ categoriesContents: [],
+ apps: [],
+ title: "",
+ };
+ }
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ const downloadPackData = objectGraph.arcade.getDownloadPackGames(objectGraph.bag.arcadeDownloadPackShelfTTLInSeconds);
+ if (isNothing(downloadPackData) || downloadPackData.apps.length === 0) {
+ return {
+ shelfContents: [],
+ categoriesContents: [],
+ apps: [],
+ title: "",
+ };
+ }
+ const downloadPackApps = downloadPackData.apps;
+ const shelfTitle = this.shelfTitle(objectGraph, arcadeOnboardingSubscriptionStatusFromString(downloadPackData.subscriptionStatus));
+ // Check whether it was a call from native to render a placeholder.
+ if (isSome(shelfUrl.query[Parameters.isArcadeDownloadPackShelfPlaceholder])) {
+ return {
+ shelfContents: [],
+ categoriesContents: [],
+ apps: downloadPackApps,
+ title: shelfTitle,
+ };
+ }
+ // Do hydration call otherwise.
+ const adamIDs = downloadPackApps.map((app) => app.adamId);
+ const categoryIDs = downloadPackApps.map((app) => app.categoryId);
+ const appsType = "apps";
+ const categoriesType = "editorial-items";
+ const batchRequest = new mediaDataFetching.Request(objectGraph)
+ .addingQuery(`ids[${appsType}]`, Array.from(adamIDs).join(","))
+ .addingQuery(`ids[${categoriesType}]`, Array.from(categoryIDs).join(","))
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .includingAttributes(developerAttributes(objectGraph));
+ return await mediaNetwork
+ .fetchData(objectGraph, batchRequest)
+ .then(async (dataContainer) => {
+ const shelfData = {
+ shelfContents: dataContainer.data.filter((data) => {
+ return data.type === appsType;
+ }),
+ categoriesContents: dataContainer.data.filter((data) => {
+ return data.type === categoriesType;
+ }),
+ apps: downloadPackApps,
+ title: shelfTitle,
+ responseTimingValues: dataContainer[ResponseMetadata.timingValues],
+ };
+ return shelfData;
+ });
+ }
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ if (shelfToken.isFirstRender) {
+ const downloadPackData = objectGraph.arcade.getDownloadPackGames(objectGraph.bag.arcadeDownloadPackShelfTTLInSeconds);
+ if (isNothing(downloadPackData) || downloadPackData.apps.length === 0) {
+ // No suggested games to display. Adding an empty hidden shelf with `refreshUrl` for future refresh from native when onboarding ends.
+ const shelf = this.emptyShelfWithRefreshUrl(objectGraph);
+ shelf.refreshUrl = this.shelfRefreshURL(shelfToken);
+ // Even though the shelf is hidden, need to increase position here, so when it becomes visible the index stays the same.
+ metricsHelpersLocation.nextPosition(groupingParseContext.metricsLocationTracker);
+ return shelf;
+ }
+ else {
+ const shelf = this.placeholderShelf(objectGraph, shelfToken, downloadPackData.apps, this.shelfTitle(objectGraph, arcadeOnboardingSubscriptionStatusFromString(downloadPackData.subscriptionStatus)));
+ metricsHelpersLocation.nextPosition(groupingParseContext.metricsLocationTracker);
+ return shelf;
+ }
+ }
+ else {
+ // If `shelfData` contains only `records` then it was a request for a placeholder shelf from native.
+ if (shelfData.apps.length > 0 &&
+ (isNothing(shelfData.shelfContents) || shelfData.shelfContents.length === 0)) {
+ return this.placeholderShelf(objectGraph, shelfToken, shelfData.apps, shelfData.title);
+ }
+ else {
+ const shelfMetricsOptions = this.shelfMetrics(shelfData.title, shelfToken);
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, shelfData.title);
+ const shelf = this.downloadPackShelf(objectGraph, shelfToken, shelfData);
+ // `refreshUrl` allows to update the shelf content if the onboarding experience was triggered again.
+ shelf.refreshUrl = this.shelfRefreshURL(shelfToken);
+ metricsHelpersLocation.popLocation(shelfToken.metricsLocationTracker);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ return shelf;
+ }
+ }
+ }
+ shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) {
+ // Otherwise `baseMetricsOptions` will be used, however, at this point shelf doesn't have a title.
+ return null;
+ }
+ emptyShelfWithRefreshUrl(objectGraph) {
+ const shelf = new models.Shelf(this.useCustomDownloadPackCardShelf(objectGraph) ? "arcadeDownloadPackCard" : "smallLockup");
+ shelf.id = this.shelfId;
+ shelf.isHidden = true;
+ return shelf;
+ }
+ placeholderShelf(objectGraph, shelfToken, downloadPackApps, title) {
+ const shelf = this.useCustomDownloadPackCardShelf(objectGraph)
+ ? this.downloadPackCardPlaceholderShelf(objectGraph, shelfToken, downloadPackApps.length)
+ : this.smallLockupsPlaceholderShelf(objectGraph, shelfToken, downloadPackApps);
+ shelf.url = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken)).build();
+ shelf.title = title;
+ return shelf;
+ }
+ downloadPackCardPlaceholderShelf(objectGraph, shelfToken, expectedItemCount) {
+ const shelf = new models.Shelf("arcadeDownloadPackCard");
+ shelf.id = this.shelfId;
+ // For `arcadeDownloadPackCard` the same `ArcadeDownloadPackCard` model is used to render lockup placeholders.
+ const card = new models.ArcadeDownloadPackCard();
+ card.numberOfPlaceholders = expectedItemCount;
+ shelf.items = [card];
+ // See `shelfFetchShouldMergeWhenFetched` where `shelfToken.shelfStyle` is used to override `shelf.mergeWhenFetched` flag.
+ shelfToken.shelfStyle = shelf.contentType;
+ return shelf;
+ }
+ smallLockupsPlaceholderShelf(objectGraph, shelfToken, downloadPackApps) {
+ const shelf = new models.Shelf("smallLockup");
+ shelf.id = this.shelfId;
+ shelf.items = [];
+ shelf.isHorizontal = true;
+ shelf.rowsPerColumn = 2;
+ // Use standard small lockup placeholders for iPad shelf.
+ shelf.items = Array(downloadPackApps.length).fill(new models.Placeholder());
+ shelf.placeholderContentType = shelf.contentType;
+ shelf.contentType = "placeholder";
+ shelfToken.showingPlaceholders = true;
+ shelfToken.remainingItems = downloadPackApps.map((value) => {
+ return {
+ id: value.adamId,
+ type: "apps",
+ };
+ });
+ return shelf;
+ }
+ downloadPackShelf(objectGraph, shelfToken, shelfData) {
+ const categories = this.categoriesMapFromResponse(objectGraph, shelfData.categoriesContents, shelfData.apps);
+ const useCustomShelf = this.useCustomDownloadPackCardShelf(objectGraph);
+ const contentType = useCustomShelf ? "arcadeDownloadPackCard" : "smallLockup";
+ const apps = this.lockupsFromResponse(objectGraph, shelfToken, categories, useCustomShelf, // In the custom `arcadeDownloadPack` shelf app lockups the game category goes into lockup header, otherwise, in a standard shelf, it goes into subtitle.
+ useCustomShelf, // In the custom `arcadeDownloadPack` shelf app lockups have dark environment.
+ content.artworkUseCaseFromShelfStyle(objectGraph, contentType), isSome(shelfData.shelfContents) ? shelfData.shelfContents : [], shelfData.apps);
+ const shelf = new models.Shelf(contentType);
+ shelf.id = this.shelfId;
+ shelf.title = shelfData.title;
+ if (useCustomShelf) {
+ const card = new models.ArcadeDownloadPackCard();
+ card.lockups = apps;
+ shelf.items = [card];
+ }
+ else {
+ shelf.items = apps;
+ shelf.isHorizontal = true;
+ shelf.rowsPerColumn = 2;
+ }
+ shelf.isHidden = apps.length === 0;
+ return shelf;
+ }
+ // On iPhone there is a bespoke 'arcadeDownloadPackCard' games card shelf.
+ // On iPad, always use `smallLockup` instead of `shelfToken.shelfStyle` from server
+ // to make editorial configuration work easier.
+ useCustomDownloadPackCardShelf(objectGraph) {
+ return objectGraph.client.isPhone;
+ }
+ shelfRefreshURL(shelfToken) {
+ return urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken))
+ .param(Parameters.isArcadeDownloadPackShelfPlaceholder, "true")
+ .build();
+ }
+ lockupsFromResponse(objectGraph, shelfToken, categoryTitlesForAdamIDs, showCategoryHeader, darkEnvironment, artworkUseCase, responseData, records) {
+ return validation.context("lockupsFromResponse", () => {
+ const lockupDataMap = new Map();
+ for (const lockupData of responseData) {
+ lockupDataMap.set(lockupData.id, lockupData);
+ }
+ const locationTracker = shelfToken.metricsLocationTracker;
+ const pageInformation = shelfToken.metricsPageInformation;
+ // Order of the apps from the local storage (records) must be preserved,
+ // so user always sees the same list of the apps like it was on the onboarding game suggestions screen.
+ const items = [];
+ for (const app of records) {
+ const lockupData = lockupDataMap.get(app.adamId);
+ if (isNothing(lockupData)) {
+ continue;
+ }
+ const lockup = lockups.lockupFromData(objectGraph, lockupData, {
+ offerStyle: darkEnvironment ? "transparent" : undefined,
+ offerEnvironment: darkEnvironment ? "dark" : undefined,
+ metricsOptions: {
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ badges: {
+ categoryId: app.categoryId,
+ },
+ },
+ metricsClickOptions: {
+ id: lockupData.id,
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ badges: {
+ categoryId: app.categoryId,
+ },
+ },
+ artworkUseCase: artworkUseCase,
+ shouldHideArcadeHeader: true,
+ isSubtitleHidden: showCategoryHeader,
+ });
+ if (isNothing(lockup)) {
+ continue;
+ }
+ lockups.cleanupArcadeDownloadPackLockupMetricsIfNeeded(lockup, objectGraph);
+ metricsHelpersLocation.nextPosition(locationTracker);
+ if (showCategoryHeader) {
+ lockup.heading = categoryTitlesForAdamIDs.get(lockup.adamId);
+ }
+ else {
+ lockup.subtitle = categoryTitlesForAdamIDs.get(lockup.adamId);
+ }
+ items.push(lockup);
+ }
+ return items;
+ });
+ }
+ categoriesMapFromResponse(objectGraph, responseData, records) {
+ const result = new Map();
+ for (const app of records) {
+ const category = responseData.find((data) => data.id === app.categoryId);
+ if (!category) {
+ continue;
+ }
+ const editorialNotesName = content.editorialNotesFromData(objectGraph, category, "name");
+ result.set(app.adamId, editorialNotesName);
+ }
+ return result;
+ }
+ shelfTitle(objectGraph, subscriptionStatus) {
+ return objectGraph.loc.string(subscriptionStatus === "new" ? "Arcade.DownloadPack.ShelfTitle.NewUser" : "Arcade.DownloadPack.ShelfTitle");
+ }
+ shelfMetrics(title, shelfToken) {
+ return {
+ id: shelfToken.id,
+ kind: null,
+ softwareType: "Arcade",
+ targetType: "swoosh",
+ title: title,
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ idType: "its_contentId",
+ fcKind: shelfToken.featuredContentId,
+ };
+ }
+}
+//# sourceMappingURL=arcade-download-pack-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-app-event-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-app-event-shelf-controller.js
new file mode 100644
index 0000000..6acbb2a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-app-event-shelf-controller.js
@@ -0,0 +1,260 @@
+import { isSome } from "@jet/environment";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaDataFetching from "../../../foundation/media/data-fetching";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import { fetchData } from "../../../foundation/media/network";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as appPromotionCommon from "../../app-promotions/app-promotions-common";
+import * as metricsHelpersUtil from "../../metrics/helpers/util";
+import * as onDevicePersonalization from "../../personalization/on-device-personalization";
+import * as placeholders from "../../placeholders/placeholders";
+import { addVariantParametersToRequestForItems } from "../../product-page/product-page-variants";
+import * as refresh from "../../refresh/page-refresh-controller";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+import { attributeAsBooleanOrFalse } from "../../../foundation/media/attributes";
+import { ResponseMetadata } from "../../../foundation/network/network";
+import { shelfTitleAttributePathForFeaturedContentId } from "./grouping-shelf-controller-common";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+export class GroupingAppEventShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingAppEventShelfController");
+ this.supportedFeaturedContentIds = new Set([
+ 519 /* groupingTypes.FeaturedContentID.AppStore_AppEventsMarker */,
+ 518 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedAppEventsMarker */,
+ ]);
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ var _a;
+ if (((_a = shelfToken.recommendationsHref) === null || _a === void 0 ? void 0 : _a.length) > 0) {
+ try {
+ const mediaApiData = await GroupingShelfController.secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters);
+ const shelfResponseData = mediaDataStructure.dataFromDataContainer(objectGraph, mediaApiData);
+ const shelfContents = this.initialShelfDataFromGroupingMediaApiData(objectGraph, shelfResponseData);
+ shelfContents.responseTimingValues = mediaApiData[ResponseMetadata.timingValues];
+ const title = mediaAttributes.attributeAsString(shelfResponseData, shelfTitleAttributePathForFeaturedContentId(objectGraph, shelfToken.featuredContentId));
+ if (serverData.isDefinedNonNull(title) && (title === null || title === void 0 ? void 0 : title.length) > 0) {
+ shelfContents.shelfTitle = title;
+ }
+ return shelfContents;
+ }
+ catch {
+ return { shelfContents: [] };
+ }
+ }
+ else {
+ const appEvents = [];
+ const contingentItems = [];
+ const offerItems = [];
+ for (const remainingItem of shelfToken.remainingItems) {
+ switch (remainingItem.type) {
+ case "contingent-items":
+ contingentItems.push(remainingItem);
+ break;
+ case "offer-items":
+ offerItems.push(remainingItem);
+ break;
+ case "app-events":
+ appEvents.push(remainingItem);
+ break;
+ default:
+ break;
+ }
+ }
+ const appEventsRequest = new mediaDataFetching.Request(objectGraph, appEvents);
+ addVariantParametersToRequestForItems(objectGraph, appEventsRequest, appEvents);
+ const contingentItemsRequest = new mediaDataFetching.Request(objectGraph, contingentItems);
+ addVariantParametersToRequestForItems(objectGraph, contingentItemsRequest, contingentItems);
+ const offerItemsRequest = new mediaDataFetching.Request(objectGraph, offerItems);
+ addVariantParametersToRequestForItems(objectGraph, offerItemsRequest, offerItems);
+ const hydrationResults = await Promise.all([
+ this.fetchRemainingItems(objectGraph, appEventsRequest),
+ this.fetchRemainingItems(objectGraph, contingentItemsRequest),
+ this.fetchRemainingItems(objectGraph, offerItemsRequest),
+ ]);
+ const hydratedDataMap = { ...hydrationResults[0], ...hydrationResults[1], ...hydrationResults[2] };
+ const shelfContents = [];
+ for (const remainingItem of shelfToken.remainingItems) {
+ const data = hydratedDataMap[remainingItem.id];
+ if (isSome(data)) {
+ shelfContents.push(data);
+ }
+ }
+ groupingShelfControllerCommon.flushRequestedItemsFromShelfToken(shelfToken, new Set([...offerItemsRequest.ids, ...contingentItemsRequest.ids, ...appEventsRequest.ids]));
+ return { shelfContents: shelfContents };
+ }
+ }
+ async fetchRemainingItems(objectGraph, remainingItemsRequest) {
+ const apiDataMap = {};
+ const addDataToMap = (data) => {
+ for (const item of data.data) {
+ apiDataMap[item.id] = item;
+ }
+ };
+ if (remainingItemsRequest.ids.size > 0) {
+ groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, remainingItemsRequest);
+ try {
+ await fetchData(objectGraph, remainingItemsRequest).then((mediaApiData) => {
+ addDataToMap(mediaApiData);
+ });
+ }
+ catch (fetchError) {
+ objectGraph.console.error("Error fetching remaining items", remainingItemsRequest.ids);
+ }
+ }
+ return apiDataMap;
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ const shouldPersonalizeData = baseShelfToken.featuredContentId === 518 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedAppEventsMarker */;
+ let personalizedDataResult = null;
+ const shelfData = this.initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData);
+ if (shouldPersonalizeData && serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)) {
+ personalizedDataResult = this.personalizedDataResultFromDataItems(objectGraph, shelfData.shelfContents);
+ }
+ const appEventShelfToken = {
+ ...baseShelfToken,
+ shouldPersonalizeData: shouldPersonalizeData,
+ personalizedDataResult: personalizedDataResult,
+ };
+ const hasContents = serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents);
+ const shelfPersonalizationAvailable = !attributeAsBooleanOrFalse(mediaApiData, "noPersonalizationAvailable");
+ if (!hasContents && shelfPersonalizationAvailable) {
+ appEventShelfToken.recommendationsHref = mediaApiData.href;
+ appEventShelfToken.isValidRecommendationsShelf = true;
+ }
+ else {
+ appEventShelfToken.isValidRecommendationsShelf = hasContents;
+ }
+ return appEventShelfToken;
+ }
+ // endregion
+ // region Metrics
+ /**
+ * Return the shelf metrics options to use for this specific shelf. Using the base options from the grouping
+ * page controller
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the
+ * grouping page controller
+ */
+ shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) {
+ const shelfMetricsOptions = { ...baseMetricsOptions };
+ if (serverData.isDefinedNonNullNonEmpty(shelfToken.personalizedDataResult)) {
+ const recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(baseMetricsOptions.recoMetricsData, shelfToken.personalizedDataResult.processingType, null);
+ shelfMetricsOptions.recoMetricsData = recoMetricsData;
+ }
+ return shelfMetricsOptions;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ var _a;
+ if (!appPromotionCommon.appEventsAreEnabled(objectGraph)) {
+ return null;
+ }
+ if (!shelfToken.isValidRecommendationsShelf) {
+ return null;
+ }
+ const metricsOptions = {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: shelfToken.metricsPageInformation.recoMetricsData,
+ };
+ // First personalize the data, as this may cause things to be re-ordered
+ let sortedShelfContents = shelfData.shelfContents;
+ // Only try to use the personalized data the first render, second renders the shelfContents has already been personalized
+ if (serverData.isDefinedNonNullNonEmpty(shelfToken.personalizedDataResult) && shelfToken.isFirstRender) {
+ sortedShelfContents = shelfToken.personalizedDataResult.personalizedData;
+ }
+ // Now find which items are hydrated, and add the rest to the token
+ const hydratedShelfContents = [];
+ for (const data of sortedShelfContents) {
+ if (serverData.isNull(data.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(data);
+ continue;
+ }
+ hydratedShelfContents.push(data);
+ }
+ // Now create our shelf from the hydrated items
+ const displayableAppEvents = appPromotionCommon.appPromotionsFromData(objectGraph, hydratedShelfContents, null, false, false, metricsOptions, false, true, shelfToken.isArcadePage, false);
+ refresh.addNextPreferredContentRefreshDate(displayableAppEvents.nextAppEventPromotionStartDate, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.refreshController);
+ const appPromotions = displayableAppEvents.appPromotions;
+ const shelfType = "appPromotion";
+ const shelf = new models.Shelf(shelfType);
+ shelf.isHorizontal = true;
+ shelf.title = (_a = shelfData.shelfTitle) !== null && _a !== void 0 ? _a : shelfToken.title;
+ shelf.items = appPromotions;
+ const willHydrateShelfLater = serverData.isNullOrEmpty(shelf.items) && shelfToken.isFirstRender;
+ if (willHydrateShelfLater && placeholders.placeholdersEnabled(objectGraph)) {
+ placeholders.insertPlaceholdersIntoGenericPageShelf(objectGraph, shelf, shelfToken, shelfToken.featuredContentId);
+ }
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ if (serverData.isNullOrEmpty(shelf.items) && serverData.isNullOrEmpty(shelf.url)) {
+ return shelfToken.isFirstRender ? null : GroupingAppEventShelfController.makeHiddenShelf(shelfToken);
+ }
+ return shelf;
+ }
+ // region Helpers
+ /**
+ * Personalizes the input list of app event data items.
+ * @param objectGraph
+ * @param dataItems The array of app event data items
+ */
+ personalizedDataResultFromDataItems(objectGraph, dataItems) {
+ // Collect the app IDs we are interested in
+ const appIds = new Set();
+ for (const data of dataItems) {
+ const appId = serverData.asString(data, "meta.personalizationData.appId");
+ if ((appId === null || appId === void 0 ? void 0 : appId.length) > 0) {
+ appIds.add(appId);
+ }
+ }
+ // Now personalize the data
+ const personalizationDataContainer = onDevicePersonalization.personalizationDataContainerForAppIds(objectGraph, appIds);
+ return onDevicePersonalization.personalizeDataItems(objectGraph, "groupingAppEvent", dataItems, false, personalizationDataContainer, null, null, null, true);
+ }
+ static makeHiddenShelf(shelfToken) {
+ const hiddenShelf = new models.Shelf(shelfToken.shelfStyle);
+ hiddenShelf.isHidden = true;
+ return hiddenShelf;
+ }
+}
+//# sourceMappingURL=grouping-app-event-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-arcade-footer-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-arcade-footer-shelf-controller.js
new file mode 100644
index 0000000..d9f324d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-arcade-footer-shelf-controller.js
@@ -0,0 +1,160 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaNetwork from "../../../foundation/media/network";
+import { ResponseMetadata } from "../../../foundation/network/network";
+import * as color from "../../../foundation/util/color-util";
+import * as arcadeCommon from "../../arcade/arcade-common";
+import * as content from "../../content/content";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+export class GroupingArcadeFooterShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingArcadeFooterShelfController");
+ this.supportedFeaturedContentIds = new Set([-1 /* groupingTypes.FeaturedContentID.Native_GroupingShelf */]);
+ this.supportedNativeGroupingShelfIds = new Set([1 /* groupingTypes.NativeGroupingShelfID.Arcade_SeeAllGamesFooter */]);
+ }
+ // endregion
+ // region Metrics
+ shouldImpressShelf() {
+ return false;
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return {
+ shelfContents: mediaDataStructure.dataCollectionFromResultsListContainer(mediaApiData),
+ responseTimingValues: null,
+ };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ const footerRequest = arcadeCommon.arcadeAppsRequestForIcons(objectGraph, this.numberOfIconsForArcadeAppGrid(objectGraph.client.deviceType));
+ return await mediaNetwork
+ .fetchData(objectGraph, footerRequest)
+ .then((mediaApiData) => {
+ const shelfData = this.initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData);
+ shelfData.responseTimingValues = mediaApiData[ResponseMetadata.timingValues];
+ return shelfData;
+ });
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ const footerShelfToken = {
+ ...baseShelfToken,
+ shouldIncludeShelfUrl: baseShelfToken.isFirstRender,
+ };
+ footerShelfToken.showingPlaceholders = baseShelfToken.isFirstRender;
+ return footerShelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ const footer = new models.ArcadeFooter();
+ const shelf = new models.Shelf("arcadeFooter");
+ shelf.items = [footer];
+ const impressionOptions = {
+ targetType: "arcadeSeeAllGamesFooter",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ title: objectGraph.loc.string("Arcade.SeeAllGames.Button.Title"),
+ id: shelfToken.id,
+ kind: "footer",
+ softwareType: "Arcade",
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, footer, impressionOptions);
+ metricsHelpersLocation.pushContentLocation(objectGraph, impressionOptions, impressionOptions.title);
+ footer.buttonAction = arcadeCommon.seeAllArcadeGamesPageFlowAction(objectGraph, "releaseDate", shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker);
+ const buttonImpressionOptions = {
+ targetType: "button",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ title: footer.buttonAction.title,
+ id: "arcade-see-all-games-button",
+ kind: "button",
+ softwareType: "Arcade",
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, footer.buttonAction, buttonImpressionOptions);
+ metricsHelpersLocation.popLocation(impressionOptions.locationTracker);
+ const termsAndConditionsUrl = objectGraph.bag.termsAndConditionsURL;
+ const shouldAddTermsAndConditions = !serverData.isNull(termsAndConditionsUrl) && objectGraph.client.deviceType !== "tv";
+ if (objectGraph.client.isiOS) {
+ if (shouldAddTermsAndConditions) {
+ const termsAndConditionTitle = objectGraph.loc.string("TermsAndConditions.Title");
+ const urlAction = new models.ExternalUrlAction(termsAndConditionsUrl);
+ const footnote = new models.Footnote(termsAndConditionTitle);
+ footnote.clickAction = urlAction;
+ footnote.presentationStyle = ["hasChevron", "textLightensOnHighlight", "hasSeparator"];
+ footer.footnote = footnote;
+ }
+ shelf.background = {
+ type: "color",
+ color: color.named("placeholderBackground"),
+ };
+ }
+ if (serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)) {
+ const metricsOptions = {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ };
+ footer.icons = content.impressionableAppIconsFromDataCollection(objectGraph, shelfData.shelfContents, metricsOptions, {
+ useCase: 2 /* content.ArtworkUseCase.LockupIconMedium */,
+ });
+ }
+ else {
+ footer.icons = [];
+ }
+ if (shelfToken.shouldIncludeShelfUrl) {
+ shelf.url = groupingShelfControllerCommon.groupingShelfUrl(shelfToken);
+ }
+ return shelf;
+ }
+ // region Helpers
+ /**
+ * The maximum number of icons that can be displayed in a device's icon grid, i.e. Arcade Grid Footer.
+ * This is used to limit the number of icon data to fetch to what we'll actually use.
+ * Note that views are expected to handle situations where we have less than the max number of icons.
+ *
+ * @param deviceType The device type that arcade icon grid is being displayed in.
+ * @returns The maximum number of icons to fetch.
+ */
+ numberOfIconsForArcadeAppGrid(deviceType) {
+ switch (deviceType) {
+ case "phone":
+ return 9;
+ default: // iPad, provided `footerShowsIconsForPlatform == true`.
+ return 20;
+ }
+ }
+}
+//# sourceMappingURL=grouping-arcade-footer-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-brick-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-brick-shelf-controller.js
new file mode 100644
index 0000000..671aac3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-brick-shelf-controller.js
@@ -0,0 +1,313 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as content from "../../content/content";
+import * as flowPreview from "../../content/flow-preview";
+import * as lockupsEditorialContext from "../../lockups/editorial-context";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as placeholders from "../../placeholders/placeholders";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+import * as metricsLocation from "../../../common/metrics/helpers/location";
+import { defaultLayoutSize } from "../../../foundation/media/data-fetching";
+import * as color from "../../../foundation/util/color-util";
+import { isSome } from "@jet/environment";
+export class GroupingBrickShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingBrickShelfController");
+ this.supportedFeaturedContentIds = new Set([
+ 421 /* groupingTypes.FeaturedContentID.AppStore_BrickRow */,
+ 422 /* groupingTypes.FeaturedContentID.AppStore_Brick */,
+ 423 /* groupingTypes.FeaturedContentID.AppStore_CustomBrick */,
+ 261 /* groupingTypes.FeaturedContentID.Sundance_BrickRow */,
+ ]);
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "children") };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters).then((shelfData) => {
+ return {
+ shelfContents: groupingShelfControllerCommon.mergeContentDataIntoEditorialData(shelfData.shelfContents, mediaRelationship.relationshipCollection(shelfToken.featuredContentData, "children")),
+ };
+ });
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ // If suppress text is not provided, default to hiding.
+ let suppressText = mediaAttributes.attributeAsBoolean(mediaApiData, "suppressText");
+ if (serverData.isNull(suppressText)) {
+ suppressText = true;
+ }
+ const brickShelfToken = {
+ ...baseShelfToken,
+ showSupplementaryText: !suppressText,
+ };
+ brickShelfToken.clientIdentifierOverride = lockupsEditorialContext.clientIdentifierForEditorialContextInData(objectGraph, mediaApiData);
+ return brickShelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ const items = [];
+ const remainingBricks = []; // Data where metadata was missing.
+ const displayStyle = serverData.asString(shelfToken.featuredContentData.attributes, "displayStyle");
+ const isCategoryBrick = displayStyle === "small";
+ let shelf;
+ if (isCategoryBrick) {
+ // i.e. category bricks shelf
+ shelf = new models.Shelf("categoryBrick");
+ const layoutSize = serverData.asNumber(shelfToken.featuredContentData.attributes, "layoutStyle.layoutSize");
+ shelf.rowsPerColumn = layoutSize !== null && layoutSize !== void 0 ? layoutSize : defaultLayoutSize(objectGraph);
+ metricsLocation.currentLocation(shelfToken.metricsLocationTracker).name = "Browse Categories";
+ }
+ else {
+ // i.e. "medium", also the default bricks shelf
+ shelf = new models.Shelf("brick");
+ }
+ shelf.isHorizontal = true;
+ for (const brickData of shelfData.shelfContents) {
+ const brickModel = GroupingBrickShelfController.createBrick(objectGraph, brickData, isCategoryBrick, shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker, shelfToken, groupingParseContext);
+ if (!brickModel) {
+ remainingBricks.push(brickData);
+ continue;
+ }
+ items.push(brickModel);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker);
+ }
+ if (serverData.isDefinedNonNull(shelfToken.presentationHints)) {
+ shelf.presentationHints = shelfToken.presentationHints;
+ }
+ if (serverData.isDefinedNonNull(shelfToken.showSupplementaryText)) {
+ shelf.presentationHints = {
+ ...shelf.presentationHints,
+ showSupplementaryText: shelfToken.showSupplementaryText,
+ };
+ }
+ // We don't need this in our incomplete shelf URL, so we'll preemptively remove it.
+ delete shelfToken.maxItemCount;
+ // Override `featuredContentData` to only have remaining bricks that must be fetched.
+ if (serverData.isDefinedNonNull(serverData.traverse(shelfToken.featuredContentData, "relationships.children.data"))) {
+ shelfToken.featuredContentData.relationships["children"].data = remainingBricks;
+ }
+ // Set metadata
+ shelf.title = shelfToken.title;
+ shelf.subtitle = shelfToken.subtitle;
+ if (isCategoryBrick) {
+ const displayCount = serverData.asNumber(shelfToken.featuredContentData.attributes, "displayCount");
+ shelf.items = items.slice(0, displayCount !== null && displayCount !== void 0 ? displayCount : items.length);
+ }
+ else {
+ shelf.items = items;
+ }
+ // See all
+ const hasSeeAll = serverData.asBooleanOrFalse(shelfToken.featuredContentData.attributes, "hasSeeAll");
+ if (isCategoryBrick && hasSeeAll && !objectGraph.client.isWeb) {
+ // Setup shelf
+ const seeAllShelf = new models.Shelf("categoryBrick");
+ seeAllShelf.items = this.sortCategories(objectGraph, items);
+ seeAllShelf.presentationHints = { isSeeAllContext: true };
+ // Setup Page
+ const seeAllPage = new models.GenericPage([seeAllShelf]);
+ seeAllPage.title = shelfToken.title;
+ // Setup action
+ const seeAllAction = new models.FlowAction("page");
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ seeAllAction.pageData = seeAllPage;
+ // Connect action
+ shelf.seeAllAction = seeAllAction;
+ // Metrics
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, null, {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ });
+ }
+ // If no items should we display placeholders for this shelf?
+ const willHydrateShelfLater = shelf && serverData.isNullOrEmpty(shelf.items) && shelfToken.isFirstRender;
+ if (willHydrateShelfLater && placeholders.placeholdersEnabled(objectGraph)) {
+ placeholders.insertPlaceholdersIntoGenericPageShelf(objectGraph, shelf, shelfToken, shelfToken.featuredContentId);
+ }
+ if (!willHydrateShelfLater &&
+ GroupingBrickShelfController.shouldDisplayChooseYourFavoritesBrick(objectGraph, shelfToken, isCategoryBrick, groupingParseContext)) {
+ const brickModel = GroupingBrickShelfController.createChooseYourFavoritesBrick(objectGraph, shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker);
+ shelf.items.splice(0, 0, brickModel);
+ }
+ shelfToken.presentationHints = shelf.presentationHints;
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ return shelf;
+ }
+ // region Static Helpers
+ // Whether to display Choose Your Favorites brick.
+ // It will return TRUE when:
+ // - This is an Arcade page
+ // - This is a Category brick
+ // - Feature flag is enabled
+ // - The user is subscribed or trial enrolled
+ static shouldDisplayChooseYourFavoritesBrick(objectGraph, shelfToken, isCategoryBrick, groupingParseContext) {
+ return (shelfToken.isArcadePage &&
+ isCategoryBrick &&
+ objectGraph.featureFlags.isEnabled("arcade_choose_your_favorites_brick_Future") &&
+ isSome(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.additionalShelfParameters) &&
+ ((groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.additionalShelfParameters.isSubscribed) === "true" ||
+ (groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.additionalShelfParameters.isTrialEnrolled) === "true"));
+ }
+ static createBrick(objectGraph, brickData, searchCategoryBricks, metricsPageInformation, metricsLocationTracker, shelfToken, groupingParseContext) {
+ const metricsOptions = {
+ targetType: searchCategoryBricks ? "tile" : "brick",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(brickData),
+ };
+ const metadata = groupingShelfControllerCommon.metadataForFCData(objectGraph, brickData, shelfToken, false, null, metricsOptions, groupingParseContext);
+ if (!metadata) {
+ return null;
+ }
+ const brickModel = new models.Brick();
+ // Setup Artwork
+ const artworkOptions = {
+ useCase: 18 /* content.ArtworkUseCase.GroupingBrick */,
+ };
+ if (searchCategoryBricks) {
+ const artworks = content.searchChartOrCategoryArtworkFromData(objectGraph, metadata.content, content.SearchChartOrCategoryBrickUseCase.categoryBreakout, models.GenericSearchPageShelfDisplayStyleDensity.Density1);
+ brickModel.artworks = artworks;
+ }
+ else if (metadata.artwork &&
+ (shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.featuredContentId) !== 261 /* groupingTypes.FeaturedContentID.Sundance_BrickRow */) {
+ let artworkDict = serverData.asDictionary(metadata.artwork, "subscriptionHero");
+ if (!artworkDict) {
+ artworkDict = serverData.asDictionary(metadata.artwork, "originalFlowcaseBrick");
+ }
+ const artwork = groupingShelfControllerCommon.groupingArtworkFromApiArtwork(objectGraph, artworkDict, artworkOptions);
+ brickModel.artworks = [artwork];
+ }
+ else {
+ const artwork = groupingShelfControllerCommon.artworkFromFC(objectGraph, brickData, 1060, 520, artworkOptions);
+ brickModel.artworks = [artwork];
+ }
+ brickModel.accessibilityLabel = metadata.title;
+ // Set supplementary text.
+ brickModel.shortEditorialDescription = metadata.title;
+ // Set action
+ brickModel.clickAction = metadata.action;
+ // Set personalization
+ const brickFeaturedContentId = mediaAttributes.attributeAsNumber(brickData, "editorialElementKind");
+ if (brickFeaturedContentId === 435 /* groupingTypes.FeaturedContentID.AppStore_MSOBrickMarker */) {
+ brickModel.personalizationStyle = "mso";
+ }
+ // Set flow preview actions
+ const contentData = mediaRelationship.relationshipData(objectGraph, brickData, "contents");
+ if (serverData.isDefinedNonNull(contentData)) {
+ const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, contentData, metricsOptions);
+ brickModel.flowPreviewActionsConfiguration = flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, brickData, true, shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.clientIdentifierOverride, brickModel.clickAction, metricsOptions, metricsClickOptions);
+ }
+ // Configure impressions
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, brickData, metadata.title, metricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, brickModel, impressionOptions);
+ // Safe area
+ brickModel.artworkSafeArea = models.ChartOrCategorySafeArea.defaultTileArtworkSafeArea;
+ brickModel.textSafeArea = models.ChartOrCategorySafeArea.defaultTileTextSafeArea;
+ if (!brickModel.isValid()) {
+ return null;
+ }
+ return brickModel;
+ }
+ /// Creates and returns Choose Your Favorites brick.
+ static createChooseYourFavoritesBrick(objectGraph, metricsPageInformation, metricsLocationTracker) {
+ const brickModel = new models.Brick();
+ // Artwork
+ const artwork = new models.Artwork("", 1060, 520, [], color.fromHex("efac78"));
+ brickModel.artworks = [artwork];
+ // Labels
+ const title = objectGraph.loc.string("ARCADE_CHOOSE_YOUR_FAVORITES_BRICK_TITLE");
+ brickModel.accessibilityLabel = title;
+ brickModel.shortEditorialDescription = title;
+ // Action
+ const flowAction = new models.FlowAction("arcadeDownloadPackCategories");
+ flowAction.presentationContext = "presentModalFormSheet";
+ flowAction.pageData = "unknown";
+ brickModel.clickAction = flowAction;
+ // Impressions
+ const metricsOptions = {
+ targetType: "brick",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ recoMetricsData: null,
+ };
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForArcadeChooseYourFavoritesBrick(metricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, brickModel, impressionOptions);
+ // Safe area
+ brickModel.artworkSafeArea = models.ChartOrCategorySafeArea.defaultTileArtworkSafeArea;
+ brickModel.textSafeArea = models.ChartOrCategorySafeArea.defaultTileTextSafeArea;
+ return brickModel;
+ }
+ // endregion
+ // region Metrics
+ /**
+ * Return the shelf metrics options to use for this specific shelf. Using the base options from the grouping
+ * page controller
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the
+ * grouping page controller
+ */
+ shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) {
+ const shelfMetricsOptions = { ...baseMetricsOptions };
+ const displayStyle = serverData.asString(shelfToken.featuredContentData.attributes, "displayStyle");
+ // If this is a Category Bricks shelf, configure metrics title accordingly.
+ if (displayStyle === "small") {
+ shelfMetricsOptions.title = "Browse Categories";
+ }
+ return shelfMetricsOptions;
+ }
+ // endregion
+ // region Sort
+ sortCategories(objectGraph, categories) {
+ return categories.sort((category1, category2) => {
+ try {
+ return category1.shortEditorialDescription.localeCompare(category2.shortEditorialDescription, objectGraph.loc.safeIdentifier, {
+ usage: "sort",
+ });
+ }
+ catch (e) {
+ return 0;
+ }
+ });
+ }
+}
+//# sourceMappingURL=grouping-brick-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-category-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-category-shelf-controller.js
new file mode 100644
index 0000000..38d8324
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-category-shelf-controller.js
@@ -0,0 +1,207 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as content from "../../content/content";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+export class GroupingCategoryShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingCategoryShelfController");
+ this.supportedFeaturedContentIds = new Set([425 /* groupingTypes.FeaturedContentID.AppStore_GenreStack */]);
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ const childListBoxes = mediaRelationship.relationshipData(objectGraph, mediaApiData, "children");
+ if (childListBoxes) {
+ return { shelfContents: mediaRelationship.relationshipCollection(childListBoxes, "children") };
+ }
+ else {
+ return { shelfContents: [] };
+ }
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters).then((shelfData) => {
+ const childListBoxes = mediaRelationship.relationshipData(objectGraph, shelfToken.featuredContentData, "children");
+ if (childListBoxes) {
+ const hydratedListBoxSections = groupingShelfControllerCommon.mergeContentDataIntoEditorialData(shelfData.shelfContents, mediaRelationship.relationshipCollection(childListBoxes, "children"));
+ return { shelfContents: hydratedListBoxSections };
+ }
+ else {
+ return { shelfContents: [] };
+ }
+ });
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ var _a;
+ if (serverData.isNullOrEmpty(shelfData.shelfContents)) {
+ return null;
+ }
+ const items = [];
+ let itemsHaveRectangularArtwork = false;
+ for (const category of shelfData.shelfContents) {
+ const grouping = mediaRelationship.relationshipData(objectGraph, category, "grouping");
+ if (serverData.isNull(grouping)) {
+ continue;
+ }
+ const adamId = groupingShelfControllerCommon.contentIdFromContentItem(objectGraph, grouping);
+ if (serverData.isNull(category.attributes) ||
+ serverData.isNull(grouping.attributes) ||
+ groupingShelfControllerCommon.shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(grouping);
+ continue;
+ }
+ const metricsBase = {
+ targetType: "listItem",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(category),
+ };
+ const action = lockups.actionFromData(objectGraph, grouping, { ...metricsBase, id: adamId }, shelfToken.clientIdentifierOverride);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, action, {
+ ...metricsBase,
+ kind: "link",
+ softwareType: serverData.asBooleanOrFalse(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage) ? "Arcade" : null,
+ title: action.title,
+ id: adamId,
+ });
+ const artwork = mediaAttributes.attributeAsDictionary(grouping, "artwork");
+ if (artwork.width > artwork.height) {
+ itemsHaveRectangularArtwork = true;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(artwork)) {
+ action.artwork = content.artworkFromApiArtwork(objectGraph, artwork, {
+ allowingTransparency: true,
+ useCase: 20 /* content.ArtworkUseCase.CategoryIcon */,
+ });
+ }
+ items.push(action);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker);
+ }
+ // The top level shelf on mac should always be sorted
+ // <rdar://problem/40954563> LOC: Global: MAS: Order of categories appears in English alphabetical order instead of in each language's alphabetical order.
+ if (objectGraph.client.isMac) {
+ this.sortCategories(objectGraph, items);
+ }
+ const shelf = this.shelfForCategoryActions(objectGraph, items, shelfToken);
+ shelf.title = shelfToken.title;
+ shelf.subtitle = shelfToken.subtitle;
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ if (itemsHaveRectangularArtwork) {
+ const existingPresentationHints = (_a = shelf.presentationHints) !== null && _a !== void 0 ? _a : {};
+ shelf.presentationHints = {
+ ...existingPresentationHints,
+ itemsHaveRectangularArtwork: true,
+ };
+ }
+ return shelf;
+ }
+ shelfForCategoryActions(objectGraph, categories, shelfToken) {
+ const shelf = new models.Shelf("action");
+ // Limit for number of items (`null` when there's no limit).
+ let itemLimit;
+ // Configure shelf orientation and limit
+ switch (objectGraph.client.deviceType) {
+ case "tv":
+ shelf.isHorizontal = true;
+ itemLimit = 8;
+ break;
+ case "mac":
+ shelf.isHorizontal = false;
+ itemLimit = null;
+ break;
+ case "web":
+ shelf.isHorizontal = true;
+ shelf.rowsPerColumn = 3;
+ itemLimit = 24;
+ break;
+ default:
+ shelf.isHorizontal = false;
+ itemLimit = 6;
+ break;
+ }
+ // Apply limit (if any)
+ if (itemLimit !== null && categories.length > itemLimit) {
+ shelf.items = categories.slice(0, itemLimit);
+ const allCategoriesAction = new models.FlowAction("page");
+ allCategoriesAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, allCategoriesAction, null, {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ });
+ shelf.seeAllAction = allCategoriesAction;
+ const allCategoriesShelf = new models.Shelf("action");
+ allCategoriesShelf.isHorizontal = false;
+ this.sortCategories(objectGraph, categories);
+ allCategoriesShelf.items = categories;
+ const allCategoriesPage = new models.GenericPage([allCategoriesShelf]);
+ allCategoriesPage.title = objectGraph.loc.string("PAGE_TITLE_CATEGORIES");
+ allCategoriesAction.pageData = allCategoriesPage;
+ }
+ else {
+ shelf.items = categories;
+ }
+ return shelf;
+ }
+ // region Helpers
+ /**
+ * Sort the category actions alphabetically
+ * @param objectGraph
+ * @param categories The cateogries we're going to be displaying
+ */
+ sortCategories(objectGraph, categories) {
+ categories.sort((category1, category2) => {
+ try {
+ return category1.title.localeCompare(category2.title, objectGraph.loc.safeIdentifier, {
+ usage: "sort",
+ });
+ }
+ catch (e) {
+ return 0;
+ }
+ });
+ }
+}
+//# sourceMappingURL=grouping-category-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-card-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-card-shelf-controller.js
new file mode 100644
index 0000000..12dc94f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-card-shelf-controller.js
@@ -0,0 +1,229 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as color from "../../../foundation/util/color-util";
+import * as flowPreview from "../../content/flow-preview";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as metricsHelpersUtil from "../../metrics/helpers/util";
+import * as onDevicePersonalization from "../../personalization/on-device-personalization";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+export class GroupingEditorialCardShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingEditorialCardShelfController");
+ this.supportedFeaturedContentIds = new Set([
+ 415 /* groupingTypes.FeaturedContentID.AppStore_HeroList */,
+ 416 /* groupingTypes.FeaturedContentID.AppStore_Hero */,
+ 501 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedHeroMarker */,
+ 417 /* groupingTypes.FeaturedContentID.AppStore_CustomHero */,
+ 258 /* groupingTypes.FeaturedContentID.Sundance_Flowcase */,
+ ]);
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "children") };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters);
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ const shelf = new models.Shelf("editorialCard");
+ shelf.isHorizontal = true;
+ const personalizationDataContainer = this.personalizationDataContainerForEditorialCardItemsDataArray(objectGraph, shelfData.shelfContents);
+ const items = [];
+ for (const data of shelfData.shelfContents) {
+ const card = GroupingEditorialCardShelfController.makeEditorialCard(objectGraph, data, personalizationDataContainer, groupingParseContext, shelfToken);
+ if (serverData.isNullOrEmpty(card) || !card.isValid()) {
+ continue;
+ }
+ items.push(card);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker);
+ }
+ // We don't need this in our incomplete shelf URL, so we'll preemptively remove it.
+ delete shelfToken.maxItemCount;
+ if (objectGraph.client.isVision) {
+ shelf.presentationHints = { ...shelf.presentationHints, showSupplementaryText: true };
+ }
+ shelf.items = items;
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ return shelf;
+ }
+ static makeEditorialCard(objectGraph, itemData, personalizationDataContainer, groupingParseContext, shelfToken) {
+ var _a, _b, _c;
+ const metricsOptions = {
+ targetType: "hero",
+ pageInformation: shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.metricsPageInformation,
+ locationTracker: shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ id: itemData.id,
+ idType: "editorial_id",
+ };
+ const featuredContentId = mediaAttributes.attributeAsNumber(itemData, "editorialElementKind");
+ const shouldPersonalizeContent = featuredContentId === 501 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedHeroMarker */;
+ const metadata = groupingShelfControllerCommon.metadataForFCData(objectGraph, itemData, shelfToken, shouldPersonalizeContent, personalizationDataContainer, metricsOptions, groupingParseContext, () => {
+ shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.remainingItems.push(itemData);
+ });
+ if (!metadata) {
+ return null;
+ }
+ const hasContentId = ((_b = (_a = metadata.content) === null || _a === void 0 ? void 0 : _a.id) === null || _b === void 0 ? void 0 : _b.length) > 0;
+ if (hasContentId) {
+ metricsOptions.id = metadata.content.id;
+ metricsOptions.idType = "its_id";
+ metricsOptions.adamId = metadata.content.id;
+ }
+ const card = new models.EditorialCard();
+ // Set caption
+ let caption = mediaAttributes.attributeAsString(itemData, "designBadge");
+ if (!caption) {
+ caption = metadata.caption;
+ }
+ card.caption = caption;
+ // Set title
+ let title = mediaAttributes.attributeAsString(itemData, "title");
+ if (!title) {
+ title = metadata.title;
+ }
+ card.title = title;
+ // Set subtitle
+ let subtitle = groupingShelfControllerCommon.unescapeHtmlString(mediaAttributes.attributeAsString(itemData, "designTag"));
+ if (!subtitle) {
+ subtitle = metadata.subtitle;
+ }
+ card.subtitle = subtitle;
+ // Setup Artwork
+ const artworkOptions = {
+ useCase: 19 /* content.ArtworkUseCase.GroupingHero */,
+ withJoeColorPlaceholder: true,
+ };
+ if (metadata.artwork && (shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.featuredContentId) !== 258 /* groupingTypes.FeaturedContentID.Sundance_Flowcase */) {
+ let artworkDict = serverData.asDictionary(metadata.artwork, "subscriptionHero");
+ if (serverData.isNull(artworkDict) && serverData.isDefinedNonNull(metadata.appEvent)) {
+ artworkDict = serverData.asDictionary(metadata.artwork, "eventCard");
+ }
+ card.artwork = groupingShelfControllerCommon.groupingArtworkFromApiArtwork(objectGraph, artworkDict, artworkOptions);
+ }
+ else {
+ card.artwork = groupingShelfControllerCommon.artworkFromFC(objectGraph, itemData, 416, 204, artworkOptions);
+ }
+ // Set action
+ card.clickAction = metadata.action;
+ // App event formatted dates
+ if (serverData.isDefinedNonNull(metadata.appEvent)) {
+ card.appEventFormattedDates = metadata.appEvent.formattedDates;
+ }
+ // Lockup
+ card.lockup = metadata.lockup;
+ // Overlay style
+ if (serverData.isDefinedNonNull(card.artwork) && serverData.isDefinedNonNull(card.artwork.backgroundColor)) {
+ const isArtworkDark = color.isDarkColor(card.artwork.backgroundColor);
+ card.mediaOverlayStyle = isArtworkDark ? "dark" : "light";
+ if (serverData.isDefinedNonNull(card.lockup) &&
+ serverData.isDefinedNonNull(card.lockup.offerDisplayProperties) &&
+ objectGraph.host.isiOS) {
+ const offerEnvironment = isArtworkDark ? "dark" : "light";
+ card.lockup.offerDisplayProperties =
+ card.lockup.offerDisplayProperties.newOfferDisplayPropertiesChangingAppearance(false, "transparent", offerEnvironment);
+ }
+ }
+ // Set adamId
+ card.adamId = serverData.asString(metadata.content, "id");
+ // Set flow preview actions
+ const contentData = mediaRelationship.relationshipData(objectGraph, itemData, "contents");
+ if (serverData.isDefinedNonNull(contentData)) {
+ const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, contentData, metricsOptions);
+ metricsClickOptions.targetType = metricsOptions.targetType;
+ card.flowPreviewActionsConfiguration = flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, itemData, false, shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.clientIdentifierOverride, card.clickAction, metricsOptions, metricsClickOptions);
+ }
+ // Configure impressions
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, (_c = metadata.content) !== null && _c !== void 0 ? _c : itemData, metadata.title, metricsOptions);
+ if (serverData.isDefinedNonNull(metadata.onDevicePersonalizationDataProcessingType)) {
+ const recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(impressionOptions.recoMetricsData, metadata.onDevicePersonalizationDataProcessingType, null);
+ impressionOptions.recoMetricsData = recoMetricsData;
+ }
+ if (serverData.isDefinedNonNull(metadata.appEvent)) {
+ impressionOptions.inAppEventId = metadata.appEvent.appEventId;
+ if (serverData.isDefinedNonNull(metadata.appEvent.lockup)) {
+ impressionOptions.relatedSubjectIds = [metadata.appEvent.lockup.adamId];
+ }
+ }
+ if (serverData.isDefinedNonNull(shelfToken)) {
+ metricsHelpersImpressions.addImpressionFields(objectGraph, card, impressionOptions);
+ }
+ return card;
+ }
+ // region Helpers
+ /**
+ * Iterates through all the editorial cards data, and creates a set of personalization data that is targetted only to these cards.
+ *
+ * @param objectGraph
+ * @param dataArray The input array of editorial card data items
+ * @returns Any relevant OnDevicePersonalizaionData
+ */
+ personalizationDataContainerForEditorialCardItemsDataArray(objectGraph, dataArray) {
+ var _a, _b;
+ if (!onDevicePersonalization.isPersonalizationAvailable(objectGraph)) {
+ return null;
+ }
+ // First iterate through and extract any relevant App IDs from meta.personalizationData for each card's contents.
+ const appIds = new Set();
+ for (const data of dataArray) {
+ const featuredContentId = mediaAttributes.attributeAsNumber(data, "editorialElementKind");
+ const shouldPersonalizeContent = featuredContentId === 501 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedHeroMarker */;
+ const isLinkNode = ((_a = serverData.asString(data, "url")) === null || _a === void 0 ? void 0 : _a.length) > 0;
+ const containsLinkNode = ((_b = mediaAttributes.attributeAsString(data, "link.url")) === null || _b === void 0 ? void 0 : _b.length) > 0;
+ const isContentNode = mediaRelationship.hasRelationship(data, "contents", false);
+ if (shouldPersonalizeContent && !isLinkNode && !containsLinkNode && isContentNode) {
+ const contentDataItems = mediaRelationship.relationshipCollection(data, "contents");
+ for (const contentData of contentDataItems) {
+ const appId = serverData.asString(contentData, "meta.personalizationData.appId");
+ if ((appId === null || appId === void 0 ? void 0 : appId.length) > 0) {
+ appIds.add(appId);
+ }
+ }
+ }
+ }
+ return onDevicePersonalization.personalizationDataContainerForAppIds(objectGraph, appIds);
+ }
+}
+//# sourceMappingURL=grouping-editorial-card-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-story-card-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-story-card-shelf-controller.js
new file mode 100644
index 0000000..73a0725
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-editorial-story-card-shelf-controller.js
@@ -0,0 +1,143 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { ShelfParameters } from "../../../foundation/network/url-constants";
+import * as color from "../../../foundation/util/color-util";
+import * as content from "../../content/content";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { createTodayBaseCard } from "../../today/cards/today-base-card-builder";
+import { TodayParseContext } from "../../today/today-types";
+import { GroupingShelfController, routesForFeaturedContentIds } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+export class GroupingEditorialStoryCardShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingEditorialStoryCardShelfController");
+ this.supportedFeaturedContentIds = new Set([475 /* groupingTypes.FeaturedContentID.AppStore_HorizontalCardSwoosh */]);
+ }
+ // endregion
+ // region Shelf Builder
+ shelfRoute(objectGraph) {
+ return routesForFeaturedContentIds(this.supportedFeaturedContentIds, [
+ `${ShelfParameters.contentType}=editorialStoryCard`,
+ ]);
+ }
+ // endregion
+ // region GroupingShelfController
+ _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) {
+ if (!super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId)) {
+ return false;
+ }
+ const displayStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle");
+ const contentType = groupingShelfControllerCommon.contentTypeForHorizontalCardDisplayStyle(objectGraph, displayStyle);
+ return contentType === "editorialStoryCard";
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "children") };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters);
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ const shelfToken = { ...baseShelfToken };
+ const displayStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle");
+ shelfToken.shelfStyle = groupingShelfControllerCommon.contentTypeForHorizontalCardDisplayStyle(objectGraph, displayStyle);
+ return shelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ const items = [];
+ for (const card of shelfData.shelfContents) {
+ if (!mediaAttributes.hasAttributes(card) || groupingShelfControllerCommon.shouldDefer(shelfToken)) {
+ shelfToken.remainingItems.push(card);
+ shelfToken.isDeferring = true;
+ continue;
+ }
+ const cardModel = GroupingEditorialStoryCardShelfController.makeStoryCard(objectGraph, card, shelfToken);
+ if (serverData.isNullOrEmpty(cardModel)) {
+ continue;
+ }
+ items.push(cardModel);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker);
+ }
+ const shelf = new models.Shelf(shelfToken.shelfStyle);
+ shelf.title = shelfToken.title;
+ shelf.items = items;
+ shelf.isHorizontal = true;
+ shelf.background = {
+ type: "interactive",
+ };
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ shelf.isHorizontal = true;
+ return shelf;
+ }
+ static makeStoryCard(objectGraph, itemData, shelfToken) {
+ // Prefer `subscriptionHero` if it's available for grouping pages, and fallback to `mediaCard` if not.
+ let artworkData = mediaAttributes.attributeAsDictionary(itemData, "editorialArtwork.subscriptionHero");
+ if (serverData.isNullOrEmpty(artworkData)) {
+ artworkData = mediaAttributes.attributeAsDictionary(itemData, "editorialArtwork.mediaCard");
+ }
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ cropCode: "fn",
+ withJoeColorPlaceholder: true,
+ useCase: 16 /* content.ArtworkUseCase.TodaySmallStoryCard */,
+ });
+ if (serverData.isNull(artwork)) {
+ return null;
+ }
+ const title = mediaAttributes.attributeAsString(itemData, "editorialNotes.name");
+ const heading = mediaAttributes.attributeAsString(itemData, "label");
+ const description = mediaAttributes.attributeAsString(itemData, "editorialNotes.short");
+ const cardModel = new models.EditorialStoryCard(title, artwork, null, heading, {
+ type: "text",
+ title: heading,
+ }, description);
+ const basicCard = createTodayBaseCard(objectGraph, itemData, null, new TodayParseContext(shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.metricsPageInformation, shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.metricsLocationTracker));
+ if (serverData.isDefinedNonNull(basicCard)) {
+ cardModel.clickAction = basicCard.clickAction;
+ }
+ const backgroundStyle = color.isDarkColor(artwork.backgroundColor)
+ ? "dark"
+ : "light";
+ cardModel.shelfBackground = {
+ type: "artwork",
+ artwork: artwork,
+ style: backgroundStyle,
+ };
+ return cardModel;
+ }
+}
+//# sourceMappingURL=grouping-editorial-story-card-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-activity-feed-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-activity-feed-shelf-controller.js
new file mode 100644
index 0000000..4fa27fe
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-activity-feed-shelf-controller.js
@@ -0,0 +1,223 @@
+import * as models from "../../../api/models";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as urls from "../../../foundation/network/urls";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+import { ActionMetrics } from "../../../api/models";
+import { makeGameCenterHeader } from "../../arcade/arcade-common";
+export class GroupingGameCenterActivityFeedController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingGameCenterActivityFeedController");
+ this.batchGroupKey = "gameCenter";
+ this.supportedFeaturedContentIds = new Set([
+ 548 /* groupingTypes.FeaturedContentID.AppStore_GameCenterActivityFeedMarker */,
+ ]);
+ }
+ // endregion
+ // region Shelf Builder
+ shelfRoute(objectGraph) {
+ return [
+ ...super.shelfRoute(objectGraph),
+ {
+ protocol: Protocol.internal,
+ path: `/${Path.grouping}/${Path.shelf}/{token}`,
+ query: [Parameters.isGameCenterActivityFeedShelf],
+ },
+ ];
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return {
+ shelfContents: [],
+ activities: [],
+ };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ const filter = this.gameCategoryFilter(shelfToken.gamesFilter);
+ const activityLimit = 20;
+ return await objectGraph.gameCenter.fetchActivityFeedCards(filter, activityLimit).then((activities) => {
+ return {
+ shelfContents: [],
+ activities: activities,
+ };
+ });
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ incompleteShelfFetchStrategy(objectGraph) {
+ return models.IncompleteShelfFetchStrategy.OnPageLoad;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ if (shelfToken.isFirstRender) {
+ return this.pendingActivityFeedShelfForGrouping(objectGraph, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage);
+ }
+ else {
+ return this.activityFeedShelfForGrouping(objectGraph, shelfData, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage);
+ }
+ }
+ pendingActivityFeedShelfForGrouping(objectGraph, shelfToken, isArcadePage) {
+ if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") {
+ return null;
+ }
+ const shelf = this.activityFeedShelfForGrouping(objectGraph, {
+ shelfContents: [],
+ activities: [],
+ }, shelfToken, isArcadePage);
+ const groupingShelfUrl = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken));
+ shelf.url = groupingShelfUrl.param(Parameters.isGameCenterActivityFeedShelf, "true").build();
+ shelf.isHidden = shelf.items.length === 0;
+ shelf.batchGroup = this.batchGroupKey;
+ return shelf;
+ }
+ activityFeedShelfForGrouping(objectGraph, shelfData, shelfToken, isArcadePage) {
+ if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") {
+ return null;
+ }
+ const shelf = this.activityFeedShelf(objectGraph, shelfData.activities, shelfToken, isArcadePage);
+ const title = objectGraph.loc.string("Arcade.ActivityFeed.RecentActivity");
+ shelf.header = makeGameCenterHeader(objectGraph, title);
+ // Connect the shelf's seeAllAction
+ groupingShelfControllerCommon.replaceShelfSeeAllAction(objectGraph, shelf, shelf.seeAllAction);
+ shelf.batchGroup = this.batchGroupKey;
+ // Hide when empty.
+ shelf.isHidden = shelf.items.length === 0;
+ return shelf;
+ }
+ // region Helpers
+ activityFeedShelf(objectGraph, activities, token, isArcadePage = false) {
+ const shelf = new models.Shelf("gameCenterActivityFeedCard");
+ shelf.isHorizontal = true;
+ shelf.mergeWhenFetched = true;
+ shelf.batchGroup = this.batchGroupKey;
+ shelf.items = activities;
+ shelf.isHidden = shelf.items.length === 0;
+ activities.forEach((item, index) => {
+ const metricsImpressionOptions = {
+ id: "friendActivity",
+ idType: "static",
+ targetType: "chiclet",
+ kind: null,
+ softwareType: isArcadePage ? "Arcade" : null,
+ title: "",
+ pageInformation: token.metricsPageInformation,
+ locationTracker: token.metricsLocationTracker,
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, item, metricsImpressionOptions);
+ // Create action metrics for click events.
+ const profileActionMetrics = new ActionMetrics();
+ const profileAvatarActionMetrics = new ActionMetrics();
+ const leaderboardActionMetrics = new ActionMetrics();
+ const achievementActionMetrics = new ActionMetrics();
+ const appActionMetrics = new ActionMetrics();
+ // Create targetId and metrics array.
+ const targetIdAndMetricsArray = [
+ {
+ targetId: "playerName",
+ metrics: profileActionMetrics,
+ },
+ {
+ targetId: "profileImage",
+ metrics: profileAvatarActionMetrics,
+ },
+ {
+ targetId: "leaderboardAchievement",
+ metrics: leaderboardActionMetrics,
+ },
+ {
+ targetId: "achievement",
+ metrics: achievementActionMetrics,
+ },
+ {
+ targetId: item.adamID || "gameIcon",
+ metrics: appActionMetrics,
+ },
+ ];
+ // Loop through `targetIdAndMetricsArray` and call `addClickEventToActivityFeedMetrics` for each metrics.
+ targetIdAndMetricsArray.forEach((targetIdAndMetricsDictionary) => metricsHelpersClicks.addClickEventToActivityFeedMetrics(objectGraph, targetIdAndMetricsDictionary.metrics, token.title, targetIdAndMetricsDictionary.targetId, {
+ pageInformation: token.metricsPageInformation,
+ locationTracker: token.metricsLocationTracker,
+ }));
+ // Assign action metrics to `item`.
+ item.profileActionMetrics = profileActionMetrics;
+ item.profileAvatarActionMetrics = profileAvatarActionMetrics;
+ item.leaderboardActionMetrics = leaderboardActionMetrics;
+ item.achievementActionMetrics = achievementActionMetrics;
+ item.appActionMetrics = appActionMetrics;
+ // Proceed to next position.
+ metricsHelpersLocation.nextPosition(token.metricsLocationTracker);
+ });
+ // Setup see all action
+ let seeAllAction;
+ if (!objectGraph.featureFlags.isGSEUIEnabled("de7bbd8e")) {
+ seeAllAction = new models.GameCenterDashboardAction();
+ seeAllAction.title = objectGraph.loc.string("Arcade.ActivityFeed.AllActivity", objectGraph.loc.string("ACTION_SEE_ALL"));
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, null, {
+ pageInformation: token.metricsPageInformation,
+ locationTracker: token.metricsLocationTracker,
+ });
+ }
+ shelf.seeAllAction = seeAllAction;
+ return shelf;
+ }
+ // endregion
+ /**
+ * Convert GameCategoryFilter to GamesFilter. Ideally these would be the same, but seed 1 has already left the station.
+ * @param gamesFilter
+ */
+ gameCategoryFilter(gamesFilter) {
+ if (gamesFilter === "nonArcade") {
+ return "nonarcade";
+ }
+ return gamesFilter;
+ }
+ // endregion
+ // region Metrics
+ shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) {
+ return {
+ ...baseMetricsOptions,
+ title: "Friend Activity",
+ badges: {
+ gameCenter: true,
+ },
+ idType: "shelf_id",
+ };
+ }
+}
+//# sourceMappingURL=grouping-game-center-activity-feed-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-continue-playing-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-continue-playing-shelf-controller.js
new file mode 100644
index 0000000..6a85582
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-continue-playing-shelf-controller.js
@@ -0,0 +1,332 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaDataFetching from "../../../foundation/media/data-fetching";
+import * as mediaNetwork from "../../../foundation/media/network";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { ResponseMetadata } from "../../../foundation/network/network";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as urls from "../../../foundation/network/urls";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as flowPreview from "../../content/flow-preview";
+import * as filtering from "../../filtering";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+import { makeGameCenterHeader } from "../../arcade/arcade-common";
+import { injectCrossfireFlowForGameCenter } from "./grouping-game-center-popular-with-your-friends-shelf-controller";
+export class GroupingGameCenterContinuePlayingShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingGameCenterContinuePlayingShelfController");
+ this.batchGroupKey = "gameCenterContinuePlaying";
+ this.supportedFeaturedContentIds = new Set([500 /* groupingTypes.FeaturedContentID.AppStore_ContinuePlayingMarker */]);
+ }
+ // endregion
+ // region GroupingShelfController
+ _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) {
+ return (super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) &&
+ this.supportsVideoCardShelf(objectGraph, objectGraph.host.platform));
+ }
+ // endregion
+ // region Shelf Builder
+ shelfRoute(objectGraph) {
+ return [
+ ...super.shelfRoute(objectGraph),
+ {
+ protocol: Protocol.internal,
+ path: `/${Path.grouping}/${Path.shelf}/{token}`,
+ query: [Parameters.isGameCenterContinuePlayingShelf],
+ },
+ ];
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ const startTime = Date.now();
+ const maxNumberOfGames = this.maximumNumberOfRecentGamesToRequest();
+ const recentlyPlayedGamesPromise = objectGraph.gameCenter.fetchRecentlyPlayedGamesWithinSeconds(this.gameCategoryFilter(shelfToken.gamesFilter), maxNumberOfGames, objectGraph.bag.recentlyPlayedGamesWindowInSeconds);
+ return await recentlyPlayedGamesPromise.then(async (recentlyPlayedIds) => {
+ const endTime = Date.now();
+ objectGraph.console.log("grouping-gamecenter-builder: requestForContinuePlaying NATIVE took " +
+ (endTime - startTime).toString(10) +
+ " milliseconds.");
+ let shelfDataPromise;
+ if (recentlyPlayedIds.length === 0) {
+ // One of the few exceptions where TS minifiers cannot derive data container type
+ // without some help, in this case using explicit type for the value passed to promise.
+ const emptyShelfData = { shelfContents: [] };
+ return await Promise.resolve(emptyShelfData);
+ }
+ else {
+ const request = new mediaDataFetching.Request(objectGraph)
+ .withIdsOfType(recentlyPlayedIds.slice(0, this.maximumNumberOfRecentGamesToShow()), "apps")
+ .includingAgeRestrictions();
+ groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, request);
+ shelfDataPromise = mediaNetwork
+ .fetchData(objectGraph, request, {})
+ .then((dataContainer) => {
+ const shelfData = {
+ shelfContents: dataContainer.data,
+ responseTimingValues: dataContainer[ResponseMetadata.timingValues],
+ };
+ return shelfData;
+ });
+ }
+ return await shelfDataPromise;
+ });
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ incompleteShelfFetchStrategy(objectGraph) {
+ return models.IncompleteShelfFetchStrategy.OnPageLoad;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ if (shelfToken.isFirstRender) {
+ return this.pendingContinuePlayingForGrouping(objectGraph, shelfToken);
+ }
+ else {
+ return this.continuePlayingShelfForGrouping(objectGraph, shelfData.shelfContents, shelfToken);
+ }
+ }
+ pendingContinuePlayingForGrouping(objectGraph, shelfToken) {
+ const shelf = this.continuePlayingShelfForGrouping(objectGraph, [], shelfToken);
+ if (!shelf) {
+ return null;
+ }
+ const groupingShelfUrl = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken));
+ shelf.url = groupingShelfUrl.param(Parameters.isGameCenterContinuePlayingShelf, "true").build();
+ shelf.batchGroup = this.batchGroupKey;
+ return shelf;
+ }
+ continuePlayingShelfForGrouping(objectGraph, shelfContents, shelfToken) {
+ return validation.context("continuePlayingShelfForGrouping", () => {
+ const shelf = this.videoCardContinuePlayingShelf(objectGraph, shelfContents, shelfToken);
+ shelf.mergeWhenFetched = false; // Always replace
+ shelf.batchGroup = this.batchGroupKey;
+ shelf.isHidden = shelf.items.length === 0;
+ shelf.header = makeGameCenterHeader(objectGraph, objectGraph.loc.string("GameCenter.ContinuePlayingShelf.Title"), shelfToken.subtitle);
+ return shelf;
+ });
+ }
+ // endregion
+ // region Video Card Shelf
+ supportsVideoCardShelf(objectGraph, platform) {
+ switch (platform) {
+ case "iOS":
+ case "tvOS":
+ case "macOS":
+ return true;
+ default:
+ return false;
+ }
+ }
+ videoCardContinuePlayingShelf(objectGraph, dataArray, shelfToken) {
+ return validation.context("videoCardContinuePlayingShelf", () => {
+ const shelf = new models.Shelf("videoCard");
+ shelf.isHorizontal = true;
+ shelf.batchGroup = this.batchGroupKey;
+ const items = [];
+ for (const data of dataArray) {
+ // Filter out unwanted content
+ if (filtering.shouldFilter(objectGraph, data)) {
+ continue;
+ }
+ const item = this.editorialSplashVideoCardForContinuePlaying(objectGraph, data, shelfToken);
+ if (item) {
+ items.push(item);
+ }
+ }
+ shelf.items = items;
+ return shelf;
+ });
+ }
+ /**
+ * Create a `VideoCard` configured for CP Shelf.
+ * Specifically, it uses:
+ * - TV Top Shelf Static Image Still
+ * - Arcade Product Page Uber Video
+ *
+ * @param objectGraph
+ * @param data
+ * @param shelfToken
+ */
+ editorialSplashVideoCardForContinuePlaying(objectGraph, data, shelfToken) {
+ return validation.context("editorialSplashVideoCardForContinuePlaying", () => {
+ var _a;
+ const lockupMetricsOptions = {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ targetType: "lockup",
+ };
+ const shouldHideArcadeHeader = objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab") &&
+ serverData.asBooleanOrFalse(shelfToken.isArcadePage);
+ const isArcadeLockup = content.isArcadeSupported(objectGraph, data);
+ const lockupOptions = {
+ metricsOptions: lockupMetricsOptions,
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ offerEnvironment: "dark",
+ offerStyle: "white",
+ canDisplayArcadeOfferButton: true,
+ shouldHideArcadeHeader: shouldHideArcadeHeader,
+ isSubtitleHidden: isArcadeLockup && !shouldHideArcadeHeader,
+ };
+ const video = this.editorialSplashVideoWithTopShelfStill(objectGraph, data);
+ if (!video || !video.preview) {
+ return null;
+ }
+ const lockup = lockups.lockupFromData(objectGraph, data, lockupOptions);
+ if (!lockup) {
+ return null;
+ }
+ lockup.clickAction = injectCrossfireFlowForGameCenter(objectGraph, lockup.clickAction);
+ const clickAction = this.clickActionForVideoCard(objectGraph, data, objectGraph.host.platform, lockupMetricsOptions, shelfToken.clientIdentifierOverride);
+ if (!clickAction) {
+ return null;
+ }
+ const videoCard = new models.VideoCard();
+ videoCard.video = video;
+ videoCard.lockup = lockup;
+ videoCard.overlayStyle = "dark";
+ videoCard.clickAction = clickAction;
+ // Set flow preview actions
+ const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, data, lockupMetricsOptions);
+ videoCard.flowPreviewActionsConfiguration = flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, data, true, shelfToken.clientIdentifierOverride, videoCard.clickAction, lockupMetricsOptions, metricsClickOptions);
+ // Configure impressions borrowing lockup values for now.
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, lockup.title, lockupMetricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, videoCard, impressionOptions);
+ (_a = videoCard.impressionMetrics) === null || _a === void 0 ? true : delete _a.fields["impressionIndex"];
+ return videoCard;
+ });
+ }
+ /**
+ * Return a video to use for continue playing with:
+ * - Product Uber
+ * - TV Top Shelf Static Still
+ */
+ editorialSplashVideoWithTopShelfStill(objectGraph, data) {
+ return validation.context("editorialSplashVideoWithTopShelfStill", () => {
+ // TV Top Shelf Still (If Any):
+ let previewOverride = null;
+ const artworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialArtwork.topShelf");
+ if (serverData.isDefinedNonNull(artworkData)) {
+ previewOverride = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ withJoeColorPlaceholder: true,
+ useCase: 23 /* content.ArtworkUseCase.VideoCardStill */,
+ cropCode: "sr",
+ });
+ }
+ return content.editorialSplashVideoFromData(objectGraph, data, previewOverride);
+ });
+ }
+ // endregion
+ // region Helpers
+ /**
+ * Returns the click action to use for given platform for video card
+ */
+ clickActionForVideoCard(objectGraph, data, platform, metricsOptions, clientIdentifierOverride) {
+ const lockupClickMetricsOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, data, metricsOptions);
+ let productPageAction = lockups.actionFromData(objectGraph, data, lockupClickMetricsOptions, clientIdentifierOverride);
+ productPageAction = injectCrossfireFlowForGameCenter(objectGraph, productPageAction);
+ // Wrap in Open for tv only.
+ if (platform === "tvOS") {
+ const openAppAction = new models.OpenAppAction(data.id, "app");
+ const openAppClickOptions = {
+ actionType: "open",
+ id: data.id,
+ contextualAdamId: data.id,
+ anonymizationOptions: metricsOptions.anonymizationOptions,
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, openAppAction, openAppClickOptions);
+ const stateAction = new models.OfferStateAction(data.id, productPageAction);
+ stateAction.openAction = openAppAction;
+ // If the app is downloading and the user clicks this item, take them to the product page instead of cancelling the download.
+ stateAction.cancelAction = productPageAction;
+ return stateAction;
+ }
+ else {
+ return productPageAction;
+ }
+ }
+ /**
+ * The maximum number of games we should ask GameCenterServer for. Note that some of the games it fetches will
+ * not be Arcade nor platform-compatible games. For this reason, you should ask for more than you need.
+ * As this call is performant enough with a time limit, we just request the maximum amount.
+ *
+ * The maximum that GameCenterServer will accept is 200.
+ */
+ maximumNumberOfRecentGamesToRequest() {
+ return 200;
+ }
+ /**
+ * The maximum number of games to display on the shelf.
+ *
+ * The maximum that Media API will accept is 100.
+ */
+ maximumNumberOfRecentGamesToShow() {
+ return 10;
+ }
+ /**
+ * Convert GameCategoryFilter to GamesFilter. Ideally these would be the same, but seed 1 has already left the station.
+ * @param gamesFilter
+ */
+ gameCategoryFilter(gamesFilter) {
+ if (gamesFilter === "nonArcade") {
+ return "nonarcade";
+ }
+ return gamesFilter;
+ }
+ // endregion
+ // region Metrics
+ shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) {
+ return {
+ ...baseMetricsOptions,
+ badges: {
+ gameCenter: true,
+ },
+ idType: "its_contentId",
+ title: objectGraph.loc.string("GameCenter.ContinuePlayingShelf.Title"),
+ };
+ }
+}
+//# sourceMappingURL=grouping-game-center-continue-playing-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-popular-with-your-friends-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-popular-with-your-friends-shelf-controller.js
new file mode 100644
index 0000000..a3eb652
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-popular-with-your-friends-shelf-controller.js
@@ -0,0 +1,314 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataFetching from "../../../foundation/media/data-fetching";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaNetwork from "../../../foundation/media/network";
+import { ResponseMetadata } from "../../../foundation/network/network";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as urls from "../../../foundation/network/urls";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+import { makeGameCenterHeader, openGamesUIAction } from "../../arcade/arcade-common";
+export class GroupingGameCenterPopularWithYourFriendsController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingGameCenterPopularWithYourFriendsController");
+ this.batchGroupKey = "gameCenter";
+ this.supportedFeaturedContentIds = new Set([
+ 495 /* groupingTypes.FeaturedContentID.AppStore_PopularWithYourFriendsMarker */,
+ ]);
+ }
+ // endregion
+ // region Shelf Builder
+ shelfRoute(objectGraph) {
+ return [
+ ...super.shelfRoute(objectGraph),
+ {
+ protocol: Protocol.internal,
+ path: `/${Path.grouping}/${Path.shelf}/{token}`,
+ query: [Parameters.isGameCenterPopularWithYourFriendsShelf],
+ },
+ ];
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return {
+ shelfContents: [],
+ };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ const popularWithYourFriendsPromise = objectGraph.gameCenter.fetchGamesPopularWithFriends(this.gameCategoryFilter(shelfToken.gamesFilter), 30);
+ return await popularWithYourFriendsPromise.then(async (response) => {
+ const gameplayRecords = response
+ .map((item) => this.gameplayHistoryFromData(item))
+ .sort((a, b) => b.records.length - a.records.length);
+ const adamIds = gameplayRecords
+ .filter((record) => this.isCompatibleGameCenterPlatform(objectGraph, record.platformId))
+ .map((record) => record.adamId);
+ if (adamIds.length === 0) {
+ // One of the few exceptions where TS minifiers cannot derive data container type
+ // without some help, in this case using explicit type for the value passed to promise.
+ const emptyShelfData = { shelfContents: [] };
+ return await Promise.resolve(emptyShelfData);
+ }
+ // <rdar://problem/62490641> Send payload to MAPI to allow reco to reorder the "Popular with friends" shelf.
+ const request = new mediaDataFetching.Request(objectGraph)
+ // MAPI will refuse to serve this request if there are more than 100 items.
+ .withIdsOfType(adamIds.slice(0, 100), "apps")
+ .includingAgeRestrictions();
+ groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, request);
+ return await mediaNetwork
+ .fetchData(objectGraph, request, {})
+ .then((dataContainer) => {
+ const shelfData = {
+ shelfContents: dataContainer.data,
+ responseTimingValues: dataContainer[ResponseMetadata.timingValues],
+ };
+ return shelfData;
+ });
+ });
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ incompleteShelfFetchStrategy(objectGraph) {
+ return models.IncompleteShelfFetchStrategy.OnPageLoad;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ if (shelfToken.isFirstRender) {
+ return this.pendingPopularWithFriendsShelfForGrouping(objectGraph, shelfData, shelfToken);
+ }
+ else {
+ return this.popularWithFriendsShelfForGrouping(objectGraph, shelfData, shelfToken);
+ }
+ }
+ pendingPopularWithFriendsShelfForGrouping(objectGraph, shelfData, shelfToken) {
+ const shelf = this.popularWithFriendsShelfForGrouping(objectGraph, shelfData, shelfToken);
+ const groupingShelfUrl = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken));
+ shelf.url = groupingShelfUrl.param(Parameters.isGameCenterPopularWithYourFriendsShelf, "true").build();
+ return shelf;
+ }
+ popularWithFriendsShelfForGrouping(objectGraph, shelfData, shelfToken) {
+ const shelf = this.popularWithFriendsShelf(objectGraph, shelfData.shelfContents, shelfToken);
+ shelf.mergeWhenFetched = true;
+ shelf.batchGroup = this.batchGroupKey;
+ // Hide when empty.
+ shelf.isHidden = shelf.items.length === 0;
+ // Configure header
+ shelf.header.title = shelfToken.title;
+ shelf.header.subtitle = shelfToken.subtitle;
+ return shelf;
+ }
+ popularWithFriendsShelf(objectGraph, shelfContents, shelfToken) {
+ const shelfStyle = shelfToken.shelfStyle || "mediumLockup";
+ const shelf = new models.Shelf(shelfStyle);
+ shelf.isHorizontal = true;
+ const maxNumberOfPlayersBeforeSeeAll = objectGraph.client.isTV ? 20 : 12;
+ const items = [];
+ for (let index = 0; index < shelfContents.length; index++) {
+ const data = shelfContents[index];
+ const lockupOptions = {
+ metricsOptions: {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ anonymizationOptions: {
+ anonymizationString: `"GAME"${index + 1}`,
+ },
+ },
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfStyle),
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfStyle),
+ shouldHideArcadeHeader: objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab") && shelfToken.isArcadePage,
+ shouldShowFriendsPlayingShowcase: true,
+ };
+ // GameCenter should *not* be sending us IDs for non-GameCenter apps, but in the odd case that they do, we
+ // we check against the app's metadata to make sure we are displaying GC apps only here.
+ const isGameCenterEnabled = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isGameCenterEnabled");
+ // It's possible that a pre-order has become available to friends, but not you yet (due to CDN/timezone reasons).
+ const isPreorder = mediaAttributes.attributeAsBooleanOrFalse(data, "isPreorder");
+ if (isPreorder || !isGameCenterEnabled) {
+ continue;
+ }
+ const lockup = lockups.lockupFromData(objectGraph, data, lockupOptions);
+ lockup.clickAction = injectCrossfireFlowForGameCenter(objectGraph, lockup.clickAction);
+ if (serverData.isDefinedNonNull(lockup)) {
+ items.push(lockup);
+ metricsHelpersLocation.nextPosition(lockupOptions.metricsOptions.locationTracker);
+ }
+ }
+ let thresholdForPlatform;
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ thresholdForPlatform = 2;
+ break;
+ case "pad":
+ thresholdForPlatform = 6;
+ break;
+ case "mac":
+ thresholdForPlatform = 6;
+ break;
+ case "tv":
+ thresholdForPlatform = 6;
+ break;
+ default:
+ thresholdForPlatform = 0;
+ }
+ shelf.header = makeGameCenterHeader(objectGraph);
+ if (items.length < thresholdForPlatform) {
+ shelf.isHidden = true;
+ return shelf;
+ }
+ shelf.items = items.slice(0, maxNumberOfPlayersBeforeSeeAll);
+ shelf.isHidden = false;
+ shelf.batchGroup = "gameCenter";
+ if (items.length > maxNumberOfPlayersBeforeSeeAll) {
+ // The shelf for the see all page
+ const shelfForSeeAllItems = new models.Shelf("mediumLockup");
+ shelfForSeeAllItems.items = items;
+ shelfForSeeAllItems.rowsPerColumn = 1;
+ // See all page
+ const seeAllPage = new models.GenericPage([shelfForSeeAllItems]);
+ seeAllPage.title = shelfToken.title;
+ // The action that opens the see all page
+ const seeAllAction = new models.FlowAction("page");
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ seeAllAction.pageData = seeAllPage;
+ // Metrics
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, null, {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ });
+ // Connect the shelf's seeAllAction
+ groupingShelfControllerCommon.replaceShelfSeeAllAction(objectGraph, shelf, seeAllAction);
+ }
+ shelf.footerTitle = objectGraph.loc.string("Lockup.Footer.GamesApp");
+ shelf.footerAction = openGamesUIAction(objectGraph);
+ shelf.footerStyle = {
+ $kind: "games",
+ bundleID: "com.apple.games",
+ width: 16,
+ height: 16,
+ };
+ return shelf;
+ }
+ // endregion
+ // region Helpers
+ /**
+ * Maps GKGamePlatform to client's `deviceType`
+ * @param objectGraph
+ * @param platformId The platform ID as defined by GameCenter's GKGamePlatform
+ */
+ isCompatibleGameCenterPlatform(objectGraph, platformId) {
+ switch (platformId) {
+ case 1:
+ return objectGraph.client.isiOS;
+ case 2:
+ return objectGraph.client.isMac;
+ case 3:
+ return objectGraph.client.isTV;
+ case 4:
+ return objectGraph.client.isWatch;
+ default:
+ return false;
+ }
+ }
+ gameplayHistoryFromData(data) {
+ const adamId = serverData.asString(data, "adamId");
+ const platformId = serverData.asNumber(data, "platformId");
+ const isArcade = serverData.asBooleanOrFalse(data, "isArcade");
+ const records = this.gameplayHistoryRecordFromData(serverData.asArrayOrEmpty(data, "records"));
+ return new models.GameCenterGameplayHistory(adamId, platformId, isArcade, records);
+ }
+ gameplayHistoryRecordFromData(data) {
+ return data.map((recordData) => {
+ const playerId = serverData.asString(recordData, "playerId");
+ const timestamp = serverData.asNumber(recordData, "timestamp");
+ return new models.GameCenterGameplayHistoryRecord(playerId, timestamp);
+ });
+ }
+ /**
+ * Convert GameCategoryFilter to GamesFilter. Ideally these would be the same, but seed 1 has already left the station.
+ * @param gamesFilter
+ */
+ gameCategoryFilter(gamesFilter) {
+ if (gamesFilter === "nonArcade") {
+ return "nonarcade";
+ }
+ return gamesFilter;
+ }
+ // endregion
+ // region Metrics
+ shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) {
+ return {
+ ...baseMetricsOptions,
+ badges: {
+ gameCenter: true,
+ },
+ idType: "its_contentId",
+ };
+ }
+}
+/**
+ * For evaluating how we attribute referral from Game Center shelves to the product page
+ * We going to inject a hardcoded refApp `com.apple.gamecenter.from.browse` through crossfire pipeline for tracking the page event and buy action
+ *
+ * - Modify the FlowAction to include the GameCenter ReferrerData.
+ * - Replace the click action with a compound action of CrossfireReferralAction + FlowAction
+ */
+export function injectCrossfireFlowForGameCenter(objectGraph, clickAction) {
+ if (["iOS", "macOS", "tvOS"].includes(objectGraph.host.platform) && clickAction instanceof models.FlowAction) {
+ const gameCenterCrossfireReferrerData = {
+ app: "com.apple.gamecenter.from.browse",
+ kind: {
+ name: "gameCenter",
+ },
+ };
+ clickAction.referrerData = gameCenterCrossfireReferrerData;
+ return new models.CompoundAction([
+ new models.CrossfireReferralAction(gameCenterCrossfireReferrerData),
+ clickAction,
+ ]);
+ }
+ else {
+ return clickAction;
+ }
+}
+//# sourceMappingURL=grouping-game-center-popular-with-your-friends-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-reengagement-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-reengagement-shelf-controller.js
new file mode 100644
index 0000000..f4b3d74
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-reengagement-shelf-controller.js
@@ -0,0 +1,254 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as actions from "../../../api/models/actions";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaDataFetching from "../../../foundation/media/data-fetching";
+import * as mediaNetwork from "../../../foundation/media/network";
+import { ResponseMetadata } from "../../../foundation/network/network";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as urls from "../../../foundation/network/urls";
+import * as color from "../../../foundation/util/color-util";
+import * as content from "../../content/content";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+import { injectCrossfireFlowForGameCenter } from "./grouping-game-center-popular-with-your-friends-shelf-controller";
+export class GroupingGameCenterReengagementShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingGameCenterReengagementShelfController");
+ this.batchGroupKey = "gameCenter";
+ this.supportedFeaturedContentIds = new Set([494 /* groupingTypes.FeaturedContentID.AppStore_GameCenterReengagement */]);
+ }
+ // endregion
+ // region Shelf Builder
+ shelfRoute(objectGraph) {
+ return [
+ ...super.shelfRoute(objectGraph),
+ {
+ protocol: Protocol.internal,
+ path: `/${Path.grouping}/${Path.shelf}/{token}`,
+ query: [Parameters.isGameCenterReengagementShelf],
+ },
+ ];
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return {
+ shelfContents: [],
+ achievementData: null,
+ achievementSummaryData: null,
+ };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await objectGraph.gameCenter.fetchRengagementDataForLocalPlayer().then(async (reengagementData) => {
+ const adamID = serverData.asString(reengagementData, "adamId");
+ const achievement = serverData.asJSONData(reengagementData["achievement"]);
+ const achievementSummary = serverData.asJSONData(reengagementData["achievementSummary"]);
+ if (serverData.isNullOrEmpty(adamID)) {
+ // One of the few exceptions where TS minifiers cannot derive data container type
+ // without some help, in this case using explicit type for the value passed to promise.
+ const emptyShelfData = {
+ shelfContents: [],
+ responseTimingValues: null,
+ achievementData: null,
+ achievementSummaryData: null,
+ };
+ return await Promise.resolve(emptyShelfData);
+ }
+ const request = new mediaDataFetching.Request(objectGraph)
+ .withIdOfType(adamID, "apps")
+ .includingAgeRestrictions();
+ groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, request);
+ return await mediaNetwork
+ .fetchData(objectGraph, request, {})
+ .then((dataContainer) => {
+ return {
+ shelfContents: dataContainer.data,
+ responseTimingValues: dataContainer[ResponseMetadata.timingValues],
+ achievementData: achievement,
+ achievementSummaryData: achievementSummary,
+ };
+ });
+ });
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ if (shelfToken.isFirstRender) {
+ return this.pendingGameCenterReengagementShelf(objectGraph, shelfData, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage);
+ }
+ else {
+ return this.gameCenterReengagementShelf(objectGraph, shelfData, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage);
+ }
+ }
+ pendingGameCenterReengagementShelf(objectGraph, shelfData, shelfToken, isArcadePage) {
+ const shelf = this.gameCenterReengagementShelf(objectGraph, shelfData, shelfToken, isArcadePage);
+ if (!shelf) {
+ return null;
+ }
+ const groupingShelfUrl = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken));
+ shelf.url = groupingShelfUrl.param(Parameters.isGameCenterReengagementShelf, "true").build();
+ shelf.batchGroup = this.batchGroupKey;
+ return shelf;
+ }
+ gameCenterReengagementShelf(objectGraph, shelfData, shelfToken, isArcadePage = false) {
+ return validation.context("gameCenterReengagementShelf", () => {
+ if (!serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)) {
+ return null;
+ }
+ const shelf = new models.Shelf("gameCenterReengagement");
+ shelf.isHorizontal = false;
+ shelf.mergeWhenFetched = false; // Always replace
+ shelf.batchGroup = this.batchGroupKey;
+ const heroMetricsOptions = {
+ id: shelfToken.id,
+ kind: null,
+ softwareType: isArcadePage ? "Arcade" : null,
+ targetType: "achievements",
+ title: "Achievements Hero",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ idType: "its_contentId",
+ fcKind: shelfToken.featuredContentId,
+ badges: {
+ gameCenter: true,
+ },
+ };
+ // Hero Background / Artwork
+ const artwork = content.productEditorialVideoFromData(objectGraph, shelfData.shelfContents[0], 21 /* content.ArtworkUseCase.Uber */);
+ let backgroundColor = color.named("componentBackground");
+ let preview = null;
+ if (serverData.isDefinedNonNullNonEmpty(artwork)) {
+ preview = artwork.preview;
+ backgroundColor = preview.backgroundColor;
+ }
+ // Build lockup used by reegnagement shelf
+ const lockupListOptions = {
+ lockupOptions: {
+ metricsOptions: {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ },
+ offerStyle: "white",
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, "smallLockup"),
+ isSubtitleHidden: true,
+ },
+ };
+ metricsHelpersLocation.pushContentLocation(objectGraph, heroMetricsOptions, heroMetricsOptions.title);
+ const heroLockup = lockups.lockupsFromData(objectGraph, shelfData.shelfContents, lockupListOptions)[0];
+ heroLockup.clickAction = injectCrossfireFlowForGameCenter(objectGraph, heroLockup.clickAction);
+ metricsHelpersLocation.popLocation(shelfToken.metricsLocationTracker);
+ // Click action used by hero view (achievement card)
+ let heroAction = null;
+ if (serverData.isDefinedNonNullNonEmpty(heroLockup)) {
+ heroAction = new actions.GameCenterAchievementsAction(heroLockup.bundleId);
+ heroAction.title = "Achievements Hero"; // `addClickEventToAction` uses `title` for `name` field currently.
+ metricsHelpersClicks.addClickEventToAction(objectGraph, heroAction, heroMetricsOptions);
+ }
+ const shelfBadge = objectGraph.loc.string("GameCenter.Reengagement.Badge.GameCenter");
+ const achievement = this.achievementFromData(objectGraph, shelfData.achievementData);
+ const achievementCounts = this.achievementCountsFromData(objectGraph, shelfData.achievementSummaryData);
+ const shelfMetadata = this.shelfMetadataForAchievement(objectGraph, achievement, achievementCounts);
+ const heroItem = new models.GameCenterReengagement("gamecenter.fill", shelfBadge, shelfMetadata.title, shelfMetadata.subtitle, achievement, heroLockup, backgroundColor, preview, heroAction);
+ shelf.items = [heroItem];
+ /**
+ * For reengagement, the shelf is a container fully fulled by `heroItem`.
+ * Match what breakouts do by impressing `heroItem` directly instead of the `shelf`.
+ */
+ metricsHelpersImpressions.addImpressionFields(objectGraph, heroItem, heroMetricsOptions);
+ return shelf;
+ });
+ }
+ // region Helpers
+ achievementStatusFromData(objectGraph, data) {
+ const statusType = serverData.asString(data, "type");
+ const status = new models.GameCenterAchievementStatus(statusType);
+ status.percent = serverData.asNumber(data, "percent");
+ status.date = serverData.asString(data, "date");
+ status.artwork = new models.Artwork(serverData.asString(data, "artwork.template"), serverData.asNumber(data, "artwork.width"), serverData.asNumber(data, "artwork.height"), []);
+ return status;
+ }
+ achievementFromData(objectGraph, data) {
+ const id = serverData.asString(data, "id");
+ const title = serverData.asString(data, "title");
+ const subtitle = serverData.asString(data, "subtitle");
+ const status = this.achievementStatusFromData(objectGraph, serverData.asDictionary(data, "status"));
+ return new models.GameCenterAchievement(id, title, subtitle, status);
+ }
+ achievementCountsFromData(objectGraph, data) {
+ const completedAchievements = serverData.asNumber(data, "completedAchievements");
+ const totalAchievements = serverData.asNumber(data, "totalAchievements");
+ return { completed: completedAchievements, total: totalAchievements };
+ }
+ shelfMetadataForAchievement(objectGraph, achievement, achievementCounts) {
+ if (!serverData.isDefinedNonNull(achievement)) {
+ return { title: "", subtitle: null };
+ }
+ if (achievementCounts.completed === 0) {
+ return {
+ title: objectGraph.loc.string("GameCenter.Reengagement.Achievement.First.Title"),
+ subtitle: objectGraph.loc.string("GameCenter.Reengagement.Achievement.First.Subtitle"),
+ };
+ }
+ switch (achievement.status.type) {
+ case "locked":
+ case "hidden":
+ case "inprogress":
+ return {
+ title: objectGraph.loc.string("GameCenter.Reengagement.Achievement.KeepPlaying.Title"),
+ subtitle: objectGraph.loc.string("GameCenter.Reengagement.Achievement.KeepPlaying.Subtitle"),
+ };
+ case "completed":
+ const achievementsTotalCount = objectGraph.loc.stringWithCount("GameCenter.AchievementSummary.TotalToCompleteCount", achievementCounts.total);
+ const achievementsCompletedCount = objectGraph.loc.stringWithCount("GameCenter.AchievementSummary.NumberCompletedCount", achievementCounts.completed);
+ const subtitle = objectGraph.loc
+ .string("GameCenter.AchievementSummary.CompletedCount.Subtitle")
+ .replace("@@completedCount@@", achievementsCompletedCount)
+ .replace("@@totalCount@@", achievementsTotalCount);
+ return {
+ title: objectGraph.loc.string("GameCenter.Reengagement.Achievement.CompletedCount.Title"),
+ subtitle: subtitle,
+ };
+ default:
+ return { title: "", subtitle: null };
+ }
+ }
+}
+//# sourceMappingURL=grouping-game-center-reengagement-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-suggested-friends-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-suggested-friends-shelf-controller.js
new file mode 100644
index 0000000..e9eb3f4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-game-center-suggested-friends-shelf-controller.js
@@ -0,0 +1,279 @@
+import * as models from "../../../api/models";
+import * as actions from "../../../api/models/actions";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as urls from "../../../foundation/network/urls";
+import { makeGameCenterHeader, openGamesUIAction } from "../../arcade/arcade-common";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+export class GroupingGameCenterSuggestedFriendsController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingGameCenterSuggestedFriendsController");
+ this.batchGroupKey = "gameCenter";
+ this.supportedFeaturedContentIds = new Set([496 /* groupingTypes.FeaturedContentID.AppStore_SuggestedFriendsMarker */]);
+ }
+ // endregion
+ // region Shelf Builder
+ shelfRoute(objectGraph) {
+ return [
+ ...super.shelfRoute(objectGraph),
+ {
+ protocol: Protocol.internal,
+ path: `/${Path.grouping}/${Path.shelf}/{token}`,
+ query: [Parameters.isGameCenterSuggestedFriendsShelf],
+ },
+ ];
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return {
+ $kind: "friendingViaPush",
+ shelfContents: [],
+ suggestedFriends: [],
+ };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await objectGraph.gameCenter.fetchSuggestedFriends(10).then((suggestions) => {
+ if (objectGraph.props.enabled("gameCenterFriendingViaPush")) {
+ return {
+ $kind: "friendingViaPush",
+ shelfContents: [],
+ suggestedFriends: suggestions,
+ };
+ }
+ else {
+ return {
+ $kind: "legacy",
+ shelfContents: [],
+ suggestedFriends: suggestions,
+ };
+ }
+ });
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ incompleteShelfFetchStrategy(objectGraph) {
+ return models.IncompleteShelfFetchStrategy.OnPageLoad;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ if (shelfToken.isFirstRender) {
+ return this.pendingSuggestedFriendsShelfForGrouping(objectGraph, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage);
+ }
+ else {
+ return this.suggestedFriendsShelfForGrouping(objectGraph, shelfData, shelfToken, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage);
+ }
+ }
+ pendingSuggestedFriendsShelfForGrouping(objectGraph, shelfToken, isArcadePage) {
+ if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") {
+ return null;
+ }
+ const shelf = this.suggestedFriendsShelfForGrouping(objectGraph, {
+ $kind: objectGraph.props.enabled("gameCenterFriendingViaPush") ? "friendingViaPush" : "legacy",
+ shelfContents: [],
+ suggestedFriends: [],
+ }, shelfToken, isArcadePage);
+ const groupingShelfUrl = urls.URL.from(groupingShelfControllerCommon.groupingShelfUrl(shelfToken));
+ shelf.url = groupingShelfUrl.param(Parameters.isGameCenterSuggestedFriendsShelf, "true").build();
+ shelf.isHidden = shelf.items.length === 0;
+ shelf.batchGroup = this.batchGroupKey;
+ return shelf;
+ }
+ suggestedFriendsShelfForGrouping(objectGraph, shelfData, shelfToken, isArcadePage) {
+ if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") {
+ return null;
+ }
+ let shelf;
+ if (shelfData.$kind === "friendingViaPush") {
+ shelf = this.suggestedFriendsShelf(objectGraph, shelfData.suggestedFriends, shelfToken, isArcadePage);
+ }
+ else {
+ shelf = this.legacySuggestedFriendsShelf(objectGraph, shelfData.suggestedFriends, shelfToken, isArcadePage);
+ }
+ shelf.header = makeGameCenterHeader(objectGraph, shelfToken.title, shelfToken.subtitle);
+ shelf.batchGroup = this.batchGroupKey;
+ // Hide when empty.
+ shelf.isHidden = shelf.items.length === 0;
+ shelf.footerTitle = objectGraph.loc.string("Lockup.Footer.GamesApp");
+ shelf.footerAction = openGamesUIAction(objectGraph);
+ shelf.footerStyle = {
+ $kind: "games",
+ bundleID: "com.apple.games",
+ width: 16,
+ height: 16,
+ };
+ return shelf;
+ }
+ // region Helpers
+ suggestedFriendsShelf(objectGraph, suggestions, token, isArcadePage = false) {
+ const suggestionPrefix = "FRIEND_SUGGESTION";
+ if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") {
+ return null;
+ }
+ const shelf = new models.Shelf("smallContactCard");
+ shelf.isHorizontal = true;
+ shelf.mergeWhenFetched = true;
+ shelf.batchGroup = "gameCenter";
+ const enrichedSuggestions = [];
+ for (let index = 0; index < suggestions.length; index++) {
+ const suggestionId = `${suggestionPrefix}${index + 1}`;
+ const suggestedFriend = suggestions[index];
+ const buttonText = objectGraph.loc.string("INVITE");
+ const subtitle = objectGraph.loc.string("FROM_CONTACTS");
+ const metricsClickOptions = {
+ pageInformation: token.metricsPageInformation,
+ locationTracker: token.metricsLocationTracker,
+ id: suggestionId,
+ anonymizationOptions: {
+ anonymizationString: suggestionId,
+ },
+ };
+ let invitationType;
+ let shouldShowMessagesBadge;
+ if (suggestedFriend.supportsFriendingViaPush && suggestedFriend.contactAssociationID) {
+ invitationType = {
+ contact: {
+ contactID: suggestedFriend.contactID,
+ contactAssociationID: suggestedFriend.contactAssociationID,
+ },
+ };
+ shouldShowMessagesBadge = false;
+ }
+ else {
+ invitationType = {
+ messages: {
+ contactID: suggestedFriend.contactID,
+ },
+ };
+ shouldShowMessagesBadge = true;
+ }
+ const buttonAction = new actions.GameCenterInvitePlayerAction(invitationType);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, buttonAction, {
+ ...metricsClickOptions,
+ actionType: "inviteFriend",
+ });
+ const removeButtonAction = new actions.GameCenterDenylistPlayerAction(suggestedFriend.contactID);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, removeButtonAction, {
+ ...metricsClickOptions,
+ actionType: "removeFriendSuggestion",
+ });
+ const metricsImpressionOptions = {
+ pageInformation: token.metricsPageInformation,
+ locationTracker: token.metricsLocationTracker,
+ title: suggestionId,
+ id: suggestionId,
+ kind: "friendSuggestion",
+ softwareType: isArcadePage ? "Arcade" : null,
+ anonymizationOptions: {
+ anonymizationString: suggestionId,
+ },
+ };
+ const enrichedSuggestion = new models.SmallContactCard(suggestedFriend.contactID, suggestedFriend.fullName, subtitle, buttonText, suggestedFriend.contactID, buttonAction, removeButtonAction, shouldShowMessagesBadge);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, enrichedSuggestion, metricsImpressionOptions);
+ enrichedSuggestions.push(enrichedSuggestion);
+ metricsHelpersLocation.nextPosition(token.metricsLocationTracker);
+ }
+ shelf.items = enrichedSuggestions;
+ shelf.isHidden = shelf.items.length === 0;
+ return shelf;
+ }
+ legacySuggestedFriendsShelf(objectGraph, cards, token, isArcadePage = false) {
+ const suggestionPrefix = "FRIEND_SUGGESTION";
+ if (objectGraph.client.deviceType !== "phone" && objectGraph.client.deviceType !== "pad") {
+ return null;
+ }
+ const shelf = new models.Shelf("smallContactCard");
+ shelf.isHorizontal = true;
+ shelf.mergeWhenFetched = true;
+ shelf.batchGroup = "gameCenter";
+ const enrichedSuggestions = [];
+ for (let index = 0; index < cards.length; index++) {
+ const suggestionId = `${suggestionPrefix}${index + 1}`;
+ const smallContactCard = cards[index];
+ smallContactCard.buttonText = objectGraph.loc.string("INVITE");
+ smallContactCard.subtitle = objectGraph.loc.string("FROM_CONTACTS");
+ const metricsClickOptions = {
+ pageInformation: token.metricsPageInformation,
+ locationTracker: token.metricsLocationTracker,
+ id: suggestionId,
+ anonymizationOptions: {
+ anonymizationString: suggestionId,
+ },
+ };
+ smallContactCard.buttonAction = new actions.LegacyGameCenterInvitePlayerAction(smallContactCard.contactId);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, smallContactCard.buttonAction, {
+ ...metricsClickOptions,
+ actionType: "inviteFriend",
+ });
+ smallContactCard.removeButtonAction = new actions.GameCenterDenylistPlayerAction(smallContactCard.contactId);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, smallContactCard.removeButtonAction, {
+ ...metricsClickOptions,
+ actionType: "removeFriendSuggestion",
+ });
+ const metricsImpressionOptions = {
+ pageInformation: token.metricsPageInformation,
+ locationTracker: token.metricsLocationTracker,
+ title: suggestionId,
+ id: suggestionId,
+ kind: "friendSuggestion",
+ softwareType: isArcadePage ? "Arcade" : null,
+ anonymizationOptions: {
+ anonymizationString: suggestionId,
+ },
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, smallContactCard, metricsImpressionOptions);
+ enrichedSuggestions.push(smallContactCard);
+ metricsHelpersLocation.nextPosition(token.metricsLocationTracker);
+ }
+ shelf.items = enrichedSuggestions;
+ shelf.isHidden = shelf.items.length === 0;
+ return shelf;
+ }
+ // endregion
+ // region Metrics
+ shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) {
+ return {
+ ...baseMetricsOptions,
+ badges: {
+ gameCenter: true,
+ },
+ idType: "its_contentId",
+ };
+ }
+}
+//# sourceMappingURL=grouping-game-center-suggested-friends-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-hero-carousel-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-hero-carousel-shelf-controller.js
new file mode 100644
index 0000000..b7a406c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-hero-carousel-shelf-controller.js
@@ -0,0 +1,182 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as breakoutsCommon from "../../arcade/breakouts-common";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as article from "../../today/article";
+import * as heroCarouselOverlayCommon from "../hero/hero-carousel-overlay-common";
+import * as heroCommon from "../hero/hero-common";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+export class GroupingHeroCarouselShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingHeroCarouselShelfController");
+ this.supportedFeaturedContentIds = new Set([480 /* groupingTypes.FeaturedContentID.AppStore_Breakout */]);
+ }
+ // endregion
+ // region GroupingShelfController
+ _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) {
+ if (!super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId)) {
+ return false;
+ }
+ const breakoutStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle");
+ return breakoutStyle === "hero";
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters);
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ // endregion
+ // region Metrics
+ shouldImpressShelf() {
+ return false;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ if (groupingParseContext.shelves.length !== 0) {
+ return null;
+ }
+ const shelf = new models.Shelf("heroCarousel");
+ const mediaApiData = shelfToken.featuredContentData;
+ const heroCarouselMetricsOptions = {
+ targetType: "swoosh",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(mediaApiData),
+ };
+ const heroCarousel = new models.HeroCarousel();
+ heroCarousel.autoScrollConfiguration = {
+ isAutoScrollEnabled: objectGraph.bag.heroCarouselAutoScrollDuration > 0.0,
+ autoScrollInterval: objectGraph.bag.heroCarouselAutoScrollDuration,
+ };
+ const heroCarouselImpressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, mediaApiData, "heroCarousel", heroCarouselMetricsOptions);
+ heroCarouselImpressionOptions.autoAdvanceInterval = heroCarousel.autoScrollConfiguration.autoScrollInterval;
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, heroCarouselImpressionOptions);
+ metricsHelpersLocation.pushContentLocation(objectGraph, heroCarouselImpressionOptions, "heroCarousel");
+ for (const itemData of shelfData.shelfContents) {
+ if (serverData.isNull(itemData.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(itemData);
+ continue;
+ }
+ const primaryContent = content.primaryContentForData(objectGraph, itemData);
+ if (breakoutsCommon.requiresPrimaryContent(objectGraph, itemData) &&
+ !mediaAttributes.hasAttributes(primaryContent)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(itemData);
+ shelfToken.relationshipToFetch = "primary-content";
+ continue;
+ }
+ const heroCarouselItemMetricsOptions = {
+ targetType: "largeBreakout",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ };
+ const heroVideoData = heroCommon.heroVideoFromData(objectGraph, itemData);
+ const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, itemData);
+ if (serverData.isNullOrEmpty(heroVideoData.video) && serverData.isNullOrEmpty(heroArtworkData.artwork)) {
+ continue;
+ }
+ const heroCarouselItem = new models.HeroCarouselItem();
+ const productData = article.productDataFromArticle(objectGraph, itemData);
+ const metricsTitle = heroCarouselOverlayCommon.heroCarouselItemTitleFromData(objectGraph, itemData);
+ const heroCarouselItemImpressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, itemData, metricsTitle, heroCarouselItemMetricsOptions);
+ heroCarouselItemImpressionOptions.isPreorder = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "isPreorder");
+ metricsHelpersImpressions.addImpressionFields(objectGraph, heroCarouselItem, heroCarouselItemImpressionOptions);
+ // Push the heroCarouselItem here so that the click action has the item in its location
+ // but we do not want to add it to the overall location tracker, so pop it right after adding it to the button
+ // action
+ // <rdar://problem/60883269> Metrics: Arcade: Container values requested in Location field
+ metricsHelpersLocation.pushContentLocation(objectGraph, heroCarouselItemImpressionOptions, metricsTitle);
+ const titleEffectArtwork = heroVideoData.artworkData || heroArtworkData.artworkData;
+ const backgroundColor = heroVideoData.backgroundColor || heroArtworkData.backgroundColor;
+ const overlayRequirements = {
+ metricsPageInformation: shelfToken.metricsPageInformation,
+ metricsLocationTracker: shelfToken.metricsLocationTracker,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfToken.shelfStyle),
+ lockupArtworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfToken.shelfStyle),
+ isContainedInPreorderExclusiveShelf: shelfToken.featuredContentId === 497 /* groupingTypes.FeaturedContentID.AppStore_ComingSoon */,
+ };
+ heroCarouselItem.overlay = heroCarouselOverlayCommon.overlayFromData(objectGraph, itemData, overlayRequirements);
+ heroCarouselItem.backgroundColor = backgroundColor;
+ if (!objectGraph.client.isMac || objectGraph.props.isNotEnabled("macOSArcadeHeaderUpdates")) {
+ heroCarouselItem.titleEffect = breakoutsCommon.titleEffectFromArtwork(objectGraph, titleEffectArtwork);
+ }
+ heroCarouselItem.artwork = heroArtworkData.artwork;
+ heroCarouselItem.video = heroVideoData.video;
+ if (objectGraph.client.isWeb) {
+ const portraitVideo = heroCommon.heroVideoFromData(objectGraph, itemData, false, true);
+ heroCarouselItem.portraitVideo = portraitVideo === null || portraitVideo === void 0 ? void 0 : portraitVideo.video;
+ }
+ const heroCarouselItemAction = breakoutsCommon.actionFromData(objectGraph, itemData);
+ const heroCarouselItemClickOptions = {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ targetType: "largeBreakout",
+ id: itemData.id,
+ };
+ if (heroCarouselItemAction) {
+ metricsHelpersClicks.addClickEventToAction(objectGraph, heroCarouselItemAction, heroCarouselItemClickOptions);
+ heroCarouselItem.clickAction = heroCarouselItemAction;
+ }
+ heroCarousel.items.push(heroCarouselItem);
+ metricsHelpersLocation.popLocation(heroCarouselItemImpressionOptions.locationTracker);
+ metricsHelpersLocation.nextPosition(heroCarouselItemImpressionOptions.locationTracker);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(heroCarousel.items)) {
+ shelf.items = [heroCarousel];
+ groupingParseContext.pageTitleEffect = heroCarousel.items[0].titleEffect;
+ }
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ metricsHelpersLocation.popLocation(heroCarouselImpressionOptions.locationTracker);
+ metricsHelpersLocation.nextPosition(heroCarouselImpressionOptions.locationTracker);
+ return shelf;
+ }
+}
+//# sourceMappingURL=grouping-hero-carousel-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-horizontal-card-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-horizontal-card-shelf-controller.js
new file mode 100644
index 0000000..d02a4b6
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-horizontal-card-shelf-controller.js
@@ -0,0 +1,122 @@
+import * as models from "../../../api/models";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as todayHorizontalCardUtil from "../../today/today-horizontal-card-util";
+import { TodayParseContext } from "../../today/today-types";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+export class GroupingHorizontalCardShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingHorizontalCardShelfController");
+ this.supportedFeaturedContentIds = new Set([475 /* groupingTypes.FeaturedContentID.AppStore_HorizontalCardSwoosh */]);
+ }
+ // endregion
+ // region GroupingShelfController
+ _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) {
+ if (!super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId)) {
+ return false;
+ }
+ const displayStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle");
+ const contentType = groupingShelfControllerCommon.contentTypeForHorizontalCardDisplayStyle(objectGraph, displayStyle);
+ return contentType !== "editorialStoryCard";
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters);
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ const shelfToken = { ...baseShelfToken };
+ const displayStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle");
+ shelfToken.shelfStyle = groupingShelfControllerCommon.contentTypeForHorizontalCardDisplayStyle(objectGraph, displayStyle);
+ return shelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ var _a;
+ const cardUnavailable = function (cardData) {
+ shelfToken.remainingItems.push(cardData);
+ return false;
+ };
+ let shelf;
+ if (objectGraph.client.isiOS && objectGraph.featureFlags.isEnabled("mini_today_cards_grouping")) {
+ const todayParseContext = new TodayParseContext(shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker);
+ shelf = todayHorizontalCardUtil.shelfForMiniTodayCards(objectGraph, (_a = shelfData.shelfContents) !== null && _a !== void 0 ? _a : [], shelfToken.title, shelfToken.subtitle, todayParseContext);
+ }
+ else {
+ const cardContext = {
+ metricsLocationTracker: shelfToken.metricsLocationTracker,
+ metricsPageInformation: shelfToken.metricsPageInformation,
+ };
+ let resolvedContentType;
+ // First check if the client explicitly supports small story cards.
+ // If it does, don't change the content type.
+ const isSmallStoryCardsSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isWeb;
+ if (isSmallStoryCardsSupported && shelfToken.shelfStyle === "smallStoryCard") {
+ resolvedContentType = shelfToken.shelfStyle;
+ }
+ else {
+ switch (objectGraph.client.deviceType) {
+ case "mac":
+ case "tv":
+ case "web":
+ resolvedContentType = shelfToken.shelfStyle;
+ break;
+ case "watch":
+ resolvedContentType = "todayCard";
+ break;
+ default:
+ resolvedContentType = "todayBrick";
+ break;
+ }
+ }
+ shelf = todayHorizontalCardUtil.shelfForHorizontalCardItems(objectGraph, shelfData.shelfContents, resolvedContentType, shelfToken.title, shelfToken.subtitle, cardContext, cardUnavailable);
+ if (shelf.contentType === "smallStoryCard" && Array.isArray(shelf.items)) {
+ shelf.items = shelf.items.filter((item) => {
+ if (!(item instanceof models.TodayCard)) {
+ return true;
+ }
+ return todayHorizontalCardUtil.isHorizontalCardSupportedForKind(objectGraph, item.media.kind, "smallStoryCard");
+ });
+ }
+ }
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ shelf.isHorizontal = true;
+ return shelf;
+ }
+}
+//# sourceMappingURL=grouping-horizontal-card-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-large-breakout-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-large-breakout-shelf-controller.js
new file mode 100644
index 0000000..7b14c97
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-large-breakout-shelf-controller.js
@@ -0,0 +1,193 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as breakoutsCommon from "../../arcade/breakouts-common";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as flowPreview from "../../content/flow-preview";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as article from "../../today/article";
+import * as heroCommon from "../hero/hero-common";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+export class GroupingLargeBreakoutShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor(builderClass = null) {
+ super(builderClass || "GroupingLargeBreakoutShelfController");
+ this.supportedFeaturedContentIds = new Set([480 /* groupingTypes.FeaturedContentID.AppStore_Breakout */]);
+ }
+ // endregion
+ // region GroupingShelfController
+ _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) {
+ if (!super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId)) {
+ return false;
+ }
+ const breakoutStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle");
+ return breakoutStyle === "large";
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters);
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ // endregion
+ // region Metrics
+ shouldImpressShelf() {
+ return false;
+ }
+ // endregion
+ // region Shelf Creation
+ isInHeroPosition() {
+ return false;
+ }
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ const isFirstShelf = serverData.isDefinedNonNullNonEmpty(groupingParseContext) &&
+ serverData.isNullOrEmpty(groupingParseContext.pageTitleEffect) &&
+ groupingParseContext.shelves.length === 0;
+ const trimmedShelfContents = serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)
+ ? [shelfData.shelfContents[0]]
+ : [];
+ const items = [];
+ for (const data of trimmedShelfContents) {
+ if (serverData.isNull(data.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(data);
+ continue;
+ }
+ const metricsOptions = {
+ targetType: this.isInHeroPosition() ? "heroBreakout" : "largeBreakout",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ };
+ const heroVideoData = heroCommon.heroVideoFromData(objectGraph, data);
+ const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, data);
+ const titleEffectArtwork = heroVideoData.artworkData || heroArtworkData.artworkData;
+ const largeHeroBreakout = GroupingLargeBreakoutShelfController.createLargeBreakout(objectGraph, data, shelfToken, this.isInHeroPosition(), isFirstShelf, metricsOptions);
+ if (serverData.isNullOrEmpty(largeHeroBreakout)) {
+ continue;
+ }
+ items.push(largeHeroBreakout);
+ if (isFirstShelf) {
+ groupingParseContext.pageTitleEffect = breakoutsCommon.titleEffectFromArtwork(objectGraph, titleEffectArtwork);
+ }
+ }
+ const shelf = new models.Shelf("largeHeroBreakout");
+ shelf.isHorizontal = false;
+ shelf.items = items;
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ if (groupingParseContext.shelves.length === 0) {
+ shelf.presentationHints = { isFirstShelf: true };
+ }
+ return shelf;
+ }
+ static createLargeBreakout(objectGraph, data, shelfToken, isHeroPosition, isFirstShelf, metricsOptions) {
+ const primaryContent = content.primaryContentForData(objectGraph, data);
+ if (breakoutsCommon.requiresPrimaryContent(objectGraph, data) &&
+ !mediaAttributes.hasAttributes(primaryContent)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(data);
+ shelfToken.relationshipToFetch = "primary-content";
+ return null;
+ }
+ const heroVideoData = heroCommon.heroVideoFromData(objectGraph, data);
+ const heroArtworkData = heroCommon.heroArtworkFromData(objectGraph, data);
+ if (serverData.isNullOrEmpty(heroVideoData.video) && serverData.isNullOrEmpty(heroArtworkData.artwork)) {
+ return null;
+ }
+ const backgroundColor = heroVideoData.backgroundColor || heroArtworkData.backgroundColor;
+ const heading = isHeroPosition ? null : mediaAttributes.attributeAsString(data, "breakoutBadge");
+ // If this EI can and should show the expected release date of an app, override the badge.
+ let badgeTitle;
+ const fallbackLabel = mediaAttributes.attributeAsString(data, "label");
+ if (contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "showExpectedReleaseDate")) {
+ badgeTitle = objectGraph.loc.uppercased(content.dynamicPreorderDateFromData(objectGraph, primaryContent, fallbackLabel));
+ }
+ else {
+ badgeTitle = fallbackLabel;
+ }
+ let badge = { type: "none" };
+ if ((badgeTitle === null || badgeTitle === void 0 ? void 0 : badgeTitle.length) > 0) {
+ badge = {
+ type: "text",
+ title: badgeTitle,
+ };
+ }
+ const title = content.editorialNotesFromData(objectGraph, data, "name") ||
+ contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "name");
+ const description = content.editorialNotesFromData(objectGraph, data, "short") ||
+ contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "tagline");
+ const backgroundStyle = breakoutsCommon.detailBackgroundStyleFromData(objectGraph, data, true, isFirstShelf, false);
+ const detailPosition = breakoutsCommon.detailPositionFromData(objectGraph, data, isFirstShelf, false);
+ const details = new models.BreakoutDetails(title, description, badge, null, backgroundStyle, breakoutsCommon.detailTextAlignmentForDetailPosition(objectGraph, detailPosition, data));
+ const largeHeroBreakout = new models.LargeHeroBreakout(details, {
+ position: detailPosition || "leading",
+ }, heading, heroArtworkData.artwork, heroVideoData.video, null, backgroundColor);
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, largeHeroBreakout.details.title, metricsOptions);
+ const productData = article.productDataFromArticle(objectGraph, data);
+ const isPreorder = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "isPreorder");
+ impressionOptions.isPreorder = isPreorder;
+ metricsHelpersImpressions.addImpressionFields(objectGraph, largeHeroBreakout, impressionOptions);
+ // Push the breakout here so that the click action has the breakout in its location
+ // but we do not want to add it to the overall location tracker, so pop it right after adding it to the button
+ // action
+ // <rdar://problem/60883269> Metrics: Arcade: Container values requested in Location field
+ metricsHelpersLocation.pushContentLocation(objectGraph, impressionOptions, largeHeroBreakout.details.title);
+ const breakoutAction = breakoutsCommon.actionFromData(objectGraph, data);
+ const breakoutClickOptions = {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ targetType: "button",
+ id: data.id,
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, breakoutAction, breakoutClickOptions);
+ largeHeroBreakout.details.callToActionButtonAction = breakoutAction;
+ largeHeroBreakout.clickAction = breakoutAction;
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ // Set flow preview actions
+ largeHeroBreakout.flowPreviewActionsConfiguration =
+ flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, data, true, shelfToken.clientIdentifierOverride, breakoutAction, metricsOptions, breakoutClickOptions);
+ return largeHeroBreakout;
+ }
+}
+//# sourceMappingURL=grouping-large-breakout-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-link-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-link-shelf-controller.js
new file mode 100644
index 0000000..09865f5
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-link-shelf-controller.js
@@ -0,0 +1,169 @@
+import { isNothing } from "@jet/environment";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { createArtworkForResource } from "../../content/artwork/artwork";
+import * as lockupsEditorialContext from "../../lockups/editorial-context";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+export class GroupingLinkShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingLinkShelfController");
+ this.supportedFeaturedContentIds = new Set([
+ 437 /* groupingTypes.FeaturedContentID.AppStore_LinkList */,
+ 265 /* groupingTypes.FeaturedContentID.Sundance_LinkList */,
+ ]);
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ let shelfContents = mediaRelationship.relationshipCollection(mediaApiData, "children");
+ if (serverData.isNullOrEmpty(shelfContents)) {
+ shelfContents = mediaAttributes.attributeAsArrayOrEmpty(mediaApiData, "links");
+ }
+ return { shelfContents: shelfContents };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters);
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ const linkShelfToken = {
+ ...baseShelfToken,
+ shouldHideShelf: mediaAttributes.attributeAsBooleanOrFalse(mediaApiData, "hide"),
+ areContentLinks: serverData.isDefinedNonNullNonEmpty(mediaRelationship.relationshipCollection(mediaApiData, "children")),
+ };
+ linkShelfToken.clientIdentifierOverride = lockupsEditorialContext.clientIdentifierForEditorialContextInData(objectGraph, mediaApiData);
+ return linkShelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ if (shelfToken.shouldHideShelf) {
+ return null;
+ }
+ const items = [];
+ for (let linkIndex = 0; linkIndex < shelfData.shelfContents.length; linkIndex++) {
+ const link = shelfData.shelfContents[linkIndex];
+ const metricsBase = {
+ targetType: "link",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ };
+ if (shelfToken.areContentLinks) {
+ metricsBase.recoMetricsData = mediaDataStructure.metricsFromMediaApiObject(link);
+ }
+ let action = null;
+ if (shelfToken.isSearchLandingPage) {
+ const searchAdAction = this.trendingSearchLinkFromData(objectGraph, link, shelfToken.metricsLocationTracker);
+ if (serverData.isNull(searchAdAction) || serverData.isNull(searchAdAction.action)) {
+ continue;
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchAdAction === null || searchAdAction === void 0 ? void 0 : searchAdAction.action, {
+ ...metricsBase,
+ kind: "link",
+ softwareType: serverData.asBooleanOrFalse(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage) ? "Arcade" : null,
+ title: searchAdAction.action.title,
+ id: `${linkIndex}`,
+ idType: "sequential",
+ });
+ action = searchAdAction;
+ }
+ else {
+ const metadata = groupingShelfControllerCommon.metadataForFCData(objectGraph, link, shelfToken, false, null, metricsBase, null);
+ if (serverData.isNullOrEmpty(metadata) || serverData.isNullOrEmpty(metadata.action.title)) {
+ continue;
+ }
+ action = metadata.action;
+ const contentId = groupingShelfControllerCommon.contentIdFromContentItem(objectGraph, link);
+ if (contentId) {
+ metricsHelpersImpressions.addImpressionFields(objectGraph, action, {
+ ...metricsBase,
+ kind: "link",
+ softwareType: serverData.asBooleanOrFalse(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage) ? "Arcade" : null,
+ title: action.title,
+ id: contentId,
+ });
+ }
+ }
+ if (serverData.isNullOrEmpty(action)) {
+ continue;
+ }
+ items.push(action);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker);
+ }
+ if (serverData.isNullOrEmpty(items)) {
+ return null;
+ }
+ const actionsShouldHaveArtwork = shelfToken.isSearchLandingPage &&
+ (objectGraph.client.isPhone || objectGraph.client.isVision) &&
+ items.length >= 6;
+ if (actionsShouldHaveArtwork) {
+ for (const item of items) {
+ if (item instanceof models.SearchAdAction) {
+ item.action.artwork = createArtworkForResource(objectGraph, "systemimage://magnifyingglass");
+ }
+ }
+ }
+ const shelf = new models.Shelf("action");
+ shelf.isHorizontal = false;
+ shelf.items = items;
+ shelf.title = shelfToken.title;
+ shelf.subtitle = shelfToken.subtitle;
+ shelf.presentationHints = { isWidthConstrained: true };
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ if (shelfToken.isSearchLandingPage && (objectGraph.client.isPhone || objectGraph.client.isPad)) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: items.length >= 6 ? 2 : 1,
+ };
+ }
+ return shelf;
+ }
+ // region Helpers
+ trendingSearchLinkFromData(objectGraph, link, locationTracker) {
+ const term = serverData.asString(link, "label");
+ if (isNothing(term) || term.length === 0) {
+ return null;
+ }
+ const searchAction = new models.SearchAction(term, term, null, "suggested");
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", locationTracker);
+ metricsHelpersLocation.nextPosition(locationTracker);
+ return new models.SearchAdAction(searchAction);
+ }
+}
+//# sourceMappingURL=grouping-link-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller-common.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller-common.js
new file mode 100644
index 0000000..5c9273f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller-common.js
@@ -0,0 +1,608 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as adIncidents from "../../ads/ad-incident-recorder";
+import * as adStitch from "../../ads/ad-stitcher";
+import { searchLandingPagePositionInfo } from "../../ads/on-device-ad-stitch";
+import * as mediaUrlMapping from "../../builders/url-mapping";
+import * as videoDefaults from "../../constants/video-constants";
+import * as content from "../../content/content";
+import { CollectionShelfDisplayStyle } from "../../editorial-pages/editorial-page-types";
+import * as filtering from "../../filtering";
+import * as adLockups from "../../lockups/ad-lockups";
+import * as lockupsEditorialContext from "../../lockups/editorial-context";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as placeholders from "../../placeholders/placeholders";
+import * as room from "../../room/room-common";
+import * as topChartsCommon from "../../top-charts-common";
+import * as pageCommon from "../../util/page-common";
+import * as groupingTypes from "../grouping-types";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+import * as color from "../../../foundation/util/color-util";
+import { pageRouter } from "../../builders/routing";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as modelsBase from "../../../api/models/base";
+import * as metricsLocation from "../../../common/metrics/helpers/location";
+import { makeGameCenterHeader, openGamesUIAction } from "../../arcade/arcade-common";
+import { makeRoomPageIntent } from "../../../api/intents/room-page-intent";
+import { makeChartsPageIntent } from "../../../api/intents/charts-page-intent";
+import { makeChartsPageURL } from "../../../common/charts/charts-page-url";
+import { getLocale } from "../../locale";
+import { getPlatform } from "../../preview-platform";
+import { genreIdFromChartURL } from "../../../common/grouping/render-grouping-page";
+import { injectCrossfireFlowForGameCenter } from "./grouping-game-center-popular-with-your-friends-shelf-controller";
+/**
+ * Determine the shelfContentType to use for a shelf given the shelf token
+ * @param objectGraph
+ * @param mediaApiData
+ * @param shelfToken
+ * @param groupingParseContext
+ */
+export function shelfContentType(objectGraph, mediaApiData, shelfToken, groupingParseContext) {
+ const featuredContentId = shelfToken.featuredContentId;
+ if (featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */ ||
+ featuredContentId === 418 /* groupingTypes.FeaturedContentID.AppStore_Shelf */ ||
+ featuredContentId === 495 /* groupingTypes.FeaturedContentID.AppStore_PopularWithYourFriendsMarker */ ||
+ groupingTypes.isRecommendationsLockupShelf(featuredContentId)) {
+ let displayStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle");
+ if (!displayStyle) {
+ if (featuredContentId === 311 /* groupingTypes.FeaturedContentID.Sundance_RecommendedAppsShelf */ ||
+ featuredContentId === 312 /* groupingTypes.FeaturedContentID.Sundance_RecommendedGamesShelf */) {
+ displayStyle = "large";
+ }
+ else if (featuredContentId === 495 /* groupingTypes.FeaturedContentID.AppStore_PopularWithYourFriendsMarker */ ||
+ featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */) {
+ displayStyle = "medium";
+ }
+ else {
+ displayStyle = "small";
+ }
+ }
+ return `${displayStyle}Lockup`;
+ }
+ else if (featuredContentId === 431 /* groupingTypes.FeaturedContentID.AppStore_iAPShelf */) {
+ return "inAppPurchaseTiledLockup";
+ }
+ else if (featuredContentId === 429 /* groupingTypes.FeaturedContentID.AppStore_ScreenShotShelf */) {
+ return "screenshotsLockup";
+ }
+ else if (featuredContentId === 304 /* groupingTypes.FeaturedContentID.Sundance_iPhoneAppTrailerShelf */ ||
+ featuredContentId === 305 /* groupingTypes.FeaturedContentID.Sundance_iPadAppTrailerShelf */ ||
+ featuredContentId === 430 /* groupingTypes.FeaturedContentID.AppStore_VideoShelf */ ||
+ featuredContentId === 420 /* groupingTypes.FeaturedContentID.AppStore_TrailerShelf */) {
+ return "appTrailerLockup";
+ }
+ else if (groupingTypes.isTopChart(featuredContentId)) {
+ return "smallLockup";
+ }
+ else if (featuredContentId === 497 /* groupingTypes.FeaturedContentID.AppStore_ComingSoon */) {
+ return "posterLockup";
+ }
+ else {
+ switch (groupingParseContext.shelves.length % 3) {
+ case 0: {
+ return "smallLockup";
+ }
+ case 1: {
+ return "mediumLockup";
+ }
+ default: {
+ return "largeLockup";
+ }
+ }
+ }
+}
+// region Shelf Token
+export function lockupShelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ const lockupShelfToken = {
+ ...baseShelfToken,
+ shouldFilter: !mediaAttributes.attributeAsBooleanOrFalse(mediaApiData, "doNotFilter"),
+ chartUrl: mediaAttributes.attributeAsString(mediaApiData, "chartHref"),
+ chartIdentifier: mediaAttributes.attributeAsString(mediaApiData, "chart"),
+ roomRelationshipData: mediaRelationship.relationshipData(objectGraph, mediaApiData, "room"),
+ };
+ if (groupingTypes.isTopChart(lockupShelfToken.featuredContentId)) {
+ lockupShelfToken.seeAllUrl = topChartsCommon.makeSeeAllUrlFromMediaApiData(objectGraph, mediaApiData, // chart shelf data includes selected chart
+ groupingParseContext.chartSet);
+ lockupShelfToken.showOrdinals = true;
+ lockupShelfToken.shouldFilter = false;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(lockupShelfToken.roomRelationshipData)) {
+ lockupShelfToken.seeAllUrl = mediaUrlMapping.hrefToRoutableUrl(objectGraph, lockupShelfToken.roomRelationshipData.href);
+ }
+ lockupShelfToken.shelfStyle = shelfContentType(objectGraph, mediaApiData, lockupShelfToken, groupingParseContext);
+ lockupShelfToken.clientIdentifierOverride = lockupsEditorialContext.clientIdentifierForEditorialContextInData(objectGraph, mediaApiData);
+ return lockupShelfToken;
+}
+// endregion
+// region Shelf Creation
+/**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+export function createLockupShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ const filterType = groupingTypes.isMacTopChart(shelfToken.featuredContentId)
+ ? 84862 /* filtering.Filter.ChartsMac */
+ : 80894 /* filtering.Filter.All */;
+ let items = [];
+ // Stitch First Position Ad (only if lockup array is nonempty)
+ if (serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)) {
+ const adLockup = lockupFromAdStitcher(objectGraph, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.adStitcher, groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.adIncidentRecorder, searchLandingPagePositionInfo, shelfToken);
+ if (adLockup && adLockup instanceof models.Lockup) {
+ items.push(adLockup);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker);
+ shelfToken.ordinalIndex++;
+ shelfData.shelfContents = shelfData.shelfContents.filter((data) => data.id !== adLockup.adamId); // Filter dupe
+ }
+ }
+ // Data to use for `seeAllContents` in Category Breakout shelf
+ let seeAllContents;
+ // Build Lockups
+ for (const lockupData of shelfData.shelfContents) {
+ // If we encounter a type of app-events, this means they have been incorrectly programmed,
+ // and we should throw the shelf away.
+ if (lockupData.type === "app-events") {
+ return null;
+ }
+ // If we encounter a type of `editorial-items` in
+ // Category Breakout shelf, it is to use for `seeAllContents`.
+ if (shelfToken.featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */ &&
+ lockupData.type === "editorial-items") {
+ seeAllContents = lockupData;
+ continue;
+ }
+ if (serverData.isNull(lockupData.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(lockupData);
+ continue;
+ }
+ // Filter out unwanted content
+ if (filtering.shouldFilter(objectGraph, lockupData, filterType)) {
+ continue;
+ }
+ const lockup = lockupFromData(objectGraph, lockupData, shelfToken);
+ if (lockup) {
+ items.push(lockup);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker);
+ shelfToken.ordinalIndex++;
+ }
+ }
+ // Truncate non-Arcade top charts to multiples of 3
+ if (groupingTypes.isTopChart(shelfToken.featuredContentId) &&
+ !groupingTypes.isArcadeTopChart(shelfToken.featuredContentId) &&
+ !groupingTypes.isGameCenterTopChart(shelfToken.featuredContentId) &&
+ !objectGraph.client.isMac) {
+ items = pageCommon.truncateItems(items, 3);
+ }
+ // We don't need this in our incomplete shelf URL, so we'll preemptively remove it.
+ delete shelfToken.maxItemCount;
+ // Create shelf header
+ let shelfHeader;
+ if (groupingTypes.isGameCenterTopChart(shelfToken.featuredContentId)) {
+ // Show Game Center eyebrow for Game Center top chart shelves
+ shelfHeader = makeGameCenterHeader(objectGraph, shelfToken.title);
+ }
+ else {
+ shelfHeader = {
+ eyebrow: shelfToken.eyebrow,
+ eyebrowArtwork: shelfToken.eyebrowArtwork,
+ title: shelfToken.title,
+ titleArtwork: shelfToken.titleArtwork,
+ subtitle: shelfToken.subtitle,
+ configuration: shelfToken.shelfHeaderConfiguration,
+ };
+ }
+ // Create shelf
+ const shelf = new models.Shelf(shelfToken.shelfStyle);
+ shelf.isHorizontal = true;
+ shelf.items = items;
+ shelf.shouldFilterApps = shelfToken.shouldFilter;
+ if (groupingTypes.isGameCenterTopChart(shelfToken.featuredContentId)) {
+ shelf.footerTitle = objectGraph.loc.string("Lockup.Footer.GamesApp");
+ shelf.footerAction = openGamesUIAction(objectGraph);
+ shelf.footerStyle = {
+ $kind: "games",
+ bundleID: "com.apple.games",
+ width: 16,
+ height: 16,
+ };
+ shelf.items.forEach((lockup, index) => {
+ if (isSome(lockup) && lockup instanceof models.Lockup) {
+ lockup.clickAction = injectCrossfireFlowForGameCenter(objectGraph, lockup.clickAction);
+ }
+ });
+ }
+ if (shelfToken.featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */) {
+ // Configure Category Breakout shelf
+ configureCategoryBreakoutShelf(objectGraph, shelfToken, shelf, shelfHeader, items, seeAllContents, groupingParseContext);
+ }
+ else if (!objectGraph.props.enabled("redownloadButtonTintUsingOfferTheme")) {
+ shelf.items.forEach((lockup, index) => {
+ if (isSome(lockup) && lockup instanceof models.Lockup) {
+ lockup.redownloadButtonTint = color.named("systemBlue");
+ }
+ });
+ }
+ // If no items should we display placeholders for this shelf?
+ const willHydrateShelfLater = shelf && serverData.isNullOrEmpty(shelf.items) && shelfToken.isFirstRender;
+ if (willHydrateShelfLater && placeholders.placeholdersEnabled(objectGraph)) {
+ placeholders.insertPlaceholdersIntoGenericPageShelf(objectGraph, shelf, shelfToken, shelfToken.featuredContentId);
+ }
+ // Create the action
+ let action;
+ if (groupingTypes.isTopChart(shelfToken.featuredContentId)) {
+ action = new models.FlowAction("topCharts");
+ }
+ else {
+ action = new models.FlowAction("page");
+ // The web client makes a separate request for See All pages, so we don't sidepack for web.
+ if (!placeholders.isPlaceholderShelf(shelf) && !objectGraph.client.isWeb) {
+ // Create the sidepacked room page
+ const preferredShelfStyle = preferredRoomSeeAllShelfStyle(objectGraph, shelfToken.shelfStyle);
+ action.pageData = room.seeAllPage(objectGraph, shelfToken.title, preferredShelfStyle);
+ }
+ }
+ if (objectGraph.client.isWeb) {
+ if (groupingTypes.isTopChart(shelfToken.featuredContentId)) {
+ const genre = (shelfToken.seeAllUrl && genreIdFromChartURL(shelfToken.seeAllUrl)) || shelfToken.pageGenreId || "";
+ const destination = makeChartsPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ chart: shelfToken.chartIdentifier,
+ genreId: genre.toString(),
+ });
+ action.destination = destination;
+ action.pageUrl = makeChartsPageURL(objectGraph, destination);
+ }
+ else {
+ const destination = makeRoomPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: shelfToken.id,
+ });
+ action.destination = destination;
+ action.pageUrl = room.makeCanonicalRoomPageUrl(objectGraph, destination);
+ }
+ }
+ else {
+ action.pageUrl = shelfToken.seeAllUrl;
+ }
+ action.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ action.referrerUrl = shelfToken.metricsPageInformation.pageUrl;
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, action, shelfToken.seeAllUrl, {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ });
+ const shouldIncludeSeeAllAction = shouldShowSeeAll(objectGraph, shelf.items, shelfToken);
+ const seeAllActionIsValid = serverData.isDefinedNonNull(action.pageUrl) || serverData.isDefinedNonNull(action.pageData);
+ if (shouldIncludeSeeAllAction && seeAllActionIsValid) {
+ groupingShelfControllerCommon.replaceShelfHeaderSeeAllAction(objectGraph, shelfHeader, action);
+ }
+ if (shelfToken.shelfStyle === "screenshotsLockup" || shelfToken.shelfStyle === "appTrailerLockup") {
+ // `suppressTagline` is passed from MediaAPI as a string (see rdar://89933411)
+ const shouldShowSupplementaryText = mediaAttributes.attributeAsString(shelfToken.featuredContentData, "suppressTagline") !== "true";
+ if (serverData.isNull(shelf.presentationHints)) {
+ shelf.presentationHints = { showSupplementaryText: shouldShowSupplementaryText };
+ }
+ else {
+ shelf.presentationHints = {
+ ...shelf.presentationHints,
+ showSupplementaryText: shouldShowSupplementaryText,
+ };
+ }
+ }
+ // On macOS, posters should take up the full width unless the shelf has enough items.
+ if (shelfToken.shelfStyle === "posterLockup" && objectGraph.client.isMac && shelf.items.length < 3) {
+ if (serverData.isNull(shelf.presentationHints)) {
+ shelf.presentationHints = { isLowDensity: true };
+ }
+ else {
+ shelf.presentationHints = { ...shelf.presentationHints, isLowDensity: true };
+ }
+ }
+ shelf.header = shelfHeader;
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ return shelf;
+}
+// endregion
+// region Lockup Creation
+/**
+ * Create a lockup for shelfContents to display within a grouping shelf
+ * @param objectGraph
+ * @param lockupData shelfContents to create lockup for.
+ * @param shelfToken Shelf shelfToken
+ */
+export function lockupFromData(objectGraph, lockupData, shelfToken) {
+ if (serverData.isNullOrEmpty(lockupData)) {
+ return null;
+ }
+ // Set the ordinal
+ let ordinalString;
+ if (shelfToken.showOrdinals) {
+ ordinalString = objectGraph.loc.decimal(shelfToken.ordinalIndex);
+ }
+ let offerStyle = null;
+ if (serverData.isDefinedNonNull(shelfToken.shelfBackground) &&
+ (shelfToken.shelfBackground.type === "color" || shelfToken.shelfBackground.type === "interactive")) {
+ offerStyle = "white";
+ }
+ let clientIdentifierOverride;
+ if (serverData.isDefinedNonNullNonEmpty(shelfToken)) {
+ clientIdentifierOverride = shelfToken.clientIdentifierOverride;
+ }
+ // Create the lockup
+ const lockupOptions = {
+ ordinal: ordinalString,
+ metricsOptions: {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(lockupData),
+ isAdvert: adLockups.isAdvert(objectGraph, lockupData),
+ },
+ clientIdentifierOverride: clientIdentifierOverride,
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfToken.shelfStyle),
+ offerStyle: offerStyle,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfToken.shelfStyle),
+ isContainedInPreorderExclusiveShelf: shelfToken.featuredContentId === 497 /* groupingTypes.FeaturedContentID.AppStore_ComingSoon */,
+ shouldHideArcadeHeader: objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab") && shelfToken.isArcadePage,
+ };
+ let lockup;
+ switch (shelfToken.shelfStyle) {
+ case "appTrailerLockup":
+ lockup = lockups.trailersLockupFromData(objectGraph, lockupData, lockupOptions, videoDefaults.defaultVideoConfiguration(objectGraph));
+ break;
+ case "screenshotsLockup":
+ lockup = lockups.screenshotsLockupFromData(objectGraph, lockupData, lockupOptions);
+ break;
+ case "posterLockup":
+ lockup = lockups.posterLockupFromData(objectGraph, lockupData, lockupOptions);
+ break;
+ case "inAppPurchaseLockup":
+ case "inAppPurchaseTiledLockup":
+ lockup = lockups.inAppPurchaseLockupFromData(objectGraph, lockupData, lockupOptions);
+ break;
+ case "smallImageLockup":
+ case "mediumImageLockup":
+ case "largeImageLockup":
+ lockup = lockups.imageLockupFromData(objectGraph, lockupData, lockupOptions, CollectionShelfDisplayStyle.EditorialLockupLarge);
+ break;
+ default:
+ lockup = lockups.lockupFromData(objectGraph, lockupData, lockupOptions);
+ }
+ if (serverData.isNull(lockup) || !lockup.isValid()) {
+ return null;
+ }
+ return lockup;
+}
+// endregion
+// region See All
+/**
+ * Determine whether lockup shelves should show "See All" on given platforms.
+ * This covers both top charts shelves and lockup shelves.
+ * Special care should be taken to not make assumptions about the hydration state of the lockup shelves.
+ *
+ * @param objectGraph The App Store object graph.
+ * @param readyItems Items that will be vended as part of `shelf.items` in this shelf. This may be subset of items in shelf following subsequent fetch.
+ * @param shelfToken Grouping shelf shelfToken for shelf. This is used for determining whether shelf is part of initial page render, and to check whether there are more items to load.
+ * @return Whether or not a lockup shelf with given properties should have a "See All" action.
+ */
+export function shouldShowSeeAll(objectGraph, readyItems, shelfToken) {
+ // We do not want to attach "See All" if the grouping type is a Category Breakout Marker.
+ if (shelfToken.featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */) {
+ return false;
+ }
+ // We do not want to attach "See All" if the grouping type is one of the Arcade Top Chart types
+ if (groupingTypes.isArcadeTopChart(shelfToken.featuredContentId)) {
+ return false;
+ }
+ // We do not want to attach "See All" if the grouping type is one of the Game Center Top Chart types
+ if (groupingTypes.isGameCenterTopChart(shelfToken.featuredContentId)) {
+ return false;
+ }
+ // On tvOS, "See All" is exposed as a clickable cell in-line with lockup cells. We want to show "See All" cells for:
+ // 1. Shelf that have completed loading, i.e. don't have `shelfToken.remainingItems`.
+ // 2. Lockup shelves that have *a lot* of content, which we clip within grouping shelves and show in full only in in the "See All" page.
+ //
+ // *Note* that we are *NOT* showing "See All" for shelves that have a "See All" url configured on tvOS.
+ // It appears that every shelf has a "See All" url, and that url returns the exact same contents as contents of shelf's `contents.data` as part of initial page fetch causing this:
+ // <rdar://problem/50230334> RFW4: Grouping: Shelves shouldn't have see all unless there's more to show.
+ //
+ // It appears we don't have capability to know in advance whether "See All" url has more to show than what is part of `contents.data` in initial page fetch.
+ // In our experiments, it looks like they are always the same. If this changes, we'd also want:
+ // const hasSeeAllRoom = shelfToken.seeAllUrl?.length > 0;
+ // if (hasSeeAllRoom) {
+ // return true;
+ // }
+ //
+ // In it's current form, we don't do (2), since we don't limit the number of lockups in a shelf we display within groupings. That is tracked under:
+ // <rdar://problem/50671635> Grouping: Limit number of items in shelves
+ if (objectGraph.client.isTV) {
+ // Whether or not this shelf is a partially hydrated shelf. This means there should be a secondary fetch request which causes the shelf will grow.
+ // We'll opt to *NEVER* show "See All" if the shelf will grow, and tack it on in the secondary shelf fetch.
+ const willHaveSubsequentFetch = serverData.isDefinedNonNullNonEmpty(shelfToken.remainingItems);
+ if (willHaveSubsequentFetch) {
+ return false;
+ }
+ // Whether or not this shelf is a nonempty top charts.
+ const isNonemptyTopCharts = groupingTypes.isTopChart(shelfToken.featuredContentId) && serverData.isDefinedNonNullNonEmpty(readyItems);
+ if (isNonemptyTopCharts) {
+ return true;
+ }
+ // Otherwise, don't show "See All" on tvOS.
+ return false;
+ }
+ // On all other platforms, we always to attach "See All", except AppStore_ComingSoon,
+ // which should not because the see all page won't drop lockups whose pre-order has
+ // been released (which the AppStore_ComingSoon swoosh will).
+ return shelfToken.featuredContentId !== 497 /* groupingTypes.FeaturedContentID.AppStore_ComingSoon */;
+}
+/**
+ * Coerces the provided shelf content type into a type suitable for use in a 'See All' room.
+ * @param objectGraph
+ * @param originalShelfStyle The content type that defines the 'See All' parent.
+ * @returns {models.ShelfContentType} The content type to use for the 'See All' room. Returns `null` if there is no
+ * preference, at which point the room builder will provide its default.
+ */
+export function preferredRoomSeeAllShelfStyle(objectGraph, originalShelfStyle) {
+ if (!originalShelfStyle) {
+ return null;
+ }
+ switch (originalShelfStyle) {
+ case "inAppPurchaseLockup":
+ case "inAppPurchaseTiledLockup":
+ return originalShelfStyle;
+ default:
+ return null;
+ }
+}
+// endregion
+// region Category Breakout
+export function configureCategoryBreakoutShelf(objectGraph, shelfToken, shelf, shelfHeader, items, seeAllContents, groupingParseContext) {
+ // If `seeAllContents` is not hydrated, fetch it from the relationship data.
+ if (isNothing(seeAllContents)) {
+ seeAllContents = mediaRelationship.relationshipData(objectGraph, shelfToken.featuredContentData, "see-all-contents");
+ }
+ // Trailing artwork
+ shelfHeader.configuration.includeTrailingArtwork = true;
+ // If `seeAllContents` is sparse, add it to the remaining items to hydrate.
+ if (!mediaAttributes.hasAttributes(seeAllContents)) {
+ const badge = mediaAttributes.attributeAsString(shelfToken.featuredContentData, "name");
+ shelfHeader.eyebrow = badge;
+ shelfHeader.title = "";
+ shelf.footerTitle = "";
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(seeAllContents);
+ return;
+ }
+ // Badge
+ const badge = mediaAttributes.attributeAsString(shelfToken.featuredContentData, "name");
+ shelfHeader.eyebrow = badge;
+ // Title
+ const editorialNotes = mediaAttributes.attributeAsDictionary(seeAllContents, "editorialNotes");
+ const title = serverData.asString(editorialNotes, "name");
+ shelfHeader.title = title;
+ // Configure location name to the updated title
+ metricsLocation.currentLocation(shelfToken.metricsLocationTracker).name = title;
+ // Configure impression name to the updated title if placeholder shelf used
+ if (serverData.isDefinedNonNullNonEmpty(shelfToken.originalPlaceholderShelfImpressionMetrics)) {
+ shelfToken.originalPlaceholderShelfImpressionMetrics.fields.name = title;
+ }
+ // Artwork
+ const editorialArtwork = mediaAttributes.attributeAsDictionary(seeAllContents, "editorialArtwork");
+ // Drop the shelf if there is no editorial artwork.
+ if (isNothing(editorialArtwork)) {
+ shelf.isHidden = true;
+ return;
+ }
+ // Background artwork
+ const backgroundArtworkDict = serverData.asDictionary(editorialArtwork, "storyBackgroundStatic16x9");
+ const backgroundArtworkOptions = {
+ useCase: 28 /* content.ArtworkUseCase.CategoryBreakoutShelf */,
+ withJoeColorPlaceholder: true,
+ };
+ const backgroundArtwork = groupingShelfControllerCommon.groupingArtworkFromApiArtwork(objectGraph, backgroundArtworkDict, backgroundArtworkOptions);
+ const backgroundStyle = color.isDarkColor(backgroundArtwork.backgroundColor) ? "dark" : "light";
+ shelf.background = {
+ type: "artwork",
+ artwork: backgroundArtwork,
+ style: backgroundStyle,
+ };
+ // Lockup offerDisplayProperties
+ items.forEach((lockup, index) => {
+ if (isSome(lockup) && lockup instanceof models.Lockup) {
+ lockup.offerDisplayProperties = lockup.offerDisplayProperties.newOfferDisplayPropertiesChangingAppearance(false, "transparent", backgroundStyle);
+ lockup.subtitleTextFilter = "plusLight";
+ if (!objectGraph.props.enabled("redownloadButtonTintUsingOfferTheme")) {
+ lockup.redownloadButtonTint = color.named("white");
+ }
+ }
+ });
+ // Trailing artwork
+ const trailingArtworkDict = serverData.asDictionary(editorialArtwork, "contentGraphicTrimmed");
+ // Drop the shelf if there is no trailing artwork.
+ if (isNothing(trailingArtworkDict)) {
+ shelf.isHidden = true;
+ return;
+ }
+ const trailingArtworkOptions = {
+ contentMode: modelsBase.ArtworkContentMode.scaleAspectFit,
+ useCase: 18 /* content.ArtworkUseCase.GroupingBrick */,
+ };
+ const trailingArtwork = groupingShelfControllerCommon.groupingArtworkFromApiArtwork(objectGraph, trailingArtworkDict, trailingArtworkOptions);
+ shelfHeader.trailingArtwork = trailingArtwork;
+ // Footer
+ const actionUrl = serverData.asString(seeAllContents.attributes, "url");
+ const footerTitle = serverData.asString(seeAllContents.attributes, "breakoutCallToActionLabel");
+ const clickOptions = {
+ id: seeAllContents.id,
+ idType: "its_id",
+ targetType: "button",
+ actionType: "navigate",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ };
+ if (isSome(actionUrl)) {
+ // Footer title
+ shelf.footerTitle = footerTitle;
+ // Footer action
+ const flowPage = objectGraph.required(pageRouter).fetchFlowPage(actionUrl);
+ const flowAction = new models.FlowAction(flowPage);
+ flowAction.title = footerTitle;
+ flowAction.pageUrl = actionUrl;
+ metricsHelpersClicks.addClickEventToAction(objectGraph, flowAction, clickOptions);
+ shelf.footerAction = flowAction;
+ }
+ else {
+ const metricsOptions = {
+ targetType: "button",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(seeAllContents),
+ };
+ const metadata = groupingShelfControllerCommon.metadataForFCData(objectGraph, seeAllContents, shelfToken, false, null, metricsOptions, groupingParseContext);
+ if (isSome(metadata.action)) {
+ // Clear `actionMetrics` before adding a click event with updated `title`.
+ metadata.action.actionMetrics.clearAll();
+ metadata.action.title = footerTitle !== null && footerTitle !== void 0 ? footerTitle : serverData.asString(editorialNotes, "callToAction");
+ metricsHelpersClicks.addClickEventToAction(objectGraph, metadata.action, clickOptions);
+ shelf.footerAction = metadata.action;
+ shelf.footerTitle = metadata.action.title;
+ }
+ }
+}
+// endregion
+// region Ads
+/**
+ * Performs `lockupFromData`, but additional with Ad stitch related side-effects.
+ * @param objectGraph The AppStore dependency graph
+ * @param adStitcher Stitcher to source shelfContents from. May be undefined
+ * @param adIncidentRecorder Incident recorder for adding ads.
+ * @param positionInfo Position this ad is being stitched into.
+ * @param shelfToken shelfToken for shelf this ad lockup will be in.
+ */
+export function lockupFromAdStitcher(objectGraph, adStitcher, adIncidentRecorder, positionInfo, shelfToken) {
+ const task = adStitch.consumeTask(adStitcher, positionInfo);
+ if (serverData.isNull(task)) {
+ return null; // no task for position
+ }
+ // Try to create lockup
+ const lockupData = task.data;
+ const lockup = lockupFromData(objectGraph, lockupData, shelfToken);
+ if (serverData.isDefinedNonNull(lockup)) {
+ shelfToken.includedAdAdamIds = [lockupData.id];
+ }
+ else {
+ adIncidents.recordLockupFromDataFailed(objectGraph, adIncidentRecorder, lockupData);
+ }
+ return lockup;
+}
+// endregion
+//# sourceMappingURL=grouping-lockup-shelf-controller-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller.js
new file mode 100644
index 0000000..211db36
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-lockup-shelf-controller.js
@@ -0,0 +1,104 @@
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as groupingTypes from "../grouping-types";
+import { createLockupShelf, lockupShelfTokenFromBaseTokenAndMediaApiData, } from "./grouping-lockup-shelf-controller-common";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+export class GroupingLockupShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor(builderClass = null) {
+ super(builderClass || "GroupingLockupShelfController");
+ this.supportedFeaturedContentIds = new Set([
+ ...groupingTypes.topChartFeaturedContentIds,
+ ...groupingTypes.lockupShelfFeaturedContentIds,
+ ]);
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ const shelfContents = mediaRelationship.relationship(mediaApiData, "contents");
+ let shelfData = shelfContents ? shelfContents.data : null;
+ if (!shelfData || shelfData.length === 0) {
+ shelfData = mediaRelationship.relationshipCollection(mediaApiData, "children");
+ }
+ return { shelfContents: shelfData };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters);
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return lockupShelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext);
+ }
+ // endregion
+ // region Metrics
+ /**
+ * Return the shelf metrics options to use for this specific shelf. Using the base options from the grouping
+ * page controller
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the
+ * grouping page controller
+ */
+ shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) {
+ const shelfMetricsOptions = { ...baseMetricsOptions };
+ shelfMetricsOptions.displayStyle = shelfToken.shelfStyle;
+ // Reconfigure Category Breakout metrics options
+ if (shelfToken.featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */) {
+ const seeAllContents = mediaRelationship.relationshipData(objectGraph, shelfToken.featuredContentData, "see-all-contents");
+ const editorialNotes = mediaAttributes.attributeAsDictionary(seeAllContents, "editorialNotes");
+ const title = serverData.asString(editorialNotes, "name");
+ shelfMetricsOptions.title = title;
+ shelfMetricsOptions.idType = "its_contentId";
+ shelfMetricsOptions.badges = { forYou: true };
+ shelfMetricsOptions.targetType = "swooshBreakout";
+ }
+ return shelfMetricsOptions;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ const isCategoryBreakoutShelf = shelfToken.featuredContentId === 557 /* groupingTypes.FeaturedContentID.AppStore_CategoryBreakoutMarker */;
+ if (isCategoryBreakoutShelf) {
+ const hasNoContents = !serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents);
+ const isClientPad = objectGraph.client.isPad;
+ // Drop Category Breakout IF
+ // - It has no contents OR
+ // - Client is an iPad OR
+ if (hasNoContents || isClientPad) {
+ return null;
+ }
+ }
+ return createLockupShelf(objectGraph, shelfToken, shelfData, groupingParseContext);
+ }
+}
+//# sourceMappingURL=grouping-lockup-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-personalized-lockup-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-personalized-lockup-shelf-controller.js
new file mode 100644
index 0000000..7e28d30
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-personalized-lockup-shelf-controller.js
@@ -0,0 +1,368 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaNetwork from "../../../foundation/media/network";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { ResponseMetadata } from "../../../foundation/network/network";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as urls from "../../../foundation/network/urls";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as odpCommon from "../../personalization/on-device-recommendations-common";
+import { defaultRoomShelfContentType } from "../../room/room-page";
+import { platformPrefersLargeTitles } from "../../room/room-common";
+import * as groupingTypes from "../grouping-types";
+import { createLockupShelf, lockupShelfTokenFromBaseTokenAndMediaApiData, } from "./grouping-lockup-shelf-controller-common";
+import { GroupingShelfController, routesForFeaturedContentIds } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+import { isNothing, isSome } from "@jet/environment";
+export class GroupingPersonalizedLockupShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingPersonalizedLockupShelfController");
+ this.supportedFeaturedContentIds = groupingTypes.recommendationsLockupShelfFeaturedContentIds;
+ }
+ // endregion
+ // region Shelf Builder
+ shelfRoute(objectGraph) {
+ return routesForFeaturedContentIds(this.supportedFeaturedContentIds, [
+ `${Parameters.isOnDeviceRecommendationsShelf}?`,
+ `${Parameters.onDeviceRecommendationsUseCase}?`,
+ ]);
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ const shelfContents = mediaRelationship.relationship(mediaApiData, "contents");
+ let shelfData = shelfContents ? shelfContents.data : null;
+ if (!shelfData || shelfData.length === 0) {
+ shelfData = mediaRelationship.relationshipCollection(mediaApiData, "children");
+ }
+ const recoMetrics = mediaDataStructure.metricsFromMediaApiObject(shelfContents);
+ return {
+ shelfContents: shelfData || [],
+ containsODPShelfContents: false,
+ recoMetrics: recoMetrics,
+ candidates: null,
+ isHiddenShelf: objectGraph.client.isWeb,
+ responseTimingValues: null,
+ };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ var _a;
+ const onDeviceRecommendationsUseCase = parameters[Parameters.onDeviceRecommendationsUseCase];
+ if ((onDeviceRecommendationsUseCase === null || onDeviceRecommendationsUseCase === void 0 ? void 0 : onDeviceRecommendationsUseCase.length) > 0) {
+ return await odpCommon
+ .recommendedAppsForUseCase(objectGraph, onDeviceRecommendationsUseCase, "shelf")
+ .then((response) => {
+ return {
+ shelfContents: mediaDataStructure.dataCollectionFromDataContainer(response.dataContainer),
+ containsODPShelfContents: true,
+ recoMetrics: response.recoMetrics,
+ candidates: response.candidates,
+ isHiddenShelf: false,
+ };
+ })
+ .catch(async (error) => {
+ if (error instanceof odpCommon.NoODPCandidatesError) {
+ return await GroupingPersonalizedLockupShelfController.fetchODPFallbackContent(objectGraph, shelfUrl, shelfToken, parameters).catch((fallbackError) => {
+ return GroupingPersonalizedLockupShelfController.makeHiddenShelfData(shelfToken);
+ });
+ }
+ else {
+ return GroupingPersonalizedLockupShelfController.makeHiddenShelfData(shelfToken);
+ }
+ });
+ }
+ else if (((_a = shelfToken.recommendationsHref) === null || _a === void 0 ? void 0 : _a.length) > 0) {
+ return await GroupingShelfController.secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters).then((mediaApiData) => {
+ const personalizedFeaturedContentData = mediaDataStructure.dataFromDataContainer(objectGraph, mediaApiData);
+ const personalizedLockupShelfData = this.initialShelfDataFromGroupingMediaApiData(objectGraph, personalizedFeaturedContentData);
+ personalizedLockupShelfData.responseTimingValues = mediaApiData[ResponseMetadata.timingValues];
+ personalizedLockupShelfData.shelfTitle = mediaAttributes.attributeAsString(personalizedFeaturedContentData, "name");
+ return personalizedLockupShelfData;
+ });
+ }
+ else {
+ return await GroupingShelfController.secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters).then((mediaApiData) => {
+ const personalizedFeaturedContentData = mediaDataStructure.dataCollectionFromDataContainer(mediaApiData);
+ const personalizedLockupShelfData = {
+ shelfContents: personalizedFeaturedContentData || [],
+ containsODPShelfContents: false,
+ recoMetrics: null,
+ candidates: null,
+ isHiddenShelf: false,
+ responseTimingValues: mediaApiData[ResponseMetadata.timingValues],
+ };
+ personalizedLockupShelfData.shelfTitle = shelfToken.title;
+ return personalizedLockupShelfData;
+ });
+ }
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ const personalizedLockupShelfToken = lockupShelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext);
+ this.addPersonalizationValuesToShelfToken(objectGraph, personalizedLockupShelfToken, mediaApiData, groupingParseContext);
+ return personalizedLockupShelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ var _a, _b;
+ if (!shelfToken.isValidRecommendationsShelf) {
+ return null;
+ }
+ let shelf = null;
+ if (isNothing(shelfToken.title)) {
+ // If we've fetched the title in a secondary lookup, update the title here
+ // in the token, since other places may rely on it.
+ shelfToken.title = shelfData.shelfTitle;
+ }
+ if (shelfData.containsODPShelfContents) {
+ shelf = this.personalizedShelf(objectGraph, shelfData, shelfToken, groupingParseContext);
+ const seeAllUrl = new urls.URL()
+ .set("protocol", Protocol.internal)
+ .path(`${Path.onDeviceRecommendations}`)
+ .param(Parameters.onDeviceRecommendationsUseCase, shelfToken.onDeviceRecommendationsUseCase)
+ .param(Parameters.token, JSON.stringify(shelfToken))
+ .build();
+ const seeAllAction = new models.FlowAction("page", seeAllUrl);
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ seeAllAction.pageData = this.odpSeeAllPage(objectGraph, shelfData.shelfTitle, defaultRoomShelfContentType);
+ groupingShelfControllerCommon.replaceShelfSeeAllAction(objectGraph, shelf, seeAllAction);
+ shelf.mergeWhenFetched = false;
+ }
+ else if (shelfData.isHiddenShelf) {
+ shelf = GroupingPersonalizedLockupShelfController.makeHiddenShelf(shelfToken);
+ }
+ else if (serverData.isDefinedNonNull(shelfToken.recommendationsHref)) {
+ shelf = this.personalizedShelf(objectGraph, shelfData, shelfToken, groupingParseContext);
+ shelf.url = this.addOnDeviceQueryParamsIfNecessary(objectGraph, shelf.url, shelfToken);
+ }
+ else {
+ shelf = createLockupShelf(objectGraph, shelfToken, shelfData, groupingParseContext);
+ shelf.url = this.addOnDeviceQueryParamsIfNecessary(objectGraph, shelf.url, shelfToken);
+ }
+ const isUsingHeader = isSome(shelf.header);
+ const isMissingHeaderTitle = isUsingHeader && serverData.isNullOrEmpty((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.title);
+ const isMissingLegacyShelfTitle = !isUsingHeader && serverData.isNullOrEmpty(shelf.title);
+ const hasShelfDataTitle = ((_b = shelfData.shelfTitle) === null || _b === void 0 ? void 0 : _b.length) > 0;
+ if (isMissingHeaderTitle && hasShelfDataTitle) {
+ shelf.header.title = shelfData.shelfTitle;
+ }
+ else if (isMissingLegacyShelfTitle && hasShelfDataTitle) {
+ shelf.title = shelfData.shelfTitle;
+ }
+ return shelf;
+ }
+ // endregion
+ // region Helpers
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * @private
+ */
+ personalizedShelf(objectGraph, shelfData, shelfToken, groupingParseContext) {
+ /**
+ * Update shelf impressions for personalized shelves.
+ * <rdar://problem/69308786> reco_algo_id doesn't get passed to Figaro for out of band personalization marker
+ *
+ * By happenstance, the location tracker encoded in the shelf token usually contains the swoosh location, given the happy path fetches *more* items.
+ * For Personalized shelves, we need to pop the "empty" shelf location and replace it with the fully populated one.
+ */
+ const location = metricsHelpersLocation.currentLocation(shelfToken.metricsLocationTracker);
+ if (location && location.fcKind === shelfToken.featuredContentId) {
+ metricsHelpersLocation.popLocation(shelfToken.metricsLocationTracker);
+ }
+ const shelfMetricsOptions = {
+ id: shelfToken.id,
+ kind: null,
+ softwareType: serverData.asBooleanOrFalse(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage) ? "Arcade" : null,
+ targetType: "swoosh",
+ title: shelfToken.title,
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ idType: "its_contentId",
+ fcKind: shelfToken.featuredContentId,
+ recoMetricsData: shelfData.recoMetrics,
+ displayStyle: shelfToken.shelfStyle,
+ };
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, shelfToken.title);
+ const shelf = createLockupShelf(objectGraph, shelfToken, shelfData, groupingParseContext);
+ metricsHelpersLocation.popLocation(shelfToken.metricsLocationTracker);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ return shelf;
+ }
+ /**
+ * Check in the media api data for this shelf and add any of the necessary personalization fields to the shelfToken.
+ *
+ * @param objectGrpah
+ * @param shelfToken The shelf token in its current state
+ * @param mediaApiData The media api data for this section of the grouping page
+ * @param groupingParseContext The parse context for the grouping page so far
+ * @private
+ */
+ addPersonalizationValuesToShelfToken(objectGrpah, shelfToken, mediaApiData, groupingParseContext) {
+ const isPersonalizedShelfMarker = shelfToken.featuredContentId === 476 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedShelfMarker */;
+ // If we don't have a logged in user don't show personalized shelves
+ // For search grouping we allow these shelves even if there is no logged in user.
+ // For the personalized shelf markers we are going to allow reco to send back a fallback list of ids so that
+ // even logged out users can see these shelves.
+ // rdar://64005675 (Client to not drop personalized marker response for non-signed users (dsId - 0))
+ if (!groupingParseContext.hasAuthenticatedUser &&
+ !shelfToken.isSearchLandingPage &&
+ !isPersonalizedShelfMarker) {
+ objectGrpah.console.log(`Skipping recommendations shelf with fcID ${shelfToken.featuredContentId}: No user logged-in`);
+ return;
+ }
+ const onDevicePersonalizationUseCase = mediaAttributes.attributeAsString(mediaApiData, "onDevicePersonalizationUseCase");
+ const usingOnDevicePersonalization = (onDevicePersonalizationUseCase === null || onDevicePersonalizationUseCase === void 0 ? void 0 : onDevicePersonalizationUseCase.length) > 0;
+ if (usingOnDevicePersonalization) {
+ shelfToken.onDeviceRecommendationsUseCase = onDevicePersonalizationUseCase;
+ shelfToken.recommendationsHref = mediaApiData.href;
+ shelfToken.isValidRecommendationsShelf = true;
+ return;
+ }
+ const shelfContents = mediaRelationship.relationshipCollection(mediaApiData, "contents");
+ const hasContents = serverData.isDefinedNonNullNonEmpty(shelfContents);
+ const shelfPersonalizationAvailable = !mediaAttributes.attributeAsBooleanOrFalse(mediaApiData, "noPersonalizationAvailable");
+ if (!hasContents && shelfPersonalizationAvailable) {
+ shelfToken.recommendationsHref = mediaApiData.href;
+ shelfToken.isValidRecommendationsShelf = true;
+ // tslint:disable-next-line:no-redundant-jump
+ }
+ else {
+ shelfToken.isValidRecommendationsShelf = hasContents;
+ }
+ }
+ /**
+ * For a given shelf url, add the on device personalization query param if its needed
+ *
+ * @param objectGraph
+ * @param urlString The original url string for this shelf
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ */
+ addOnDeviceQueryParamsIfNecessary(objectGraph, urlString, shelfToken) {
+ var _a;
+ if (serverData.isNullOrEmpty(urlString)) {
+ return null;
+ }
+ const isOnDeviceRecommendationsGamesForYouEnabled = objectGraph.host.isiOS;
+ if (!isOnDeviceRecommendationsGamesForYouEnabled) {
+ return urlString;
+ }
+ if (((_a = shelfToken.onDeviceRecommendationsUseCase) === null || _a === void 0 ? void 0 : _a.length) > 0) {
+ const url = new urls.URL(urlString);
+ url.param(Parameters.isOnDeviceRecommendationsShelf, "true");
+ url.param(Parameters.onDeviceRecommendationsUseCase, shelfToken.onDeviceRecommendationsUseCase);
+ return url.build();
+ }
+ else {
+ return urlString;
+ }
+ }
+ /**
+ * Creates a page that can be used for side-packing see all pages into a room.
+ *
+ * @param objectGraph
+ * @param {string} title The title of the destination page
+ * @param {ShelfContentType} preferredShelfContentType The content type to use for the page
+ * @returns {GenericPage} A GenericPage which will use the parentShelfItems from the see all to render the initial room
+ */
+ odpSeeAllPage(objectGraph, title, preferredShelfContentType) {
+ const shelf = new models.Shelf(preferredShelfContentType || defaultRoomShelfContentType);
+ shelf.isHorizontal = false;
+ shelf.items = "parentShelfItems";
+ const page = new models.GenericPage([shelf]);
+ page.isIncomplete = true;
+ page.title = title;
+ if (platformPrefersLargeTitles(objectGraph)) {
+ page.presentationOptions = ["prefersLargeTitle"];
+ }
+ return page;
+ }
+ // endregion
+ // region ODP Fallback
+ static async fetchODPFallbackContent(objectGraph, url, shelfToken, parameters) {
+ return await new Promise((resolve, reject) => {
+ const fallbackRequest = groupingShelfControllerCommon.generateShelfRequest(objectGraph, shelfToken, parameters);
+ if (!fallbackRequest) {
+ const errorMessage = `OnDeviceRecommendationsShelfController: Could not construct media API request for: ${url}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ return;
+ }
+ groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, fallbackRequest);
+ fallbackRequest.attributingTo(url.build());
+ mediaNetwork
+ .fetchData(objectGraph, fallbackRequest)
+ .then((dataContainer) => {
+ const shelfData = mediaDataStructure.dataFromDataContainer(objectGraph, dataContainer);
+ const shelfContents = mediaRelationship.relationship(shelfData, "contents");
+ const recoMetrics = mediaDataStructure.metricsFromMediaApiObject(shelfContents);
+ const fallbackShelfData = {
+ shelfContents: mediaDataStructure.dataCollectionFromDataContainer(shelfContents),
+ containsODPShelfContents: false,
+ recoMetrics: recoMetrics,
+ candidates: null,
+ responseTimingValues: dataContainer[ResponseMetadata.timingValues],
+ };
+ resolve(fallbackShelfData);
+ })
+ .catch((error) => {
+ const errorMessage = `OnDeviceRecommendationsShelfController: Failed to fetch fallback shelf contents: ${url}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(error);
+ });
+ });
+ }
+ static makeHiddenShelfData(shelfToken) {
+ const hiddenShelfData = {
+ shelfContents: [],
+ containsODPShelfContents: false,
+ recoMetrics: null,
+ candidates: null,
+ isHiddenShelf: true,
+ };
+ return hiddenShelfData;
+ }
+ static makeHiddenShelf(shelfToken) {
+ const hiddenShelf = new models.Shelf(shelfToken.shelfStyle);
+ hiddenShelf.isHidden = true;
+ return hiddenShelf;
+ }
+}
+//# sourceMappingURL=grouping-personalized-lockup-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-ribbon-bar-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-ribbon-bar-shelf-controller.js
new file mode 100644
index 0000000..bc0211c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-ribbon-bar-shelf-controller.js
@@ -0,0 +1,269 @@
+import { attributeAsString } from "@apple-media-services/media-api";
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { seeAllArcadeGamesPageFlowAction } from "../../arcade/arcade-common";
+import { pageRouter } from "../../builders/routing";
+import { hrefToRoutableUrl } from "../../builders/url-mapping";
+import { categoryArtworkData } from "../../categories";
+import * as artworkBuilder from "../../content/artwork/artwork";
+import * as content from "../../content/content";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { areAppTagsEnabled } from "../../util/app-tags-util";
+import * as lottery from "../../util/lottery";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+export class GroupingRibbonBarShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingRibbonBarShelfController");
+ this.supportedFeaturedContentIds = new Set([556 /* groupingTypes.FeaturedContentID.AppStore_RibbonBarMarker */]);
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters);
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ var _a;
+ const items = [];
+ const shelf = new models.Shelf("ribbonBar");
+ shelf.isHorizontal = true;
+ const isSAGUpliftEnabledForCurrentUser = lottery.isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.arcadeCategoryBarSAGUpliftDisplayRate);
+ // Display See All Games facet when:
+ // - This is an Arcade page AND
+ // - This is the first render of the shelf AND
+ // - Bag is enabled for current user
+ if (shelfToken.isArcadePage && shelfToken.isFirstRender && isSAGUpliftEnabledForCurrentUser) {
+ const seeAllGamesRibbonItem = GroupingRibbonBarShelfController.createSeeAllGamesRibbonItem(objectGraph, shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker);
+ items.push(seeAllGamesRibbonItem);
+ metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker);
+ }
+ // If we have any hydrated items stored in the token, it means we found an unhydrated priorized item. In this
+ // case, we combine the initial hydrated items with the secondary hydrated items, and build the entire set
+ // of ribbon items from this list. As we are manipulating the order, we also need to replace the original
+ // shelf rather than merge.
+ let combinedShelfContents = (_a = shelfData.shelfContents) !== null && _a !== void 0 ? _a : [];
+ if (isSome(shelfToken.initialHydratedItems) && shelfToken.initialHydratedItems.length > 0) {
+ combinedShelfContents = shelfToken.initialHydratedItems.concat(combinedShelfContents);
+ }
+ // Tracks the initial set of hydrated items from the initial shelf load. This will be stored in the shelf
+ // token only if we find a prioritized item that is deferred.
+ const initialHydratedItems = [];
+ let isPrioritizedItemDeferred = false;
+ for (const ribbonData of combinedShelfContents) {
+ if (serverData.isNull(ribbonData.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(ribbonData);
+ if (GroupingRibbonBarShelfController.shouldPriorizeItemWithData(objectGraph, ribbonData)) {
+ isPrioritizedItemDeferred = true;
+ }
+ continue;
+ }
+ let isTextOnly = false;
+ if (isSome(shelfToken.featuredContentData)) {
+ const displayStyle = attributeAsString(shelfToken.featuredContentData, "displayStyle");
+ isTextOnly = displayStyle === "textOnly";
+ }
+ const ribbonModel = GroupingRibbonBarShelfController.createRibbonItem(objectGraph, ribbonData, shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker, isTextOnly, shelfToken, groupingParseContext);
+ if (isSome(ribbonModel)) {
+ if (GroupingRibbonBarShelfController.shouldPriorizeItemWithData(objectGraph, ribbonData)) {
+ items.unshift(ribbonModel);
+ }
+ else {
+ items.push(ribbonModel);
+ }
+ initialHydratedItems.push(ribbonData);
+ }
+ metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker);
+ }
+ if (objectGraph.client.isiOS ||
+ objectGraph.featureFlags.isEnabled("shelves_2_0_arcade") ||
+ objectGraph.featureFlags.isEnabled("shelves_2_0_generic")) {
+ shelf.items = items;
+ if (isPrioritizedItemDeferred && initialHydratedItems.length > 0) {
+ shelfToken.initialHydratedItems = initialHydratedItems;
+ }
+ }
+ else {
+ // Only set `shelf.items` if there are any `items` present
+ // so that hydrated items reload when they are fetched.
+ if (items.length > 0) {
+ const ribbonBar = new models.RibbonBar(items);
+ shelf.items = [ribbonBar];
+ }
+ }
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ return shelf;
+ }
+ // region Static Helpers
+ static createRibbonItem(objectGraph, itemData, metricsPageInformation, metricsLocationTracker, isTextOnly, shelfToken, groupingParseContext) {
+ var _a, _b, _c;
+ const metricsOptions = {
+ targetType: "facet",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ };
+ let metadata;
+ if (itemData.type === "tags") {
+ if (!areAppTagsEnabled(objectGraph, "grouping")) {
+ return null;
+ }
+ metadata = groupingShelfControllerCommon.metadataForTag(objectGraph, itemData, shelfToken, metricsOptions);
+ }
+ else {
+ metadata = groupingShelfControllerCommon.metadataForFCData(objectGraph, itemData, shelfToken, false, null, metricsOptions, groupingParseContext);
+ }
+ const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, itemData, metricsOptions);
+ metricsClickOptions.targetType = metricsOptions.targetType;
+ const actionFromData = lockups.actionFromData(objectGraph, itemData, metricsClickOptions, shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.clientIdentifierOverride);
+ const action = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.action) !== null && _a !== void 0 ? _a : actionFromData;
+ const editorialNotesName = (_b = content.editorialNotesFromData(objectGraph, itemData, "name")) !== null && _b !== void 0 ? _b : attributeAsString(itemData, "name");
+ const title = (_c = metadata === null || metadata === void 0 ? void 0 : metadata.title) !== null && _c !== void 0 ? _c : editorialNotesName;
+ const ribbonModel = new models.RibbonBarItem(title, action);
+ // Setup Artwork
+ const artworkDict = categoryArtworkData(objectGraph, itemData);
+ let artwork;
+ if (isTextOnly) {
+ artwork = null;
+ }
+ else {
+ if (isSome(artworkDict)) {
+ artwork = content.artworkFromApiArtwork(objectGraph, artworkDict, {
+ useCase: 29 /* content.ArtworkUseCase.RibbonBarFacet */,
+ });
+ }
+ else {
+ let resource;
+ if (shelfToken.isArcadePage) {
+ resource = "resource://arcade-ribbon-bar-fallback-icon";
+ }
+ else {
+ resource = "resource://appstore-ribbon-bar-fallback-icon";
+ }
+ artwork = artworkBuilder.createArtworkForResource(objectGraph, resource, 36, 36);
+ }
+ }
+ ribbonModel.artwork = artwork;
+ ribbonModel.accessibilityLabel = title;
+ // Configure impressions
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, itemData, title, metricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, ribbonModel, impressionOptions);
+ if (!ribbonModel.isValid()) {
+ return null;
+ }
+ return ribbonModel;
+ }
+ /// Creates and returns Arcade See All Games ribbon item.
+ static createSeeAllGamesRibbonItem(objectGraph, metricsPageInformation, metricsLocationTracker) {
+ const title = objectGraph.loc.string("Arcade.CategoryBar.AllGames.Title");
+ const action = seeAllArcadeGamesPageFlowAction(objectGraph, "releaseDate", metricsPageInformation, metricsLocationTracker, title, "AllGames", "none", "facet");
+ const ribbonModel = new models.RibbonBarItem(title, action);
+ // Setup Artwork
+ // To update this artwork, run `cat artwork.png | base64` and prefix with `data:image/png;base64,`.
+ const resource = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAACACAYAAADHy7H2AAABdWlDQ1BrQ0dDb2xvclNwYWNlRGlzcGxheVAzAAAokXWQvUvDUBTFT6tS0DqIDh0cMolD1NIKdnFoKxRFMFQFq1OafgltfCQpUnETVyn4H1jBWXCwiFRwcXAQRAcR3Zw6KbhoeN6XVNoi3sfl/Ticc7lcwBtQGSv2AijplpFMxKS11Lrke4OHnlOqZrKooiwK/v276/PR9d5PiFlNu3YQ2U9cl84ul3aeAlN//V3Vn8maGv3f1EGNGRbgkYmVbYsJ3iUeMWgp4qrgvMvHgtMunzuelWSc+JZY0gpqhrhJLKc79HwHl4plrbWD2N6f1VeXxRzqUcxhEyYYilBRgQQF4X/8044/ji1yV2BQLo8CLMpESRETssTz0KFhEjJxCEHqkLhz634PrfvJbW3vFZhtcM4v2tpCAzidoZPV29p4BBgaAG7qTDVUR+qh9uZywPsJMJgChu8os2HmwiF3e38M6Hvh/GMM8B0CdpXzryPO7RqFn4Er/QcXKWq8UwZBywAAAARjSUNQDA0AAW4D4+8AAAB4ZVhJZk1NACoAAAAIAAUBEgADAAAAAQABAAABGgAFAAAAAQAAAEoBGwAFAAAAAQAAAFIBKAADAAAAAQACAACHaQAEAAAAAQAAAFoAAAAAAAAASAAAAAEAAABIAAAAAQACoAIABAAAAAEAAACCoAMABAAAAAEAAACAAAAAACBAcQEAAAAJcEhZcwAACxMAAAsTAQCanBgAAAIGaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4yMTk2PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjIxNjA8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KIUPY5gAAGmtJREFUeAHtXXuMJMV5r+qZ2Qe7h299bA7O3B0GGwOXBwEsbMXCC1H+QJjHgpbHEcyBEiLl4cgJxLKElIkUS4DtQCCSpZOSEAx3hjW+YwmclAfa4MQScfBL2Tg5ED5uyfFYw0Lu9nZvHl35/b7qmu3Z65numZ3dndnpupvt7np+31e/+qq6uuorpVKXSiCVQCqBVAKpBFIJpBJIJZBKIJVAKoF6EtD1ApcbZpRa0fxJHwpAMe3hVprfleS1ZRVlRkayqr8/ozbPa7Wh36gTW3118KBRw8NG7djR2sqamrJ0D73mqdmztRqY8QQKc8M+yirpfN5fSWgYA/zdOJarKnfoNQNabLkrwe/MjFbnnqvV9LRXkfGm+bLOT5ZaweuygWCuvLKXhOgDB060gqDl5mHyylNTY6SpoMfHy8vNL5ze5PPIe2pF8g6X08i9GRvrUacf0/qR5cm/aSCIUA5N9uhHJxcc4ebma7aoTHabMmYLNPZpaDcbAJE+hGfg13RZLn939Y0xnucVlPGPI/9ZlPUO7t9Qpb7XUfnHXDyza6QvTJ/zb+YqgD9woAAmRLuhAvpVdmG70t6ZyujTkecQaBnwld/jad0yXgNayyjjBCB+FGX8HH5vqkJxWo8/e9jxIoCYmfH1ZHMaoimCTR7dgJr0dV755o6xYVUo3YiKvgJEfgJyolAAANWjPKuxhdimSnJsLrm6jgY6GuVSHc/jbhbiP4xq+gGENqH37vtHpjK7dgEMj1bAuiSnRI8EgdN45pbRzyDX6wDyS5D4LFw/jDIHlNYefja/leCVOVt+i7gj2N/B7xXw+qLKZZ/Uj40LKAgINIYCwhpyDZNs7rorp3fvJjHK3Dx6p9LmbuXp86XSSajvs5oYTrXMSqIvZcRLSxwydCIH0lABymThw58UpMrl4xDQ8ypT/rJ+fOLV5WgGJ1gzduWwyvU+AAauA68bAQBwB5aMKYOeEoq2/LaQz4qwOCYBZ/ixZWVQak5D7Qjrvoj4Z/B/WD+x/yFcVRi4fE7imHlixwGhUz1m57V/CUK+IML3/XmQeYI1LjVkQDDIRNVL/ga1whsE0xvRws8n+yMWo8KFuxP6uWcXbiPhr0iDCRApi7JOUdlMRpXK00iyC9rhhWbA4Pg1t157AYh/Gt3eeQAZKxxdklQ8LsIjKygkS0un47eaboYt5WWRLysbKyOXOa+ShhLlP2SIH3lmmTkIfQCNERSZx1Qx+9vUCA7ACE/kyEAiZzgI4xsAnNl53UMqm/0CxF6EBkAfrUogrQe13YP7HpCHrgPjAotgkEgcoOO0fad9to8MQqDghF2rPCM9gYSfe+YVz4xHvyDfIF4GVwhDyu1FIHk6BhB8gJa7FTL/trn1+k9xrMBuAmGJHMdABL0Zu3obyj2gvAxB8AESUy2TPvLJH8smDSw3+JFGchPQKtfwPdPjORwuz6xOGw+PEkF4DvyQP8uB5pMye3GlvAENM6vK/hy6iM/72eJfw08JGKQL51O8o1ATOSCsH5nPm1tHb0eCR1E4+6HjATEWo4lyWoVI5Mq2nKKocd9Mqb7yp/XfTBwNd21JKDE7R19QWe9yAOs9xOcbAytbGkSS9Csex/JKFcOBbAYNdFCVS3ejm/g6/LTK53WS12kyFesCNTNvdl6FkbH5kiTQAQgokvYRi5DG/gEioIiyxi/PoZ3tUAven0hg4SA1VV1n7ro4xwgCeguC9/FIbWLFzsB2cVb27FuoHU5Aa6HT0F80O2/4OPyMOnIkll+ykggIiGcz0z03Ievz8eMoPFEBLGS1HWsrACdVbRndFwcPNxLI0kXw3bue2/2ynaQx5nehCRiTKjnIsl7CNQ0jJPqkm8hlPoLX6RtIDQf28qofQ1osEKANMuwSmA9e1H5d3g6UKUCwmBuIyX2tgwUR0oqL6HC3o9FcJiQtLHAME+lkgAhWzW2jv4gIv4IfB4bCq80uMlm7eLI+MYhExWhzBTW5JWwytp5jIyAjafnm86ObIMxz2bTg7LDfltK+fy1Q2UkUMd5C/24uFmLPss08kvDhYSuTkvokQM8xgaiEyLjt6ekFWuw8lfPPFBKPHI3FcDwQBgZsnEIZo2ezOQBCe4ogmiq+dPky8DbqYxJl06Xx6tIzO6Kza2NfO4eBhmvYdaOu/K1CrfsGUof0eCDMzVk0efoM5DOIH/vPWITVKXO1g0gr1CWclqlgJaPoyRrqkh+P6Hy9XUDfSZxaylGnmm9LPZiXxlQ/3FPjnPqqy0k8EAYGbPZKnYYsOXKmqqybKcLbz0mXZjaaO6/h9LfCnEg071uutl2BNsMSL5gUk/vO+MO6KXNGIqPVJpKMLt2+TNehP1oY4QRvv22BoD0KkGqns0RTBVnTr+Yx60h37FhVCL3YakLv3IP4tkXvTnPkCxqQF3Nqhfg/y5/EbyUMN/FAGBwMpGH6pJ/l9GhnQYH8BjzoHIa5diRtRwthWSiVt5qOb0pIgVlKbbuU6ljt/WSrO6gh+fKbiN54ICxmY+cN7IBk0bez7sCvtnxE0p23vjuwCESmjIl59x0gMkGbe9bjtZr0RoBQnbJLnoCIjuwfpHoaWAOSAiEW0J2sEWKZq0RIgVARRfRN56qDaH5q+aZAqCEZ+25Ud6BdI2VneqdAqFtv3aIPkrw+1hVUGrheJJBqhPVSk8vkIwXCMgW4XpKnQFgvNblMPlIgLFOA6yV5CoT1UpPL5CMFwjIFuF6Sp0BYLzW5TD5SICxTgOsleQqE9VKTy+Sju4DQPZ8OGoZFdwGhgU8Hss4P4uwW7HQXEBpuJ0zQwQtTGuA3BUIDwlrPUZMDwV8XLQOWG7z4DmKSVe747eAVSl7yxbfxQKgs+4YtBDqsjxVJxotTorfJH9fVl1WxYDe4vhpBWT5vuZqcxN4QE2zkcYCIiN++XpZf2q9I6OKB4JZ9e3oOwuFSf2ydIBwSlrDW0cKAhdE/1Z+z9pQqy/SrCUR0jBMF7tz42ylcLjJh+Q3Wmpu5xYD6d/FAOLrZCsMo2ggoQEpMExZv/RLaKVTDKtnMAi2ewAba0Ek8CADGxgKZwFobIdF5jnx5sl1PLM5JZdFgxkn8hlmLB4Lb+6j020jIVsJ9AXUzDRfQJvdYlS6V+q6zjqZmZ2ttXrH7HgxM2DFNJ+3jIIta9nlmxWCGljrDxp08OalbZ/FAGJgJMtBv4IatJD5Nm9R+QAbpB8286NfpJxZRxsejgRCMiXxP2VFEwD3TdYTjzm+Y+AHZ7wMM/ys019rwG2IovlJ7zrWbQgc3TQNVh4OWFYOvUAlrfSstBDt+YKUT28T/S8iZPZuW16KrOBg7QDA/oqUVOETtEGdHbtgA62UxxH0dw/ppofzcVthH2L27tGiASv8gkF8WBUS3qDaTmd3Harjf8V2A+HtC3uIO75OphS1n8SzkXkI/+xbQwrTRoDk59Zr5hAiELRvAWOvv673P0kor9HhgI7oOdbEaQVrO7CwrHq68Hy3r/3DD7fEwPkG/tnacBOD4gHz+u96z/0cQmFZnnVXTMil3Q7PrgLmg95DuOZ3xepCmRF5Dwm47pjkIgCOd/apYQgdRfpYeYgoogU3qWCAwM1g8PyF2B/dMvIiCngPiOKBKsOteUq/lH2qtHrRsvE+bB4WQsbG+0Nb3aNoqLShzP/rZBcgYecB0RnTs9vC13cIJmNfjtv/n1HkX/b0QNpKMvERAEMG99JKYnINhx7vxO4TZBNpLoBptx4ZCmggCDxZYqdof0Xue+SdnJhDPdR20QVmMiO15+hXg/R4YsuxHAlgdsR1N3cSrHCjCtwiF9Vt9GrTBDOrmXtFstMuc0Ix/IiCQN752iRnbb00cwSBqDGJBgd6HQIgTOgXPe6GNaVbRuXJJQ0AHbDNnswOqWN6LLuGPA1rswDcBYQQDoyHtX6lS8avI61T0MZQX8hdArDG/jgZcjRgLg3FwQwOot+vH9/04eDNKPLPYsLZzBp/NTaPnm4x5XGcyF0F9oiJgRl7DAB/JWn0wOD5YUbCJCGMYtNquzdf1E8+IoU1p4Qn6SqSvODKFjMmPglX2P4KQv4IfbRlyjEGgIIwVsurT0MAk/uG0AvQIOdRBBlr6FYwM7szt3fevYj0/P1l2tJP+OOcEGBevKjxQsbS1qNXO0buN9u+EMM6B9WDbfTC2AJaCopDwv6WOZBtmbMdIwUjJ1gnen43C24F/v+aYhjGtrcjE2iBMKsvgM4UK49y/hLt7UfTleMR5FEHBlldGY3T88L+lzvKLLDnwreSM+X5W9mF471XHyvfpCZgYXi0z/Y6KxVdKsE3DjtnSZyGDSxF+Pn4fAXE018uBC8HBltpqh/GJwakx+igynsH1Z3j+iSrr7+on9/2UhQld4+NFCGtZNYPEWo2N8U1C3jbMbVd/VJUzn0URF+J3NkJ/AddTUQrMC0Ej2YEbvFrmSD/VPFU/p/rfRBn/A2h+X/WbSdqYZknNgoBpF+HFpwadPcXlEE5xOflgDDnxZMg7RRVP6VWZEx5U17LKqiItA42Yy5RUrjyvtl00t/QtgK9MPF+qMp1clbj5BwEWvlG48yrCOSGMpgf7wWuupbyykIH+sppTBTUwcDxS1jipRs0NF924JkxX0vuWVA6EkAGROcXvEkNDpShBJSWomXgCyHfxVvPWIDVPWbVAC9SjQwDBsQin35dZAfXKqRUmA0HMjkr4zEzRnaFRK34S/5YAYWlBUjGc3+apZLOzK1KGfD3cglPkpnCGBL4boJBlqf+lPCR9DroNT9EA15EV5pen5o2M+PySuFb8JpVLGi+VQCqBVAKpBFIJpBJYHxJYkYFcOlhcAXBwaV0wWFz6utyK0loChPT1sbVHD8dVbNu9Pq7phFJ/L05lObGgtk1gQkk+NFXkl04oVUSR+KZpjVA1xcyDwjf2XYYp5k+h5PPwSzLFzLL57i9Xvo/jxj3DuzIv4OLRT1wQKXKKuYAp5t6VnmLeecPZmLe6DMQ0MsXs+JAvVHwIXBXPITm4cF4Zp/2mmDmzxnl30QgHf3gPiNyFejwHVdnAR6eKXAI+cRHn/Gs9O3/5umM/wbgPMfbjz/uQ2ve0b+7T33rmu4zNrqvZ6VdWDPPAH2NuueaX8SX6Xjxejt+m6I9OFQAzWcg5vsJXBrOOnV8oetWthPNP1UcnUMT1INOQxB5V6LkPPB5zdVOVPMEDM2/IVT5D3zZ6Po6gfVy182doo76m9+7/EhlsBgwEAQTEmsJn6Ou4puErWPTRiwoIfYaWcInDeKvkKp+hQWIOdYDP0OVXcLzEHfqJiX9b8c/Q7nxlc/M1l+CI3OchlGHj++GjUByw3HWV5FJphaEK4cKUTB9W7OwFGHaSkGbAIOluvfYBlcndo0ol7uvA52xZCsmyHJ/uyuir4Ryf0IE8cE9jraL+EACKownNGFZjPS8DSpxfCcJc3Lp0Jf48LJqA5yv/5vVnYGXSU/iwPIyVSh+gIAqB+fDHe/5W27lyQ3RAbZZKOC85cwuO9f2LgCCutUzkCBpGxGmwv6eyAgIu2uWaBpQhfVGb8Esc4FO/MVxsewq+gP8dzu6+UO9+uchP5+QhiSMzsU7GAoOD7I+w3sP/GhavfhRL1fgNnF/A1qLiSUk9R5rIm48zEKnGfx8rjH5DxjU447peQoY5zWFuuu4cCPirqljkfknml0hezGO1nAjftvl+0PpznctywcyfBzxgHIdP8glcMsampno52IIwP4M8P4fv7XaZFrHY3o78FaTFaPNFIXV8fEGAXY/uodesXDycg53JADhchqcS7Kevl+kKh9mteb3Qgsexcukq1Vu+SkqcTFZuLBAANlljINlpM4pxAU8Osy0kUe+TjJAVikWgYo8T1i8q9UmMbX4VHkYdOsSVzZGOIKFalQPRtQboRaNkk/W0kVmuimewWi6L67zOZqG4/WtYMNcquG6uHiGxQFB3XZyVg6a56kepi4OegN1EfNp6Ja9WGLUWV/na171PS7GVjb0RRLjT1U0Pl93x0FN2LaL52ln9OdpwxSY/n+rrEnPL1TyrEzu/Aw0nD9F/4iuzsMEOsLZs2oYstqIfsjm5kqPzbR9f2nPQ2H/Bv0rbY36HXsOCX1u5JxE6Pe1kciEGxQxuf73nmLDdA/Z5+iW812zHC8VWCTq4IZYHx7TL6uTr3HBQ5cUzEci181SzneRIP2gmDhTBrIIRdTTvFQMa5uOdxGSFVmu/4gS43YjxDWZ44bCqqSbwg4TRwggC5VLZMJrZjMy4KpkDxQAc4YhtfY/XbTYKc5osqiWpQ0O1eCd/5PCMQPt1Dq/ConTZJYAA7OJQd7oYIxmMUksYDLNugzsSWG2ERPBeqjsRCJYXowfVcB9XG2OH8MlrKaXVVOwmmKEACDZt5/wlcLG5hxfwAIc7Q2MZvK/l4oHwapDUNwPInHmybdneqFau7eQvUggI0qpXzRe5kzvybGh6C4PCn+Z8A9tYZzlb3cHOCj2QlPh4IFT6zOCDEiAgZdkCk5azpvFYuQEBGZXrsRMsH4sgybWakRFum2M8gN6yGxG7nb0sv+GPgDHUxgPBZeB1pEAc9e7KV6t4CI8wusO7fDV26Tvr6pvE9Zs4YmdJIKW2UQmkQIiV2LrQhLFcdhcQ4juFisAwOJDYbnBRCVinN90FhG6p1SbA2l1AaEJA3ZIkBUK31HQMnykQYgTULcEpELqlpmP4TIEQI6BuCU6B0C01HcNnCoQYAXVLcAqEbqnpGD5TIMQIqFuCUyB0S03H8JkCIUZA3RKcAqFGTWMtVld9mUiBUAMIzruBD5YuSUdeUyDEVlsHr1CK5W0xQgqERVlE3nXomkXLi04O4uRA8GU/A5eBdrK2xEYXY/ctRFZ73vrSrK8cm8f9csmFGZnlmnrW47WasHgguLOhPb1g1/lj6VbnDaQC8OJsJy3WTlRwqmO1NPJ2gChmdjSOD1bJF39WZ7SGT8H6ZcuJsccfJyAnHgib3ZHAPu0hYPNrh42nq8b+eh6G9GFVBK6yTF+e5A/QYipb5nl8sGwSWQzvkDtyjHrlRdO4h3V/mq+ShPN213ggLO4c5hmC3BnMTbF1M3WZt9VVKlW/7w65UDMz0Xs4jzxrN/362p6Z2Hnaj3XDA09xsTzI+suYDj0eCG7vo2/eRM7OSkonAYG0Cp8wlPAW7pW0epq7j3KzZ1uRaWNPve0kTi3l4AubkXy/gObKOlPqxjGP2i6KXednd/24p6jr3JwVWE/msCqZd6AuN9uxQlTktvTj8Naenq6DkQEO+dCPHMAYoI7TeqpOaHsG2YE8BsOG2/pgds+bFkKtfYQ6g+Qkm2Dt7melH9v3LiB1MOg3NTdAtr2zLYRvADQ2hfOE9MtC86GsVf9RDLguwy//ByzGua4wKmZb+qFafFiTI20/VUXvDSFySwvsI4jtpMAAFeT6z7TGAcT14L7c9i+SFqz8S+tih1SP+RcRTF8fLb5EOncsjt478RPw+GNEsqYA8NABjvv50A2SWPMCjYdZmmt0gyGG4scINrJVK8Xckyjkv6FyqHrqqppQGat+K/VvK46nt2YCyydP6Uf3vy+2IisCiiaN5oUlxJhv4OBP3pbr97DR+ayyLzlegMYegkGtN2A752mWL6aScd51HC2JgEBkmV27+nB9Txn/gSDTUyAcWiyzAIwraRXD5QWBHQJed7WXGQDN/6mKOUt3z9FYALvDyfTeZ/4WBjsnoWo3gkm+k7NHtBBbRX7qFmWp4V+2fpyoh27B6Af14xOvCq1btsTyy/wTAYERFU5YF9t9FI5RD0M4tEwGIWPixQTawYHCXSVh6I8l2nq4OLX8wuHuninj4kMM+EfjWbCb5G1EPz+L1vFbAPEx0QY0RJnAVeYTcrnbYatxGjaYPsw8UTwFbodlji53XZqvo9VdGe7uw2ncvQtbGs/lG4onlcxnmQEVK3dZlcsNQht8U+/dZw2M5kcySc92CBftiqt5lSPoYa6NEWDd8yGooT+UwaPv44BqAQRbDF9VZG42EBdjyzytna519+TBysVeOf6kOVk6N60b3oDqwl2YROQfJpeBS3DPkdIAgJqRCjR6FwTzgjMfjLDEzvGLE2AvAE3fhtlh2J8uc2x0HKW6lkaS2aBCsnQ8ktZqHuxz2H/pPcljGscvnwOeOafBf4s8s8wc6mBApF72vwnNR9Cz0YrhdKZO4kLEJ4kOCtB/OtVpbrn2DhBxN34XSD9MGPgyk8FWR0HZCmrxpAyKCQAjAyNUgsnChz/IDL9ymbOHBzCv8mWNE9/NH1zZG/u6WIN9J1AxVedl7kc1wNakNyTVTlZxLC/oKaFYxy8hwcpqnVvUPwRcBuXhPGiUyDPLOXg35hAKfVjv2fcgC3WG03mf1DUMBGZszbqO+FQ7ENSw6indCGKuADGfQCgNONEoZ08wSGMSCqd1zomZwBPVaOaROWwR493ZmB8CDBP6if3/wAJlbBNxUm0jxISBZG69/tcg/FGUdQnyOAtXWJqDBtKoFYKQbiV4Zb7CL76XwPAPfu/g9wqUx4so9ym95zuv45nmgxvSBExD1zTJ0ocemsRxwJOVDxuwbLpFZbLbQPAWkASbwGoDiuAbBs5LDqtIKbv5P2LGycNA1UfL17Mo6x0MCN9Qpb7XORZwGTfTHbi0S69ije3SA0WdFy1Hgfer7MJ2tMozURmnIz4MV0FFa8M3DrbcVjq8tcAMsIeZXaPeRcUfUYXitB5/9rArhADgtLl7/XX+Sa9NA8EV4MzVtfocZpd/o1c0Go0pVYKvAFC4frzRbCLjC/hhl3ol8o4sMIGnAOD0Y7rZrs8VsWwguIzcOUpq8zz0QD/eJbb6cirZML7t0+3Y4RS6S9L8dWrK0s2pU34bGJixLXBu2Ec5paQj5WYJELD9zl1ZVThoZyhZ7tBrBrTY9/VW8koiye8MjhzmEcu0DOtkvGm+rPN28N4sLy5dy4DgMgxfUfMrmj/LQgGtA1iY+CbuV5rfduK1CfGkSVIJpBJIJZBKIJVAKoFUAqkEUgmkEuhoCfw/vJfHMO4YqncAAAAASUVORK5CYII=";
+ const artwork = artworkBuilder.createArtworkForResource(objectGraph, resource, 36, 36);
+ ribbonModel.artwork = artwork;
+ ribbonModel.accessibilityLabel = title;
+ // Configure impressions
+ const metricsOptions = {
+ targetType: "facet",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ recoMetricsData: null,
+ };
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForArcadeSeeAllGamesRibbonItem(metricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, ribbonModel, impressionOptions);
+ return ribbonModel;
+ }
+ static createTagsRibbonShelf(objectGraph, data) {
+ const tagsData = mediaRelationship.relationshipViewsCollection(data, "categorizations");
+ const ribbonBarShelf = new models.Shelf("ribbonFlow");
+ const ribbonItems = [];
+ for (const tagData of tagsData) {
+ const name = attributeAsString(tagData, "name");
+ let pageUrl = null;
+ switch (tagData.type) {
+ case "genres":
+ pageUrl = attributeAsString(tagData, "url");
+ break;
+ case "tags":
+ const href = serverData.asString(tagData, "href");
+ pageUrl = hrefToRoutableUrl(objectGraph, href);
+ break;
+ default:
+ break;
+ }
+ if (isNothing(pageUrl)) {
+ continue;
+ }
+ const flowPage = objectGraph.required(pageRouter).fetchFlowPage(pageUrl);
+ const flowAction = new models.FlowAction(flowPage);
+ flowAction.pageUrl = pageUrl;
+ if (isSome(name)) {
+ const ribbonItem = new models.RibbonBarItem(name, flowAction);
+ ribbonItems.push(ribbonItem);
+ }
+ }
+ ribbonBarShelf.items = ribbonItems;
+ ribbonBarShelf.isHorizontal = true;
+ return ribbonBarShelf;
+ }
+ /**
+ * Determines if an item should be given priority and moved to the front of the list.
+ * @param objectGraph Current object graph
+ * @param data The data for the item
+ * @returns True if the item should be moved to the front of the list.
+ */
+ static shouldPriorizeItemWithData(objectGraph, data) {
+ var _a, _b;
+ const displayDeviceDrivenContent = (_b = (_a = objectGraph.userDefaults) === null || _a === void 0 ? void 0 : _a.bool("displayDeviceDrivenContent")) !== null && _b !== void 0 ? _b : false;
+ return (displayDeviceDrivenContent &&
+ objectGraph.bag.ribbonBarVisionEditorialItemIds.includes(data.id) &&
+ objectGraph.bag.enableDeviceDrivenDiscoveryContent);
+ }
+}
+//# sourceMappingURL=grouping-ribbon-bar-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller-common.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller-common.js
new file mode 100644
index 0000000..a1b05cd
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller-common.js
@@ -0,0 +1,992 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataFetching from "../../../foundation/media/data-fetching";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as urls from "../../../foundation/network/urls";
+import * as appEvents from "../../app-promotions/app-event";
+import * as appPromotionsCommon from "../../app-promotions/app-promotions-common";
+import { pageRouter } from "../../builders/routing";
+import * as legacyArtwork from "../../content/artwork/legacy-artwork";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as externalDeepLink from "../../linking/external-deep-link";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as onDevicePersonalization from "../../personalization/on-device-personalization";
+import * as productPageVariants from "../../product-page/product-page-variants";
+import * as refresh from "../../refresh/page-refresh-controller";
+import * as mediaRequestUtils from "../../builders/url-mapping-utils";
+import { defaultTodayCardConfiguration, lockupsForRelatedContent } from "../../today/today-card-util";
+import { isLockupShelf, } from "../grouping-types";
+import { categoryArtworkData } from "../../categories";
+import { hrefToRoutableUrl } from "../../builders/url-mapping";
+import { clientIdentifierForEditorialContextInData } from "../../lockups/editorial-context";
+import { areAppTagsEnabled } from "../../util/app-tags-util";
+import { AppEventsAttributes } from "../../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types";
+/**
+ * Create the base 2 requirements to start parsing a grouping page shelf. These include the base shelf token and the
+ * base metrics options
+ *
+ * @param objectGraph The App Store dependency graph for making native calls and viewing properties
+ * @param mediaApiData The media api data for this specific shelf
+ * @param groupingParseContext The grouping page parsing context.
+ */
+export function createBaseShelfRequirements(objectGraph, mediaApiData, groupingParseContext) {
+ const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind");
+ // Populate the gamesFilter, if it is provided.
+ let gamesFilter = null;
+ const rawGamesFilter = mediaAttributes.attributeAsString(mediaApiData, "gamesFilter");
+ switch (rawGamesFilter) {
+ case "arcade":
+ case "nonArcade":
+ case "all":
+ gamesFilter = rawGamesFilter;
+ break;
+ default:
+ if (featuredContentId === 495 /* FeaturedContentID.AppStore_PopularWithYourFriendsMarker */ ||
+ featuredContentId === 500 /* FeaturedContentID.AppStore_ContinuePlayingMarker */) {
+ gamesFilter = "arcade"; // Defaulting to Arcade because this param won't be passed until 20I.
+ }
+ break;
+ }
+ // Build Shelf eyebrow, title, and subtitle
+ let eyebrow = null;
+ let title = mediaAttributes.attributeAsString(mediaApiData, shelfTitleAttributePathForFeaturedContentId(objectGraph, featuredContentId));
+ let titleArtwork = null;
+ let badges = null;
+ let subtitle = mediaAttributes.attributeAsString(mediaApiData, "tagline");
+ const shelfHeaderConfiguration = {};
+ // 'Similar To' Personalised Shelf
+ // Check if personalised shelf has a badge-content, if it's got valid content, and the clients support eyebrows and title artwork:
+ // - Reco shelf title becomes the eyebrow
+ // - Featured App in badge-content becomes the shelf title, and we add the icon as title artwork
+ // - No subtitle should be shown
+ // - Flag to use the eyebrown name for metrics
+ let wantsEyebrowNameForMetrics = false;
+ const badgeContent = mediaRelationship.relationshipCollection(mediaApiData, "badge-content")[0];
+ if (featuredContentId === 476 /* FeaturedContentID.AppStore_PersonalizedShelfMarker */ &&
+ serverData.isDefinedNonNullNonEmpty(badgeContent)) {
+ eyebrow = objectGraph.loc.uppercased(mediaAttributes.attributeAsString(mediaApiData, shelfTitleAttributePathForFeaturedContentId(objectGraph, featuredContentId)));
+ subtitle = null;
+ const dataType = badgeContent.type;
+ if (dataType === "collections") {
+ title = content.notesFromData(objectGraph, badgeContent, "name");
+ const artworkData = categoryArtworkData(objectGraph, badgeContent, false);
+ // Reconfigure eyebrow text color only if there is eyebrow artwork.
+ if (isSome(artworkData)) {
+ titleArtwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ style: "unadorned",
+ });
+ const eyebrowColor = { type: "named", name: "secondaryText" };
+ shelfHeaderConfiguration.eyebrowColor = eyebrowColor;
+ }
+ badges = { forYou: true };
+ }
+ else {
+ wantsEyebrowNameForMetrics = true;
+ title = mediaAttributes.attributeAsString(badgeContent, shelfTitleAttributePathForFeaturedContentId(objectGraph, featuredContentId));
+ titleArtwork = content.iconFromData(objectGraph, badgeContent, {
+ useCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ });
+ }
+ }
+ const shelfToken = {
+ featuredContentId,
+ id: serverData.asString(mediaApiData, "id"),
+ presentationHints: {},
+ metricsPageInformation: groupingParseContext.metricsPageInformation,
+ metricsLocationTracker: groupingParseContext.metricsLocationTracker,
+ pageGenreId: groupingParseContext.pageGenreId,
+ featuredContentData: mediaApiData,
+ title: title,
+ subtitle: subtitle,
+ eyebrow: eyebrow,
+ titleArtwork: titleArtwork,
+ shelfHeaderConfiguration: shelfHeaderConfiguration,
+ shouldFilter: false,
+ gamesFilter: gamesFilter,
+ remainingItems: [],
+ isFirstRender: true,
+ isDeferring: false,
+ showOrdinals: false,
+ hasExistingContent: false,
+ showingPlaceholders: false,
+ ordinalIndex: 1,
+ isSearchLandingPage: groupingParseContext.isSearchLandingPage,
+ isArcadePage: groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage,
+ };
+ const shelfMetricsOptions = {
+ id: shelfToken.id,
+ kind: null,
+ softwareType: serverData.asBooleanOrFalse(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage) ? "Arcade" : null,
+ targetType: "swoosh",
+ title: wantsEyebrowNameForMetrics ? shelfToken.eyebrow : shelfToken.title,
+ badges: badges,
+ pageInformation: groupingParseContext.metricsPageInformation,
+ locationTracker: groupingParseContext.metricsLocationTracker,
+ idType: "its_contentId",
+ fcKind: featuredContentId,
+ recoMetricsData: recoMetricsDataForFCData(objectGraph, mediaApiData),
+ };
+ return {
+ shelfToken: shelfToken,
+ metricsOptions: shelfMetricsOptions,
+ };
+}
+// endregion
+// region Shelf Tokens
+/**
+ * Whether we should defer building the rest of a shelf given a shelf token
+ * @param token
+ */
+export function shouldDefer(token) {
+ return token && token.isDeferring && token.isFirstRender;
+}
+/**
+ * Due to upstream oddities, we get the shelf title in the form of `title` for these markers. These are
+ * unique markers in that they are not filled by reco.
+ *
+ * For personalization markers, they are programmed in DJ with the name field hidden, and it is filled in by
+ * Reco. Without wanting to expose loc strings to that name field, we unfortunately have to use this other
+ * attribute key.
+ *
+ * This will all go away once these markers are replaced with proper Reco equivalents (i.e. when popular-
+ * with-your-friends and suggested-friends move to Reco).
+ */
+export function shelfTitleAttributePathForFeaturedContentId(objectGraph, id) {
+ switch (id) {
+ case 548 /* FeaturedContentID.AppStore_GameCenterActivityFeedMarker */:
+ case 495 /* FeaturedContentID.AppStore_PopularWithYourFriendsMarker */:
+ case 496 /* FeaturedContentID.AppStore_SuggestedFriendsMarker */:
+ return "title";
+ default:
+ return "name";
+ }
+}
+/**
+ * Returns the URL schema for grouping shelves that may need to fetch additional content.
+ * Grouping-Related builders can extend on this scheme if needed, e.g. query param on Continue Playing shelves.
+ * @param token Token to encode in URL for subsequent fetch.
+ */
+export function groupingShelfUrl(token) {
+ let shelfUrl = new urls.URL()
+ .set("protocol", Protocol.internal)
+ .append("pathname", Path.grouping)
+ .append("pathname", Path.shelf)
+ .append("pathname", encodeURIComponent(JSON.stringify(token)))
+ .param(Parameters.groupingFeaturedContentId, `${token.featuredContentId}`);
+ if (isSome(token.nativeGroupingShelfId)) {
+ shelfUrl = shelfUrl.param(Parameters.nativeGroupingShelfId, `${token.nativeGroupingShelfId}`);
+ }
+ return shelfUrl.build();
+}
+/**
+ * Configure `url` on a standard grouping shelf if it needs to fetch more content.
+ * @param shelf Shelf to add url to.
+ * @param token Token tode encode in URL for subsequent fetch.
+ */
+export function createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, token) {
+ if (serverData.isNullOrEmpty(token)) {
+ return null;
+ }
+ // Web does not curently support pagination via "fetch more", so shelf token URLs are not needed.
+ // Note: removing these URLs for web reduces view model size by ~45%.
+ if (objectGraph.client.isWeb) {
+ return null;
+ }
+ // Ensure the token has a `shelfStyle` so we can construct an empty shelf in the case of a hydration error,
+ // or the shelf being empty upon hydration.
+ if (serverData.isNull(token.shelfStyle)) {
+ token.shelfStyle = shelf.contentType;
+ }
+ const hasNonPlaceholderItems = shelf.contentType !== "placeholder" && serverData.isDefinedNonNullNonEmpty(shelf.items);
+ token.hasExistingContent = token.hasExistingContent || (hasNonPlaceholderItems && token.isFirstRender);
+ const shouldAddFirstRenderUrl = token.remainingItems.length || token.recommendationsHref || token.onDeviceRecommendationsUseCase;
+ if (shouldAddFirstRenderUrl && token.isFirstRender) {
+ return groupingShelfUrl(token);
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Updates a shelf URL based on the provided token.
+ * @param shelf Shelf to add url to.
+ * @param token Token to encode in the url.
+ */
+export function updateShelfUrlWithNewToken(objectGraph, shelf, token) {
+ const originalShelfUrl = urls.URL.from(shelf.url);
+ const updatedUrl = urls.URL.from(createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, token));
+ // Add missing query params to the updated URL from the original.
+ for (const key of Object.keys(originalShelfUrl.query)) {
+ if (serverData.isNull(updatedUrl.query[key])) {
+ updatedUrl.query[key] = originalShelfUrl.query[key];
+ }
+ }
+ // Finally, update the shelf's URL.
+ shelf.url = updatedUrl.build();
+}
+/**
+ * From a grouping shelf token determine the list of unhydrated MAPI data to fetch
+ * this will also handle the case were whe need to fetch additional relationship data
+ *
+ * This method will also hoist up ids of content needed to be fetched from an unhydrated relationship if needed. For <rdar://problem/42797176>.
+ *
+ * This is necessary to accommodate some MAPI objects which have a nested relationship that isn't hydrated and cannot
+ * be hydrated by re-fetching the content at parent ID.
+ *
+ * @param token The shelfToken for the shelf we're fetching data for
+ */
+export function unhydratedRemainingItemsFromShelfToken(objectGraph, token) {
+ var _a;
+ const shouldFetchRelationshipItems = ((_a = token.relationshipToFetch) === null || _a === void 0 ? void 0 : _a.length) > 0;
+ let remainingItems = token.remainingItems;
+ if (shouldFetchRelationshipItems) {
+ remainingItems = token.remainingItems.map((remainingItem) => {
+ return mediaRelationship.relationshipData(objectGraph, remainingItem, token.relationshipToFetch);
+ });
+ }
+ return remainingItems;
+}
+/**
+ * Given a MAPI response for a list of unhydrated shelf items, and the original grouping shelf token determine
+ * the list of hydrated MAPI data. This also handles the case where we need to hoist up relationship data
+ *
+ * If ids to be fetched were hoisted from nested relationships, replace the original unhydrated nested
+ * relationship on parent with hydrated data.
+ *
+ * @param token
+ * @param mediaApiData
+ */
+export function hydratedRemainingItemsForShelfTokenFromMediaApiData(objectGraph, token, mediaApiData) {
+ var _a;
+ const didFetchRelationshipItems = ((_a = token.relationshipToFetch) === null || _a === void 0 ? void 0 : _a.length) > 0;
+ let hyrdatedItems = mediaDataStructure.dataCollectionFromDataContainer(mediaApiData);
+ if (didFetchRelationshipItems) {
+ const dataMapping = {};
+ for (const dataItem of mediaApiData.data) {
+ dataMapping[dataItem.id] = dataItem;
+ }
+ hyrdatedItems = [];
+ for (const remainingItem of token.remainingItems) {
+ const unhydratedRelationshipData = mediaRelationship.relationshipData(objectGraph, remainingItem, token.relationshipToFetch);
+ if (serverData.isDefinedNonNullNonEmpty(unhydratedRelationshipData)) {
+ remainingItem.relationships[token.relationshipToFetch].data = [
+ dataMapping[unhydratedRelationshipData.id],
+ ];
+ }
+ hyrdatedItems.push(remainingItem);
+ }
+ }
+ return hyrdatedItems;
+}
+/**
+ * Deletes all remainingItems that have been requested for hydration from the shelfToken.
+ *
+ * @param shelfToken
+ * @param requestedItems
+ */
+export function flushRequestedItemsFromShelfToken(shelfToken, requestedItemIds) {
+ shelfToken.remainingItems = shelfToken.remainingItems.filter((remainingItem) => {
+ return !requestedItemIds.has(remainingItem.id);
+ });
+}
+// endregion
+// region Shelf Requests
+/**
+ * Generates a media API request for fetching a shelf's data
+ * @param objectGraph
+ * @param {URL} url The URL of the page being requested
+ * @param {Parameters} parameters The parameters that were extracted from the URL
+ * @returns {Request} A media API request
+ */
+export function generateShelfRequest(objectGraph, token, parameters) {
+ var _a;
+ // Whether or not token is for fetching additional items that were unhydated.
+ // These tokens are used when a server returned explicit IDs for the contents of shelves, and some (or all) of those IDs were fully unhydrated.
+ const isFetchingAdditionalContent = serverData.isDefinedNonNullNonEmpty(token.remainingItems);
+ // Whether or not token is for fetching personalized recommendations.
+ // Recommendation shelves can sometimes return with no IDs provided within its shelf. We are expected to use the `recommendationsHref` to fetch in this case.
+ // Note that server can sometimes choose to prepopulate IDs for personalized shelves, even if `recommendationsHref` is present.
+ const isPersonalizedRefetch = !isFetchingAdditionalContent && ((_a = token.recommendationsHref) === null || _a === void 0 ? void 0 : _a.length) > 0;
+ if (isFetchingAdditionalContent) {
+ // Remaining items to fetch, since they were unhydrated in original page response.
+ const remainingItemsToFetch = unhydratedRemainingItemsFromShelfToken(objectGraph, token);
+ // MAPI Request
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph, remainingItemsToFetch, true);
+ productPageVariants.addVariantParametersToRequestForItems(objectGraph, mediaApiRequest, remainingItemsToFetch);
+ prepareGroupingShelfRequest(objectGraph, mediaApiRequest);
+ return mediaApiRequest;
+ }
+ else if (isPersonalizedRefetch) {
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph, token.recommendationsHref).includingAgeRestrictions();
+ prepareGroupingShelfRequest(objectGraph, mediaApiRequest);
+ if (appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
+ mediaApiRequest.enablingFeature("appEvents");
+ mediaApiRequest.includingMetaKeys("editorial-elements:contents", ["personalizationData", "cppData"]);
+ mediaApiRequest.includingScopedAttributes("app-events", AppEventsAttributes);
+ mediaApiRequest.includingScopedRelationships("app-events", ["app"]);
+ }
+ if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
+ mediaApiRequest.enablingFeature("contingentItems");
+ mediaRequestUtils.configureContingentItemsForGroupingRequest(mediaApiRequest);
+ }
+ if (appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) {
+ mediaApiRequest.enablingFeature("offerItems");
+ mediaRequestUtils.configureOfferItemsForMediaRequest(mediaApiRequest);
+ }
+ if (areAppTagsEnabled(objectGraph, "grouping")) {
+ mediaRequestUtils.configureTagsForMediaRequest(mediaApiRequest);
+ }
+ return mediaApiRequest;
+ }
+ return null;
+}
+/**
+ * Modify request for a items fetched for grouping shelf items. (i.e. individual IDs).
+ * This should be ideally be in grouping builder's `prepareRequest`, but that would change how url-driven requests are configured.
+ * @param objectGraph
+ * @param request Request to modify.
+ */
+export function prepareGroupingShelfRequest(objectGraph, request) {
+ request
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .includingRelationshipsForUpsell(true)
+ .includingMacOSCompatibleIOSAppsWhenSupported(true)
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph));
+ let attributes = ["editorialArtwork", "editorialVideo", "minimumOSVersion"];
+ if (request.includesResourceType("app-events") && appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
+ request.enablingFeature("appEvents");
+ request.includingMetaKeys("editorial-elements:contents", ["personalizationData", "cppData"]);
+ request.includingScopedAttributes("app-events", AppEventsAttributes);
+ request.includingScopedRelationships("app-events", ["app"]);
+ }
+ if (request.includesResourceType("contingent-items") &&
+ appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
+ request.enablingFeature("contingentItems");
+ mediaRequestUtils.configureContingentItemsForGroupingRequest(request);
+ attributes = [];
+ }
+ if (request.includesResourceType("offer-items") && appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) {
+ request.enablingFeature("offerItems");
+ mediaRequestUtils.configureOfferItemsForMediaRequest(request);
+ attributes = [];
+ }
+ if (request.includesResourceType("apps") || request.includesResourceType("app-events")) {
+ attributes = attributes.concat("screenshotsByType", "videoPreviewsByType", "expectedReleaseDateDisplayFormat");
+ }
+ if (areAppTagsEnabled(objectGraph, "grouping")) {
+ mediaRequestUtils.configureTagsForMediaRequest(request);
+ }
+ request.includingAttributes(attributes);
+}
+// endregion
+// region Metrics
+export function recoMetricsDataForFCData(objectGraph, mediaApiData) {
+ const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind");
+ switch (featuredContentId) {
+ // All of these kinds just require walking their children
+ case 425 /* FeaturedContentID.AppStore_GenreStack */:
+ case 415 /* FeaturedContentID.AppStore_HeroList */:
+ case 416 /* FeaturedContentID.AppStore_Hero */:
+ case 417 /* FeaturedContentID.AppStore_CustomHero */:
+ case 501 /* FeaturedContentID.AppStore_PersonalizedHeroMarker */:
+ case 258 /* FeaturedContentID.Sundance_Flowcase */:
+ case 421 /* FeaturedContentID.AppStore_BrickRow */:
+ case 422 /* FeaturedContentID.AppStore_Brick */:
+ case 423 /* FeaturedContentID.AppStore_CustomBrick */:
+ case 261 /* FeaturedContentID.Sundance_BrickRow */:
+ case 584 /* FeaturedContentID.AppStore_TagsBrick */:
+ case 587 /* FeaturedContentID.AppStore_PersonalizedTagsBrick */: {
+ const childrenRelationship = mediaRelationship.relationship(mediaApiData, "children");
+ return mediaDataStructure.metricsFromMediaApiObject(childrenRelationship);
+ }
+ case 437 /* FeaturedContentID.AppStore_LinkList */:
+ case 265 /* FeaturedContentID.Sundance_LinkList */: {
+ const contentRelationship = mediaRelationship.relationship(mediaApiData, "children");
+ const textLinks = mediaAttributes.attributeAsArrayOrEmpty(mediaApiData, "links");
+ if (serverData.isDefinedNonNullNonEmpty(contentRelationship)) {
+ return mediaDataStructure.metricsFromMediaApiObject(contentRelationship);
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(textLinks)) {
+ return mediaDataStructure.metricsFromMediaApiObject(mediaApiData);
+ }
+ return null;
+ }
+ case 414 /* FeaturedContentID.AppStore_TabRoot */:
+ case 424 /* FeaturedContentID.AppStore_ChartSet */:
+ case 566 /* FeaturedContentID.AppStore_ArcadeDownloadPackMarker */: {
+ return null; // unapplicable fckinds
+ }
+ default: {
+ if (isLockupShelf(featuredContentId)) {
+ let childrenRelationship = mediaRelationship.relationship(mediaApiData, "contents");
+ if (serverData.isNull(childrenRelationship)) {
+ return null;
+ }
+ const contentItems = childrenRelationship.data;
+ if (!contentItems || contentItems.length === 0) {
+ childrenRelationship = mediaRelationship.relationship(mediaApiData, "children");
+ }
+ return mediaDataStructure.metricsFromMediaApiObject(childrenRelationship);
+ }
+ else {
+ objectGraph.console.warn("Unknown featured content ID:", featuredContentId);
+ return null;
+ }
+ }
+ }
+}
+// endregion
+// region Artwork
+/**
+ * Creating an artwork model with the crop required for a grouping page piece of art
+ */
+export function groupingArtworkFromApiArtwork(objectGraph, artworkData, options) {
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, options);
+ if (artwork) {
+ artwork.crop = "sr";
+ }
+ return artwork;
+}
+export function artworkFromFC(objectGraph, node, width, height, options) {
+ const artworkData = mediaAttributes.attributeAsDictionary(node, "artwork");
+ if (artworkData instanceof Array) {
+ const artwork = legacyArtwork.closestArtworkMatchingSize(objectGraph, artworkData, width, height);
+ artwork.crop = "bb";
+ return artwork;
+ }
+ else if (artworkData != null) {
+ return groupingArtworkFromApiArtwork(objectGraph, artworkData, options);
+ }
+ return null;
+}
+export function artworkForTags(objectGraph, node, width, height, options, metricsOptions) {
+ const lockupData = serverData.asArrayOrEmpty(node.meta, "associations.apps.data");
+ const artworks = [];
+ if (isSome(lockupData)) {
+ for (const lockup of lockupData) {
+ const lockupOptions = {
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ metricsOptions: metricsOptions,
+ useJoeColorIconPlaceholder: true,
+ joeColorPlaceholderSelectionLogic: content.bestJoeColorPlaceholderSelectionLogic,
+ };
+ const lockupFromData = lockups.lockupFromData(objectGraph, lockup, lockupOptions);
+ const lockupIcon = lockupFromData === null || lockupFromData === void 0 ? void 0 : lockupFromData.icon;
+ if (isSome(lockupIcon)) {
+ artworks.push(lockupIcon);
+ }
+ }
+ }
+ return artworks;
+}
+// endregion
+// region FC Metadata
+export function metadataForFCData(objectGraph, fcData, token, shouldPersonalizeContent, personalizationDataContainer, metricsOptions, context, unavailableCallback) {
+ var _a, _b, _c;
+ const callUnavailable = function (contentData) {
+ if (unavailableCallback) {
+ unavailableCallback();
+ }
+ else {
+ token === null || token === void 0 ? void 0 : token.remainingItems.push(contentData);
+ }
+ };
+ const isLinkNode = ((_a = serverData.asString(fcData, "url")) === null || _a === void 0 ? void 0 : _a.length) > 0;
+ const containsLinkNode = ((_b = mediaAttributes.attributeAsString(fcData, "link.url")) === null || _b === void 0 ? void 0 : _b.length) > 0;
+ const isContentNodeUsingPrimaryContent = mediaRelationship.hasRelationship(fcData, "primary-content", false);
+ const isContentNode = mediaRelationship.hasRelationship(fcData, "contents", false) || isContentNodeUsingPrimaryContent;
+ // Define whether data is a Category Grouping kind.
+ let isCategoryGroupingKind = mediaAttributes.attributeAsString(fcData, "kind") === "CategoryGrouping";
+ if (containsLinkNode || isLinkNode) {
+ return metadataForLink(objectGraph, fcData, token, metricsOptions, unavailableCallback);
+ }
+ else if (isContentNode) {
+ let contentData;
+ // Define category grouping content to set when content is a Category Grouping kind.
+ let categoryGroupingContent;
+ let personalizedDataResult;
+ if (shouldPersonalizeContent && !isContentNodeUsingPrimaryContent) {
+ const contentDataItems = mediaRelationship.relationshipCollection(fcData, "contents");
+ personalizedDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "groupingCommon", contentDataItems, true, personalizationDataContainer, false, 1);
+ const personalizedContentDataItems = personalizedDataResult.personalizedData;
+ if (personalizedContentDataItems.length === 0) {
+ return null;
+ }
+ contentData = personalizedContentDataItems[0];
+ }
+ else {
+ contentData = isContentNodeUsingPrimaryContent
+ ? mediaRelationship.relationshipData(objectGraph, fcData, "primary-content")
+ : mediaRelationship.relationshipData(objectGraph, fcData, "contents");
+ }
+ // Check whether content is a Category Grouping kind.
+ if (mediaAttributes.attributeAsString(contentData, "kind") === "CategoryGrouping") {
+ categoryGroupingContent = contentData;
+ // Update content to its primary content.
+ contentData = mediaRelationship.relationshipData(objectGraph, contentData, "primary-content");
+ isCategoryGroupingKind = true;
+ }
+ // If the content data is null don't even bother with the call unavailable since we have no way of knowing how to fetch it
+ if (serverData.isNull(contentData)) {
+ return null;
+ }
+ else if (serverData.isNull(contentData.attributes) || shouldDefer(token)) {
+ if (serverData.isDefinedNonNullNonEmpty(token)) {
+ token.isDeferring = true;
+ }
+ callUnavailable(contentData);
+ return null;
+ }
+ // Generate the subtitle
+ let subtitle = content.notesFromData(objectGraph, contentData, "tagline") ||
+ lockups.subtitleFromData(objectGraph, contentData);
+ // Generate the click action
+ // Using the fcData here because the ids need to match the id used for the impressions, otherwise the reporting
+ // heat maps dont work.
+ // rdar://61527868 (Metrics: Arcade Data Mismatch (Location and Impressions have different name fields))
+ const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, fcData, metricsOptions);
+ metricsClickOptions.targetType = metricsOptions.targetType;
+ let action = lockups.actionFromData(objectGraph, contentData, metricsClickOptions, token === null || token === void 0 ? void 0 : token.clientIdentifierOverride);
+ // Understand if we're dealing with an article here, so when we go to generate app events, we know if we
+ // should use the action we've created above or the app event action.
+ const isArticle = mediaAttributes.attributeAsBooleanOrFalse(contentData, "isCanvasAvailable");
+ // Find the artwork and title depending on the content type
+ let artwork = null;
+ let caption = null;
+ // Find lockup (if any)
+ let lockup = null;
+ // Find app event (if any)
+ let appEvent;
+ const shortEditorialDescription = mediaAttributes.attributeAsString(contentData, "itunesNotes.short");
+ const hasContentId = ((_c = contentData.id) === null || _c === void 0 ? void 0 : _c.length) > 0;
+ const contentMetricsOptions = {
+ ...metricsOptions,
+ id: hasContentId ? contentData.id : fcData.id,
+ idType: hasContentId ? "its_id" : "editorial_id",
+ };
+ switch (contentData.type) {
+ case "groupings": {
+ artwork = mediaAttributes.attributeAsDictionary(contentData, "artwork");
+ // If data is a Category Grouping kind, reconfigure content.
+ if (isCategoryGroupingKind) {
+ contentData = categoryGroupingContent !== null && categoryGroupingContent !== void 0 ? categoryGroupingContent : fcData;
+ }
+ break;
+ }
+ case "editorial-items": {
+ // Check for an app-event relationship
+ const relatedCardContents = mediaRelationship.relationshipData(objectGraph, contentData, "card-contents");
+ if (serverData.isDefinedNonNullNonEmpty(relatedCardContents)) {
+ const clickOptions = {
+ ...contentMetricsOptions,
+ inAppEventId: relatedCardContents.id,
+ };
+ const parentAppData = mediaRelationship.relationshipData(objectGraph, relatedCardContents, "app");
+ if (serverData.isDefinedNonNull(parentAppData)) {
+ clickOptions.relatedSubjectIds = [parentAppData.id];
+ }
+ const appEventOrDate = appEvents.appEventOrPromotionStartDateFromData(objectGraph, relatedCardContents, null, false, true, "dark", "white", false, clickOptions, false, true, null, token.isArcadePage, false);
+ const cardDisplayStyle = mediaAttributes.attributeAsString(contentData, "cardDisplayStyle");
+ if (cardDisplayStyle === "AppEventCard") {
+ if (appEventOrDate instanceof Date) {
+ // If we get a date back, we have a valid app event, but it starts in the future.
+ // We don't want the object containing this event to render yet, so return early.
+ refresh.addNextPreferredContentRefreshDate(appEventOrDate, context.refreshController);
+ return null;
+ }
+ else if (isNothing(appEventOrDate)) {
+ return null;
+ }
+ else {
+ appEvent = appEventOrDate;
+ if (!isArticle) {
+ action = appEvent.clickAction;
+ }
+ if (serverData.isNullOrEmpty(subtitle)) {
+ subtitle = content.notesFromData(objectGraph, relatedCardContents, "short");
+ }
+ }
+ }
+ }
+ caption = mediaAttributes.attributeAsString(contentData, "label");
+ if (caption) {
+ // This is a hack for AOTD/GOTD
+ caption = caption.replace(/\n/g, " ");
+ }
+ const relatedContent = mediaRelationship.relationshipData(objectGraph, contentData, "contents");
+ const tagline = serverData.asString(contentData, "editorialNotes.tagline");
+ if (serverData.isNullOrEmpty(subtitle)) {
+ if (tagline) {
+ subtitle = tagline;
+ }
+ else if (relatedContent) {
+ subtitle = content.notesFromData(objectGraph, relatedContent, "short");
+ }
+ }
+ if (serverData.isNullOrEmpty(subtitle) && serverData.isDefinedNonNull(appEvent)) {
+ subtitle = appEvent.subtitle;
+ }
+ let crossLinkSubtitle = mediaAttributes.attributeAsString(contentData, "editorialNotes.short");
+ if (isNothing(crossLinkSubtitle) || crossLinkSubtitle.length === 0) {
+ crossLinkSubtitle = subtitle;
+ }
+ const cardConfig = defaultTodayCardConfiguration(objectGraph);
+ if (serverData.isNull(appEvent) &&
+ externalDeepLink.deepLinkUrlFromData(objectGraph, contentData) &&
+ !objectGraph.client.isiOS) {
+ cardConfig.crossLinkSubtitle = crossLinkSubtitle;
+ }
+ cardConfig.clientIdentifierOverride = clientIdentifierForEditorialContextInData(objectGraph, contentData);
+ if (serverData.isDefinedNonNull(appEvent)) {
+ lockup = appEvent.lockup;
+ }
+ else {
+ // On iOS and Web, we always attempt to create a lockup. On other platforms, only do this is there is a cross link.
+ // iOS offer style will be determined later based on artwork.
+ const offerEnvironment = objectGraph.client.isiOS ? null : "dark";
+ const offerStyle = objectGraph.client.isiOS ? null : "white";
+ if (objectGraph.client.isiOS ||
+ objectGraph.client.isWeb ||
+ externalDeepLink.deepLinkUrlFromData(objectGraph, contentData)) {
+ metricsHelpersLocation.pushContentLocation(objectGraph, contentMetricsOptions, token === null || token === void 0 ? void 0 : token.title);
+ const relatedLockups = lockupsForRelatedContent(objectGraph, mediaRelationship.relationshipCollection(contentData, "card-contents"), cardConfig, metricsOptions.pageInformation, metricsOptions.locationTracker, offerEnvironment, offerStyle, externalDeepLink.deepLinkUrlFromData(objectGraph, contentData));
+ if (relatedLockups.length === 1) {
+ lockup = relatedLockups[0];
+ }
+ metricsHelpersLocation.popLocation(contentMetricsOptions.locationTracker);
+ }
+ }
+ }
+ // falls through
+ default: {
+ // Create a lockup if possible on iOS
+ const validLockupContentTypes = [
+ "apps",
+ "arcade-apps",
+ "app-bundles",
+ "in-apps",
+ ];
+ if (serverData.isNull(lockup) &&
+ validLockupContentTypes.indexOf(contentData.type) > -1 &&
+ objectGraph.host.isiOS) {
+ metricsHelpersLocation.pushContentLocation(objectGraph, contentMetricsOptions, token === null || token === void 0 ? void 0 : token.title);
+ const lockupOptions = {
+ metricsOptions: {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(contentData),
+ },
+ clientIdentifierOverride: token === null || token === void 0 ? void 0 : token.clientIdentifierOverride,
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, token === null || token === void 0 ? void 0 : token.shelfStyle),
+ canDisplayArcadeOfferButton: true,
+ shouldHideArcadeHeader: objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab") &&
+ token.isArcadePage,
+ };
+ lockup = lockups.lockupFromData(objectGraph, contentData, lockupOptions);
+ metricsHelpersLocation.popLocation(contentMetricsOptions.locationTracker);
+ }
+ artwork =
+ contentAttributes.contentAttributeAsDictionary(objectGraph, contentData, "editorialArtwork") ||
+ mediaAttributes.attributeAsDictionary(contentData, "editorialArtwork");
+ if (serverData.isNullOrEmpty(subtitle) && serverData.isDefinedNonNull(lockup)) {
+ subtitle = lockup.subtitle;
+ }
+ break;
+ }
+ }
+ if (serverData.isDefinedNonNull(action)) {
+ action.presentationStyle = ["textFollowsTintColor"];
+ // If data is NOT a Category Grouping kind, reconfigure action title.
+ if (!isCategoryGroupingKind) {
+ // Prefer design tag or editorial name for title when available
+ const designTag = unescapeHtmlString(mediaAttributes.attributeAsString(fcData, "designTag"));
+ const editorialTitle = content.notesFromData(objectGraph, contentData, "name");
+ action.title = designTag || editorialTitle || action.title || subtitle || caption;
+ }
+ }
+ return {
+ action: action,
+ caption: caption,
+ title: action === null || action === void 0 ? void 0 : action.title,
+ subtitle: subtitle,
+ artwork: artwork,
+ shortEditorialDescription: shortEditorialDescription,
+ content: contentData,
+ lockup: lockup,
+ appEvent: appEvent,
+ onDevicePersonalizationDataProcessingType: personalizedDataResult === null || personalizedDataResult === void 0 ? void 0 : personalizedDataResult.processingType,
+ };
+ }
+ return null;
+}
+/// Gets the action and title for a brick that is backed by tags.
+export function metadataForTag(objectGraph, fcData, token, metricsOptions) {
+ const shortEditorialDescription = mediaAttributes.attributeAsString(fcData, "name");
+ const href = serverData.asString(fcData, "href");
+ const url = hrefToRoutableUrl(objectGraph, href);
+ const flowPage = objectGraph.required(pageRouter).fetchFlowPage(url);
+ const flowAction = new models.FlowAction(flowPage);
+ flowAction.pageUrl = url;
+ return {
+ action: flowAction,
+ caption: "null",
+ title: shortEditorialDescription,
+ subtitle: "null",
+ artwork: null,
+ shortEditorialDescription: shortEditorialDescription,
+ };
+}
+function metadataForLink(objectGraph, fcData, token, metricsOptions, unavailableCallback) {
+ const isFCLinkData = serverData.isDefinedNonNull(serverData.asString(fcData, "url"));
+ const linkData = isFCLinkData ? fcData : mediaAttributes.attributeAsDictionary(fcData, "link");
+ const callUnavailable = function (contentData) {
+ if (unavailableCallback) {
+ unavailableCallback();
+ }
+ else {
+ token === null || token === void 0 ? void 0 : token.remainingItems.push(contentData);
+ }
+ };
+ if (serverData.isNull(linkData) || shouldDefer(token)) {
+ callUnavailable(fcData);
+ return null;
+ }
+ const target = serverData.asString(linkData, "target");
+ const url = serverData.asString(linkData, "url");
+ // Prefer design tag for title when available
+ const label = serverData.asString(linkData, "label");
+ const designTag = unescapeHtmlString(mediaAttributes.attributeAsString(fcData, "designTag"));
+ const title = designTag || label;
+ let action = null;
+ if (target === "external") {
+ action = new models.ExternalUrlAction(url);
+ action.title = title;
+ }
+ else {
+ const flowPage = objectGraph.required(pageRouter).fetchFlowPage(url);
+ const flowAction = new models.FlowAction(flowPage);
+ flowAction.pageUrl = url;
+ flowAction.title = title;
+ action = flowAction;
+ }
+ action.presentationStyle = ["textFollowsTintColor"];
+ // Configure metrics
+ const clickOptions = {
+ ...metricsOptions,
+ id: "",
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, clickOptions);
+ return {
+ action: action,
+ caption: null,
+ title: title,
+ subtitle: null,
+ artwork: null,
+ shortEditorialDescription: null,
+ };
+}
+// endregion
+// region Editorial Data Merging
+export function mergeContentDataIntoEditorialData(contentDataArray, editorialItemsDataArray) {
+ const contentDataMap = {};
+ for (const contentData of contentDataArray) {
+ contentDataMap[contentData.id] = contentData;
+ }
+ // The relationships we're interested in merging fetched content for
+ const relationShipsToMerge = ["contents", "grouping"];
+ const mergedEditorialData = [];
+ // Steps for merging
+ // 1. Loop through the editorial items array
+ // 2. For each editorial item loop through the relationships we're interested in
+ // 3. If this editorial item has any of these relationships loop through that relationsip looking in our content map
+ // for the hydrated version of each piece of data in the relationship
+ // 4. If all the content has been fetched for each relationship we can add it to our resulting array of hydrated editorial items.
+ for (const editorialItemData of editorialItemsDataArray) {
+ let hasHydratedAllRelationships = true;
+ for (const relationshipType of relationShipsToMerge) {
+ const unhydratedRelationshipCollection = mediaRelationship.relationshipCollection(editorialItemData, relationshipType);
+ if (serverData.isDefinedNonNull(unhydratedRelationshipCollection)) {
+ const hydratedRelationship = [];
+ for (const unhydratedData of unhydratedRelationshipCollection) {
+ const hydratedData = contentDataMap[unhydratedData.id];
+ if (serverData.isDefinedNonNullNonEmpty(hydratedData)) {
+ hydratedRelationship.push(hydratedData);
+ }
+ }
+ if (hydratedRelationship.length === unhydratedRelationshipCollection.length) {
+ editorialItemData.relationships[relationshipType] = { data: hydratedRelationship };
+ }
+ else {
+ hasHydratedAllRelationships = false;
+ }
+ }
+ }
+ if (hasHydratedAllRelationships) {
+ mergedEditorialData.push(editorialItemData);
+ }
+ }
+ return mergedEditorialData;
+}
+// endregion
+// region Shelf Info
+/**
+ * For incomplete shelf fetches via a secondary lookup, whether a given shelf w/ token should merge or replace.
+ * @param objectGraph
+ * @param token The token corresponding to the shelf being built
+ */
+export function shelfFetchShouldMergeWhenFetched(objectGraph, token) {
+ /**
+ * <rdar://problem/60069585> [POLISH] Arcade Coming Soon: If too few items, the coming soon swoosh should show larger items.
+ * Always reload posterLockup shelves on macOS to adapt if the presentationHint "isLowDensity" changed.
+ */
+ if (token.shelfStyle === "posterLockup" && objectGraph.client.isMac) {
+ return false;
+ }
+ else if (token.showingPlaceholders) {
+ return false;
+ }
+ // Always reload Arcade download pack shelf
+ // as there is always only a single cell (card) that contains lockups or their placeholders.
+ if (token.shelfStyle === "arcadeDownloadPackCard") {
+ return false;
+ }
+ if (token.shelfStyle === "ribbonBar" &&
+ isSome(token.initialHydratedItems) &&
+ token.initialHydratedItems.length > 0) {
+ return false;
+ }
+ // Generally true to support partially hydrated shelves.
+ return true;
+}
+// endregion
+// region Search Landing Shelves
+/**
+ * Modify `shelf` in place for global customization for all SLP shelves.
+ * @param objectGraph
+ * @param shelf
+ * @param token
+ */
+export function modifyShelfForSearchLandingGrouping(objectGraph, shelf, token) {
+ shelf.seeAllAction = null;
+ shelf.isHorizontal = false;
+ if (shelf.shouldFilterApps) {
+ // <rdar://problem/64772261> App Store: SLP: Installed app 'Open' button show up inconsistently (Filtering is sometimes not applied)
+ shelf.filteredItemsMinimumCount = 0;
+ shelf.filteringExcludedItems = token.includedAdAdamIds;
+ }
+}
+// endregion
+// region Card Shelves
+/**
+ * The shelf content type to use for the inline card display style.
+ * @param objectGraph
+ * @param {HorizontalCardDisplayStyle} style The display style for the inline card.
+ * @returns {ShelfContentType} The shelf content type to use.
+ */
+export function contentTypeForHorizontalCardDisplayStyle(objectGraph, style) {
+ switch (style) {
+ case "small":
+ return "smallStoryCard";
+ case "medium":
+ return "mediumStoryCard";
+ case "large":
+ return "largeStoryCard";
+ case "card":
+ if (objectGraph.client.isiOS) {
+ return "editorialStoryCard";
+ }
+ else {
+ return null;
+ }
+ default:
+ return null;
+ }
+}
+// endregion
+/**
+ * Determine the best content id to use given a media api data object
+ * @param objectGraph
+ * @param contentItem The media api data model to find the id from
+ */
+export function contentIdFromContentItem(objectGraph, contentItem) {
+ let contentId = mediaAttributes.attributeAsString(contentItem, "adamId");
+ if (!contentId) {
+ contentId = mediaAttributes.attributeAsString(contentItem, "contentId");
+ }
+ if (!contentId) {
+ contentId = mediaAttributes.attributeAsString(contentItem, "id");
+ }
+ return contentId;
+}
+export function unescapeHtmlString(str) {
+ if (serverData.isNull(str)) {
+ return null;
+ }
+ const escapedString = str
+ .replace(/&amp;/g, "&")
+ .replace(/&gt;/g, ">")
+ .replace(/&lt;/g, "<")
+ .replace(/&quot;/g, '"')
+ .replace(/&#39;/g, "'")
+ .replace(/&#96;/g, "`")
+ .replace(/\r\n/g, " ")
+ .replace(/&nbsp;/g, " ")
+ .replace(/<span>/g, "")
+ .replace(/<\/span>/g, "")
+ .replace(/<br>/g, " ")
+ .replace(/\u23ce/g, "")
+ .replace(/<i>/g, "")
+ .replace(/<\/i>/g, "")
+ .replace(/<b>/g, "")
+ .replace(/<\/b>/g, "");
+ if (escapedString.match(/^\s*$/)) {
+ return null;
+ }
+ return escapedString;
+}
+/**
+ * Update the shelf header to use the provided seeAll action.
+ * @param objectGraph The App Store object graph used to check feature flags
+ * @param shelfHeader The shelf header to update
+ * @param seeAllAction The "See All" action to apply
+ */
+export function replaceShelfHeaderSeeAllAction(objectGraph, shelfHeader, seeAllAction) {
+ if (objectGraph.featureFlags.isEnabled("shelf_header")) {
+ // Modern headers make title tappable with a chevron (>).
+ shelfHeader.titleAction = seeAllAction;
+ }
+ else {
+ // Legacy headers show "See All" textual button on trailing edge.
+ shelfHeader.accessoryAction = seeAllAction;
+ }
+}
+/**
+ * Update the shelf header to use the provided seeAll action.
+ * @param objectGraph The App Store object graph used to check feature flags
+ * @param shelf The shelf whose header needs updating
+ * @param seeAllAction The see all action to apply
+ */
+export function replaceShelfSeeAllAction(objectGraph, shelf, seeAllAction) {
+ if (objectGraph.featureFlags.isEnabled("shelf_header")) {
+ if (isSome(shelf.header)) {
+ replaceShelfHeaderSeeAllAction(objectGraph, shelf.header, seeAllAction);
+ }
+ else {
+ shelf.header = {
+ titleAction: seeAllAction,
+ };
+ }
+ }
+ else {
+ shelf.seeAllAction = seeAllAction;
+ }
+}
+//# sourceMappingURL=grouping-shelf-controller-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller.js
new file mode 100644
index 0000000..fabf85c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-shelf-controller.js
@@ -0,0 +1,395 @@
+import { isSome } from "@jet/environment";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaDataFetching from "../../../foundation/media/data-fetching";
+import * as mediaNetwork from "../../../foundation/media/network";
+import { ResponseMetadata } from "../../../foundation/network/network";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as urls from "../../../foundation/network/urls";
+import * as mediaUrlMapping from "../../builders/url-mapping";
+import { shouldUsePrerenderedIconArtwork } from "../../content/content";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as impressionDemotion from "../../personalization/on-device-impression-demotion";
+import * as productVariants from "../../product-page/product-page-variants";
+import * as refresh from "../../refresh/page-refresh-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+/**
+ * A GroupingShelfController is responsible for all the logic around parsing and rending
+ * a single grouping page shelf.
+ *
+ * The `ShelfMetadata` is a type the is specific for a given shelf and has some additional data needed to render
+ * that shelf.
+ */
+export class GroupingShelfController {
+ // endregion
+ // region AnyGroupingShelfController
+ /**
+ * Indicates whether this grouping shelf controller can create a shelf for the given mediaApiData.
+ * @param objectGraph The App Store dependency graph
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param featuredContentId The featured content id for this shelf data
+ * @param nativeGroupingShelfId The id of the custom shelf type, one not defined on the server
+ */
+ supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) {
+ return this._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId);
+ }
+ /**
+ * Indicates whether this grouping shelf controller can create a shelf for the given mediaApiData.
+ * @param objectGraph The App Store dependency graph
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param featuredContentId The featured content id for this shelf data
+ * @param nativeGroupingShelfId The id of the custom shelf type, one not defined on the server
+ */
+ _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) {
+ const isFeaturedContentIdSupported = this.supportedFeaturedContentIds.has(featuredContentId);
+ let isNativeGroupingShelfIdSupported;
+ if (serverData.isDefinedNonNull(nativeGroupingShelfId)) {
+ isNativeGroupingShelfIdSupported = this.supportedNativeGroupingShelfIds.has(nativeGroupingShelfId);
+ }
+ else {
+ isNativeGroupingShelfIdSupported = true;
+ }
+ return isFeaturedContentIdSupported && isNativeGroupingShelfIdSupported;
+ }
+ /**
+ * This method will return a grouping page shelf regardless of the type of controller
+ * @param objectGraph The App Store dependency graph
+ * @param groupingParseContext The parse context for the grouping page so far
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the
+ * grouping page controller
+ */
+ createShelf(objectGraph, mediaApiData, groupingParseContext, baseShelfToken, baseMetricsOptions) {
+ var _a, _b, _c;
+ const typedMediaApiData = mediaApiData;
+ const shelfData = this.initialShelfDataFromGroupingMediaApiData(objectGraph, typedMediaApiData);
+ const shelfToken = this.shelfTokenFromBaseTokenAndMediaApiData(objectGraph, typedMediaApiData, baseShelfToken, groupingParseContext);
+ const shelfMetricsOptions = this.shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions);
+ const hasShelfMetricsOptions = serverData.isDefinedNonNullNonEmpty(shelfMetricsOptions);
+ if (hasShelfMetricsOptions && this.shouldImpressShelf()) {
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, shelfToken.title);
+ }
+ /// Reorder the shelf contents based on the impression data if available
+ if (serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)) {
+ shelfData.shelfContents = impressionDemotion.personalizeDataItems(shelfData.shelfContents, (_a = groupingParseContext.recoImpressionData) !== null && _a !== void 0 ? _a : {}, (_b = baseMetricsOptions === null || baseMetricsOptions === void 0 ? void 0 : baseMetricsOptions.recoMetricsData) !== null && _b !== void 0 ? _b : {});
+ }
+ const shelf = this._createShelf(objectGraph, shelfToken, shelfData, groupingParseContext);
+ if (hasShelfMetricsOptions && this.shouldImpressShelf()) {
+ metricsHelpersLocation.popLocation(shelfMetricsOptions.locationTracker);
+ if (serverData.isDefinedNonNull(shelf)) {
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ // rdar://84952935 (Placeholder shelves are not being impressed
+ // For placeholder shelves we end up replacing the entire shelf, so we need to make sure the original
+ // impression metrics are included in the token, so they can be added when the real content is fetched
+ // We're doing this here because this is where we decide whether the original shelf should be impressed
+ if (((_c = shelf.url) === null || _c === void 0 ? void 0 : _c.length) > 0 &&
+ serverData.isDefinedNonNullNonEmpty(shelf.impressionMetrics) &&
+ shelfToken.showingPlaceholders) {
+ const originalShelfUrlString = shelf.url;
+ try {
+ // Extract the token from the URL.
+ // Note: Although we have access to the shelfToken here, we do not know that
+ // the url was constructed from token in its current state. To be safe,
+ // if not efficient, we reverse engineer the URL to get the token.
+ const originalShelfUrl = urls.URL.from(originalShelfUrlString);
+ const encodedToken = originalShelfUrl.pathComponents().pop();
+ const shelfTokenFromUrl = JSON.parse(decodeURIComponent(encodedToken));
+ // Modify the token to include the impressions metrics.
+ shelfTokenFromUrl.originalPlaceholderShelfImpressionMetrics = shelf.impressionMetrics;
+ groupingShelfControllerCommon.updateShelfUrlWithNewToken(objectGraph, shelf, shelfTokenFromUrl);
+ }
+ catch {
+ shelf.url = originalShelfUrlString;
+ }
+ }
+ }
+ }
+ this.finalizeInitialShelfForDisplay(objectGraph, shelf, shelfToken, shelfData, groupingParseContext);
+ if (hasShelfMetricsOptions && this.shouldPrepareLocationTrackerForNextPosition()) {
+ metricsHelpersLocation.nextPosition(groupingParseContext.metricsLocationTracker);
+ }
+ return shelf;
+ }
+ /**
+ * Initialize a builder with globally unique name.
+ *
+ * @param {string} builderClass Globally unique name.
+ */
+ constructor(builderClass) {
+ // region Supported Types
+ this.supportedFeaturedContentIds = new Set([]);
+ this.supportedNativeGroupingShelfIds = new Set([]);
+ this.builderClass = builderClass;
+ }
+ /**
+ * Determines the strategy for fetching incomplete shelves based on feature flags and shelf type
+ *
+ * @param objectGraph - The application store object graph.
+ * @returns The strategy for fetching incomplete shelves, either on shelf appearance or on page load.
+ */
+ incompleteShelfFetchStrategy(objectGraph) {
+ if (objectGraph.client.isiOS) {
+ return models.IncompleteShelfFetchStrategy.OnShelfWillAppear;
+ }
+ else {
+ return models.IncompleteShelfFetchStrategy.OnPageLoad;
+ }
+ }
+ // endregion
+ // region Metrics
+ /**
+ * Return the shelf metrics options to use for this specific shelf. Using the base options from the grouping
+ * page controller
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the
+ * grouping page controller
+ */
+ shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) {
+ return baseMetricsOptions;
+ }
+ /**
+ * Whether the shelf itself should be impressed, there are some cases where the shelf itself
+ * does not get impressed, just the contents.
+ */
+ shouldImpressShelf() {
+ return true;
+ }
+ /**
+ * Whether we should move the location tracker to the next position after creating our shelf
+ */
+ shouldPrepareLocationTrackerForNextPosition() {
+ return true;
+ }
+ // endregion
+ // Shelf Finalization
+ /**
+ * This method will set any required fields on our shelf once it has created as part of the initial page rendering.
+ * This includes things like timing metrics, hiding empty shelves etc.
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelf The created shelf
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context for the grouping page so far
+ * @private
+ */
+ finalizeInitialShelfForDisplay(objectGraph, shelf, shelfToken, shelfData, groupingParseContext) {
+ var _a, _b;
+ if (serverData.isNullOrEmpty(shelf)) {
+ return;
+ }
+ // Should not show see all links on search groupings
+ if (shelfToken.isSearchLandingPage) {
+ groupingShelfControllerCommon.modifyShelfForSearchLandingGrouping(objectGraph, shelf, shelfToken);
+ }
+ if (((_a = shelf.url) === null || _a === void 0 ? void 0 : _a.length) > 0 &&
+ serverData.isDefinedNonNullNonEmpty(groupingParseContext.additionalShelfParameters)) {
+ shelf.url = urls.URL.from(shelf.url)
+ .append("query", groupingParseContext.additionalShelfParameters)
+ .build();
+ }
+ // If we're on iOS and no prior fetch strategy has been defined, set the fetchStrategy to OnShelfWillAppear.
+ shelf.fetchStrategy = this.incompleteShelfFetchStrategy(objectGraph);
+ // Shelf will fetch content after sending follow-up fetch request.
+ const willFetchShelfContent = isSome(shelf) && ((_b = shelf.url) === null || _b === void 0 ? void 0 : _b.length) > 0;
+ if (serverData.isNullOrEmpty(shelf.items) && !willFetchShelfContent) {
+ shelf.isHidden = true;
+ }
+ shelf.accessibilityMetadata = createShelfAccessibilityMetadata(objectGraph, shelf);
+ }
+ /**
+ * This method will set any required fields on our shelf once it has been fetched as a result of a secondary fetch.
+ * This includes things like timing metrics, hiding empty shelves etc.
+ *
+ * @param objectGraph The AppStore dependency graph
+ * @param shelf The created shelf
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @private
+ */
+ finalizeSecondaryShelfForDisplay(objectGraph, shelf, shelfToken, shelfData) {
+ if (serverData.isNullOrEmpty(shelf)) {
+ return;
+ }
+ if (shelfToken.remainingItems.length) {
+ const remainingIds = shelfToken.remainingItems.map((data) => {
+ return data.id;
+ });
+ objectGraph.console.warn("Could not load items for: " + remainingIds.join(","));
+ }
+ if (shelf) {
+ shelf.mergeWhenFetched = groupingShelfControllerCommon.shelfFetchShouldMergeWhenFetched(objectGraph, shelfToken);
+ shelf.networkTimingMetrics = shelfData.responseTimingValues;
+ shelf.nextPreferredContentRefreshDate = refresh.nextPreferredContentRefreshDateForController(refresh.newPageRefreshController());
+ }
+ // Merge the original impression metrics with the newly created impression metrics.
+ if (serverData.isDefinedNonNullNonEmpty(shelfToken.originalPlaceholderShelfImpressionMetrics)) {
+ // If the `shelf.impressionMetrics` is null, we just defer to the original metrics.
+ if (serverData.isNull(shelf.impressionMetrics)) {
+ shelf.impressionMetrics = shelfToken.originalPlaceholderShelfImpressionMetrics;
+ }
+ else {
+ for (const key in shelfToken.originalPlaceholderShelfImpressionMetrics.fields) {
+ if (Object.prototype.hasOwnProperty.call(shelfToken.originalPlaceholderShelfImpressionMetrics.fields, key)) {
+ shelf.impressionMetrics.fields[key] =
+ shelfToken.originalPlaceholderShelfImpressionMetrics.fields[key];
+ }
+ }
+ }
+ }
+ if (!shelfToken.hasExistingContent && serverData.isNullOrEmpty(shelf.items)) {
+ shelf.isHidden = true;
+ }
+ // Should not show see all links on search groupings
+ if (shelfToken.isSearchLandingPage) {
+ groupingShelfControllerCommon.modifyShelfForSearchLandingGrouping(objectGraph, shelf, shelfToken);
+ }
+ shelf.accessibilityMetadata = createShelfAccessibilityMetadata(objectGraph, shelf);
+ }
+ // endregion
+ // region ShelfBuilder
+ async handleShelf(objectGraph, url, parameters, matchedRuleIdentifier) {
+ const tokenJson = parameters["token"];
+ const shelfToken = JSON.parse(tokenJson);
+ shelfToken.isFirstRender = false;
+ try {
+ const shelfData = await this.secondaryShelfDataForShelfUrl(objectGraph, url, shelfToken, parameters);
+ const shelf = this._createShelf(objectGraph, shelfToken, shelfData, null);
+ this.finalizeSecondaryShelfForDisplay(objectGraph, shelf, shelfToken, shelfData);
+ return shelf;
+ }
+ catch (error) {
+ if (shelfToken && !shelfToken.hasExistingContent) {
+ const hiddenShelf = new models.Shelf(shelfToken.shelfStyle);
+ hiddenShelf.isHidden = true;
+ return hiddenShelf;
+ }
+ else {
+ throw error;
+ }
+ }
+ }
+ shelfRoute(objectGraph) {
+ if (serverData.isDefinedNonNullNonEmpty(this.supportedNativeGroupingShelfIds)) {
+ return routesForNativeGroupingShelfIds(this.supportedNativeGroupingShelfIds);
+ }
+ else {
+ return routesForFeaturedContentIds(this.supportedFeaturedContentIds);
+ }
+ }
+ // endregion
+ // region Static Base Helpers
+ /**
+ * This is a standard default implementation for the secondary shelf data fetch. This can be used for all the
+ * grouping shelf controls that dont implement a custom ShelfDataType
+ * @param objectGraph
+ * @param shelfUrl
+ * @param parameters
+ */
+ static async secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters).then((mediaApiData) => {
+ const hydratedItems = groupingShelfControllerCommon.hydratedRemainingItemsForShelfTokenFromMediaApiData(objectGraph, shelfToken, mediaApiData);
+ return {
+ shelfContents: hydratedItems,
+ responseTimingValues: mediaApiData[ResponseMetadata.timingValues],
+ };
+ });
+ }
+ /**
+ * This is a standard default implementation for the media api request, for an incomplete grouping shelf.
+ *
+ * @param objectGraph
+ * @param shelfUrl
+ * @param parameters
+ */
+ static async secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters) {
+ const urlString = shelfUrl.build();
+ let request;
+ if (mediaUrlMapping.isMediaUrl(objectGraph, shelfUrl)) {
+ request = new mediaDataFetching.Request(objectGraph, urlString);
+ }
+ else {
+ request = groupingShelfControllerCommon.generateShelfRequest(objectGraph, shelfToken, parameters);
+ }
+ if (!request) {
+ return await Promise.reject(new Error(`Could not construct media API request for: ${shelfUrl}`));
+ }
+ request.includingAdditionalPlatforms(defaultMediaApiPlatforms(objectGraph));
+ request.includingAttributes(defaultMediaApiAttributes(objectGraph));
+ request.usingCustomAttributes(productVariants.shouldFetchCustomAttributes(objectGraph));
+ request.attributingTo(shelfUrl.build());
+ return await mediaNetwork.fetchData(objectGraph, request).then((mediaApiData) => {
+ groupingShelfControllerCommon.flushRequestedItemsFromShelfToken(shelfToken, request.ids);
+ return mediaApiData;
+ });
+ }
+}
+export function createShelfAccessibilityMetadata(objectGraph, shelf) {
+ var _a;
+ let accessibilityLabel = objectGraph.loc.string("Shelves.Accessibility.Label");
+ if (isSome(shelf.title)) {
+ accessibilityLabel = `${shelf.title}, ${accessibilityLabel}`;
+ }
+ else if (isSome((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.title)) {
+ accessibilityLabel = `${shelf.header.title}, ${accessibilityLabel}`;
+ }
+ const accessibilityRoleDescription = objectGraph.loc.string("Shelves.Accessibility.RoleDescription");
+ return {
+ label: accessibilityLabel,
+ roleDescription: accessibilityRoleDescription,
+ };
+}
+function routeForFeaturedContentId(featuredContentId, nativeGroupingShelfId, additionalQueryParams) {
+ const query = serverData.isDefinedNonNullNonEmpty(additionalQueryParams)
+ ? [...additionalQueryParams]
+ : [];
+ query.push(`${Parameters.groupingFeaturedContentId}=${featuredContentId}`);
+ if (serverData.isDefinedNonNullNonEmpty(nativeGroupingShelfId)) {
+ query.push(`${Parameters.nativeGroupingShelfId}=${nativeGroupingShelfId}`);
+ }
+ return {
+ protocol: Protocol.internal,
+ path: `/${Path.grouping}/${Path.shelf}/{token}`,
+ query: query,
+ };
+}
+export function routesForFeaturedContentIds(featuredContentIds, additonalQueryParams) {
+ const routes = [];
+ for (const featuredContentId of featuredContentIds) {
+ routes.push(routeForFeaturedContentId(featuredContentId, null, additonalQueryParams));
+ }
+ return routes;
+}
+export function routesForNativeGroupingShelfIds(nativeGroupingShelfIds, additonalQueryParams) {
+ const routes = [];
+ for (const nativeGroupingShelfId of nativeGroupingShelfIds) {
+ routes.push(routeForFeaturedContentId(-1 /* FeaturedContentID.Native_GroupingShelf */, nativeGroupingShelfId, additonalQueryParams));
+ }
+ return routes;
+}
+// region Media Api Attributes
+function defaultMediaApiPlatforms(objectGraph) {
+ return mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph);
+}
+function defaultMediaApiAttributes(objectGraph) {
+ const attributes = ["editorialArtwork", "isAppleWatchSupported", "requiredCapabilities", "badge-content"];
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ return attributes;
+}
+// endregion
+//# sourceMappingURL=grouping-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-small-breakout-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-small-breakout-shelf-controller.js
new file mode 100644
index 0000000..d7eac20
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-small-breakout-shelf-controller.js
@@ -0,0 +1,168 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaPlatformAttributes from "../../../foundation/media/platform-attributes";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as color from "../../../foundation/util/color-util";
+import * as breakoutsCommon from "../../arcade/breakouts-common";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as flowPreview from "../../content/flow-preview";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as article from "../../today/article";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+export class GroupingSmallBreakoutShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingSmallBreakoutShelfController");
+ this.supportedFeaturedContentIds = new Set([480 /* groupingTypes.FeaturedContentID.AppStore_Breakout */]);
+ }
+ // endregion
+ // region GroupingShelfController
+ _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) {
+ if (!super._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId)) {
+ return false;
+ }
+ const breakoutStyle = mediaAttributes.attributeAsString(mediaApiData, "displayStyle");
+ return breakoutStyle === "small";
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters);
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ // endregion
+ // region Metrics
+ shouldImpressShelf() {
+ return false;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ const items = [];
+ for (const data of shelfData.shelfContents) {
+ if (serverData.isNull(data.attributes) || groupingShelfControllerCommon.shouldDefer(shelfToken)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(data);
+ continue;
+ }
+ const primaryContent = content.primaryContentForData(objectGraph, data);
+ if (breakoutsCommon.requiresPrimaryContent(objectGraph, data) &&
+ !mediaAttributes.hasAttributes(primaryContent)) {
+ shelfToken.isDeferring = true;
+ shelfToken.remainingItems.push(data);
+ shelfToken.relationshipToFetch = "primary-content";
+ continue;
+ }
+ const metricsOptions = {
+ targetType: "smallBreakout",
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ };
+ // If this EI can and should show the expected release date of an arcade app, override the badge.
+ let badgeTitle;
+ const fallbackLabel = mediaAttributes.attributeAsString(data, "label");
+ if (contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "showExpectedReleaseDate")) {
+ badgeTitle = objectGraph.loc.uppercased(content.dynamicPreorderDateFromData(objectGraph, primaryContent, fallbackLabel));
+ }
+ else {
+ badgeTitle = fallbackLabel;
+ }
+ let badge = { type: "none" };
+ if ((badgeTitle === null || badgeTitle === void 0 ? void 0 : badgeTitle.length) > 0) {
+ badge = {
+ type: "text",
+ title: badgeTitle,
+ };
+ }
+ const title = content.editorialNotesFromData(objectGraph, data, "name") ||
+ contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "name");
+ const description = content.editorialNotesFromData(objectGraph, data, "short") ||
+ contentAttributes.contentAttributeAsString(objectGraph, primaryContent, "tagline");
+ const artwork = content.iconFromData(objectGraph, primaryContent, {
+ useCase: 5 /* content.ArtworkUseCase.ArcadeSmallBreakout */,
+ withJoeColorPlaceholder: true,
+ });
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
+ const artworkData = mediaPlatformAttributes.platformAttributeAsDictionary(primaryContent, attributePlatform, "artwork");
+ const backgroundColor = color.fromHex(serverData.asString(artworkData, "textColor4")) || (artwork === null || artwork === void 0 ? void 0 : artwork.backgroundColor);
+ const details = new models.BreakoutDetails(title, description, badge, null, breakoutsCommon.detailBackgroundStyleFromColor(objectGraph, backgroundColor), null);
+ const smallBreakout = new models.SmallBreakout(details, artwork, backgroundColor);
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, smallBreakout.details.title, metricsOptions);
+ const productData = article.productDataFromArticle(objectGraph, data);
+ const isPreorder = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "isPreorder");
+ impressionOptions.isPreorder = isPreorder;
+ metricsHelpersImpressions.addImpressionFields(objectGraph, smallBreakout, impressionOptions);
+ // Push the breakout here so that the click action has the breakout in its location
+ // but we do not want to add it to the overall location tracker, so pop it right after adding it to the button
+ // action
+ // <rdar://problem/60883269> Metrics: Arcade: Container values requested in Location field
+ metricsHelpersLocation.pushContentLocation(objectGraph, impressionOptions, smallBreakout.details.title);
+ const breakoutAction = breakoutsCommon.actionFromData(objectGraph, data);
+ const breakoutClickOptions = {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ targetType: "button",
+ id: data.id,
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, breakoutAction, breakoutClickOptions);
+ smallBreakout.details.callToActionButtonAction = breakoutAction;
+ smallBreakout.clickAction = breakoutAction;
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ // Set flow preview actions
+ smallBreakout.flowPreviewActionsConfiguration =
+ flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, data, true, shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.clientIdentifierOverride, breakoutAction, metricsOptions, breakoutClickOptions);
+ items.push(smallBreakout);
+ }
+ const shelf = new models.Shelf("smallBreakout");
+ shelf.isHorizontal = false;
+ shelf.items = items;
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ if (groupingParseContext.shelves.length === 0) {
+ shelf.presentationHints = { isFirstShelf: true };
+ }
+ return shelf;
+ }
+}
+//# sourceMappingURL=grouping-small-breakout-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tag-brick-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tag-brick-shelf-controller.js
new file mode 100644
index 0000000..66baba8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tag-brick-shelf-controller.js
@@ -0,0 +1,246 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as content from "../../content/content";
+import * as flowPreview from "../../content/flow-preview";
+import * as lockupsEditorialContext from "../../lockups/editorial-context";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as placeholders from "../../placeholders/placeholders";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
+import { defaultLayoutSize } from "../../../foundation/media/data-fetching";
+import { isSome } from "@jet/environment";
+export class GroupingTagBrickShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingTagBrickShelfController");
+ this.supportedFeaturedContentIds = new Set([
+ 584 /* groupingTypes.FeaturedContentID.AppStore_TagsBrick */,
+ 587 /* groupingTypes.FeaturedContentID.AppStore_PersonalizedTagsBrick */,
+ ]);
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ var _a;
+ if (isSome(mediaApiData)) {
+ const shelfContents = (_a = mediaRelationship.relationshipCollection(mediaApiData, "contents")) !== null && _a !== void 0 ? _a : null;
+ return { shelfContents: shelfContents };
+ }
+ return { shelfContents: null };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters).then((shelfData) => {
+ return {
+ shelfContents: shelfData.data,
+ };
+ });
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ // If suppress text is not provided, default to hiding.
+ let suppressText = mediaAttributes.attributeAsBoolean(mediaApiData, "suppressText");
+ if (serverData.isNull(suppressText)) {
+ suppressText = true;
+ }
+ const brickShelfToken = {
+ ...baseShelfToken,
+ showSupplementaryText: !suppressText,
+ };
+ brickShelfToken.clientIdentifierOverride = lockupsEditorialContext.clientIdentifierForEditorialContextInData(objectGraph, mediaApiData);
+ return brickShelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ const items = [];
+ const remainingBricks = []; // Data where metadata was missing.
+ const displayStyle = serverData.asString(shelfToken.featuredContentData.attributes, "displayStyle");
+ const isCategoryBrick = displayStyle === "small";
+ const shelf = new models.Shelf("tagBrick");
+ const layoutSize = serverData.asNumber(shelfToken.featuredContentData.attributes, "layoutStyle.layoutSize");
+ shelf.rowsPerColumn = layoutSize !== null && layoutSize !== void 0 ? layoutSize : defaultLayoutSize(objectGraph);
+ shelf.isHorizontal = true;
+ if (isSome(shelfData.shelfContents)) {
+ for (const tagData of shelfData.shelfContents) {
+ const brickModel = GroupingTagBrickShelfController.createBrick(objectGraph, tagData, isCategoryBrick, shelfToken.metricsPageInformation, shelfToken.metricsLocationTracker, shelfToken, groupingParseContext);
+ if (!isSome(brickModel === null || brickModel === void 0 ? void 0 : brickModel.shortEditorialDescription)) {
+ shelfToken.remainingItems.push(tagData);
+ remainingBricks.push(tagData);
+ continue;
+ }
+ if (isSome(brickModel)) {
+ items.push(brickModel);
+ }
+ metricsHelpersLocation.nextPosition(shelfToken.metricsLocationTracker);
+ }
+ }
+ if (serverData.isDefinedNonNull(shelfToken.presentationHints)) {
+ shelf.presentationHints = shelfToken.presentationHints;
+ }
+ if (serverData.isDefinedNonNull(shelfToken.showSupplementaryText)) {
+ shelf.presentationHints = {
+ ...shelf.presentationHints,
+ showSupplementaryText: shelfToken.showSupplementaryText,
+ };
+ }
+ // We don't need this in our incomplete shelf URL, so we'll preemptively remove it.
+ delete shelfToken.maxItemCount;
+ // Override `featuredContentData` to only have remaining bricks that must be fetched.
+ if (serverData.isDefinedNonNull(serverData.traverse(shelfToken.featuredContentData, "relationships.contents.data")) &&
+ isSome(shelfToken.featuredContentData.relationships)) {
+ shelfToken.featuredContentData.relationships["contents"].data = remainingBricks;
+ }
+ // Set metadata
+ shelf.title = shelfToken.title;
+ shelf.subtitle = shelfToken.subtitle;
+ if (isCategoryBrick) {
+ const displayCount = serverData.asNumber(shelfToken.featuredContentData.attributes, "displayCount");
+ shelf.items = items.slice(0, displayCount !== null && displayCount !== void 0 ? displayCount : items.length);
+ }
+ else {
+ shelf.items = items;
+ }
+ // See all
+ const hasSeeAll = serverData.asBooleanOrFalse(shelfToken.featuredContentData.attributes, "hasSeeAll");
+ if (isCategoryBrick && hasSeeAll) {
+ // Setup shelf
+ const seeAllShelf = new models.Shelf("categoryBrick");
+ seeAllShelf.items = items;
+ seeAllShelf.presentationHints = { isSeeAllContext: true };
+ // Setup Page
+ const seeAllPage = new models.GenericPage([seeAllShelf]);
+ seeAllPage.title = shelfToken.title;
+ // Setup action
+ const seeAllAction = new models.FlowAction("page");
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ seeAllAction.pageData = seeAllPage;
+ // Connect action
+ shelf.seeAllAction = seeAllAction;
+ // Metrics
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, "", {
+ pageInformation: shelfToken.metricsPageInformation,
+ locationTracker: shelfToken.metricsLocationTracker,
+ });
+ }
+ // If no items should we display placeholders for this shelf?
+ const willHydrateShelfLater = shelf && serverData.isNullOrEmpty(shelf.items) && shelfToken.isFirstRender;
+ if (willHydrateShelfLater && placeholders.placeholdersEnabled(objectGraph)) {
+ placeholders.insertPlaceholdersIntoGenericPageShelf(objectGraph, shelf, shelfToken, shelfToken.featuredContentId);
+ }
+ shelfToken.presentationHints = shelf.presentationHints;
+ shelf.url = groupingShelfControllerCommon.createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, shelfToken);
+ return shelf;
+ }
+ // region Static Helpers
+ static createBrick(objectGraph, brickData, searchCategoryBricks, metricsPageInformation, metricsLocationTracker, shelfToken, groupingParseContext) {
+ var _a, _b, _c;
+ const metricsBrickData = (_a = mediaDataStructure.metricsFromMediaApiObject(brickData)) !== null && _a !== void 0 ? _a : undefined;
+ const metricsOptions = {
+ targetType: searchCategoryBricks ? "tile" : "brick",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ recoMetricsData: metricsBrickData,
+ };
+ const metadata = groupingShelfControllerCommon.metadataForTag(objectGraph, brickData, shelfToken !== null && shelfToken !== void 0 ? shelfToken : null, metricsOptions);
+ if (!metadata) {
+ const brickModel = new models.Brick();
+ brickModel.shortEditorialDescription = mediaAttributes.attributeAsString(brickData, "name");
+ return brickModel;
+ }
+ const brickModel = new models.Brick();
+ // Setup Artwork
+ const artworkOptions = {
+ useCase: 18 /* content.ArtworkUseCase.GroupingBrick */,
+ };
+ const collectionIcons = groupingShelfControllerCommon.artworkForTags(objectGraph, brickData, 1060, 520, artworkOptions, metricsOptions);
+ if (collectionIcons.length > 0) {
+ const collectionIconBackgroundColor = collectionIcons[0].backgroundColor;
+ brickModel.collectionIcons = collectionIcons;
+ if (isSome(collectionIconBackgroundColor) && (collectionIconBackgroundColor === null || collectionIconBackgroundColor === void 0 ? void 0 : collectionIconBackgroundColor.type) === "rgb") {
+ brickModel.backgroundColor =
+ (_b = content.closestTagBackgroundColorForIcon(collectionIconBackgroundColor)) !== null && _b !== void 0 ? _b : undefined;
+ }
+ }
+ brickModel.accessibilityLabel = metadata.title;
+ // Set supplementary text.
+ brickModel.shortEditorialDescription = metadata.shortEditorialDescription;
+ // Set action
+ brickModel.clickAction = metadata.action;
+ // Set personalization
+ const brickFeaturedContentId = mediaAttributes.attributeAsNumber(brickData, "editorialElementKind");
+ if (brickFeaturedContentId === 435 /* groupingTypes.FeaturedContentID.AppStore_MSOBrickMarker */) {
+ brickModel.personalizationStyle = "mso";
+ }
+ // Set flow preview actions
+ const contentData = mediaRelationship.relationshipData(objectGraph, brickData, "contents");
+ if (serverData.isDefinedNonNull(contentData)) {
+ const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, contentData, metricsOptions);
+ const clientIdentifierOverride = (_c = shelfToken === null || shelfToken === void 0 ? void 0 : shelfToken.clientIdentifierOverride) !== null && _c !== void 0 ? _c : null;
+ brickModel.flowPreviewActionsConfiguration = flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, brickData, true, clientIdentifierOverride, brickModel.clickAction, metricsOptions, metricsClickOptions);
+ }
+ // Configure impressions
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, brickData, metadata.title, metricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, brickModel, impressionOptions);
+ // Safe area
+ brickModel.artworkSafeArea = models.ChartOrCategorySafeArea.defaultTileArtworkSafeArea;
+ brickModel.textSafeArea = models.ChartOrCategorySafeArea.defaultTileTextSafeArea;
+ if (!brickModel.isValid()) {
+ return null;
+ }
+ return brickModel;
+ }
+ // endregion
+ // region Metrics
+ /**
+ * Return the shelf metrics options to use for this specific shelf. Using the base options from the grouping
+ * page controller
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the
+ * grouping page controller
+ */
+ shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) {
+ const shelfMetricsOptions = { ...baseMetricsOptions };
+ const displayStyle = serverData.asString(shelfToken.featuredContentData.attributes, "displayStyle");
+ // If this is a Category Bricks shelf, configure metrics title accordingly.
+ if (displayStyle === "small") {
+ shelfMetricsOptions.title = "Browse Categories";
+ }
+ return shelfMetricsOptions;
+ }
+}
+//# sourceMappingURL=grouping-tag-brick-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tags-header-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tags-header-shelf-controller.js
new file mode 100644
index 0000000..bb6a950
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/grouping/shelf-controllers/grouping-tags-header-shelf-controller.js
@@ -0,0 +1,92 @@
+import * as models from "../../../api/models";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { areAppTagsEnabled } from "../../util/app-tags-util";
+import { GroupingShelfController } from "./grouping-shelf-controller";
+import { attributeAsDictionary } from "@apple-media-services/media-api";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { isSome } from "@jet/environment";
+import * as content from "../../content/content";
+import { artworkDictionaryFromData } from "../../arcade/breakouts-common";
+import * as color from "../../../foundation/util/color-util";
+/**
+ * This groupings controller adds the logic to display a header shelf on the groupings page. Th primary expereince
+ * is artwork and the secondary experience includes fallback icons.
+ */
+export class GroupingMediaPageHeaderShelfController extends GroupingShelfController {
+ // region Constructors
+ constructor() {
+ super("GroupingMediaPageHeaderShelfController");
+ this.supportedFeaturedContentIds = new Set([585 /* groupingTypes.FeaturedContentID.AppStore_MediaPageHeader */]);
+ }
+ // endregion
+ // region Shelf Creation Prerequisites
+ /**
+ * For a given mediaApiData extract the actual shelfContents array needed to render this shelf
+ *
+ * @param mediaApiData The outer shelfContents object containing the shelf contents
+ */
+ initialShelfDataFromGroupingMediaApiData(objectGraph, mediaApiData) {
+ return { shelfContents: mediaRelationship.relationshipCollection(mediaApiData, "contents") };
+ }
+ /**
+ * For a given url that this controller handles, we should return a promise that will result in the `ShelfData`
+ * needed to render this shelf
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfUrl The url that this controller handled on a secondary fetch
+ * @param parameters The extracted parameters from the shelf url
+ */
+ async secondaryShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
+ return await GroupingShelfController.secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters);
+ }
+ /**
+ * For a given mediaApiData create an updated shelf token that contains all the additional data for this specific shelf type
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param baseShelfToken The base grouping shelf token created by the grouping-controller
+ * @param mediaApiData The outer data object containing the FC properties and data
+ * @param groupingParseContext The parse context for the grouping page so far
+ */
+ shelfTokenFromBaseTokenAndMediaApiData(objectGraph, mediaApiData, baseShelfToken, groupingParseContext) {
+ return baseShelfToken;
+ }
+ // endregion
+ // region Shelf Creation
+ /**
+ *
+ * @param objectGraph The App Store dependency graph
+ * @param shelfToken The shelf shelfToken for this current shelf creation request
+ * @param shelfData The media api shelfContents array for this shelf
+ * @param groupingParseContext The parse context used to generate the grouping page on the initial page load,
+ * this will be missing when this controller renders a secondary or incomplete shelf fetch.
+ */
+ _createShelf(objectGraph, shelfToken, shelfData, groupingParseContext) {
+ var _a;
+ if (!areAppTagsEnabled(objectGraph, "grouping")) {
+ return null;
+ }
+ const headerData = shelfData.shelfContents[0];
+ let title;
+ if (isSome(headerData)) {
+ const editorialNotes = attributeAsDictionary(headerData, "editorialNotes");
+ title = (_a = serverData.asString(editorialNotes, "name")) !== null && _a !== void 0 ? _a : null;
+ const artworkDictionary = artworkDictionaryFromData(objectGraph, headerData);
+ const artworkData = serverData.asDictionary(artworkDictionary, "categoryDetailStatic16x9");
+ const artworkFromData = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ withJoeColorPlaceholder: true,
+ useCase: 0 /* content.ArtworkUseCase.Default */,
+ cropCode: "CDS.ApTCHM01",
+ });
+ if (isSome(title) && isSome(artworkFromData)) {
+ const isArtworkDark = color.isDarkColor(artworkFromData.backgroundColor);
+ const pageHeader = new models.MediaPageHeader(null, title, null, artworkFromData, null, null, false, null, null, isArtworkDark ? "dark" : "light");
+ const shelf = new models.Shelf("mediaPageHeader");
+ shelf.items = [pageHeader];
+ return shelf;
+ }
+ return null;
+ }
+ return null;
+ }
+}
+//# sourceMappingURL=grouping-tags-header-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/linking/external-deep-link.js b/node_modules/@jet-app/app-store/tmp/src/common/linking/external-deep-link.js
new file mode 100644
index 0000000..00031e4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/linking/external-deep-link.js
@@ -0,0 +1,73 @@
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as objects from "../../foundation/util/objects";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+/**
+ * "External Deep Link" feature description
+ *
+ * This feature is one where a particular offer will behave like a normal offer when the app is not openable, but in
+ * which the openable state for the offer actually deep links to the third party app in question. This deep link uses
+ * a universal link that is plumbed through by Media API; the deep link is submitted and approved through
+ * App Store Connect and will be interpreted by the third party app in accordance with the external action.
+ *
+ * This feature's name is prefixed by the term "external" to differentiate from deep linking into the App Store app
+ * itself (such as deep linking into the search tab via Siri or the user's purchases through a finance page).
+ */
+/// The query parameter name to use when attaching the external deep link to a product URL.
+export const externalDeepLinkQueryParameter = "externalDeepLinkUrl";
+/// Query parameter name to use when attaching eligibility information for cpp deep links.
+export const cppDeepLinkDisabledQueryParameter = "isCppDeepLinkDisabled";
+/// Query parameter name to use when attaching the aligned region deep link to a product URL.
+export const alignedRegionDeepLinkQueryParameter = "alignedRegionDeepLinkUrl";
+/**
+ * Pulls out the external deep link url from the given data.
+ * @param {Data} data The data for the app that has the deep link.
+ * @return {string | null} The external deep link url, if it exists.
+ */
+export function deepLinkUrlFromData(objectGraph, data) {
+ if (!serverData.isDefinedNonNull(data)) {
+ return null;
+ }
+ return mediaAttributes.attributeAsString(data, "customUrl");
+}
+/**
+ * Wraps an action in a state action that executes an external deep link when the app is openable.
+ * @param {Action} action The action for the product that is being deep linked into.
+ * @param {string | null} adamId The adam ID of the app.
+ * @param {string | null} bundleId The bundle ID of the app.
+ * @param {string | null} deepLinkUrl The url for the deep link action.
+ * @param {boolean} includeBetaApps Whether to include beta apps in the resulting action.
+ * @param metricsClickOptions The metrics click options for the action.
+ * @return {Action} The action to use for the app that is deep linked.
+ */
+export function deepLinkActionWrappingAction(objectGraph, action, adamId, bundleId, deepLinkUrl, includeBetaApps, metricsClickOptions) {
+ // Only wrap in OfferStateAction on devices that are supported when there's a deep link present.
+ if (!serverData.isDefinedNonNullNonEmpty(deepLinkUrl) && objectGraph.client.deviceType !== "mac") {
+ return action;
+ }
+ let openAction;
+ if (objectGraph.client.isiOS) {
+ const openAppAction = new models.OpenAppAction(adamId, "app");
+ openAction = new models.AppLaunchTrampolineAction(bundleId, deepLinkUrl, openAppAction);
+ }
+ else {
+ openAction = new models.ExternalUrlAction(deepLinkUrl);
+ }
+ const clickOptions = objects.shallowCopyOf(metricsClickOptions);
+ clickOptions.actionType = "open";
+ clickOptions.actionDetails = { actionUrl: deepLinkUrl };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, openAction, clickOptions, true, "button");
+ if (action instanceof models.OfferStateAction) {
+ action.openAction = openAction;
+ action.includeBetaApps = includeBetaApps;
+ return action;
+ }
+ else {
+ const stateAction = new models.OfferStateAction(adamId, action);
+ stateAction.openAction = openAction;
+ stateAction.includeBetaApps = includeBetaApps;
+ return stateAction;
+ }
+}
+//# sourceMappingURL=external-deep-link.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/linking/os-update-links.js b/node_modules/@jet-app/app-store/tmp/src/common/linking/os-update-links.js
new file mode 100644
index 0000000..cce8e9d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/linking/os-update-links.js
@@ -0,0 +1,42 @@
+/**
+ * Created by ls on 12/15/18.
+ *
+ * A home for building application URL links for jumping to other applications.
+ */
+// region System Preference: Updates
+/**
+ * Build an URL that links to software update pref pane on given platform.
+ * Optionally, supply an major OS version to signal Preferences to initiate download for given version if that is supported by the platform.
+ * @param {DeviceType} deviceType Device type to request the update url for.
+ * @param {string | null} majorOSBundle Optional major OS bundle identifier, e.g. com.apple.InstallAssistant.Catalina
+ * @returns {string | null} URL that links into the "Software Updates" settings for given platform. `null` for platforms that we don't currently link into.
+ * @note Currently, only MAS is supported by this function as it is the only platform linking to Preference > OS Update.
+ */
+export function osUpdateUrl(deviceType, majorOSBundle = null) {
+ switch (deviceType) {
+ case "mac":
+ return macOSUpdateUrl(majorOSBundle);
+ default:
+ return null;
+ }
+}
+/**
+ * Build an URL that links to software update pref pane on macOS.
+ * Optionally, supply an major OS version to signal Preferences to initiate download for given version.
+ * @param {string | null} majorOSBundle Optional major OS bundle identifier, e.g. com.apple.InstallAssistant.Catalina
+ * @returns {string | null} URL that links into the "Software Updates" settings for macOS.
+ */
+function macOSUpdateUrl(majorOSBundle) {
+ /**
+ * Workaround for <rdar://problem/47124330> Parsing and building URLs should follow unified spec
+ * Manually build url.
+ */
+ // Preference URLs have no authority component.
+ let updateUrl = `x-apple.systempreferences:com.apple.preferences.softwareupdate?client=AppStore&variant=CUSTOMER`;
+ if (majorOSBundle) {
+ updateUrl += `&installMajorOSBundle=${majorOSBundle}`;
+ }
+ return updateUrl;
+}
+// endregion
+//# sourceMappingURL=os-update-links.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/locale.js b/node_modules/@jet-app/app-store/tmp/src/common/locale.js
new file mode 100644
index 0000000..b829f75
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/locale.js
@@ -0,0 +1,36 @@
+/**
+ * Retrieve the active {@linkcode NormalizedLocale} for the client
+ *
+ * This can be useful in cases where you need to create a `RoutableIntent`
+ * but do not have access to an existing `RoutableIntent` to read the locale
+ * information off of.
+ *
+ * Note: this should be used only as a last resort. Wherever possible, prefer to
+ * retrieve the {@linkcode NormalizedLocale} data off of an existing `RoutableIntent`
+ * instead of depending on the global state in the {@linkcode AppStoreObjectGraph}.
+ */
+export function getLocale(objectGraph) {
+ return {
+ storefront: objectGraph.locale.activeStorefront,
+ language: objectGraph.locale.activeLanguage,
+ };
+}
+/**
+ * Determine how the `NormalizedStorefront` should be encoded into a URL
+ *
+ * This provides a means by which the app can determine how the storefront and language
+ * should be represented when creating a URL that includes locale information.
+ *
+ * This can be used, for example, to opt out of expressing an explicit language when it would
+ * be the default for the given storefront.
+ */
+export function deriveLocaleForUrl(objectGraph, storefront) {
+ return objectGraph.locale.deriveLocaleForUrl(storefront);
+}
+/**
+ * Turns an {@linkcode UnnormalizedLocale} into a {@linkcode NormalizedLocale}
+ */
+export function normalizeLocale(objectGraph, unnormalizedLocale) {
+ return objectGraph.locale.normalize(unnormalizedLocale);
+}
+//# sourceMappingURL=locale.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/lockups/ad-lockups.js b/node_modules/@jet-app/app-store/tmp/src/common/lockups/ad-lockups.js
new file mode 100644
index 0000000..f668735
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/lockups/ad-lockups.js
@@ -0,0 +1,439 @@
+/**
+ * Builder code specific to Ad Lockups
+ */
+import { isSome } from "@jet/environment";
+import { AdInteractionAction, AdTransparencyAction, CompoundAction, Screenshots, SearchAd, SearchAdOpportunity, } from "../../api/models";
+import { AdvertActionMetrics, } from "../../api/models/metrics/advert-action-metrics";
+import { isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, asBooleanOrFalse, asArray, } from "../../foundation/json-parsing/server-data";
+import { attributeAsArrayOrEmpty, attributeAsBooleanOrFalse, attributeAsDictionary, attributeAsString, } from "../../foundation/media/attributes";
+import { advertInstanceIdForData } from "../ads/ad-common";
+import * as content from "../content/content";
+import { addClickEventsToAdLockup } from "../metrics/helpers/clicks";
+import { addImpressionsFieldsToAd, impressionOptionsForLockup } from "../metrics/helpers/impressions";
+import { metricsPlatformDisplayStyleFromData } from "../metrics/helpers/util";
+import { appDataHasVariant } from "../product-page/product-page-variants";
+import { adLogger } from "../search/search-ads";
+import { asString } from "@apple-media-services/media-api";
+// region Shared
+/**
+ * Whether or not data is for ad.
+ *
+ * Note that this attribute *may not be initially part of the original response*.
+ * - Sponsored Search: Since Ad Rotation, `iad` attribute is populated from contents of `iads` dictionary.
+ * - Search Landing: `iad` attribute is populated from contents of `OnDeviceAdvert`.
+ *
+ * @param data Data to check if it is an ad.
+ * @see`iadAttributesForType` and `decorateiAdAttributeFromOnDeviceAd`
+ */
+export function isAdvert(objectGraph, data) {
+ const adDictionary = attributeAsDictionary(data, "iad");
+ return isDefinedNonNullNonEmpty(adDictionary);
+}
+// endregion
+// region Data Overrides
+/**
+ * Populates Ad-specific customizations for given lockup to a **otherwise fully configured ad lockup**
+ * @param objectGraph The object graph.
+ * @param data Data that will provide the ad data.
+ * @param lockup The lockup to modify.
+ * @param metricsOptions The lockup metrics options.
+ * @param applyAdOfferDisplayProperties Whether to apply the default ad OfferDisplayProperties. Some callers of this function want to enforce their own offer styling.
+ */
+export function performAdOverridesforLockup(objectGraph, data, lockup, metricsOptions, applyAdOfferDisplayProperties = true) {
+ // If the lockup is not an ad, it's ad eligible. Just apply the metrics modifications and return early.
+ if (!metricsOptions.isAdvert) {
+ performMetricsOverridesForLockup(objectGraph, data, lockup, metricsOptions);
+ return;
+ }
+ let searchAd;
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ lockup.searchAdOpportunity = searchAdOpportunityFromData(objectGraph, data, metricsOptions.pageInformation);
+ searchAd = lockup.searchAdOpportunity.searchAd;
+ }
+ else {
+ lockup.searchAd = searchAdFromData(objectGraph, data, metricsOptions.pageInformation);
+ searchAd = lockup.searchAd;
+ }
+ /**
+ * Advert Action Metrics (AzulC+)
+ */
+ const advertType = content.isArcadeSupported(objectGraph, data)
+ ? "arcadeApp"
+ : "standardApp";
+ const reportingDestination = reportingDestinationFromMetricsOptions(objectGraph, metricsOptions.pageInformation);
+ const bundleId = attributeAsString(data, "platformAttributes.ios.bundleId");
+ const isPreorder = attributeAsBooleanOrFalse(data, "isPreorder");
+ const purchaseType = isPreorder ? "preorder" : "standard";
+ const clickActionAdActionMetrics = new AdvertActionMetrics(searchAd.instanceId, data.id, bundleId, advertType, "advertPressed", purchaseType, reportingDestination);
+ lockup.clickAction = attachAdActionMetricsToAction(objectGraph, lockup.clickAction, clickActionAdActionMetrics);
+ const buttonActionAdActionMetrics = new AdvertActionMetrics(searchAd.instanceId, data.id, bundleId, advertType, "offerButtonPress", purchaseType, reportingDestination);
+ lockup.buttonAction = attachAdActionMetricsToAction(objectGraph, lockup.buttonAction, buttonActionAdActionMetrics);
+ lockup.itemBackground = objectGraph.props.enabled("insetAdItemBackground") ? "insetAd" : "ad";
+ // Offer button style
+ if (lockup.offerDisplayProperties && applyAdOfferDisplayProperties) {
+ lockup.offerDisplayProperties = lockup.offerDisplayProperties.newOfferDisplayPropertiesChangingAppearance(false, "colored", "ad");
+ }
+ // Rating
+ if (!attributeAsBooleanOrFalse(data, "iad.format.userRating")) {
+ lockup.rating = null;
+ lockup.ratingCount = null;
+ }
+ performMetricsOverridesForLockup(objectGraph, data, lockup, metricsOptions);
+}
+/**
+ * Whether or not an advert is eligible to use CPP deeplinks, regardless of whether or not one exists
+ * @param data Data that will provide the ad data.
+ */
+export function isCppDeeplinkEnabledForAdvert(data) {
+ const iAd = attributeAsDictionary(data, "iad");
+ const searchAdCppDeepLinkEnabled = asBooleanOrFalse(iAd, "passthroughAdInfo.deepLinkEligible");
+ const targetedAdCppDeeplinkingEnabled = asBooleanOrFalse(data, "meta.passthroughAdInfo.deepLinkEligible");
+ return searchAdCppDeepLinkEnabled || targetedAdCppDeeplinkingEnabled;
+}
+/**
+ * Whether or not a custom creative advert is eligible to use CPP deeplinks, regardless of whether or not one exists
+ * @param data Data that will provide the ad data.
+ */
+export function isCustomCreativeDeeplinkEnabledForAdvert(data) {
+ const extraAdInfo = getExtraAdInfo(data);
+ if (isSome(extraAdInfo)) {
+ return asBooleanOrFalse(extraAdInfo, "passthroughAdInfo.deepLinkEligible");
+ }
+ else {
+ return asBooleanOrFalse(data, "meta.passthroughAdInfo.deepLinkEligible");
+ }
+}
+/**
+ * The custom creative ad deeplink url, if there is one.
+ * @param data Data that will provide the ad data.
+ */
+export function getCustomCreativeDeepLinkUrl(data) {
+ const extraAdInfo = getExtraAdInfo(data);
+ const creativeDetails = asArray(extraAdInfo, "creativeDetails");
+ if (isSome(creativeDetails)) {
+ return asString(creativeDetails[0], "deepLink");
+ }
+ else {
+ return asString(data, "meta.alignedRegionDetails.deepLink");
+ }
+}
+/**
+ * The tapDestination of the custom creative advert which is also the cppId for the PP.
+ * @param data Data that will provide the ad data.
+ */
+export function getTapDestinationIdForAdvert(data) {
+ const extraAdInfo = getExtraAdInfo(data);
+ const creativeDetails = asArray(extraAdInfo, "creativeDetails");
+ if (isSome(creativeDetails)) {
+ return asString(creativeDetails[0], "tapDestination");
+ }
+ else {
+ return asString(data, "meta.alignedRegionDetails.tapDestination");
+ }
+}
+/**
+ * Local function to extract the extraAdInfo from the meta in the data.
+ * @param data Data that will provide the ad data.
+ */
+function getExtraAdInfo(data) {
+ const extraAdInfoString = asString(data, "meta.extraAdInfo");
+ if (isSome(extraAdInfoString)) {
+ return JSON.parse(extraAdInfoString);
+ }
+ return null;
+}
+/**
+ * Perform overrides for metrics for Ad Lockups
+ * @param data Data for ad lockup
+ * @param lockup Lockup to apply overrides to
+ * @param baseMetricsOptions Base metrics options to extend.
+ */
+function performMetricsOverridesForLockup(objectGraph, data, lockup, baseMetricsOptions) {
+ const platformDisplayStyle = metricsPlatformDisplayStyleFromData(objectGraph, data, lockup.icon, null);
+ const impressionsMetricOptions = impressionOptionsForLockup(objectGraph, data, lockup, platformDisplayStyle, baseMetricsOptions, true);
+ addClickEventsToAdLockup(objectGraph, lockup, impressionsMetricOptions);
+ addImpressionsFieldsToAd(objectGraph, lockup, impressionsMetricOptions, impressionsMetricOptions.pageInformation.iAdInfo);
+}
+/**
+ * Creates a template type string for the medium ad format
+ * @param lockup The lockup whose artwork to use for the template type string
+ * @returns The native ad template type for the medium format ad
+ */
+export function getTemplateTypeForMediumAdFromLockupWithScreenshots(screenshots) {
+ var _a;
+ const templateString = [
+ "MEDRIVER_",
+ "U",
+ "I",
+ ((_a = screenshots === null || screenshots === void 0 ? void 0 : screenshots.artwork) !== null && _a !== void 0 ? _a : []).length, // Count of assets used.
+ ].join("");
+ return templateString;
+}
+/**
+ * Creates a template type string for the medium ad format
+ * @param lockup The lockup whose artwork to use for the template type string
+ * @returns The native ad template type for the medium format ad
+ */
+export function getTemplateTypeForMediumAdFromLockupWithCustomCreative() {
+ const templateString = [
+ "MEDRIVER_",
+ "U",
+ "I",
+ 1,
+ "_2x1", // Aspect Ratio used.
+ ].join("");
+ return templateString;
+}
+/**
+ * Create an opportunity for an ad model
+ * @param objectGraph The object graph.
+ * @param data Data to build `SearchAd` for.
+ * @param pageInformation Metrics page information.
+ * @returns The search ad opportunity represented in the data
+ */
+export function searchAdOpportunityFromData(objectGraph, data, pageInformation) {
+ const searchAd = searchAdFromData(objectGraph, data, pageInformation);
+ const lifecycleEventPayloads = searchAdLifecycleEventPayloads(searchAd.instanceId, pageInformation);
+ return new SearchAdOpportunity(searchAd.instanceId, lifecycleEventPayloads, searchAd);
+}
+/**
+ * Create a missed opportunity for an ad model. This differentiates from `searchAdOpportunityFromData` because the
+ * opportunity is represented by an organic content that is in an ad eligible position.
+ * @param objectGraph The object graph.
+ * @param pageInformation Metrics page information.
+ * @returns The search ad opportunity for the missed opportunity
+ */
+export function searchAdMissedOpportunityFromId(objectGraph, pageInformation) {
+ let adInstanceId;
+ const placementType = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo.placementType;
+ if (isDefinedNonNull(placementType) && objectGraph.props.enabled("advertSlotReporting")) {
+ try {
+ adInstanceId = objectGraph.ads.getIdentifierForMissedOpportunity(placementType);
+ }
+ catch {
+ adInstanceId = objectGraph.random.nextUUID();
+ adLogger(objectGraph, `Error: getIdentifierForMissedOpportunity threw exception. Assigned ${adInstanceId}`);
+ }
+ }
+ else {
+ adInstanceId = objectGraph.random.nextUUID();
+ adLogger(objectGraph, `Error: placementType was null or empty. Assigned ${adInstanceId}`);
+ }
+ const lifecycleEventPayloads = searchAdLifecycleEventPayloads(adInstanceId, pageInformation);
+ return new SearchAdOpportunity(adInstanceId, lifecycleEventPayloads);
+}
+/**
+ * Create the backing search ad model for any representation of ad.
+ * @param objectGraph The object graph.
+ * @param data Data to build `SearchAd` for.
+ * @param pageInformation Information of the page hosting the lockup
+ * @returns The search ad model
+ */
+function searchAdFromData(objectGraph, data, pageInformation) {
+ let instanceId = advertInstanceIdForData(objectGraph, data);
+ if (isNull(instanceId) || instanceId.length === 0) {
+ instanceId = objectGraph.random.nextUUID();
+ adLogger(objectGraph, `Error: instanceId was null or empty. Assigned ${instanceId}`);
+ }
+ const iAd = attributeAsDictionary(data, "iad");
+ const impressionId = attributeAsString(data, "iad.impressionId");
+ const transparencyData = attributeAsString(data, "iad.privacy");
+ const bundleId = attributeAsString(data, "platformAttributes.ios.bundleId");
+ const transparencyAction = new AdTransparencyAction(transparencyData);
+ transparencyAction.title = objectGraph.adsLoc.string("IAD_PRIVACY_MARKER_BUTTON_TITLE");
+ const advertType = content.isArcadeSupported(objectGraph, data)
+ ? "arcadeApp"
+ : "standardApp";
+ const isPreorder = attributeAsBooleanOrFalse(data, "isPreorder");
+ const purchaseType = isPreorder ? "preorder" : "standard";
+ const reportingDestination = reportingDestinationFromMetricsOptions(objectGraph, pageInformation);
+ const adActionMetrics = new AdvertActionMetrics(instanceId, data.id, bundleId, advertType, "markerPress", purchaseType, reportingDestination);
+ const lifecycleEventPayloads = searchAdLifecycleEventPayloads(instanceId, pageInformation);
+ const action = attachAdActionMetricsToAction(objectGraph, transparencyAction, adActionMetrics);
+ return new SearchAd(instanceId, iAd, lifecycleEventPayloads, impressionId, action);
+}
+/**
+ * Create the backing metadata for any representation of ad opportunity.
+ * @param instanceId The identifier for the ad opportunity we're tracking
+ * @param pageInformation Information of the page hosting the lockup
+ * @returns Payloads to be sent to PromotedContent
+ */
+export function searchAdLifecycleEventPayloads(instanceId, pageInformation) {
+ const pageIdentifierRaw = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.baseFields.pageId;
+ const pageIdentifier = typeof pageIdentifierRaw === "string" ? pageIdentifierRaw : "unknown";
+ return {
+ placed: {
+ adInstanceId: instanceId,
+ pageIdentifier: pageIdentifier,
+ },
+ pageEnter: {
+ pageIdentifier: pageIdentifier,
+ },
+ pageExit: {
+ pageIdentifier: pageIdentifier,
+ },
+ onScreen: {
+ adInstanceId: instanceId,
+ },
+ offScreen: {
+ adInstanceId: instanceId,
+ },
+ visible: {
+ adInstanceId: instanceId,
+ },
+ completed: {
+ adInstanceId: instanceId,
+ },
+ };
+}
+/**
+ * Populates Ad-specific customizations for given card to an otherwise fully configured Today card
+ * @param objectGraph The object graph.
+ * @param data Data that will provide the ad data.
+ * @param card The Today card to modify.
+ * @param metricsOptions The card metrics options.
+ */
+export function performAdOverridesForCard(objectGraph, data, card, metricsOptions) {
+ let instanceId = advertInstanceIdForData(objectGraph, data);
+ if (isNull(instanceId) || instanceId.length === 0) {
+ instanceId = objectGraph.random.nextUUID();
+ adLogger(objectGraph, `Error: instanceId was null or empty. Assigned ${instanceId}`);
+ }
+ const advertType = content.isArcadeSupported(objectGraph, data)
+ ? "arcadeApp"
+ : "standardApp";
+ const reportingDestination = reportingDestinationFromMetricsOptions(objectGraph, metricsOptions.pageInformation);
+ const bundleId = attributeAsString(data, "platformAttributes.ios.bundleId");
+ const isPreorder = attributeAsBooleanOrFalse(data, "isPreorder");
+ const purchaseType = isPreorder ? "preorder" : "standard";
+ const adActionMetrics = new AdvertActionMetrics(instanceId, data.id, bundleId, advertType, "advertPressed", purchaseType, reportingDestination);
+ card.clickAction = attachAdActionMetricsToAction(objectGraph, card.clickAction, adActionMetrics);
+}
+/**
+ * Attaches `AdActionMetrics` to an existing action enabling native ad interaction instrumentation.
+ * This works by wrapping the existing action, along with a new `AdInteractionAction`, in a `CompoundAction`, which are executed at the same time.
+ *
+ * @param objectGraph The Object Graph.
+ * @param existingAction The existing Action to wrap.
+ * @param adActionMetrics The adActionMetrics object for the `AdInteractionAction`.
+ * @returns The new Action containing the existing and new Actions.
+ */
+export function attachAdActionMetricsToAction(objectGraph, existingAction, adActionMetrics) {
+ const adInteractionAction = new AdInteractionAction(adActionMetrics);
+ const action = new CompoundAction([adInteractionAction, existingAction]);
+ // Re-use the title from the existing action.
+ action.title = existingAction.title;
+ return action;
+}
+// endregion
+// region Asset Overrides
+/**
+ * Performs iAd Asset overrides, where a field dictates which assets to use
+ * @param data The data to source overrides from
+ * @param lockup The lockup to modify
+ * @param metricsOptions The metrics options that this operation has side effects on.
+ */
+export function performAssetOverridesForMixedMediaAdLockupIfNeeded(objectGraph, data, lockup, metricsOptions) {
+ if (!shouldPerformAssetOverridesForData(objectGraph, data)) {
+ return;
+ }
+ const iAdAssetOverrides = attributeAsArrayOrEmpty(data, "iad.assetOverride");
+ if (iAdAssetOverrides.length) {
+ const didApplyAssetOverrides = applyAssetOverridesToMixedMediaAdLockup(objectGraph, lockup, iAdAssetOverrides);
+ if (metricsOptions.pageInformation.iAdInfo && metricsOptions.pageInformation.iAdInfo.iAdIsPresent) {
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ metricsOptions.pageInformation.iAdInfo.setSpecifiedAlignedRegionUsed(didApplyAssetOverrides);
+ }
+ }
+ }
+}
+/**
+ * Whether or not an asset override should occur.
+ * This is used to control specific scenarios when asset overrides should not occur, e.g. when it clashes with other features.
+ *
+ * @param data The apps resource to determine if asset overrides should be done for.
+ */
+function shouldPerformAssetOverridesForData(objectGraph, data) {
+ /**
+ * If an AB Test is being run on given app, don't accept the override.
+ */
+ const appHasABTest = appDataHasVariant(objectGraph, data, "abExperiment");
+ return !appHasABTest;
+}
+/**
+ * Apply asset overrides to the ad lock up, based on a given list of overrides
+ * @param lockup Lockup to modify
+ * @param iAdAssetOverrides Asset overrides specified
+ * @return {boolean} - Return whether or not the overrides were applied
+ */
+function applyAssetOverridesToMixedMediaAdLockup(objectGraph, lockup, iAdAssetOverrides) {
+ const unusedAssetOverrides = new Set(iAdAssetOverrides);
+ const videoOverrides = [];
+ const screenshotOverrides = [];
+ const isOverrideMedia = function (url, checksum) {
+ if (unusedAssetOverrides.size === 0) {
+ return false;
+ }
+ for (const assetOverride of iAdAssetOverrides) {
+ if (assetOverride === checksum || url.indexOf(assetOverride) !== -1) {
+ unusedAssetOverrides.delete(assetOverride);
+ return true;
+ }
+ }
+ return false;
+ };
+ if (iAdAssetOverrides.length && (lockup.screenshots.length || lockup.trailers.length)) {
+ if (lockup.trailers.length) {
+ for (const video of lockup.trailers[0].videos) {
+ if (isOverrideMedia(video.videoUrl, video.preview.checksum)) {
+ videoOverrides.push(video);
+ }
+ }
+ }
+ if (lockup.screenshots.length) {
+ for (const screenshot of lockup.screenshots[0].artwork) {
+ if (isOverrideMedia(screenshot.template, screenshot.checksum)) {
+ screenshotOverrides.push(screenshot);
+ }
+ }
+ }
+ }
+ if (unusedAssetOverrides.size === 0 && (videoOverrides.length || screenshotOverrides.length)) {
+ if (lockup.trailers.length) {
+ lockup.trailers[0].videos = videoOverrides;
+ }
+ if (lockup.screenshots.length) {
+ lockup.screenshots[0] = new Screenshots(screenshotOverrides, lockup.screenshots[0].mediaPlatform);
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+// endregion
+// region Reporting Destionation
+/**
+ * Determine the reporting destination based on metrics options
+ */
+export function reportingDestinationFromMetricsOptions(objectGraph, pageInformation) {
+ const iadInfo = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo;
+ if (isNull(iadInfo)) {
+ return "undefined";
+ }
+ const placementType = iadInfo.placementType;
+ switch (placementType) {
+ case "searchLanding":
+ return "promotedContent"; // SLP uses PC-backed Journey reporting
+ case "searchResults":
+ return "searchAds"; // SRP uses SA-backed Journey reporting
+ case "today":
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ return "promotedContent"; // Chainlink uses PC-backed Journey reporting
+ default:
+ throw new Error(`This method should never be called with value: ${placementType}`);
+ }
+}
+// endregion
+//# sourceMappingURL=ad-lockups.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/lockups/editorial-context.js b/node_modules/@jet-app/app-store/tmp/src/common/lockups/editorial-context.js
new file mode 100644
index 0000000..7273a4f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/lockups/editorial-context.js
@@ -0,0 +1,25 @@
+import * as mediaAttributes from "../../foundation/media/attributes";
+/**
+ * Use to obtain an clientIdentifier for an editorial context.
+ * @param data The data from a media response
+ * @return {ClientIdentifier} A client identifier that corresponds to the given editorial context.
+ */
+export function clientIdentifierForEditorialContextInData(objectGraph, data) {
+ const rawEditorialContext = mediaAttributes.attributeAsString(data, "editorialContext");
+ switch (rawEditorialContext) {
+ case "Watch":
+ return "com.apple.AppStore.BridgeStoreExtension" /* ClientIdentifier.AppStore_BridgeStoreExtension */;
+ case "Messages":
+ return "com.apple.MobileSMS" /* ClientIdentifier.MobileSMS */;
+ case "RealityDevice":
+ if (objectGraph.bag.enableDeviceDrivenDiscoveryContent) {
+ return "VisionAppStore" /* ClientIdentifier.VisionAppStore */;
+ }
+ else {
+ return null;
+ }
+ default:
+ return null;
+ }
+}
+//# sourceMappingURL=editorial-context.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/lockups/lockups.js b/node_modules/@jet-app/app-store/tmp/src/common/lockups/lockups.js
new file mode 100644
index 0000000..1de013a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/lockups/lockups.js
@@ -0,0 +1,1920 @@
+//
+// lockups.ts
+// AppStoreKit
+//
+// Created by Jonathan Ellenbogen on 11/15/16.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import * as validation from "@jet/environment/json/validation";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import { makeBundlePageIntent } from "../../api/intents/bundle-page-intent";
+import { makeEditorialPageIntentByID } from "../../api/intents/editorial/editorial-page-intent";
+import { makeGroupingPageIntentByID } from "../../api/intents/grouping-page-intent";
+import { makeProductPageIntent } from "../../api/intents/product-page-intent";
+import { makeRoutableArticlePageIntent, } from "../../api/intents/routable-article-page-intent";
+import * as models from "../../api/models";
+import { AdvertActionMetrics, } from "../../api/models/metrics/advert-action-metrics";
+import * as productPageUtil from "../../common/product-page/product-page-util";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { editorialCardFromData } from "../../foundation/media/associations";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaPlatformAttributes from "../../foundation/media/platform-attributes";
+import { platformAttributeAsDictionary } from "../../foundation/media/platform-attributes";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import * as mediaUrlBuilder from "../../foundation/media/url-builder";
+import { BuyParameters } from "../../foundation/metrics/buy-parameters";
+import * as http from "../../foundation/network/http";
+import { InAppPurchaseInstallPageParameters, Parameters, Path, ProductPageParameters, Protocol, } from "../../foundation/network/url-constants";
+import * as urls from "../../foundation/network/urls";
+import * as color from "../../foundation/util/color-util";
+import * as objects from "../../foundation/util/objects";
+import * as gameModels from "../../gameservicesui/src/api/data-models/game";
+import { makeClickMetrics } from "../../gameservicesui/src/utility/metrics";
+import * as adCommon from "../ads/ad-common";
+import * as appEvent from "../app-promotions/app-event";
+import * as appPromotionsCommon from "../app-promotions/app-promotions-common";
+import * as arcadeCommon from "../arcade/arcade-common";
+import * as arcadeUpsell from "../arcade/arcade-upsell";
+import * as mediaUrlMapping from "../builders/url-mapping";
+import * as ageRatings from "../content/age-ratings";
+import { createArtworkForResource } from "../content/artwork/artwork";
+import * as contentAttributes from "../content/attributes";
+import * as content from "../content/content";
+import * as flowPreview from "../content/flow-preview";
+import { isMediaDark } from "../editorial-pages/editorial-media-util";
+import * as editorialComponentMediaUtil from "../editorial-pages/editorial-page-component-media-util";
+import { makeEditorialPageURL } from "../editorial-pages/editorial-page-intent-controller-utils";
+import * as filtering from "../filtering";
+import { makeGroupingPageCanonicalURL } from "../grouping/grouping-page-url";
+import * as externalDeepLink from "../linking/external-deep-link";
+import { getLocale } from "../locale";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersModels from "../metrics/helpers/models";
+import * as metricsUtil from "../metrics/helpers/util";
+import * as offerFormatting from "../offers/offer-formatting";
+import * as offers from "../offers/offers";
+import { getPlatform, inferPreviewPlatformFromDeviceFamilies } from "../preview-platform";
+import * as variants from "../product-page/product-page-variants";
+import * as reviews from "../product-page/reviews";
+import { customCreativeArtworkFromData, customCreativeVideoFromData } from "../search/custom-creative";
+import * as metadataRibbon from "../search/metadata-ribbon/metadata-ribbon";
+import * as searchTagsRibbon from "../search/metadata-ribbon/search-tags-ribbon";
+import * as sharing from "../sharing";
+import { makeRoutableArticlePageCanonicalUrl } from "../today/routable-article-page-url-utils";
+import * as adLockup from "./ad-lockups";
+import { reportingDestinationFromMetricsOptions } from "./ad-lockups";
+export const forTesting = {
+ copyDataIntoLockup,
+};
+export function offerTypeForMediaType(objectGraph, type, supportsArcade) {
+ switch (type) {
+ case "in-apps":
+ return supportsArcade ? "arcade" : "inAppPurchase";
+ default:
+ return supportsArcade ? "arcadeApp" : "app";
+ }
+}
+function isArcadeLockupWordmarkSupported(objectGraph) {
+ if (objectGraph.client.isTV && objectGraph.host.clientIdentifier === "com.apple.Arcade") {
+ return false;
+ }
+ else {
+ return true;
+ }
+}
+/**
+ * Takes the metadata ribbon items type candidates from the `SearchExperimentData` and determines which should be shown in each slot,
+ * prioritizing the natural order from the candidates arrays
+ * @param objectGraph The ObjectGraph instance for the App Store
+ * @param data The data representing the mixed media lockup data
+ * @param lockup The lockup object the metadata object will be added to
+ * @param searchExperimentData The experiment data for the search experiments, to use its displayStyle object
+ */
+function copyMetadataRibbonInfoIntoLockup(objectGraph, data, lockup, searchExperimentDataForPage, options) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
+ if (serverData.isNullOrEmpty(data)) {
+ return;
+ }
+ lockup.showMetadataInformationInLockup = objectGraph.bag.isLLMSearchTagsEnabled || objectGraph.client.isPad;
+ /// This needs to be guarded against iPhone as natively this will affect all iOS UI components, including iPad.
+ if (!objectGraph.client.isPhone) {
+ return;
+ }
+ /// If the individual lockup has a metadata ribbon field, that takes precedence over the page level metadata ribbon
+ const searchExperimentDataForLockup = serverData.asDictionary(data, "meta");
+ const metadataRibbonItemSlots = (_b = (_a = searchExperimentDataForLockup === null || searchExperimentDataForLockup === void 0 ? void 0 : searchExperimentDataForLockup.displayStyle) === null || _a === void 0 ? void 0 : _a.metadataRibbon) !== null && _b !== void 0 ? _b : (_c = searchExperimentDataForPage === null || searchExperimentDataForPage === void 0 ? void 0 : searchExperimentDataForPage.displayStyle) === null || _c === void 0 ? void 0 : _c.metadataRibbon;
+ let llmMetadataRibbonItems = [];
+ if (objectGraph.bag.isLLMSearchTagsEnabled) {
+ llmMetadataRibbonItems =
+ (_g = (_e = (_d = searchExperimentDataForLockup === null || searchExperimentDataForLockup === void 0 ? void 0 : searchExperimentDataForLockup.displayStyle) === null || _d === void 0 ? void 0 : _d.llmRibbon) !== null && _e !== void 0 ? _e : (_f = searchExperimentDataForLockup === null || searchExperimentDataForLockup === void 0 ? void 0 : searchExperimentDataForLockup.displayStyle) === null || _f === void 0 ? void 0 : _f.metadataRibbon) !== null && _g !== void 0 ? _g : (_h = searchExperimentDataForPage === null || searchExperimentDataForPage === void 0 ? void 0 : searchExperimentDataForPage.displayStyle) === null || _h === void 0 ? void 0 : _h.metadataRibbon;
+ }
+ else {
+ llmMetadataRibbonItems =
+ (_k = (_j = searchExperimentDataForLockup === null || searchExperimentDataForLockup === void 0 ? void 0 : searchExperimentDataForLockup.displayStyle) === null || _j === void 0 ? void 0 : _j.metadataRibbon) !== null && _k !== void 0 ? _k : (_l = searchExperimentDataForPage === null || searchExperimentDataForPage === void 0 ? void 0 : searchExperimentDataForPage.displayStyle) === null || _l === void 0 ? void 0 : _l.metadataRibbon;
+ }
+ const metadataRibbonItems = metadataRibbon.createMetadataRibbonItemsForLockup(objectGraph, data, lockup, metadataRibbonItemSlots, options);
+ const searchTagsRibbonItems = searchTagsRibbon.createSearchTagsRibbonItemsForLockup(objectGraph, data, lockup, llmMetadataRibbonItems, options);
+ lockup.metadataRibbonItems = metadataRibbonItems;
+ lockup.searchTagRibbonItems = searchTagsRibbonItems;
+ lockup.shouldEvenlyDistributeRibbonItems = !objectGraph.bag.isLLMSearchTagsEnabled;
+}
+/**
+ * Configures all the data into the lockup object
+ * @param objectGraph The ObjectGraph instance for the App Store
+ * @param data The branch IAP used by the contingent offer
+ * @param lockup The lockup object the metadata object will be added to
+ * @param options Lockup configuration options
+ * @param isParentAppFree A flag used when creating an IAP lockup with missing parent app data in its relationships property.
+ */
+function copyDataIntoLockup(objectGraph, data, lockup, options, parentData, copyAdditionalDataIntoLockup) {
+ if (!data) {
+ return;
+ }
+ validation.context("copyDataIntoLockup", () => {
+ var _a, _b, _c, _d, _e, _f;
+ const isPreorder = mediaAttributes.attributeAsBooleanOrFalse(data, "isPreorder");
+ // Drop this item if it's not a preorder, but contained in a pre-order exclusive shelf.
+ if (options.isContainedInPreorderExclusiveShelf && !isPreorder) {
+ return null;
+ }
+ const attributePlatform = (_a = options === null || options === void 0 ? void 0 : options.attributePlatformOverride) !== null && _a !== void 0 ? _a : contentAttributes.defaultAttributePlatform(objectGraph);
+ const productVariantData = variants.productVariantDataForData(objectGraph, data);
+ lockup.productVariantID = variants.productVariantIDForVariantData(productVariantData);
+ options.metricsOptions.productVariantData = productVariantData;
+ lockup.adamId = data.id;
+ const bundleId = contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId", options === null || options === void 0 ? void 0 : options.attributePlatformOverride);
+ lockup.bundleId = bundleId;
+ lockup.decorations = [];
+ let clientIdentifierOverride = null;
+ if (options && options.clientIdentifierOverride) {
+ clientIdentifierOverride = options.clientIdentifierOverride;
+ }
+ lockup.icon = content.iconFromData(objectGraph, data, {
+ useCase: options.artworkUseCase,
+ withJoeColorPlaceholder: options.useJoeColorIconPlaceholder,
+ joeColorPlaceholderSelectionLogic: options.joeColorPlaceholderSelectionLogic,
+ overrideTextColorKey: options.overrideArtworkTextColorKey,
+ }, clientIdentifierOverride, productVariantData, options.attributePlatformOverride);
+ if (options && options.titleObjectPath) {
+ lockup.title = contentAttributes.contentAttributeAsString(objectGraph, data, options.titleObjectPath, options === null || options === void 0 ? void 0 : options.attributePlatformOverride);
+ }
+ else {
+ lockup.title = mediaAttributes.attributeAsString(data, "name");
+ }
+ if (objectGraph.client.isWeb) {
+ lockup.isIOSBinaryMacOSCompatible = mediaAttributes.attributeAsBooleanOrFalse(data, "isIOSBinaryMacOSCompatible");
+ }
+ // Only use an ad override language if this is an ad.
+ lockup.useAdsLocale = options.metricsOptions.isAdvert && isSome(objectGraph.bag.adsOverrideLanguage);
+ // Don't display "Apple Arcade" on a lockup if it's not an arcade app or we're running in the
+ // apple arcade app (tvOS)
+ const isArcadeLockup = content.isArcadeSupported(objectGraph, data, options === null || options === void 0 ? void 0 : options.attributePlatformOverride);
+ if (isArcadeLockup && isArcadeLockupWordmarkSupported(objectGraph) && !options.shouldHideArcadeHeader) {
+ // tvOS uses a 'pill' decoration over artwork to display apple arcade
+ // while everywhere else just uses the heading text slot to display a string
+ if (objectGraph.client.isTV) {
+ lockup.decorations.push("arcade");
+ }
+ else {
+ lockup.heading = options.metricsOptions.isAdvert
+ ? objectGraph.adsLoc.string("Lockup.Heading.Arcade")
+ : objectGraph.loc.string("Lockup.Heading.Arcade");
+ }
+ }
+ if (options.shouldShowFriendsPlayingShowcase) {
+ lockup.decorations.push("showcaseFriendsPlaying");
+ }
+ // Subtitle
+ const allowMultilineTertiaryLabel = !isArcadeLockup && !isPreorder && ((_b = options.isMultilineTertiaryTitleAllowed) !== null && _b !== void 0 ? _b : true);
+ if (!options.isSubtitleHidden && !isBadgeMultilineFromData(objectGraph, data, allowMultilineTertiaryLabel)) {
+ lockup.subtitle = subtitleFromData(objectGraph, data, options);
+ }
+ // Tertiary badge
+ lockup.tertiaryTitle = badgeFromData(objectGraph, data, allowMultilineTertiaryLabel, options.hideCompatibilityBadge);
+ lockup.tertiaryTitleAction = badgeActionFromData(objectGraph, data);
+ lockup.tertiaryTitleArtwork = badgeArtworkFromData(objectGraph, data);
+ lockup.developerTagline = contentAttributes.contentAttributeAsString(objectGraph, data, "subtitle", options === null || options === void 0 ? void 0 : options.attributePlatformOverride);
+ lockup.editorialTagline = content.notesFromData(objectGraph, data, "tagline", false, options === null || options === void 0 ? void 0 : options.attributePlatformOverride);
+ lockup.editorialDescription = content.notesFromData(objectGraph, data, "standard", false, options === null || options === void 0 ? void 0 : options.attributePlatformOverride);
+ lockup.shortEditorialDescription = content.notesFromData(objectGraph, data, "short", false, options === null || options === void 0 ? void 0 : options.attributePlatformOverride);
+ lockup.ageRating = ageRatings.name(objectGraph, data, true);
+ lockup.productDescription = contentAttributes.contentAttributeAsString(objectGraph, data, "description.standard", options === null || options === void 0 ? void 0 : options.attributePlatformOverride);
+ if ((_c = options === null || options === void 0 ? void 0 : options.shouldShowSupportedPlatformLabel) !== null && _c !== void 0 ? _c : false) {
+ copySupportedPlatformLabelIntoLockup(objectGraph, data, lockup, options);
+ }
+ if (!reviews.shouldSuppressReviews(objectGraph, data)) {
+ const ratingCount = mediaAttributes.attributeAsNumber(data, "userRating.ratingCount");
+ if (ratingCount > 0 || !(options && options.hideZeroRatings)) {
+ lockup.rating = mediaAttributes.attributeAsNumber(data, "userRating.value");
+ const count = mediaAttributes.attributeAsNumber(data, "userRating.ratingCount");
+ const adsOverrideLanguage = options.metricsOptions.isAdvert
+ ? objectGraph.bag.adsOverrideLanguage
+ : null;
+ lockup.ratingCount = objectGraph.loc.formattedCountForPreferredLocale(objectGraph, count, adsOverrideLanguage);
+ }
+ }
+ const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, data, options.metricsOptions, options.metricsClickOptions);
+ metricsHelpersLocation.pushContentLocation(objectGraph, metricsClickOptions, lockup.title);
+ const offerData = offers.offerDataFromData(objectGraph, data, options === null || options === void 0 ? void 0 : options.attributePlatformOverride);
+ const includeBetaApps = (_d = options === null || options === void 0 ? void 0 : options.includeBetaApps) !== null && _d !== void 0 ? _d : false;
+ const metricsPlatformDisplayStyle = metricsUtil.metricsPlatformDisplayStyleFromData(objectGraph, data, lockup.icon, clientIdentifierOverride);
+ const offerButtonMetricsClickOptions = objects.shallowCopyOf(metricsClickOptions);
+ const offerAction = offers.offerActionFromOfferData(objectGraph, offerData, data, isPreorder, includeBetaApps, metricsPlatformDisplayStyle, offerButtonMetricsClickOptions, "default", options === null || options === void 0 ? void 0 : options.referrerData);
+ const allOfferDiscountData = serverData.asArrayOrEmpty(offerData, "discounts");
+ let buttonAction = offers.wrapOfferActionIfNeeded(objectGraph, offerAction, data, isPreorder, offerButtonMetricsClickOptions, "default", clientIdentifierOverride, options.shouldNavigateToProductPage);
+ const cppDeeplinkUrl = contentAttributes.customAttributeAsString(objectGraph, data, productVariantData, "customDeepLink", attributePlatform);
+ const isAdvert = adLockup.isAdvert(objectGraph, data);
+ // CPP deeplinks used for ads have a requirement to not be used when targeting criteria is too narrow.
+ const isCppDeeplinkingEnabled = !isAdvert || adLockup.isCppDeeplinkEnabledForAdvert(data);
+ const hasCppDeepLink = isCppDeeplinkingEnabled && (cppDeeplinkUrl === null || cppDeeplinkUrl === void 0 ? void 0 : cppDeeplinkUrl.length) > 0;
+ const hasExternalDeepLink = ((_e = options === null || options === void 0 ? void 0 : options.externalDeepLinkUrl) === null || _e === void 0 ? void 0 : _e.length) > 0;
+ let deepLinkUrl;
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ if (objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) {
+ const customCreativeDeepLinkUrl = adLockup.getCustomCreativeDeepLinkUrl(data);
+ const isCustomCreativeDeeplinkingEnabled = isAdvert && adLockup.isCustomCreativeDeeplinkEnabledForAdvert(data);
+ const hasCustomCreativeDeepLink = isCustomCreativeDeeplinkingEnabled && (customCreativeDeepLinkUrl === null || customCreativeDeepLinkUrl === void 0 ? void 0 : customCreativeDeepLinkUrl.length) > 0;
+ if (hasCustomCreativeDeepLink) {
+ deepLinkUrl = customCreativeDeepLinkUrl;
+ }
+ else if (hasCppDeepLink) {
+ deepLinkUrl = cppDeeplinkUrl;
+ }
+ else if (hasExternalDeepLink) {
+ deepLinkUrl = options.externalDeepLinkUrl;
+ }
+ }
+ else if (hasCppDeepLink || hasExternalDeepLink) {
+ deepLinkUrl = hasCppDeepLink ? cppDeeplinkUrl : options.externalDeepLinkUrl;
+ }
+ }
+ else if (hasCppDeepLink || hasExternalDeepLink) {
+ deepLinkUrl = hasCppDeepLink ? cppDeeplinkUrl : options.externalDeepLinkUrl;
+ }
+ if (isSome(deepLinkUrl)) {
+ // Configure cross link as well as deep link action.
+ buttonAction = externalDeepLink.deepLinkActionWrappingAction(objectGraph, buttonAction, offerAction.adamId, bundleId, deepLinkUrl, includeBetaApps, offerButtonMetricsClickOptions);
+ // Configure cross link title and subtitle.
+ if (((_f = options.crossLinkSubtitle) === null || _f === void 0 ? void 0 : _f.length) > 0) {
+ lockup.crossLinkTitle = objectGraph.loc.uppercased(mediaAttributes.attributeAsString(data, "name"));
+ lockup.crossLinkSubtitle = options.crossLinkSubtitle;
+ }
+ }
+ lockup.buttonAction = buttonAction;
+ // Beta apps
+ lockup.includeBetaApps = includeBetaApps;
+ lockup.developerName = mediaAttributes.attributeAsString(data, "artistName");
+ if (isNothing(lockup.developerName)) {
+ // Some newer APIs use `developerName` instead of `artistName`.
+ lockup.developerName = mediaAttributes.attributeAsString(data, "developerName");
+ }
+ // Ensure we grab its children.
+ lockup.children = childrenFromLockupData(objectGraph, data, options);
+ if (isSome(copyAdditionalDataIntoLockup)) {
+ copyAdditionalDataIntoLockup();
+ }
+ metricsHelpersLocation.popLocation(options.metricsOptions.locationTracker);
+ const shareSheetData = sharing.shareSheetDataForProductFromProductData(objectGraph, data, clientIdentifierOverride);
+ if (shareSheetData) {
+ const shareMenuAction = new models.BlankAction();
+ const shareMetricsOptions = objects.shallowCopyOf(metricsClickOptions);
+ shareMetricsOptions.actionType = "share";
+ shareMetricsOptions.targetType = "lockup";
+ metricsHelpersClicks.addClickEventToAction(objectGraph, shareMenuAction, shareMetricsOptions);
+ const shareMenuData = new models.LockupContextMenuData();
+ shareMenuData.shareAction = shareMenuAction;
+ shareMenuData.shareSheetData = shareSheetData;
+ lockup.contextMenuData = shareMenuData;
+ }
+ const resolvedParentData = parentData !== null && parentData !== void 0 ? parentData : parentDataFromInAppData(objectGraph, data);
+ let isParentAppFree = false;
+ if (resolvedParentData) {
+ const parentOfferData = offers.offerDataFromData(objectGraph, resolvedParentData);
+ const parentPrice = offers.priceFromOfferData(objectGraph, parentOfferData);
+ isParentAppFree = !(parentPrice > 0);
+ }
+ const offerType = offerTypeForMediaType(objectGraph, data.type, isArcadeLockup);
+ if (options) {
+ lockup.offerDisplayProperties = offers.displayPropertiesFromOfferAction(objectGraph, offerAction, offerType, data, isPreorder, options.isContainedInPreorderExclusiveShelf, options.offerStyle, options.offerEnvironment, allOfferDiscountData[0], isParentAppFree, "default", options.shouldNavigateToProductPage, options.metricsOptions.isAdvert, null, options.parentAppData, options.isBuyDisallowed);
+ }
+ else {
+ lockup.offerDisplayProperties = offers.displayPropertiesFromOfferAction(objectGraph, offerAction, offerType, data, isPreorder, options.isContainedInPreorderExclusiveShelf, null, null, allOfferDiscountData[0], isParentAppFree, "default");
+ }
+ if (!options || !options.skipDefaultClickAction) {
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ lockup.clickAction = productActionFromData(objectGraph, data, metricsClickOptions, {
+ clientIdentifierOverride: clientIdentifierOverride,
+ productVariantData: productVariantData,
+ alignedRegionDeepLinkUrl: adLockup.getCustomCreativeDeepLinkUrl(data),
+ isCppDeepLinkEligible: isCppDeeplinkingEnabled,
+ });
+ }
+ else {
+ lockup.clickAction = productActionFromData(objectGraph, data, metricsClickOptions, {
+ clientIdentifierOverride: clientIdentifierOverride,
+ productVariantData: productVariantData,
+ isCppDeepLinkEligible: isCppDeeplinkingEnabled,
+ });
+ }
+ }
+ if (options && options.ordinal) {
+ lockup.ordinal = options.ordinal;
+ }
+ const editorialBadgeInfo = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialBadgeInfo", options === null || options === void 0 ? void 0 : options.attributePlatformOverride);
+ const supportsVisionOSCompatibleIOSBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ if (editorialBadgeInfo && !supportsVisionOSCompatibleIOSBinary) {
+ const badgeType = serverData.asString(editorialBadgeInfo, "editorialBadgeType");
+ const hasEditorsChoiceBadge = badgeType && badgeType === "editorialPriority";
+ lockup.isEditorsChoice = hasEditorsChoiceBadge;
+ }
+ // Flow preview actions
+ if (!isAdvert) {
+ lockup.flowPreviewActionsConfiguration = flowPreview.flowPreviewActionsConfigurationForProductFromData(objectGraph, data, false, clientIdentifierOverride, lockup.clickAction, options.metricsOptions, metricsClickOptions, options.externalDeepLinkUrl, lockup.subtitle, lockup.title);
+ }
+ const metricsImpressionOptions = metricsHelpersImpressions.impressionOptionsForLockup(objectGraph, data, lockup, metricsPlatformDisplayStyle, options.metricsOptions, options.canDisplayArcadeOfferButton);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, lockup, metricsImpressionOptions);
+ });
+}
+/**
+ * Configures the tertiary icons and label for showing unsupported apps in a bundle.
+ * @param objectGraph The ObjectGraph instance for the App Store
+ * @param data The branch IAP used by the contingent offer
+ * @param lockup The lockup object the metadata object will be added to
+ * @param options Lockup configuration options
+ */
+function copySupportedPlatformLabelIntoLockup(objectGraph, data, lockup, options) {
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ const supportsMacOSCompatibleIOSBinary = content.supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, objectGraph.client.isMac);
+ const supportsVisionOSCompatibleBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ const isBuyable = content.buyableOnDevice(objectGraph, data, appPlatforms, objectGraph.client.deviceType, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleBinary);
+ /// If its buyable no need to display compatibility label
+ if (isBuyable) {
+ return;
+ }
+ const additionalPlatforms = appPlatforms.filter((platform) => platform !== objectGraph.client.deviceType);
+ if (additionalPlatforms.length === 0) {
+ return;
+ }
+ lockup.tertiaryTitleIcons = additionalPlatforms.map((platform) => content.systemImageNameForAppPlatform(platform));
+ if (additionalPlatforms.length === 1) {
+ const platformTitle = content.appPlatformTitle(objectGraph, additionalPlatforms[0]);
+ lockup.tertiaryTitle = objectGraph.loc
+ .string("AppStore.Bundles.ProductPage.OnlyAvailable.Message")
+ .replace("@@platform@@", platformTitle);
+ }
+ else {
+ // We only support showing up to 2 platforms. Grab the first 2
+ const platformTitle1 = content.appPlatformTitle(objectGraph, additionalPlatforms[0]);
+ const platformTitle2 = content.appPlatformTitle(objectGraph, additionalPlatforms[1]);
+ lockup.tertiaryTitle = objectGraph.loc
+ .string("AppStore.Bundles.ProductPage.AvailableOnTwo.Message")
+ .replace("@@platform1@@", platformTitle1)
+ .replace("@@platform2@@", platformTitle2);
+ }
+}
+export function childrenFromLockupData(objectGraph, data, options) {
+ const childrenRelationship = mediaRelationship.relationship(data, "apps");
+ if (childrenRelationship) {
+ const listOptions = {
+ lockupOptions: {
+ ...options,
+ shouldCreateScreenshotsLockup: options === null || options === void 0 ? void 0 : options.shouldIncludeScreenshotsForChildren,
+ },
+ filter: 0 /* filtering.Filter.None */,
+ };
+ return lockupsFromDataContainer(objectGraph, childrenRelationship, listOptions);
+ }
+ return null;
+}
+function copyDataIntoInAppPurchaseLockup(objectGraph, data, lockup, options) {
+ if (!data) {
+ return;
+ }
+ validation.context("copyDataIntoInAppPurchaseLockup", () => {
+ var _a;
+ const parentData = (_a = parentDataFromInAppData(objectGraph, data)) !== null && _a !== void 0 ? _a : options.parentAppData;
+ const isStreamlinedBuy = mediaAttributes.attributeAsBooleanOrFalse(parentData, "supportsStreamlinedBuy");
+ const iapData = iapDataFromData(objectGraph, data);
+ data = iapData;
+ copyDataIntoLockup(objectGraph, data, lockup, options, parentData);
+ lockup.productIdentifier = mediaAttributes.attributeAsString(data, "offerName");
+ lockup.parent = lockupFromData(objectGraph, parentData, options);
+ lockup.description = mediaAttributes.attributeAsString(data, "description.standard");
+ lockup.isVisibleByDefault = mediaAttributes.attributeAsBooleanOrFalse(data, "isMerchandisedVisibleByDefault");
+ lockup.isSubscription = mediaAttributes.attributeAsBooleanOrFalse(data, "isSubscription");
+ const offerData = offers.offerDataFromData(objectGraph, data);
+ const discountOfferData = serverData.asArrayOrEmpty(offerData, "discounts");
+ lockup.offerDisplayProperties.hasDiscount = discountOfferData.length > 0;
+ lockup.offerDisplayProperties.subscriptionFamilyId = mediaAttributes.attributeAsString(data, "subscriptionFamilyId");
+ // Action for App Install (needed in case the parent app is not installed)
+ const installRequiredAction = new models.FlowAction("inAppPurchaseInstall");
+ installRequiredAction.presentationContext = "presentModalFormSheet";
+ const installRequiredActionUrl = configureIAPInstallPageUrl(objectGraph, lockup.adamId, parentData.id);
+ installRequiredAction.pageUrl = installRequiredActionUrl;
+ // Install Page
+ const sidepackInstallPage = new models.InAppPurchaseInstallPage();
+ sidepackInstallPage.parentLockup = objects.shallowCopyOf(lockup.parent);
+ sidepackInstallPage.lockup = objects.shallowCopyOf(lockup);
+ sidepackInstallPage.preInstallOfferDescription = offerFormatting.installPagePreInstallTrialDescription(objectGraph, offerData);
+ installRequiredAction.pageData = sidepackInstallPage;
+ const productIdentifier = mediaAttributes.attributeAsString(data, "offerName");
+ const parentBundleId = contentAttributes.contentAttributeAsString(objectGraph, parentData, "bundleId");
+ const firstVersionSupportingMerchIAP = mediaAttributes.attributeAsString(parentData, "firstVersionSupportingInAppPurchaseApi");
+ const hasDiscountedOffer = serverData.isDefinedNonNullNonEmpty(discountedOfferFromData(data));
+ const metricsOptions = metricsHelpersImpressions.impressionOptionsForLockup(objectGraph, data, lockup, "iap", options.metricsOptions, options.canDisplayArcadeOfferButton);
+ metricsHelpersLocation.pushContentLocation(objectGraph, metricsOptions, lockup.title);
+ if (isStreamlinedBuy && hasDiscountedOffer) {
+ const inAppPurchaseAction = new models.InAppPurchaseAction(productIdentifier, parentData.id, parentBundleId, lockup.parent.buttonAction);
+ if (lockup.parent) {
+ inAppPurchaseAction.appTitle = lockup.parent.title;
+ }
+ inAppPurchaseAction.productTitle = lockup.title;
+ inAppPurchaseAction.streamlineBuyAction = streamlineBuyActionForIAP(objectGraph, data, parentData, lockup, options);
+ lockup.buttonAction = inAppPurchaseAction;
+ lockup.subtitle = mediaAttributes.attributeAsString(parentData, "name");
+ }
+ else if (firstVersionSupportingMerchIAP) {
+ const inAppPurchaseAction = new models.InAppPurchaseAction(productIdentifier, parentData.id, parentBundleId, installRequiredAction, firstVersionSupportingMerchIAP);
+ if (lockup.parent) {
+ inAppPurchaseAction.appTitle = lockup.parent.title;
+ }
+ inAppPurchaseAction.productTitle = lockup.title;
+ const clickOptions = {
+ ...options.metricsOptions,
+ id: lockup.adamId,
+ idType: "its_id",
+ actionDetails: { parentAdamId: parentData.id },
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, inAppPurchaseAction, clickOptions);
+ lockup.buttonAction = inAppPurchaseAction;
+ }
+ else {
+ const alert = new models.AlertAction("default");
+ alert.title = objectGraph.loc.string("SEED_IN_APP_UNSUPPORTED_MESSAGE_OPTION_1");
+ alert.message = "";
+ alert.isCancelable = true;
+ lockup.buttonAction = alert;
+ }
+ metricsHelpersLocation.popLocation(options.metricsOptions.locationTracker);
+ // Click action to the product page
+ if (!options || !options.skipDefaultClickAction) {
+ const clickAction = iAPActionFromData(objectGraph, data, metricsOptions);
+ lockup.clickAction = clickAction;
+ lockup.productAction = clickAction;
+ }
+ metricsHelpersImpressions.addImpressionFieldsToInAppPurchaseLockup(objectGraph, lockup, metricsOptions);
+ }, "item.offer.buyParams");
+}
+/**
+ * Creates the discounted buy action for the streamline buy Action
+ * @param objectGraph The ObjectGraph instance for the App Store
+ * @param data The branch IAP used by the contingent offer
+ * @param parentData The branch app used by the contingent offer
+ * @param lockup The lockup object the metadata object will be added to
+ * @param options Lockup configuration options
+ */
+function streamlineBuyActionForIAP(objectGraph, data, parentData, lockup, options) {
+ var _a;
+ const clickOptions = {
+ ...options.metricsOptions,
+ id: parentData.id,
+ targetId: parentData.id,
+ idType: "its_id",
+ actionDetails: { parentAdamId: parentData.id },
+ };
+ const metricsPlatformDisplayStyle = metricsUtil.metricsPlatformDisplayStyleFromData(objectGraph, data, lockup.icon, options.clientIdentifierOverride);
+ const offerButtonMetricsClickOptions = objects.shallowCopyOf(clickOptions);
+ const discountedOfferData = discountedOfferFromData(data);
+ const offerData = offers.offerDataFromData(objectGraph, data);
+ const appOfferData = offers.offerDataFromData(objectGraph, parentData);
+ const appBuyParams = new BuyParameters(serverData.asString(appOfferData, "buyParams"));
+ let currentBuyParams = (_a = serverData.asString(discountedOfferData, "buyParams")) !== null && _a !== void 0 ? _a : serverData.asString(offerData, "buyParams");
+ currentBuyParams += `&appAdamId=${serverData.asString(parentData, "id")}`;
+ currentBuyParams += `&appExtVrsId=${appBuyParams.get("appExtVrsId", "")}`;
+ currentBuyParams += `&bid=${contentAttributes.contentAttributeAsString(objectGraph, parentData, "bundleId")}`;
+ currentBuyParams += `&bvrs=1.0`;
+ currentBuyParams += `&offerName=${contentAttributes.contentAttributeAsString(objectGraph, data, "offerName")}`;
+ const offerId = serverData.asString(discountedOfferData, "offerId");
+ if (serverData.isDefinedNonNullNonEmpty(offerId)) {
+ currentBuyParams += `&adHocOfferId=${offerId}`;
+ }
+ offerData["buyParams"] = currentBuyParams;
+ const subscribeAction = offers.offerActionFromOfferData(objectGraph, offerData, data, false, false, metricsPlatformDisplayStyle, offerButtonMetricsClickOptions, "default", options === null || options === void 0 ? void 0 : options.referrerData, false, parentData.id);
+ return subscribeAction;
+}
+function copyDataIntoTrailersLockup(objectGraph, data, lockup, videoConfiguration, options) {
+ if (!data) {
+ return;
+ }
+ validation.context("copyDataIntoTrailersLockup", () => {
+ copyDataIntoLockup(objectGraph, data, lockup, options);
+ lockup.trailers = content.trailersFromData(objectGraph, data, videoConfiguration, options.metricsOptions, lockup.adamId);
+ });
+}
+function copyMediaIntoMixedMediaLockup(objectGraph, data, lockup, videoConfiguration, options, cropCode) {
+ var _a;
+ if (!data) {
+ return;
+ }
+ if (options.isNetworkConstrained) {
+ return;
+ }
+ const isAd = (_a = options.metricsOptions.isAdvert) !== null && _a !== void 0 ? _a : false;
+ validation.context("copyMediaIntoMixedMediaLockup", () => {
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ if (objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) {
+ const customCreativeData = platformAttributeAsDictionary(data, contentAttributes.bestAttributePlatformFromData(objectGraph, data), "creativeAttributes");
+ if (isSome(customCreativeData)) {
+ lockup.alignedRegionArtwork = customCreativeArtworkFromData(objectGraph, data, customCreativeData, cropCode);
+ lockup.alignedRegionVideo = customCreativeVideoFromData(objectGraph, data, customCreativeData, videoConfiguration, cropCode);
+ }
+ }
+ }
+ lockup.screenshots = content.screenshotsFromData(objectGraph, data, 4 /* content.ArtworkUseCase.LockupScreenshots */, null, options.clientIdentifierOverride, null, isAd, cropCode);
+ const firstScreenshots = lockup.screenshots[0];
+ lockup.trailers = [];
+ const trailers = content.trailersFromData(objectGraph, data, videoConfiguration, options.metricsOptions, lockup.adamId, isAd, cropCode);
+ if (serverData.isDefinedNonNull(trailers)) {
+ if (serverData.isNullOrEmpty(firstScreenshots) ||
+ trailers.mediaPlatform.isEqualTo(firstScreenshots.mediaPlatform)) {
+ lockup.trailers.push(trailers);
+ }
+ }
+ });
+}
+/**
+ * Copy the data contained in a platform response into a `ScreenshotsLockup`.
+ * @param data The store platform response data to read from.
+ * @param lockup The ScreenshotsLockup data model to copy store platform data to
+ * @param options Options customizing what data is needed.
+ */
+function copyDataIntoScreenshotsLockup(objectGraph, data, lockup, options) {
+ if (!data) {
+ return;
+ }
+ validation.context("copyDataIntoScreenshotsLockup", () => {
+ copyDataIntoLockup(objectGraph, data, lockup, options);
+ lockup.screenshots = content.screenshotsFromData(objectGraph, data, 4 /* content.ArtworkUseCase.LockupScreenshots */, null, options.clientIdentifierOverride);
+ });
+}
+/**
+ * Copy the data contained in a platform response into a `PosterLockup`.
+ * @param data The store platform response data to read from.
+ * @param lockup The PosterLockup data model to copy store platform data to
+ * @param options Options customizing what data is needed.
+ */
+function copyDataIntoPosterLockup(objectGraph, data, lockup, options) {
+ if (!data) {
+ return;
+ }
+ validation.context("copyDataIntoPosterLockup", () => {
+ copyDataIntoLockup(objectGraph, data, lockup, options);
+ lockup.epicHeading = content.posterEpicHeadingArtworkFromData(objectGraph, data);
+ lockup.posterArtwork = content.posterArtworkFromData(objectGraph, data);
+ lockup.posterVideo = content.posterEditorialVideoFromData(objectGraph, data, 19 /* content.ArtworkUseCase.GroupingHero */);
+ if (lockup.offerDisplayProperties) {
+ lockup.offerDisplayProperties = lockup.offerDisplayProperties.newOfferDisplayPropertiesChangingAppearance(false, "white", "lightOverArtwork");
+ }
+ if (lockup.posterVideo) {
+ lockup.isDark = color.isDarkColor(lockup.posterVideo.preview.backgroundColor);
+ }
+ else if (lockup.posterArtwork) {
+ lockup.isDark = color.isDarkColor(lockup.posterArtwork.backgroundColor);
+ }
+ else {
+ lockup.isDark = false;
+ }
+ const supportsArcade = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "supportsArcade");
+ const isPreorder = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isPreorder");
+ if (isPreorder) {
+ const fallbackString = supportsArcade ? objectGraph.loc.string("Offer.Label.ComingSoon") : null;
+ let preorderDate;
+ if (objectGraph.client.isVision || objectGraph.client.isWeb) {
+ preorderDate = content.dynamicPreorderDateFromData(objectGraph, data, fallbackString);
+ }
+ else {
+ preorderDate = objectGraph.loc.uppercased(content.dynamicPreorderDateFromData(objectGraph, data, fallbackString));
+ }
+ if (isSome(preorderDate)) {
+ lockup.footerText = preorderDate;
+ }
+ }
+ });
+}
+export function subtitleFromData(objectGraph, data, lockupOptions = null) {
+ if (isNothing(data)) {
+ return null;
+ }
+ return validation.context("subtitleFromData", () => {
+ let subtitle;
+ if (lockupOptions && lockupOptions.subtitleObjectPath) {
+ subtitle = contentAttributes.contentAttributeAsString(objectGraph, data, lockupOptions.subtitleObjectPath, lockupOptions === null || lockupOptions === void 0 ? void 0 : lockupOptions.attributePlatformOverride);
+ }
+ if (serverData.isNullOrEmpty(subtitle)) {
+ subtitle = contentAttributes.contentAttributeAsString(objectGraph, data, "subtitle", lockupOptions === null || lockupOptions === void 0 ? void 0 : lockupOptions.attributePlatformOverride);
+ }
+ if (subtitle) {
+ return subtitle;
+ }
+ else {
+ return categoryFromData(objectGraph, data, lockupOptions);
+ }
+ });
+}
+/**
+ * Creates the compability badge text for showing on a lockup.
+ *
+ * @param objectGraph Current object graph
+ * @param data Product data
+ * @returns Built badge text, or null
+ */
+export function badgeFromData(objectGraph, data, allowMultiline = false, hideCompatibilityBadge) {
+ return validation.context("badgeFromData", () => {
+ if (hideCompatibilityBadge) {
+ return null;
+ }
+ const doesClientSupportMacOSCompatibleIOSBinary = objectGraph.client.isMac || objectGraph.client.isWeb;
+ const supportsMacOSCompatibleIOSBinary = content.supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, doesClientSupportMacOSCompatibleIOSBinary);
+ const supportsVisionOSCompatibleBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ if (supportsMacOSCompatibleIOSBinary || supportsVisionOSCompatibleBinary) {
+ // 1. Build loc string base.
+ let locKey = "";
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ if (content.supportsPlatform(appPlatforms, "pad")) {
+ locKey = "Platform.DesignedForPad";
+ }
+ else if (content.supportsPlatform(appPlatforms, "phone")) {
+ locKey = "Platform.DesignedForPhone";
+ }
+ if ((locKey === null || locKey === void 0 ? void 0 : locKey.length) > 0) {
+ if (supportsMacOSCompatibleIOSBinary) {
+ // 2. Check if app is verified.
+ const isVerifiedForAppleSiliconMac = isVerifiedForAppleSiliconMacFromData(objectGraph, data);
+ if (!isVerifiedForAppleSiliconMac) {
+ locKey += ".NotVerified";
+ // 3. Use expanded two-line not verified badge.
+ if (allowMultiline) {
+ locKey += ".Expanded";
+ }
+ }
+ }
+ return objectGraph.loc.string(locKey);
+ }
+ }
+ return null;
+ });
+}
+/**
+ * Creates the compability badge artwork for showing on a lockup.
+ *
+ * @param objectGraph Current object graph
+ * @param data Product data
+ * @returns An iPhone/iPad symbol artwork, or null
+ */
+export function badgeArtworkFromData(objectGraph, data) {
+ const supportsVisionOSCompatibleBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ if (supportsVisionOSCompatibleBinary && objectGraph.client.isVision) {
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ if (content.supportsPlatform(appPlatforms, "pad")) {
+ return createArtworkForResource(objectGraph, "systemimage://ipad.landscape");
+ }
+ else if (content.supportsPlatform(appPlatforms, "phone")) {
+ return createArtworkForResource(objectGraph, "systemimage://iphone");
+ }
+ }
+ return null;
+}
+export function badgeActionFromData(objectGraph, data) {
+ return validation.context("badgeActionFromData", () => {
+ // Add action for unverified macOS apps.
+ const supportsMacOSCompatibleIOSBinary = content.supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, objectGraph.client.isMac);
+ if (!supportsMacOSCompatibleIOSBinary) {
+ return null;
+ }
+ const isVerifiedForAppleSiliconMac = isVerifiedForAppleSiliconMacFromData(objectGraph, data);
+ if (isVerifiedForAppleSiliconMac) {
+ return null;
+ }
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ if (!content.supportsPlatform(appPlatforms, "pad") && !content.supportsPlatform(appPlatforms, "phone")) {
+ return null;
+ }
+ const linkAction = new models.FlowAction("article");
+ linkAction.pageUrl = `https://apps.apple.com/story/id${objectGraph.bag.appleSiliconMacUnverifiedBadgeEditorialItemId}`;
+ return linkAction;
+ });
+}
+/// Does the badge require multiple lines?
+export function isBadgeMultilineFromData(objectGraph, data, allowMultiline) {
+ return validation.context("isBadgeMultilineFromData", () => {
+ if (!allowMultiline) {
+ return false;
+ }
+ const supportsMacOSCompatibleIOSBinary = content.supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, objectGraph.client.isMac);
+ if (!supportsMacOSCompatibleIOSBinary) {
+ return false;
+ }
+ const isVerifiedForAppleSiliconMac = isVerifiedForAppleSiliconMacFromData(objectGraph, data);
+ if (isVerifiedForAppleSiliconMac) {
+ return false;
+ }
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ return content.supportsPlatform(appPlatforms, "pad") || content.supportsPlatform(appPlatforms, "phone");
+ });
+}
+function isVerifiedForAppleSiliconMacFromData(objectGraph, data) {
+ // Don't badge unverified apps when Apple Silicon support is not enabled.
+ if (!objectGraph.appleSilicon.isSupportEnabled) {
+ return true;
+ }
+ // Check response.
+ const isVerifiedForAppleSiliconMac = contentAttributes.contentAttributeAsBoolean(objectGraph, data, "isVerifiedForAppleSiliconMac", "ios");
+ if (serverData.isDefinedNonNull(isVerifiedForAppleSiliconMac)) {
+ return isVerifiedForAppleSiliconMac;
+ }
+ return false;
+}
+export function categoryFromData(objectGraph, data, lockupOptions = null) {
+ return validation.context("categoryFromData", () => {
+ // genreDisplayName is the preferred field to use for the determining the category name
+ // It should always be available, but we have fallbacks below just in case.
+ const genreName = contentAttributes.contentAttributeAsString(objectGraph, data, "genreDisplayName", lockupOptions === null || lockupOptions === void 0 ? void 0 : lockupOptions.attributePlatformOverride);
+ if ((genreName === null || genreName === void 0 ? void 0 : genreName.length) > 0) {
+ return genreName;
+ }
+ // Fallback to interating over the genres list
+ const genres = mediaRelationship.relationshipCollection(data, "genres");
+ if (genres.length > 0) {
+ let candidateGenre = genres[0];
+ // If the primary genre is Games, search for the first sub-genre of games
+ const gamesId = 6014 /* constants.GenreIds.Games */.toString();
+ if (candidateGenre.id === gamesId) {
+ for (const genre of genres) {
+ const parentGenreId = mediaAttributes.attributeAsString(genre, "parentId");
+ if (genre.id !== gamesId && parentGenreId === gamesId) {
+ candidateGenre = genre;
+ break;
+ }
+ }
+ }
+ return mediaAttributes.attributeAsString(candidateGenre, "name");
+ }
+ else {
+ // Fallback to genreNames
+ const genreNames = mediaAttributes.attributeAsArrayOrEmpty(data, "genreNames");
+ return genreNames.length > 0 ? genreNames[0] : null;
+ }
+ });
+}
+/**
+ * Configures an internal URL for use as the pageUrl of an 'install' FlowAction,
+ * using the provided parameters.
+ * @param inAppPurchaseAdamId The adamId for the lockup to appear in the header.
+ * @param parentAdamId The adamId for the lockup that is being offered.
+ * @returns A fully configured internal URL for fetching an app install page.
+ */
+export function configureIAPInstallPageUrl(objectGraph, inAppPurchaseAdamId, parentAdamId) {
+ const parameters = new http.FormBuilder()
+ .param(Parameters.id, parentAdamId)
+ .param(InAppPurchaseInstallPageParameters.inAppPurchaseId, inAppPurchaseAdamId)
+ .build();
+ return `${Protocol.internal}:/${Path.product}/${Path.install}/?${parameters}`;
+}
+/**
+ * Configures an internal url to be used as the url for a clickAction from an IAP
+ * lockup to a product page.
+ * @param productUrl The URL for the product.
+ * @param inAppPurchaseAdamId The adamId for the lockup from which the click occurs.
+ * @param inAppPurchaseType The type for the in-app purchase.
+ * @returns A fully configured internal URL for a product click action via IAP.
+ */
+export function configureProductUrlFromInAppPurchase(objectGraph, productUrl, inAppProductIdentifier, isSubscription) {
+ const parameters = new http.FormBuilder()
+ .param(ProductPageParameters.url, productUrl)
+ .param(Parameters.offerName, inAppProductIdentifier)
+ .param(ProductPageParameters.isSubscription, isSubscription.toString())
+ .build();
+ return `${Protocol.internal}:/${Path.product}/${Path.lookup}/?${parameters}`;
+}
+export function lockupFromData(objectGraph, data, options) {
+ return validation.context("lockupFromData", () => {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
+ if (!data) {
+ validation.unexpectedNull("ignoredValue", "data");
+ return null;
+ }
+ // Must setup iAdInfo before any builder methods.
+ const isAd = adLockup.isAdvert(objectGraph, data);
+ options.metricsOptions.isAdvert = isAd;
+ const isAdEligible = adCommon.isAdEligible((_b = (_a = options.metricsOptions.pageInformation) === null || _a === void 0 ? void 0 : _a.iAdInfo) === null || _b === void 0 ? void 0 : _b.placementType, options.metricsOptions.locationTracker);
+ options.metricsOptions.isAdEligible = isAdEligible;
+ if (isAd || isAdEligible) {
+ (_d = (_c = options.metricsOptions.pageInformation) === null || _c === void 0 ? void 0 : _c.iAdInfo) === null || _d === void 0 ? void 0 : _d.apply(objectGraph, data);
+ }
+ if (isAd) {
+ (_f = (_e = options.metricsOptions.pageInformation) === null || _e === void 0 ? void 0 : _e.iAdInfo) === null || _f === void 0 ? void 0 : _f.setTemplateType("APPLOCKUP");
+ }
+ if (!mediaAttributes.hasAttributes(data)) {
+ return null;
+ }
+ switch (data.type) {
+ case "in-apps":
+ options.offerEnvironment = "widthConstrainedLockup";
+ return inAppPurchaseLockupFromData(objectGraph, data, options);
+ case "app-events":
+ const parentAppData = mediaRelationship.relationshipData(objectGraph, data, "app");
+ if (serverData.isNullOrEmpty(parentAppData)) {
+ return null;
+ }
+ const appLockup = new models.Lockup();
+ copyDataIntoLockup(objectGraph, parentAppData, appLockup, options);
+ return appLockup;
+ case "contingent-items":
+ case "offer-items":
+ return appPromotionOfferLockupFromData(objectGraph, data, options);
+ default:
+ const lockup = new models.Lockup();
+ copyDataIntoLockup(objectGraph, data, lockup, options);
+ if (isSome((_g = options.metricsOptions.pageInformation) === null || _g === void 0 ? void 0 : _g.iAdInfo)) {
+ if (isAd || isAdEligible) {
+ adLockup.performAdOverridesforLockup(objectGraph, data, lockup, options.metricsOptions);
+ }
+ if (isAd) {
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_h = lockup.searchAdOpportunity) === null || _h === void 0 ? void 0 : _h.setTemplateType("APPLOCKUP");
+ }
+ else {
+ (_j = lockup.searchAd) === null || _j === void 0 ? void 0 : _j.setTemplateType("APPLOCKUP");
+ }
+ }
+ }
+ return lockup;
+ }
+ });
+}
+/**
+ * Configures a app promotion lockup with a contingent-items or offer-items object
+ * @param objectGraph The App Store Object Graph.
+ * @param data The data for the app to go into the lockup.
+ * @param options A set of options customising the lockup.
+ * @returns A Lockup with the desired configuration.
+ */
+export function appPromotionOfferLockupFromData(objectGraph, data, options) {
+ return validation.context("appPromotionOfferLockupFromData", () => {
+ var _a, _b, _c;
+ const parentData = (_a = parentDataFromInAppData(objectGraph, data)) !== null && _a !== void 0 ? _a : options.parentAppData;
+ const iapData = iapDataFromData(objectGraph, data);
+ const supportsStreamlinedBuy = mediaAttributes.attributeAsBooleanOrFalse(parentData, "supportsStreamlinedBuy");
+ if (supportsStreamlinedBuy) {
+ const lockup = inAppPurchaseLockupFromData(objectGraph, data, options);
+ lockup.offerDisplayProperties.titles["standard"] = objectGraph.loc.string("OfferButton.Title.Subscribe");
+ lockup.offerDisplayProperties.isStreamlinedBuy = true;
+ // Override any discounts to force showing "Subscribe" title on the offer button.
+ lockup.offerDisplayProperties.hasDiscount = false;
+ // Setup the artwork
+ const rawArtwork = mediaAttributes.attributeAsDictionary(iapData, "artwork");
+ const backupArtwork = appPromotionsCommon.artworkFromPlatformData(objectGraph, parentData, "artwork");
+ const iapArtwork = content.artworkFromApiArtwork(objectGraph, rawArtwork, {
+ useCase: options.artworkUseCase,
+ withJoeColorPlaceholder: options.useJoeColorIconPlaceholder,
+ style: "iap",
+ overrideTextColorKey: options.overrideArtworkTextColorKey,
+ });
+ lockup.icon = iapArtwork !== null && iapArtwork !== void 0 ? iapArtwork : backupArtwork;
+ return lockup;
+ }
+ else {
+ const lockup = new models.Lockup();
+ copyDataIntoLockup(objectGraph, parentData, lockup, options);
+ const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, data, options.metricsOptions, options.metricsClickOptions);
+ metricsHelpersLocation.pushContentLocation(objectGraph, metricsClickOptions, lockup.title);
+ const parentBundleId = contentAttributes.contentAttributeAsString(objectGraph, parentData, "bundleId");
+ const productIdentifier = mediaAttributes.attributeAsString(iapData, "offerName");
+ const firstVersionSupportingMerchIAP = mediaAttributes.attributeAsString(parentData, "firstVersionSupportingInAppPurchaseApi");
+ const offerAction = new models.InAppPurchaseAction(productIdentifier, parentData.id, parentBundleId, lockup.buttonAction, firstVersionSupportingMerchIAP);
+ offerAction.appTitle = (_b = mediaAttributes.attributeAsString(parentData, "name")) !== null && _b !== void 0 ? _b : "";
+ offerAction.productTitle = (_c = mediaAttributes.attributeAsString(iapData, "name")) !== null && _c !== void 0 ? _c : "";
+ if (data.type === "offer-items") {
+ const discountedOffer = discountedOfferFromData(iapData);
+ const offerId = serverData.asString(discountedOffer, "offerId");
+ if (isSome(offerId) && offerId.length > 0) {
+ offerAction.additionalBuyParams = "adHocOfferId=" + offerId;
+ }
+ }
+ else {
+ // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
+ offerAction.additionalBuyParams = "contingentItemId=" + data.id;
+ }
+ lockup.buttonAction = offerAction;
+ metricsHelpersLocation.popLocation(options.metricsOptions.locationTracker);
+ return lockup;
+ }
+ });
+}
+export function inAppPurchaseLockupFromData(objectGraph, data, options) {
+ return validation.context("inAppPurchaseLockupFromData", () => {
+ const lockup = new models.InAppPurchaseLockup();
+ copyDataIntoInAppPurchaseLockup(objectGraph, data, lockup, options);
+ return lockup;
+ });
+}
+/**
+ * Create a new `ScreenshotsLockup` from a platform response data blob.
+ * @param data The platform response data to read from.
+ * @param options Options customizing what data the returned store item will contain.
+ * @returns A new `Lockup` object.
+ */
+export function screenshotsLockupFromData(objectGraph, data, options) {
+ return validation.context("screenshotsLockupFromData", () => {
+ const lockup = new models.ScreenshotsLockup();
+ copyDataIntoScreenshotsLockup(objectGraph, data, lockup, options);
+ return lockup;
+ });
+}
+/**
+ * Create a new `PosterLockup` from a platform response data blob.
+ * @param data The platform response data to read from.
+ * @param options Options customizing what data the returned store item will contain.
+ * @returns A new `Lockup` object.
+ */
+export function posterLockupFromData(objectGraph, data, options) {
+ return validation.context("posterLockupFromData", () => {
+ const lockup = new models.PosterLockup();
+ copyDataIntoPosterLockup(objectGraph, data, lockup, options);
+ return lockup;
+ });
+}
+/**
+ *
+ * @param data
+ * @param options
+ * @param videoConfiguration
+ * @returns {TrailersLockup}
+ */
+export function trailersLockupFromData(objectGraph, data, options, videoConfiguration) {
+ return validation.context("trailersLockupFromData", () => {
+ const lockup = new models.TrailersLockup();
+ copyDataIntoTrailersLockup(objectGraph, data, lockup, videoConfiguration, options);
+ return lockup;
+ });
+}
+/**
+ *Build a mixed media lockup from given data
+ * @param objectGraph
+ * @param data
+ * @param options
+ * @param videoConfiguration
+ * @param searchExperimentsData
+ * @param cropCode The crop code to use for the media
+ * @returns {MixedMediaLockup}
+ */
+export function mixedMediaLockupFromData(objectGraph, data, options, videoConfiguration, searchExperimentsData = null, cropCode) {
+ return validation.context("mixedMediaLockupFromData", () => {
+ const lockup = new models.MixedMediaLockup();
+ copyDataIntoLockup(objectGraph, data, lockup, options, null, () => {
+ copyMetadataRibbonInfoIntoLockup(objectGraph, data, lockup, searchExperimentsData, options);
+ copyMediaIntoMixedMediaLockup(objectGraph, data, lockup, videoConfiguration, options, cropCode);
+ copyScreenshotsDisplayStyleIntoMixedMediaLockup(objectGraph, data, lockup, searchExperimentsData);
+ });
+ return lockup;
+ });
+}
+/**
+ * Create am image lockup for shelfContents to display within a grouping shelf
+ * @param objectGraph
+ * @param mediaApiData shelfContents to create lockup for.
+ * @param lockupOptions The options needed to customize this lockup
+ * @param collectionShelfDisplayStyle
+ */
+export function imageLockupFromData(objectGraph, mediaApiData, lockupOptions, collectionDisplayStyle) {
+ const isEditorialApiData = mediaApiData.type === "editorial-items";
+ const lockupData = isEditorialApiData
+ ? mediaRelationship.relationshipData(objectGraph, mediaApiData, "primary-content")
+ : mediaApiData;
+ const lockup = lockupFromData(objectGraph, lockupData, lockupOptions);
+ // Determine which artwork to use, giving priority to an attached EI first, and then falling back
+ // to the lockup artwork. Currently we only use the attached EI on visionOS.
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, lockupData);
+ const lockupEditorialMediaData = editorialComponentMediaUtil.editorialMediaDataFromData(objectGraph, lockupData, collectionDisplayStyle);
+ const articleEditorialMediaData = editorialComponentMediaUtil.editorialMediaDataFromData(objectGraph, mediaApiData, collectionDisplayStyle);
+ const lockupArtwork = lockupEditorialMediaData === null || lockupEditorialMediaData === void 0 ? void 0 : lockupEditorialMediaData.artwork;
+ const articleArtwork = articleEditorialMediaData === null || articleEditorialMediaData === void 0 ? void 0 : articleEditorialMediaData.artwork;
+ let artwork;
+ let isDark;
+ if (isSome(articleArtwork) && objectGraph.client.isVision) {
+ isDark = isMediaDark(objectGraph, articleEditorialMediaData);
+ artwork = articleArtwork;
+ }
+ else if (isSome(lockupArtwork)) {
+ isDark = isMediaDark(objectGraph, lockupEditorialMediaData);
+ artwork = lockupArtwork;
+ }
+ if (isSome(artwork) && isSome(lockup)) {
+ const imageLockup = new models.ImageLockup(artwork, lockup, null, null, isDark);
+ imageLockup.caption = mediaPlatformAttributes.platformAttributeAsString(lockupData, attributePlatform, "editorialNotes.badge");
+ if (isSome(imageLockup.caption) && objectGraph.client.isVision) {
+ imageLockup.caption = objectGraph.loc.uppercased(imageLockup.caption);
+ }
+ imageLockup.title =
+ mediaPlatformAttributes.platformAttributeAsString(lockupData, attributePlatform, "editorialNotes.tagline") || mediaAttributes.attributeAsString(lockupData, "genreDisplayName");
+ imageLockup.impressionMetrics = lockup.impressionMetrics;
+ return imageLockup;
+ }
+ else {
+ return null;
+ }
+}
+function copyScreenshotsDisplayStyleIntoMixedMediaLockup(objectGraph, data, lockup, searchExperimentsData) {
+ var _a;
+ if (!objectGraph.client.isPhone) {
+ return;
+ }
+ const displayStyle = serverData.asString(data.meta, "imageLockupFromData");
+ if (serverData.isDefinedNonNull(displayStyle)) {
+ lockup.screenshotsDisplayStyle = displayStyle;
+ }
+ else if (serverData.isDefinedNonNull((_a = searchExperimentsData === null || searchExperimentsData === void 0 ? void 0 : searchExperimentsData.displayStyle) === null || _a === void 0 ? void 0 : _a.screenshots)) {
+ lockup.screenshotsDisplayStyle = searchExperimentsData.displayStyle.screenshots;
+ }
+}
+/**
+ * Create a mixed media lockup, with some specific ad-related modifications, from the supplied data.
+ * @param objectGraph The App Store Object Graph.
+ * @param data The data for the app to go into the lockup.
+ * @param options A set of options customising the lockup.
+ * @param videoConfiguration A configuration object for any videos in the lockup.
+ * @param searchExperimentsData Data for any search results experiments being run currently.
+ * @param applyAdOfferDisplayProperties Whether to apply the default ad OfferDisplayProperties. Some callers of this function want to enforce their own offer styling.
+ * @returns A MixedMediaLockup with the desired configuration.
+ */
+export function mixedMediaAdLockupFromData(objectGraph, data, options, videoConfiguration, searchExperimentsData, applyAdOfferDisplayProperties = true) {
+ return validation.context("mixedMediaAdLockupFromData", () => {
+ const lockup = new models.MixedMediaLockup();
+ if (!mediaAttributes.attributeAsBooleanOrFalse(data, "iad.format.images")) {
+ copyDataIntoLockup(objectGraph, data, lockup, options, null, () => {
+ copyMetadataRibbonInfoIntoLockup(objectGraph, data, lockup, searchExperimentsData, options);
+ });
+ lockup.screenshots = [];
+ }
+ else {
+ copyMediaIntoMixedMediaLockup(objectGraph, data, lockup, videoConfiguration, options);
+ adLockup.performAssetOverridesForMixedMediaAdLockupIfNeeded(objectGraph, data, lockup, options.metricsOptions);
+ copyDataIntoLockup(objectGraph, data, lockup, options, null, () => {
+ copyScreenshotsDisplayStyleIntoMixedMediaLockup(objectGraph, data, lockup, searchExperimentsData);
+ copyMetadataRibbonInfoIntoLockup(objectGraph, data, lockup, searchExperimentsData, options);
+ });
+ }
+ adLockup.performAdOverridesforLockup(objectGraph, data, lockup, options.metricsOptions, applyAdOfferDisplayProperties);
+ return lockup;
+ });
+}
+/**
+ * Create an lockup that describes the arcade service lockup from upsell data.
+ * This lockup:
+ * - Has a title, e.g. "Apple Arcade".
+ * - May have a subtitle text, e.g. "Play 100+ games".
+ * - Does *NOT* have a `clickAction` on itself (There is no Arcade product page).
+ * - Has two actions for each subscription state.
+ */
+export function arcadeLockupFromData(objectGraph, upsellData, metricsOptions, context, offerStyle, offerEnvironment) {
+ return validation.context("arcadeLockupFromData", () => {
+ const data = upsellData.marketingItemData;
+ const lockup = new models.ArcadeLockup();
+ lockup.title = objectGraph.loc.string("ARCADE_LOCKUP_TITLE", "Apple Arcade");
+ const marketingItemData = upsellData.marketingItemData;
+ metricsOptions = {
+ ...metricsOptions,
+ mercuryMetricsData: metricsUtil.marketingItemTopLevelBaseFieldsFromData(objectGraph, marketingItemData),
+ };
+ // This `name` attribute contains strings like "Some Games. \nAll you can play.". Weird.
+ // We trim newlines for arcade service footer lockup since the multiline text looks ugly.
+ let subtitle = arcadeUpsell.descriptionFromData(objectGraph, data);
+ if ((subtitle === null || subtitle === void 0 ? void 0 : subtitle.length) > 0) {
+ // This subtitle string on the upsell data blob is shared by many different views. However, editorial has a tendency to program explicit newlines, since some views look better with them (notably sheets).
+ // For the arcade lockup on some platforms, e.g. iOS, the string appears in a vertically constrained space and we ideally want to fit in 1 line if possible, We'll trim for now... Long term we need an editorial notes key
+ // for strings w/o newlines for this use case.
+ const platformIgnoresSubtitleNewlines = objectGraph.host.isiOS;
+ subtitle = platformIgnoresSubtitleNewlines ? subtitle.replace(/\n/g, " ") : subtitle;
+ lockup.nonsubscribedSubtitle = subtitle;
+ lockup.subscribedSubtitle = subtitle;
+ }
+ // Unsubscribed state action
+ let unsubscribedAction;
+ const unsubscribedActionTitle = arcadeUpsell.callToActionLabelFromData(objectGraph, data);
+ const arcadeLockupInNavBarEnabled = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.client.isVision;
+ if (arcadeLockupInNavBarEnabled) {
+ // This is configured as an `ArcadeAction` directly if the pricing token is present.
+ unsubscribedAction = arcadeUpsell.arcadeOfferButtonActionFromData(objectGraph, upsellData.marketingItemData, context, metricsOptions);
+ unsubscribedAction.title = unsubscribedActionTitle;
+ }
+ else if ((unsubscribedActionTitle === null || unsubscribedActionTitle === void 0 ? void 0 : unsubscribedActionTitle.length) > 0) {
+ unsubscribedAction = arcadeCommon.arcadeSubscribePageFlowAction(objectGraph, models.marketingItemContextFromString("editorialItemCanvas"), null, null, {
+ ...metricsOptions,
+ id: data.id,
+ });
+ unsubscribedAction.title = unsubscribedActionTitle;
+ }
+ else {
+ // If Upsell data is misconfigured and missing description, default to opening Arcade app for unsubscribed state.
+ unsubscribedAction = arcadeCommon.openArcadeMainAction(objectGraph, metricsOptions.pageInformation, metricsOptions.locationTracker);
+ if (preprocessor.GAMES_TARGET) {
+ unsubscribedAction.title = objectGraph.loc.string("OfferButton.Arcade.Title.Explore");
+ }
+ else {
+ unsubscribedAction.title = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE");
+ }
+ }
+ lockup.unsubscribedButtonAction = unsubscribedAction;
+ // Subscribed state action
+ const subscribedAction = arcadeCommon.openArcadeMainAction(objectGraph, metricsOptions.pageInformation, metricsOptions.locationTracker);
+ if (preprocessor.GAMES_TARGET) {
+ subscribedAction.title = objectGraph.loc.string("OfferButton.Arcade.Title.Explore");
+ }
+ else {
+ subscribedAction.title = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE");
+ }
+ lockup.subscribedButtonAction = subscribedAction;
+ // Impressions:
+ const metricsImpressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, upsellData.marketingItemData, lockup.title, metricsOptions);
+ metricsImpressionOptions.displaysArcadeUpsell = true;
+ // If no targetType is provided, set the correct value for the platform.
+ if (serverData.isNullOrEmpty(metricsImpressionOptions.targetType)) {
+ metricsImpressionOptions.targetType = objectGraph.client.isVision ? "lockupSmall" : "lockup";
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, lockup, metricsImpressionOptions);
+ const displayProperties = new models.OfferDisplayProperties("arcade", objectGraph.bag.arcadeAppAdamId, null, offerStyle, null, offerEnvironment, null, null, null, null, null, null, null, null, null, null, null, null, objectGraph.bag.arcadeProductFamilyId);
+ if (preprocessor.GAMES_TARGET) {
+ displayProperties.titles["subscribed"] = objectGraph.loc.string("OfferButton.Arcade.Title.Explore");
+ }
+ else {
+ displayProperties.titles["subscribed"] = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE");
+ }
+ lockup.offerDisplayProperties = displayProperties;
+ return lockup;
+ });
+}
+export function lockupsFromDataContainer(objectGraph, dataContainer, options) {
+ if (serverData.isNull(dataContainer)) {
+ return [];
+ }
+ return lockupsFromData(objectGraph, dataContainer.data, options);
+}
+export function lockupsFromData(objectGraph, dataArray, options) {
+ return validation.context("lockupsFromData", () => {
+ var _a;
+ if (!dataArray) {
+ return [];
+ }
+ const items = [];
+ let isDeferring = false;
+ for (let index = 0; index < dataArray.length; index++) {
+ if (isDeferring) {
+ break;
+ }
+ const lockupData = dataArray[index];
+ if (!mediaAttributes.hasAttributes(lockupData)) {
+ if (options.contentUnavailable) {
+ isDeferring = options.contentUnavailable(index, lockupData);
+ }
+ continue;
+ }
+ const lockupOptions = options.lockupOptions;
+ let filter = 80894 /* filtering.Filter.All */;
+ if (options.includeOrdinals) {
+ const ordinal = options.ordinalDirection === "descending" ? dataArray.length - index : index + 1;
+ lockupOptions.ordinal = objectGraph.loc.decimal(ordinal).toString();
+ }
+ if (serverData.isDefinedNonNull(options.filter)) {
+ filter = options.filter;
+ }
+ if (filtering.shouldFilter(objectGraph, lockupData, filter) && !options.shouldShowOnUnsupportedPlatform) {
+ continue;
+ }
+ const lockup = ((_a = options.lockupOptions.shouldCreateScreenshotsLockup) !== null && _a !== void 0 ? _a : false)
+ ? screenshotsLockupFromData(objectGraph, lockupData, lockupOptions)
+ : lockupFromData(objectGraph, lockupData, lockupOptions);
+ if (serverData.isNull(lockup) || !lockup.isValid()) {
+ continue;
+ }
+ items.push(lockup);
+ metricsHelpersLocation.nextPosition(options.lockupOptions.metricsOptions.locationTracker);
+ }
+ return items;
+ });
+}
+export function screenshotsLockupsFromData(objectGraph, dataArray, options) {
+ return validation.context("screenshotsLockupsFromData", () => {
+ if (!dataArray) {
+ return [];
+ }
+ const items = [];
+ for (let index = 0; index < dataArray.length; index++) {
+ const lockupData = dataArray[index];
+ if (serverData.isNull(lockupData.attributes)) {
+ if (options.contentUnavailable) {
+ options.contentUnavailable(index, lockupData);
+ }
+ continue;
+ }
+ const lockupOptions = options.lockupOptions;
+ let filter = 80894 /* filtering.Filter.All */;
+ if (serverData.isDefinedNonNull(options.filter)) {
+ filter = options.filter;
+ }
+ if (filtering.shouldFilter(objectGraph, lockupData, filter)) {
+ continue;
+ }
+ const lockup = screenshotsLockupFromData(objectGraph, lockupData, lockupOptions);
+ if (!lockup.isValid()) {
+ continue;
+ }
+ items.push(lockup);
+ metricsHelpersLocation.nextPosition(options.lockupOptions.metricsOptions.locationTracker);
+ }
+ return items;
+ });
+}
+/**
+ * Create an action for the provided data
+ * @param objectGraph The App Store Object Graph.
+ * @param data The data to create the action for.
+ * @param metricsOptions The metrics options to use for the action.
+ * @param clientIdentifierOverride A client identifier override, if any.
+ * @param externalDeepLinkUrl A custom deeplink URL, if any.
+ * @param isCppDeepLinkEligible Whether the action should be eligible for CPP Deep Links, if any. Used for restricting deep links on ads.
+ * @returns A configured action for the provided data.
+ */
+export function actionFromData(objectGraph, data, metricsOptions, clientIdentifierOverride, externalDeepLinkUrl = null, isCppDeepLinkEligible) {
+ return validation.context(`actionFromData: ${data.type}`, () => {
+ switch (data.type) {
+ case "apps":
+ case "app-bundles": {
+ return productActionFromData(objectGraph, data, metricsOptions, {
+ clientIdentifierOverride: clientIdentifierOverride,
+ externalDeepLinkUrl: externalDeepLinkUrl,
+ isCppDeepLinkEligible: isCppDeepLinkEligible,
+ });
+ }
+ case "in-apps": {
+ return iAPActionFromData(objectGraph, data, metricsOptions);
+ }
+ case "editorial-items": {
+ return editorialItemActionFromData(objectGraph, data, metricsOptions, clientIdentifierOverride);
+ }
+ case "tags":
+ case "editorial-pages": {
+ return editorialPageActionFromData(objectGraph, data, metricsOptions);
+ }
+ case "multiple-system-operators":
+ return msoActionFromData(objectGraph, data, metricsOptions);
+ case "groupings":
+ return groupingActionFromData(objectGraph, data, metricsOptions);
+ case "developers":
+ default: {
+ return genericActionFromData(objectGraph, data, metricsOptions);
+ }
+ }
+ });
+}
+function iAPActionFromData(objectGraph, data, options) {
+ return validation.context("iAPActionFromData", () => {
+ var _a;
+ const parentData = parentDataFromInAppData(objectGraph, data);
+ if (!parentData) {
+ return null;
+ }
+ const clickAction = new models.FlowAction("product");
+ const parentUrl = urls.URL.from(mediaAttributes.attributeAsString(parentData, "url"));
+ // Attach the productVariantID/cppId/ppid to the parent URL, if it exists.
+ const productVariantData = (_a = options.productVariantData) !== null && _a !== void 0 ? _a : variants.productVariantDataForData(objectGraph, parentData);
+ const productVariantID = variants.productVariantIDForVariantData(productVariantData);
+ if (serverData.isDefinedNonNull(productVariantID)) {
+ parentUrl.param(Parameters.productVariantID, productVariantID);
+ }
+ const parentUrlString = parentUrl.toString();
+ const inAppProductIdentifier = mediaAttributes.attributeAsString(data, "offerName");
+ const isSubscription = mediaAttributes.attributeAsBooleanOrFalse(data, "isSubscription");
+ clickAction.pageUrl = configureProductUrlFromInAppPurchase(objectGraph, parentUrlString, inAppProductIdentifier, isSubscription);
+ clickAction.title = mediaAttributes.attributeAsString(data, "name");
+ metricsHelpersClicks.addClickEventToAction(objectGraph, clickAction, options);
+ return clickAction;
+ });
+}
+export function editorialItemActionTypeFromData(objectGraph, data) {
+ if (serverData.isNullOrEmpty(data)) {
+ return 0 /* EditorialItemActionType.Unknown */;
+ }
+ return validation.context("editorialItemActionFromData", () => {
+ const link = mediaAttributes.attributeAsDictionary(data, "link");
+ if (linkIsExternal(link)) {
+ return 1 /* EditorialItemActionType.ExternalLink */;
+ }
+ const substyle = mediaAttributes.attributeAsString(data, "displaySubStyle");
+ const isListArticle = substyle === "List" || substyle === "NumberedList";
+ if (mediaAttributes.attributeAsBooleanOrFalse(data, "isCanvasAvailable") || isListArticle) {
+ return 2 /* EditorialItemActionType.Article */;
+ }
+ // This is a bit of a workaround to get the product data from `primary-content`, if it's available, for app events
+ // as this is where it's currently made available for an EI.
+ const primaryContent = mediaRelationship.relationshipCollection(data, "primary-content");
+ const cardContents = mediaRelationship.relationshipCollection(data, "card-contents");
+ const isPrimaryContentProduct = serverData.isDefinedNonNullNonEmpty(primaryContent) && primaryContent.length === 1;
+ const isProduct = serverData.isDefinedNonNullNonEmpty(cardContents) && cardContents.length === 1;
+ if ((isProduct && cardContents[0].type === "app-events") ||
+ (isPrimaryContentProduct && primaryContent[0].type === "app-events")) {
+ return 4 /* EditorialItemActionType.AppEvent */;
+ }
+ if (isProduct) {
+ return 3 /* EditorialItemActionType.Product */;
+ }
+ if ((objectGraph.client.isVision || preprocessor.GAMES_TARGET) && isSome(data.href) && data.href.length > 0) {
+ return 5 /* EditorialItemActionType.Href */;
+ }
+ return 0 /* EditorialItemActionType.Unknown */;
+ });
+}
+/**
+ * Create an action for an editorial story
+ * @param objectGraph The App Store Object Graph.
+ * @param data The data for the editorial story
+ * @param options A set of options customising the lockup.
+ * @param clientIdentifierOverride A client identifier override.
+ * @param articleRecoMetricsData The recommendation metrics data for the editorial item
+ * @param todayCardConfig The config options for the editorial Item
+ * @param canvasFilter The canvas filter for this editorial item, used to fetch a specific story
+ * @returns A click action for an editorial item
+ */
+export function editorialItemActionFromData(objectGraph, data, options, clientIdentifierOverride, articleRecoMetricsData, todayCardConfig) {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ return validation.context("editorialItemActionFromData", () => {
+ var _a;
+ let flowDestination;
+ let pageUrlString;
+ let destinationIntent;
+ let presentation;
+ switch (editorialItemActionTypeFromData(objectGraph, data)) {
+ case 1 /* EditorialItemActionType.ExternalLink */:
+ return editorialItemExternalLinkActionFromData(objectGraph, data, options);
+ case 2 /* EditorialItemActionType.Article */:
+ flowDestination = "article";
+ const pageUrl = urls.URL.from(mediaAttributes.attributeAsString(data, "url"));
+ if (serverData.isDefinedNonNull(articleRecoMetricsData)) {
+ pageUrl.param(Parameters.recoMetrics, JSON.stringify(articleRecoMetricsData));
+ }
+ if (objectGraph.client.isiOS && isSome(todayCardConfig) && !todayCardConfig.isHorizontalShelfContext) {
+ pageUrl.param(Parameters.todayCardConfig, JSON.stringify(todayCardConfig));
+ }
+ const editorialCardId = (_a = editorialCardFromData(data)) === null || _a === void 0 ? void 0 : _a.id;
+ if (isSome(editorialCardId)) {
+ pageUrl.param(Parameters.editorialCardId, editorialCardId);
+ }
+ pageUrlString = pageUrl.build();
+ if (objectGraph.client.isWeb) {
+ destinationIntent = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: data.id,
+ });
+ pageUrlString = makeRoutableArticlePageCanonicalUrl(objectGraph, destinationIntent);
+ }
+ if (preprocessor.GAMES_TARGET) {
+ // For Luck Seed1 we always show stories as modal across all platform,
+ // in the future we should follow App Store and push them on Mac,
+ // according to the specs https://quip-apple.com/CEplADSB1MJR
+ // Need to support proper dual column layout first:
+ // rdar://141251194 ([Seed 3] [Stories] Dynamic page layout (single and dual column))
+ if (objectGraph.host.isMac) {
+ presentation = "stackPush";
+ }
+ else {
+ presentation = "sheetPresent";
+ }
+ }
+ break;
+ case 3 /* EditorialItemActionType.Product */:
+ const productData = mediaRelationship.relationshipCollection(data, "card-contents")[0];
+ return actionFromData(objectGraph, productData, options, clientIdentifierOverride);
+ case 4 /* EditorialItemActionType.AppEvent */:
+ // This is a bit of a workaround to get the product data from `primary-content`, if it's available, for app events
+ // as this is where it's currently made available for an EI.
+ const primaryContent = mediaRelationship.relationshipCollection(data, "primary-content");
+ const isPrimaryContentProduct = serverData.isDefinedNonNullNonEmpty(primaryContent) && primaryContent.length === 1;
+ const appEventData = isPrimaryContentProduct
+ ? primaryContent[0]
+ : mediaRelationship.relationshipCollection(data, "card-contents")[0];
+ const eventProductData = mediaRelationship.relationshipData(objectGraph, appEventData, "app");
+ if (serverData.isNullOrEmpty(eventProductData)) {
+ return null;
+ }
+ const appEventOrDate = appEvent.appEventOrPromotionStartDateFromData(objectGraph, appEventData, eventProductData, false, false, "dark", "infer", false, options, false, true, null, false, false);
+ // Return early if we received a Date, as this means the App Event shouldn't be accessible yet.
+ if (serverData.isNull(appEventOrDate) || appEventOrDate instanceof Date) {
+ return null;
+ }
+ return appPromotionsCommon.detailPageClickActionFromData(objectGraph, appEventData, eventProductData, appEventOrDate, options, true);
+ case 5 /* EditorialItemActionType.Href */:
+ flowDestination = "page";
+ pageUrlString = mediaUrlMapping.hrefToRoutableUrl(objectGraph, data.href);
+ break;
+ default:
+ flowDestination = "unknown";
+ const link = mediaAttributes.attributeAsDictionary(data, "link");
+ if (objectGraph.client.isWeb) {
+ destinationIntent = makeEditorialPageIntentByID({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: data.id,
+ });
+ pageUrlString = makeEditorialPageURL(objectGraph, destinationIntent);
+ }
+ else {
+ pageUrlString = serverData.asString(link, "url");
+ }
+ }
+ if (isNothing(pageUrlString)) {
+ return null;
+ }
+ const action = new models.FlowAction(flowDestination);
+ action.pageUrl = pageUrlString;
+ if (isSome(presentation)) {
+ action.presentation = presentation;
+ }
+ let title = content.notesFromData(objectGraph, data, "name");
+ if (serverData.isNull(title)) {
+ title = serverData.asString(data, "label");
+ }
+ action.title = title;
+ if (destinationIntent) {
+ action.destination = destinationIntent;
+ }
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, options);
+ return action;
+ });
+}
+/**
+ * Creates a flow action to display a product page.
+ * @param objectGraph Dependency soup
+ * @param data The media api data to create the action from.
+ * @param metricsOptions Metrics dependencies
+ * @param options.clientIdentifierOverride The preferred client identifier to use for the product page.
+ * @param options.externalDeepLinkUrl An external deep link for the propuct to be used when opening the app, if any.
+ * @param options.isCppDeepLinkEligible Whether a CPP deep link can be used for this lockup, if available.
+ * @param options.productVariantData A variant to use. This can be populated as an optimization to avoid re-resolving the same variant data, e.g. in a product page.
+ * @returns A `FlowAction` object pointing to a product page.
+ */
+function productActionFromData(objectGraph, data, metricsOptions, options) {
+ var _a, _b, _c, _d;
+ if (!data) {
+ return null;
+ }
+ const clientIdentifierOverride = (_a = options.clientIdentifierOverride) !== null && _a !== void 0 ? _a : null;
+ const externalDeepLinkUrl = (_b = options.externalDeepLinkUrl) !== null && _b !== void 0 ? _b : null;
+ const isCppDeepLinkEligible = (_c = options.isCppDeepLinkEligible) !== null && _c !== void 0 ? _c : false;
+ const isCppDeepLinkDisabled = !isCppDeepLinkEligible;
+ const productVariantData = (_d = options.productVariantData) !== null && _d !== void 0 ? _d : variants.productVariantDataForData(objectGraph, data);
+ return validation.context("productActionFromData", () => {
+ var _a, _b, _c;
+ let productUrl = mediaAttributes.attributeAsString(data, "url");
+ if (!productUrl) {
+ validation.unexpectedNull("ignoredValue", "string", "url");
+ return null;
+ }
+ let productPageOptions = {};
+ const url = new urls.URL(productUrl);
+ if (metricsOptions.isAdvert) {
+ const lineItem = serverData.asString(data, "iad.lineItem");
+ if (lineItem !== null && lineItem.length > 0) {
+ url.param(metricsHelpersModels.iAdURLLineItemParameterStringToken, lineItem);
+ }
+ const iAdClickFields = (_a = metricsOptions.pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.clickFields;
+ url.param(metricsHelpersModels.iAdURLParameterStringToken, JSON.stringify(iAdClickFields));
+ productPageOptions = {
+ iAdClickFields: serverData.asJSONData(iAdClickFields),
+ iAdLineItem: lineItem,
+ };
+ const instanceId = adCommon.advertInstanceIdForData(objectGraph, data);
+ if (isSome(instanceId)) {
+ const advertType = content.isArcadeSupported(objectGraph, data)
+ ? "arcadeApp"
+ : "standardApp";
+ const reportingDestination = reportingDestinationFromMetricsOptions(objectGraph, metricsOptions.pageInformation);
+ const bundleId = mediaAttributes.attributeAsString(data, "platformAttributes.ios.bundleId");
+ const isPreorder = mediaAttributes.attributeAsBooleanOrFalse(data, "isPreorder");
+ const purchaseType = isPreorder ? "preorder" : "standard";
+ const dismissAdActionMetrics = new AdvertActionMetrics(instanceId, data.id, bundleId, advertType, "productPageDismissed", purchaseType, reportingDestination);
+ url.param(metricsHelpersModels.iAdDismissAdActionMetricsParameterStringToken, JSON.stringify(dismissAdActionMetrics));
+ productPageOptions.iAdDismissAdActionMetrics = serverData.asJSONData(dismissAdActionMetrics);
+ }
+ }
+ const productVariantID = variants.productVariantIDForVariantData(productVariantData);
+ if (serverData.isDefinedNonNull(productVariantID)) {
+ url.param(Parameters.productVariantID, productVariantID);
+ }
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ if (objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) {
+ const alignedRegionDeeplinkUrl = adLockup.getCustomCreativeDeepLinkUrl(data);
+ if (isSome(alignedRegionDeeplinkUrl)) {
+ url.param(externalDeepLink.alignedRegionDeepLinkQueryParameter, alignedRegionDeeplinkUrl);
+ }
+ else {
+ const tapDestinationCppId = adLockup.getTapDestinationIdForAdvert(data);
+ if (isSome(tapDestinationCppId)) {
+ url.param(Parameters.productVariantID, tapDestinationCppId);
+ }
+ }
+ }
+ }
+ productPageOptions.externalDeepLinkUrl = externalDeepLinkUrl;
+ if (serverData.isDefinedNonNull(externalDeepLinkUrl)) {
+ url.param(externalDeepLink.externalDeepLinkQueryParameter, externalDeepLinkUrl);
+ }
+ const platformFromIntent = getPlatform(objectGraph).platform;
+ const platformInferredFromData = safelyInferPlatformFromData(objectGraph, data);
+ if (objectGraph.client.isWeb) {
+ // For the web client, we add the platform query parameter if the active intent's platform
+ // differs from the "default" platform inferred from the app's data.
+ if (platformFromIntent && platformFromIntent !== platformInferredFromData) {
+ url.param("platform", platformFromIntent);
+ }
+ }
+ else {
+ // For non-web clients, propagate CPP, search term, and client identifier parms, which are not needed for web.
+ productPageOptions.isCppDeepLinkDisabled = isCppDeepLinkDisabled;
+ url.param(externalDeepLink.cppDeepLinkDisabledQueryParameter, isCppDeepLinkDisabled.toString());
+ // Add a searchTerm to the product URL to propagate to purchases on the subsequent product page.
+ const searchTerm = (_c = (_b = metricsOptions.pageInformation) === null || _b === void 0 ? void 0 : _b.searchTermContext) === null || _c === void 0 ? void 0 : _c.term;
+ if (isSome(searchTerm)) {
+ url.param("searchTerm", searchTerm);
+ }
+ productPageOptions.clientIdentifierOverride = clientIdentifierOverride;
+ if ((clientIdentifierOverride === null || clientIdentifierOverride === void 0 ? void 0 : clientIdentifierOverride.length) > 0) {
+ url.param("clientIdentifierOverride", clientIdentifierOverride);
+ }
+ }
+ productUrl = url.toString();
+ if (preprocessor.GAMES_TARGET) {
+ return gameModels.viewGameActionWithMediaAPIData(data, objectGraph, makeClickMetrics(objectGraph, data.id, "lockup", "navigate", [data.id]));
+ }
+ const action = new models.FlowAction("product");
+ action.pageUrl = productUrl;
+ action.pageData = productPageUtil.createProductPageSidePackFromResponse(objectGraph, data, productPageOptions);
+ action.title = mediaAttributes.attributeAsString(data, "name");
+ if (metricsOptions && metricsOptions.pageInformation) {
+ action.referrerUrl = metricsOptions.pageInformation.pageUrl;
+ }
+ // Extract and add additional options for pre-orders
+ const isPreorder = mediaAttributes.attributeAsBooleanOrFalse(data, "isPreorder");
+ if (isPreorder) {
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, {
+ ...metricsOptions,
+ offerType: "preorder",
+ offerReleaseDate: offers.expectedReleaseDateFromData(objectGraph, data),
+ }, true);
+ }
+ else {
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, metricsOptions, true);
+ }
+ const productId = serverData.asString(data, "id");
+ if (isSome(productId)) {
+ action.destination =
+ data.type === "app-bundles"
+ ? makeBundlePageIntent({
+ ...getLocale(objectGraph),
+ id: productId,
+ })
+ : makeProductPageIntent({
+ ...getLocale(objectGraph),
+ platform: inferPreviewPlatform(objectGraph, data),
+ id: productId,
+ });
+ }
+ return action;
+ });
+}
+/**
+ * Safely infers a preview platform from media data, handling any potential errors.
+ */
+function safelyInferPlatformFromData(objectGraph, data) {
+ try {
+ return inferPreviewPlatformFromDeviceFamilies(objectGraph, { data: [data] });
+ }
+ catch (error) {
+ objectGraph.console.error(`Error inferring preview platform from data: ${error}`);
+ return undefined;
+ }
+}
+function inferPreviewPlatform(objectGraph, data) {
+ const platformFromIntent = getPlatform(objectGraph).platform;
+ if (platformFromIntent) {
+ return platformFromIntent;
+ }
+ return safelyInferPlatformFromData(objectGraph, data);
+}
+/**
+ * Creates a flow action to display a grouping page
+ * @param data The media api data to create the action from.
+ * @returns A `FlowAction` object pointing to a grouping page.
+ */
+function groupingActionFromData(objectGraph, data, options) {
+ if (!data) {
+ return null;
+ }
+ return validation.context("groupingActionFromData", () => {
+ if (!data.href) {
+ validation.unexpectedNull("ignoredValue", "string", "href");
+ return null;
+ }
+ const action = new models.FlowAction("page");
+ if (objectGraph.client.isWeb) {
+ const destination = makeGroupingPageIntentByID({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: data.id,
+ });
+ action.destination = destination;
+ action.pageUrl = makeGroupingPageCanonicalURL(objectGraph, destination);
+ }
+ else {
+ action.pageUrl = mediaUrlBuilder
+ .buildURLFromRequest(objectGraph, mediaUrlMapping.mediaApiGroupingURLFromHref(objectGraph, data.href))
+ .toString();
+ }
+ action.title = nameFromGroupingData(objectGraph, data);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, options);
+ return action;
+ });
+}
+/**
+ * Creates a flow action to display a mso-page.
+ * @param data The media api data to create the action from.
+ * @returns A `FlowAction` object pointing to a mso page.
+ */
+function msoActionFromData(objectGraph, data, options) {
+ if (!data) {
+ return null;
+ }
+ return validation.context("msoActionFromPlatformData", () => {
+ const roomUrl = mediaAttributes.attributeAsString(data, "url");
+ if (!roomUrl) {
+ validation.unexpectedNull("ignoredValue", "string", "url");
+ return null;
+ }
+ const action = new models.FlowAction("mso");
+ action.pageUrl = roomUrl;
+ action.title = mediaAttributes.attributeAsString(data, "name");
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, options);
+ return action;
+ });
+}
+/**
+ * Creates a flow action to display an editorial page.
+ *
+ * editorial-pages asset type does not have a url attribute, so we have to synthesize one
+ * - The "web" client uses the URL defined by the `EditorialPageIntentController`
+ * - Other clients use the `href` attribute
+ *
+ * @param data The media api data to create the action from.
+ * @param options The required options passed to the lockup creation method.
+ * @returns A `FlowAction` object pointing to a product page.
+ */
+function editorialPageActionFromData(objectGraph, data, options) {
+ if (!data) {
+ return null;
+ }
+ return validation.context("editorialPageActionFromData", () => {
+ const action = new models.FlowAction("page");
+ if (objectGraph.client.isWeb) {
+ const editorialPageIntent = makeEditorialPageIntentByID({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: data.id,
+ });
+ action.destination = editorialPageIntent;
+ action.pageUrl = makeEditorialPageURL(objectGraph, editorialPageIntent);
+ }
+ else {
+ const href = data.href;
+ if (serverData.isNullOrEmpty(href)) {
+ validation.unexpectedNull("ignoredValue", "string", "href");
+ return null;
+ }
+ action.pageUrl = mediaUrlMapping.hrefToRoutableUrl(objectGraph, href);
+ }
+ action.title = content.editorialNotesFromData(objectGraph, data, "name");
+ if (serverData.isNullOrEmpty(action.title)) {
+ action.title = mediaAttributes.attributeAsString(data, "name");
+ }
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, options);
+ return action;
+ });
+}
+/**
+ * Creates a flow action to display a generic page
+ * @param data The media api data to create the action from.
+ * @returns A `FlowAction` object pointing to a product page.
+ */
+function genericActionFromData(objectGraph, data, options) {
+ if (!data) {
+ return null;
+ }
+ return validation.context("genericActionFromData", () => {
+ const type = serverData.asString(data, "type");
+ const url = mediaAttributes.attributeAsString(data, "url");
+ if (!url) {
+ validation.unexpectedNull("ignoredValue", "string", "url");
+ return null;
+ }
+ const action = new models.FlowAction("page");
+ action.pageUrl = url;
+ if (type === "groupings") {
+ action.title = nameFromGroupingData(objectGraph, data);
+ }
+ else {
+ action.title = mediaAttributes.attributeAsString(data, "name");
+ }
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, options);
+ return action;
+ });
+}
+/**
+ * Whether or not link blob is to external url.
+ * @param link JSON blob for 'link'
+ * @returns boolean whether url is external link.
+ */
+function linkIsExternal(link) {
+ const target = serverData.asString(link, "target");
+ return target && target === "external";
+}
+/**
+ * Create an `ExternalUrlAction` for an External Link card.
+ * The title of the action is either the url domain or provided short description.
+ * @param data Media Api data
+ * @returns A configured `ExternalUrlAction` for card.
+ */
+export function editorialItemExternalLinkActionFromData(objectGraph, data, options) {
+ return validation.context("editorialItemExternalLinkActionFromData", () => {
+ const link = mediaAttributes.attributeAsDictionary(data, "link");
+ const urlString = serverData.asString(link, "url");
+ const action = new models.ExternalUrlAction(urlString);
+ // Title is short description or url domain
+ const linkDescription = content.notesFromData(objectGraph, data, "short");
+ if (linkDescription) {
+ action.title = linkDescription;
+ }
+ else {
+ const url = new urls.URL(urlString);
+ action.title = url.host;
+ }
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, options);
+ return action;
+ });
+}
+/**
+ * Creates a shallow copy of the provided lockups array, overriding any of the properties
+ * supplied. This function makes sure to override the properties non-destructively; that
+ * is, the overrides are only applied to the copied array's lockups.
+ * @param lockups The lockups to copy.
+ * @param overrideStyle The style to apply for the copied lockups' offerDisplayProperties.
+ * @param filterAds Whether to filter ads. In some places where we add ads to a set of lockups, showing that ad in a copied location isn't supported.
+ * @returns {Lockup[]}
+ */
+export function shallowCopyLockupsOverridingProperties(objectGraph, lockups, overrideStyle, filterAds = false) {
+ var _a, _b;
+ if (!lockups) {
+ return null;
+ }
+ // Store a count of the removed ads so we know how many items we've filtered from the beginning of the list of lockups.
+ let removedAdCount = 0;
+ const copies = [];
+ for (const lockup of lockups) {
+ if (filterAds && serverData.isDefinedNonNull((_a = lockup.searchAd) !== null && _a !== void 0 ? _a : (_b = lockup.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.searchAd)) {
+ removedAdCount += 1;
+ continue;
+ }
+ const copy = objects.shallowCopyOf(lockup);
+ if (overrideStyle && copy.offerDisplayProperties) {
+ copy.offerDisplayProperties = copy.offerDisplayProperties.newOfferDisplayPropertiesChangingAppearance(false, overrideStyle);
+ }
+ /// Adjust the impression index of the copy by the number of ads that have been removed prior to this item.
+ if (removedAdCount > 0) {
+ // Take a copy of the impressionMetrics.fields, as the copy is only shallow and we don't want to affect the original.
+ const impressionMetricsFields = objects.shallowCopyOf(copy.impressionMetrics.fields);
+ const impressionIndex = serverData.asNumber(impressionMetricsFields.impressionIndex);
+ if (isSome(impressionIndex)) {
+ impressionMetricsFields.impressionIndex = impressionIndex - removedAdCount;
+ copy.impressionMetrics = new models.ImpressionMetrics(impressionMetricsFields, copy.impressionMetrics.id, copy.impressionMetrics.custom);
+ }
+ }
+ copies.push(copy);
+ }
+ return copies;
+}
+/**
+ * Generates a name from a grouping media api resource
+ * @param data The grouping data from a genre response
+ * @return {string} A string of the grouping page's name
+ */
+export function nameFromGroupingData(objectGraph, data) {
+ const genreNames = mediaAttributes.attributeAsArrayOrEmpty(data, "genreNames");
+ if (serverData.isDefinedNonNullNonEmpty(genreNames)) {
+ return genreNames[0];
+ }
+ else {
+ return mediaAttributes.attributeAsString(data, "name");
+ }
+}
+export function deviceHasCapabilitiesFromData(objectGraph, data) {
+ if (!data) {
+ return false;
+ }
+ if (objectGraph.client.isWatch || objectGraph.client.isWeb || objectGraph.client.isCompanionVisionApp) {
+ // For the "watch" client, opt out of capability checks until we land <rdar://47322408>.
+ // For the "web" client, we treat it like it can run anything, since we can't infer actual capabilites.
+ // For the companion app, we can't call down to the remote device to check its capabilities,
+ // so we defer this check to later on.
+ return true;
+ }
+ const requiredCapabilitiesString = content.requiredCapabilitiesFromData(objectGraph, data, objectGraph.appleSilicon.isSupportEnabled);
+ if (serverData.isNullOrEmpty(requiredCapabilitiesString)) {
+ // If we don't have any capabilities, this is vacuously true.
+ return true;
+ }
+ const splitCapabilities = requiredCapabilitiesString.split(" ");
+ const supportsVisionOSCompatibleIOSBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ return objectGraph.client.deviceHasCapabilitiesIncludingCompatibilityCheckIsVisionOSCompatibleIOSApp(splitCapabilities, supportsVisionOSCompatibleIOSBinary);
+}
+/**
+ * Finds the IAP data from an app promotion
+ * @param data The app promotion data
+ * @returns data of the IAP
+ */
+export function iapDataFromData(objectGraph, data) {
+ if (!data) {
+ return null;
+ }
+ switch (data.type) {
+ case "contingent-items":
+ return mediaRelationship.relationshipData(objectGraph, data, "branch");
+ case "offer-items":
+ return mediaRelationship.relationshipData(objectGraph, data, "salables");
+ default:
+ break;
+ }
+ return data;
+}
+/**
+ * Finds the discounted offer data from an IAP.
+ * @param data The IAP data
+ * @returns the discounted offer
+ */
+export function discountedOfferFromData(data) {
+ if (!data) {
+ return null;
+ }
+ const contingentOffer = serverData.asDictionary(data, "meta.contingentItemOffer");
+ if (serverData.isDefinedNonNullNonEmpty(contingentOffer)) {
+ return contingentOffer;
+ }
+ const winbackOffer = serverData.asDictionary(data, "meta.discountOffer");
+ if (serverData.isDefinedNonNullNonEmpty(winbackOffer)) {
+ return winbackOffer;
+ }
+ return null;
+}
+/**
+ * Finds the parent app data from a app promotion.
+ * @param data The contingent-offer or offer-item data
+ * @returns the parent app data
+ */
+export function parentDataFromInAppData(objectGraph, data) {
+ if (!data) {
+ return null;
+ }
+ switch (data.type) {
+ case "contingent-items":
+ return mediaRelationship.relationshipData(objectGraph, data, "branch-app");
+ case "offer-items":
+ const iapData = mediaRelationship.relationshipData(objectGraph, data, "salables");
+ return mediaRelationship.relationshipData(objectGraph, iapData, "app");
+ default:
+ break;
+ }
+ return mediaRelationship.relationshipData(objectGraph, data, "app");
+}
+export function cleanupArcadeDownloadPackLockupMetricsIfNeeded(lockup, objectGraph) {
+ if (!objectGraph.bag.arcadeDownloadPacksMetricsEventsEnabled) {
+ lockup.clickAction.actionMetrics.clearAll();
+ lockup.buttonAction.actionMetrics.clearAll();
+ if (lockup.buttonAction instanceof models.OfferStateAction) {
+ lockup.buttonAction.defaultAction.actionMetrics.clearAll();
+ }
+ }
+ if (!objectGraph.bag.arcadeDownloadPacksImpressionEventsEnabled) {
+ lockup.impressionMetrics = null;
+ }
+}
+//# sourceMappingURL=lockups.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/builder.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/builder.js
new file mode 100644
index 0000000..ffb205f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/builder.js
@@ -0,0 +1,257 @@
+import { isSome } from "@jet/environment";
+import { isNothing } from "@jet/environment/types/optional";
+import { AppStoreMetricsData, } from "../../api/models/metrics/metrics";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as client from "../../foundation/wrappers/client";
+import { EventLinter } from "./event-linter";
+import { optOutOfLegacyMetricsIdFieldsProvider } from "./helpers/legacy-metrics-identifier-fields-opt-out";
+export function createMetricsClickData(objectGraph, targetId, targetType, eventFields, additionalIncludingFields, isDefaultBrowser) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "click";
+ fields["targetType"] = targetType;
+ fields["targetId"] = targetId;
+ const include = ["impressionsSnapshot", "pageFields"];
+ if (!preprocessor.GAMES_TARGET) {
+ include.push("contentRestrictionReasons");
+ }
+ if (additionalIncludingFields) {
+ include.push(...additionalIncludingFields);
+ }
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, [], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields, null, isDefaultBrowser)));
+}
+export function createMetricsBackClickData(objectGraph, eventFields) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["actionType"] = "back";
+ const clickData = createMetricsClickData(objectGraph, "back", "button", fields);
+ return clickData;
+}
+export function createMetricsPageData(objectGraph, isReferralEligible, isCrossfireReferralCandidate, timingMetrics, eventFields, isDefaultBrowser) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "page";
+ if (timingMetrics) {
+ fields["clientCorrelationKey"] = timingMetrics.clientCorrelationKey;
+ fields["requestStartTime"] = timingMetrics.requestStartTime;
+ fields["responseStartTime"] = timingMetrics.responseStartTime;
+ fields["responseEndTime"] = timingMetrics.responseEndTime;
+ }
+ const include = ["pageFields", "pageReferrer"];
+ if (!preprocessor.GAMES_TARGET) {
+ include.push("userContentRestriction");
+ }
+ if (isReferralEligible) {
+ include.push("crossfireReferral");
+ }
+ else if (isCrossfireReferralCandidate) {
+ include.push("crossfireReferralCandidate"); // Only possible when not crossfire eligible.
+ }
+ addAltAb2DataToEventFields(objectGraph, fields);
+ addWebClientEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, [], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields, null, isDefaultBrowser)));
+}
+export function createMetricsSearchData(objectGraph, term, target, actionType, actionUrl, eventFields, additionalIncludingFields) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["term"] = term;
+ fields["targetType"] = target;
+ fields["actionType"] = actionType;
+ if (actionUrl) {
+ fields["actionUrl"] = actionUrl; // actionUrl is defined for `hints` but not for searches fired from elsewhere.
+ }
+ fields["eventType"] = "search";
+ const include = ["pageReferrer"];
+ if (additionalIncludingFields) {
+ include.push(...additionalIncludingFields);
+ }
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, [], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields)));
+}
+export function createMetricsPageRenderFields(objectGraph, timingMetrics, eventFields) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "pageRender";
+ if (timingMetrics) {
+ if (!fields["pageUrl"]) {
+ fields["pageUrl"] = timingMetrics.pageURL;
+ }
+ fields["clientCorrelationKey"] = timingMetrics.clientCorrelationKey;
+ fields["platformRequestStartTime"] = timingMetrics.requestStartTime;
+ fields["platformResponseStartTime"] = timingMetrics.responseStartTime;
+ fields["platformResponseEndTime"] = timingMetrics.responseEndTime;
+ fields["platformResponseWasCached"] = timingMetrics.responseWasCached;
+ fields["platformJsonParseStartTime"] = timingMetrics.parseStartTime;
+ fields["platformJsonParseEndTime"] = timingMetrics.parseEndTime;
+ }
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return fields;
+}
+/**
+ * Create a metrics data for regular impressions.
+ * @param objectGraph The dependency graph for the App Store.
+ * @param eventFields Base fields to build off on.
+ * @param shouldIncludeAdFieldsForPad Whether or not metrics data should include iPad related fields for adverts.
+ * @param includeAdRotationFields Whether or not metrics data should include advert related fields.
+ * @param hasImpressionsAppendix Whether condensed format search results should track appendix data.
+ */
+export function createMetricsImpressionsData(objectGraph, eventFields, shouldIncludeAdFieldsForPad, includeAdRotationFields, hasImpressionsAppendix) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "impressions";
+ fields["impressionQueue"] = "data-metrics";
+ fields["eventVersion"] = 4;
+ const include = ["impressions", "pageFields", "pageReferrer"];
+ if (!preprocessor.GAMES_TARGET) {
+ include.push("contentRestrictionReasons");
+ }
+ if (shouldIncludeAdFieldsForPad) {
+ include.push("advertDeviceWindow");
+ }
+ if (includeAdRotationFields) {
+ include.push("advertRotation");
+ }
+ if (hasImpressionsAppendix) {
+ include.push("impressionsAppendix");
+ }
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, ["eventVersion"], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields)));
+}
+/**
+ * Create a metrics data for fast impressions, a special type for SearchAds on Search Page.
+ * Included fields assume fields are for search page w/ Ads.
+ * @param baseFields Base fields to build off on.
+ */
+export function createMetricsFastImpressionsData(objectGraph, baseFields, pageInformation) {
+ var _a, _b;
+ const shouldIncludeAdFieldsForPad = serverData.isDefinedNonNull(pageInformation.iAdInfo) &&
+ objectGraph.client.isPad &&
+ (isNothing(pageInformation.iAdInfo.missedOpportunityReason) ||
+ pageInformation.iAdInfo.missedOpportunityReason.length === 0);
+ const shouldIncludeAdRotationFields = (_b = (_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.shouldIncludeAdRotationFields) !== null && _b !== void 0 ? _b : false;
+ const exclude = [];
+ const fields = createMetricsImpressionsData(objectGraph, baseFields, shouldIncludeAdFieldsForPad, shouldIncludeAdRotationFields, false).fields;
+ fields["impressionQueue"] = "data-metrics-impressions-low-latency";
+ if (pageInformation !== null && serverData.isDefinedNonNull(pageInformation.iAdInfo)) {
+ const eventVersion = pageInformation.iAdInfo.fastImpressionsEventVersion;
+ fields["eventVersion"] = eventVersion;
+ exclude.push("eventVersion");
+ // Make some manual adjustments to events for v5.
+ if (eventVersion === 5) {
+ // Indicates the viewable area where elements can be impressed excludes the tab bar
+ fields["viewableArea"] = "excludingTabBar";
+ // Remove the `iAdPlacementId` for v5. It's added for compatibility with v4 impressions.
+ delete fields["iAdPlacementId"];
+ }
+ }
+ const include = ["fastImpressions", "pageFields", "pageReferrer"];
+ if (shouldIncludeAdFieldsForPad) {
+ include.push("advertDeviceWindow");
+ }
+ if (shouldIncludeAdRotationFields) {
+ include.push("advertRotation");
+ }
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, exclude, topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields, pageInformation)));
+}
+export function createMetricsMediaData(objectGraph, eventFields) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "media";
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, [], [], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields)));
+}
+export function createMetricsMediaClickData(objectGraph, targetId, targetType, eventFields) {
+ const fields = {};
+ Object.assign(fields, eventFields);
+ fields["eventType"] = "click";
+ fields["targetType"] = targetType;
+ fields["targetId"] = targetId;
+ const include = ["pageFields"];
+ addWebClientEventFields(objectGraph, fields);
+ addAltAb2DataToEventFields(objectGraph, fields);
+ return optOutOfLegacyMetricsIdFieldsProvider(objectGraph, new AppStoreMetricsData(fields, include, [], topicFromEventFields(objectGraph, fields), shouldFlushFromEventFields(objectGraph, fields)));
+}
+function shouldFlushFromEventFields(objectGraph, eventFields, pageInformation = null, isDefaultBrowser) {
+ var _a, _b;
+ const eventType = eventFields["eventType"];
+ let shouldFlush = false;
+ if (!serverData.isDefinedNonNullNonEmpty(eventType)) {
+ return shouldFlush;
+ }
+ const isDefaultBrowserContext = isDefaultBrowser !== null && isDefaultBrowser !== void 0 ? isDefaultBrowser : false;
+ switch (eventType) {
+ case "click":
+ shouldFlush =
+ serverData.asBooleanOrFalse(eventFields, EventLinter.hasIAdData) ||
+ isDefaultBrowserContext;
+ break;
+ case "exit":
+ shouldFlush = true;
+ break;
+ case "impressions":
+ shouldFlush = serverData.asBooleanOrFalse(eventFields, EventLinter.hasIAdData);
+ // Not my best work here.
+ // For most ad placements on a page, it's fine to check the `hasIAdData` as an indicator for whether we should flush after a given event.
+ // The challenge for the product page placements is that they're fetched asynchronously, and we don't create an entirely new impressions
+ // event when that happens (we just update fields within the event via the `pageChange` mechanism). To ensure that fast impression events on
+ // the product page are actually fast, we have to force the events to flush if they meet the below conditions that effectively guarantee
+ // we will be impressing something on the low latency queue.
+ if (eventFields["impressionQueue"] === "data-metrics-impressions-low-latency" &&
+ (((_a = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.placementType) === "productPageYMAL" ||
+ ((_b = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.placementType) === "productPageYMALDuringDownload")) {
+ shouldFlush = true;
+ }
+ break;
+ case "page":
+ // rdar://98487650 (CL seed: Product Page observed lower % of events coming in <2 mins ( page change + page events ))
+ // In order to resolve issues with flushing timeliness in the PPE: rdar://93127678 ([Metrics] Dismissing store sheet does not fire clickstream events)
+ // we allow page events with ad data in them to cause a flush on product pages in the PPE.
+ // We also pre-emptively flush for page events where it's the default browser flow in order to "prewarm" the metrics pipeline, ensuring flushes can occur
+ // if and when the user taps "Open" on an installed app.
+ const isProductPageExtension = objectGraph.host.clientIdentifier === client.productPageExtensionIdentifier;
+ shouldFlush =
+ isProductPageExtension &&
+ (serverData.asBooleanOrFalse(eventFields, EventLinter.hasIAdData) ||
+ isDefaultBrowserContext);
+ break;
+ default:
+ break;
+ }
+ const isSubscribePageExtensionEnterExitEventsEnabled = objectGraph.host.isiOS;
+ if (objectGraph.host.clientIdentifier === client.subscribePageExtensionIdentifier &&
+ !isSubscribePageExtensionEnterExitEventsEnabled) {
+ // <rdar://problem/55865604> Metrics: missing metrics events for arcade upsell from springboard app open on unsubscribed user
+ // Until a native fix is in to address enter/exit events in SPE (subscribePageExtensionEnterExitEvents), we
+ // need to force events in the SubscribePageExtension to trigger a flush.
+ shouldFlush = true;
+ }
+ return shouldFlush;
+}
+function topicFromEventFields(objectGraph, eventFields) {
+ const topic = eventFields["topic"] || objectGraph.bag.metricsTopic;
+ return topic;
+}
+function addAltAb2DataToEventFields(objectGraph, eventFields) {
+ if (objectGraph.bag.isMetricsAb2DataFallbackEnabled && isSome(objectGraph.experimentCache)) {
+ eventFields["alt_ab2_data"] = JSON.stringify(objectGraph.experimentCache.createAb2Data());
+ }
+}
+/**
+ * Attach expected metrics fields specific to the "web" client, if necessary
+ */
+function addWebClientEventFields(objectGraph, eventFields) {
+ var _a;
+ if (!objectGraph.client.isWeb) {
+ return;
+ }
+ // The `platformContext` field is expected to reflect the "platform" that the user is browsing
+ // at the time of the event
+ eventFields["platformContext"] = (_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.previewPlatform;
+}
+//# sourceMappingURL=builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/event-linter.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/event-linter.js
new file mode 100644
index 0000000..ad9706c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/event-linter.js
@@ -0,0 +1,563 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import { LintedMetricsEvent } from "../../api/models/metrics/metrics";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { BuyParameters } from "../../foundation/metrics/buy-parameters";
+import { cookiesOf } from "../../foundation/metrics/cookies";
+import { MetricsIdentifierType } from "../../foundation/metrics/metrics-identifiers-cache";
+import { URL } from "../../foundation/network/urls";
+import { reduceSignificantDigits } from "../../foundation/util/math-util";
+import * as constants from "./helpers/constants";
+import { stripUniqueImpressionIdsIfNecessary } from "./helpers/impressions";
+import { iAdDismissAdActionMetricsParameterStringToken, iAdURLLineItemParameterStringToken, iAdURLParameterStringToken, } from "./helpers/models";
+import * as searchResultImpressions from "./helpers/search-result-impressions";
+import * as searchFocusImpressions from "./helpers/search-focus-impressions";
+import * as util from "./helpers/util";
+import { MetricsReferralContext } from "./metrics-referral-context";
+/**
+ * A type which applies App Store business rules to metrics fields
+ * and generates events which are ready for posting to figaro.
+ */
+export class EventLinter {
+ /**
+ * Create an event linter.
+ *
+ * @param options The options which specify various behaviors of the new linter.
+ * This object will be frozen.
+ */
+ constructor(options) {
+ this._options = Object.freeze(options);
+ }
+ // endsection
+ // section Public Properties
+ /**
+ * Topic to use if an event fields blob does not specify one.
+ */
+ get defaultTopic() {
+ return this._options.defaultTopic;
+ }
+ // endsection
+ // section Utilities
+ /**
+ * Reduce the accuracy of fields in a blob according to
+ * a given array of rules found in a metrics objectGraph.bag.
+ *
+ * @param eventFields The fields of an event to reduce the accuracy of.
+ * @param rules An array of rules from a metrics objectGraph.bag.
+ */
+ _reduceFieldAccuracy(eventFields, rules) {
+ for (const rule of rules) {
+ const fieldName = serverData.asString(rule, "fieldName");
+ if (serverData.isNull(fieldName)) {
+ continue;
+ }
+ const value = serverData.asNumber(eventFields, fieldName);
+ if (serverData.isNull(value)) {
+ continue;
+ }
+ let magnitude = serverData.asNumber(rule, "magnitude");
+ if (serverData.isNull(magnitude)) {
+ magnitude = 1024 * 1024;
+ }
+ let significantDigits = serverData.asNumber(rule, "significantDigits");
+ if (serverData.isNull(significantDigits)) {
+ significantDigits = 2;
+ }
+ if (magnitude <= 0.0 || significantDigits < 0.0) {
+ // This is the failure mode from MetricsKit.
+ eventFields[fieldName] = Number.NaN;
+ continue;
+ }
+ const scaledValue = value / magnitude;
+ eventFields[fieldName] = reduceSignificantDigits(scaledValue, significantDigits);
+ }
+ }
+ /**
+ * Returns a new URL by scrubbing any ad fields we've inserted into URLs to pass
+ * ad attribution information between pages.
+ * @param urlString The original URL to be scrubbed
+ * @returns A scrubbed URL.
+ */
+ _urlScrubbingAdParameters(urlString) {
+ const url = new URL(urlString);
+ url.removeParam(iAdURLParameterStringToken);
+ url.removeParam(iAdURLLineItemParameterStringToken);
+ url.removeParam(iAdDismissAdActionMetricsParameterStringToken);
+ return url.build();
+ }
+ /**
+ * Returns a new URL by scrubbing everything but the protocol, domain, and port (e.g. host).
+ * @param urlString The original URL to be scrubbed
+ * @returns A scrubbed URL.
+ */
+ _urlScrubbingExtRefUrl(urlString) {
+ const url = new URL(urlString);
+ url.username = "";
+ url.password = "";
+ url.pathname = undefined;
+ url.query = undefined;
+ url.hash = undefined;
+ return url.build();
+ }
+ // endsection
+ // section Fast Impressions Deresolution
+ _derezFastImpressions(eventFields) {
+ const impressionQueue = serverData.asString(eventFields, "impressionQueue");
+ const eventVersion = serverData.asNumber(eventFields, "eventVersion");
+ if (impressionQueue !== "data-metrics-impressions-low-latency") {
+ return; // Only scrub data going to the AP low latency queue.
+ }
+ // Scrub `viewedInfo` of a v4 event.
+ if (eventVersion === 4) {
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ eventFields["impressions"] = impressions.map((impression) => {
+ if (isNothing(impression)) {
+ return impression;
+ }
+ const viewedInfo = serverData.asArrayOrEmpty(impression, "viewedInfo");
+ if (viewedInfo.length === 0) {
+ return impression; // V3. No modification.
+ }
+ /**
+ * <rdar://problem/64497066> Metrics: iAd: JS must clear impression start times, and derezz duration to 2 sig figs
+ * - start time to 0 (strange ask - we already send this as part of `impressionTimes`)
+ * - duration to 2 sig fig.
+ */
+ impression["viewedInfo"] = viewedInfo.map((interval) => {
+ if (isNothing(interval)) {
+ return interval;
+ }
+ const duration = serverData.asNumber(interval, "d");
+ interval["s"] = 0;
+ if (isSome(duration)) {
+ interval["d"] = reduceSignificantDigits(duration, 2);
+ }
+ return interval;
+ });
+ return impression;
+ });
+ }
+ // Scrub `viewedInfoDetailed` of a v5 event.
+ if (eventVersion === 5) {
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ eventFields["impressions"] = impressions.map((impression) => {
+ if (isNothing(impression)) {
+ return impression;
+ }
+ // Impression items in a low latency impressions event shouldn't have a `cardType` field.
+ delete impression["cardType"];
+ if (serverData.isNullOrEmpty(serverData.asString(impression, "iAdMetadata")) ||
+ serverData.isNullOrEmpty(serverData.asString(impression, "iAdImpressionId"))) {
+ // If the iAdMetadata or iAdImpressionId for this impression on the fast queue is null,
+ // it's an organic result that has added instrumentation as it's in a defined ad slot.
+ // For these items, we have to scrub the adamId for privacy reasons.
+ delete impression["id"];
+ }
+ // Get the dictionary of values, formatted like so:
+ // {0: [{s: 11636658562756, d: 23281}], 50: [{s: 1636658559350, d: 378}]}
+ const viewedInfoDetailed = serverData.asDictionary(impression, "viewedInfoDetailed");
+ if (isNothing(viewedInfoDetailed) || serverData.isNullOrEmpty(viewedInfoDetailed)) {
+ return impression; // Nothing to modify.
+ }
+ /**
+ * rdar://89785026 (Chainlink: v5 Impressions ViewedInfoDetailed - Fixes for startTime and duration)
+ * - match scrubbing "s" value and de-rezing "d" value as per above v4 implementation.
+ */
+ Object.entries(viewedInfoDetailed).forEach(([key, value]) => {
+ // For each key/value pair, grab the array of traditional "viewedInfo".
+ const viewedInfo = serverData.asArrayOrEmpty(value);
+ // Iterate over the array of values, scrubbing the required children.
+ viewedInfoDetailed[key] = viewedInfo.map((interval) => {
+ if (isNothing(interval)) {
+ return interval;
+ }
+ const duration = serverData.asNumber(interval, "d");
+ interval["s"] = 0;
+ if (isSome(duration)) {
+ interval["d"] = reduceSignificantDigits(duration, 2);
+ }
+ return interval;
+ });
+ });
+ // Re-set the modified dictionary on the `impression` object.
+ impression["viewedInfoDetailed"] = viewedInfoDetailed;
+ return impression;
+ });
+ }
+ }
+ _stripContentRatingImpressionFields(eventFields) {
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ eventFields["impressions"] = impressions.map((impression) => {
+ if (isSome(impression)) {
+ // "contentRating" and "bundleId" are used by native to
+ // determine the value of "contentRestrictionReasons" (whether a
+ // lockup's offer is disabled due to content restrictions).
+ // However we don't want these fields in the final impression.
+ delete impression["contentRating"];
+ delete impression["bundleId"];
+ }
+ return impression;
+ });
+ }
+ // endsection
+ // section Search Results Page Impressions
+ /**
+ * Decorate `impressions` field for click and impressions events on SRP.
+ */
+ _decorateSearchResultImpressions(eventFields) {
+ const pageType = serverData.asString(eventFields, "pageType");
+ const pageId = serverData.asString(eventFields, "pageId");
+ /**
+ * Only run on SRP.
+ */
+ const isSearchResultsPage = pageType === "Search" && pageId !== "hints";
+ if (isSearchResultsPage) {
+ searchResultImpressions.decorateImpressionParentId(eventFields);
+ }
+ }
+ // endsection
+ // section Search Focus Page Impressions
+ /**
+ * Decorate `impressions` field for click and impressions events on SFP.
+ */
+ _decorateSearchFocusImpressions(eventFields) {
+ const pageType = serverData.asString(eventFields, "pageType");
+ const pageId = serverData.asString(eventFields, "pageId");
+ /**
+ * Only run on SFP.
+ */
+ if (pageType === "SearchFocus" && pageId === "Focus") {
+ searchFocusImpressions.decorateImpressionParentId(eventFields);
+ }
+ }
+ // endsection
+ // section Rules
+ /**
+ * Apply the rules which are universal to all metrics events
+ * to a given metrics fields linter.
+ *
+ * @param eventFields The fields which will be used to construct a built event.
+ * @param topic The topic the built event will be submitted to.
+ */
+ _decorateAll(objectGraph, eventFields, topic) {
+ var _a, _b, _c;
+ const getBagValue = this._options.bagProvider;
+ // - Metrics base fields
+ const metricsBase = getBagValue("metricsBase", topic);
+ if (!serverData.isNull(metricsBase) && typeof metricsBase === "object") {
+ Object.assign(eventFields, metricsBase);
+ }
+ // - Universal basic fields
+ eventFields["clientBuildType"] = this._options.buildType;
+ eventFields["resourceRevNum"] = this._options.jsVersion;
+ eventFields["xpSendMethod"] = "jet-js";
+ this._options.buyDecorator.useApp(serverData.asString(eventFields, "app"));
+ // - Universal scrubbing
+ delete eventFields[constants.contextualAdamIdKey];
+ // - Cookie-based fields
+ const cookies = cookiesOf(serverData.asString(eventFields, "cookie"));
+ for (const cookie of cookies) {
+ if (cookie.key === "xp_ci") {
+ // Always update buy decorator.
+ this._options.buyDecorator.useClientId(cookie.value);
+ break;
+ }
+ }
+ delete eventFields["cookie"];
+ const clientIdFields = (_b = (_a = objectGraph.metricsIdentifiersCache) === null || _a === void 0 ? void 0 : _a.getMetricsFieldsForTypes([MetricsIdentifierType.client])) !== null && _b !== void 0 ? _b : {};
+ Object.assign(eventFields, clientIdFields);
+ delete eventFields["clientGeneratedId"];
+ // - page
+ const pageType = serverData.asString(eventFields, "pageType");
+ const pageId = serverData.asString(eventFields, "pageId");
+ if (!serverData.isNull(pageType) && !serverData.isNull(pageId)) {
+ const separator = serverData.asString(getBagValue("compoundSeparator", topic)) || "_";
+ eventFields["page"] = `${pageType}${separator}${pageId}`;
+ }
+ // - Field value resolution reduction
+ const rules = serverData.asArrayOrEmpty(getBagValue("deResFields", topic));
+ this._reduceFieldAccuracy(eventFields, rules);
+ // Scrub sensitive urls in event from data that should not leave the device.
+ // At times we insert ad metadata into URLs in order to pass ad attribution information between pages.
+ // Additionally, scrub extRefUrl of everything but the origin.
+ const urlFieldsToScrub = ["pageUrl", "actionUrl", "extRefUrl", "refUrl", "url", "parentPageUrl"];
+ for (const urlField of urlFieldsToScrub) {
+ const urlString = serverData.asString(eventFields, urlField);
+ if (isSome(urlString) && urlString.length > 0) {
+ eventFields[urlField] =
+ urlField === "extRefUrl"
+ ? this._urlScrubbingExtRefUrl(urlString)
+ : this._urlScrubbingAdParameters(urlString);
+ }
+ }
+ // Where an `overridePageContext` has been provided, use it as the `pageContext` value.
+ const overridePageContext = serverData.asString(eventFields, "overridePageContext");
+ if (isSome(overridePageContext)) {
+ delete eventFields["overridePageContext"];
+ eventFields["pageContext"] = overridePageContext;
+ }
+ if (objectGraph.bag.isMetricsUserIdFallbackEnabled) {
+ const existingUserId = serverData.asString(eventFields, "userId");
+ let metricsUserDSID = null;
+ if (isNothing(existingUserId) ||
+ existingUserId.length === 0 ||
+ existingUserId.length === EventLinter.clientGeneratedUserIdLength) {
+ metricsUserDSID = (_c = objectGraph.user.dsid) !== null && _c !== void 0 ? _c : null;
+ }
+ if (isSome(metricsUserDSID) && metricsUserDSID.length > 0) {
+ eventFields["dsId"] = metricsUserDSID;
+ }
+ }
+ }
+ _decorateClick(eventFields) {
+ util.adjustGhostHintFieldsForClick(eventFields);
+ // clicks have snapshot impressions field.
+ this._decorateSearchResultImpressions(eventFields);
+ this._decorateSearchFocusImpressions(eventFields);
+ MetricsReferralContext.shared.addReferralDataToEventIfNecessary(eventFields);
+ this._filterBuyParams(eventFields);
+ /// rdar://118948967 (Disable snapshot impressions on iOS 17)
+ const pageType = serverData.asString(eventFields, "pageType");
+ if (isNothing(pageType) || !pageType.toLowerCase().includes("search")) {
+ delete eventFields["impressions"];
+ }
+ stripUniqueImpressionIdsIfNecessary(eventFields);
+ }
+ /**
+ * Apply the rules specific to the `impression` event.
+ *
+ * @param objectGraph Current object graph
+ * @param eventFields The fields which will be used to construct a built event.
+ * @returns Whether or not the impressions event is valid and non-empty
+ */
+ _decorateImpressions(objectGraph, eventFields) {
+ if (serverData.isNullOrEmpty(eventFields["impressions"])) {
+ return false;
+ }
+ this._derezFastImpressions(eventFields);
+ this._decorateSearchResultImpressions(eventFields);
+ this._stripContentRatingImpressionFields(eventFields);
+ const refUrl = serverData.asString(eventFields, "refUrl");
+ if (isSome(refUrl) && refUrl.length > 0) {
+ eventFields["searchTerm"] = util.searchTermFromRefURL(refUrl);
+ delete eventFields["refUrl"];
+ }
+ if (objectGraph.client.isVision) {
+ const pageUrl = serverData.asString(eventFields, "pageUrl");
+ if (isSome(pageUrl) && isNothing(eventFields["searchTerm"])) {
+ const searchTerm = util.searchTermFromProductURL(pageUrl);
+ if (isSome(searchTerm)) {
+ eventFields["searchTerm"] = searchTerm;
+ }
+ }
+ }
+ stripUniqueImpressionIdsIfNecessary(eventFields);
+ // We need the impressionQueue for _derezFastImpressions, so we know which events to derez, but then we need to
+ // remove it from the event
+ // rdar://144333694 ([Ad Platforms][CrystalE][iOS]Seeing 'Impression Queue' as a top level
+ // field for impressions which was removed earlier)
+ delete eventFields["impressionQueue"];
+ return true;
+ }
+ /**
+ * Apply the rules specific to the `media` event.
+ *
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _decorateMedia(eventFields) {
+ const position = serverData.asNumber(eventFields, "position");
+ if (!serverData.isNull(position)) {
+ eventFields["position"] = Math.round(position);
+ }
+ }
+ /**
+ * Apply the rules specific to the `buyparams` .
+ *
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _filterBuyParams(eventFields) {
+ const buyParamsString = serverData.asString(eventFields, "actionDetails.buyParams");
+ if (isSome(buyParamsString) && buyParamsString.length > 0) {
+ const buyParams = new BuyParameters(buyParamsString);
+ const disallowedFields = ["ownerDsid"];
+ disallowedFields.forEach((key) => {
+ buyParams.set(key, null, null);
+ });
+ if (isSome(eventFields["actionDetails"])) {
+ eventFields["actionDetails"]["buyParams"] = buyParams.toString();
+ }
+ }
+ }
+ /**
+ * Apply the rules specific to the `page` event.
+ *
+ * @param objectGraph Current object graph
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _decoratePage(objectGraph, eventFields) {
+ const page = serverData.asString(eventFields, "page");
+ if (!serverData.isNull(page)) {
+ eventFields["pageHistory"] = this._options.buyDecorator.getPageHistoryFor(page);
+ }
+ MetricsReferralContext.shared.setReferralDataForProductPageExtensionIfNecessary(eventFields);
+ MetricsReferralContext.shared.beginReferralContextForPageIfNecessary(eventFields);
+ MetricsReferralContext.shared.addReferralDataToEventIfNecessary(eventFields);
+ // Make sure to add the referral data before checking for valid refUrl
+ const refUrl = serverData.asString(eventFields, "refUrl");
+ if (!serverData.isNull(refUrl)) {
+ const refApp = util.extractSiriRefAppFromRefURL(refUrl);
+ const searchTerm = util.searchTermFromRefURL(refUrl);
+ if (refApp !== null && refApp.length > 0) {
+ eventFields["refApp"] = refApp;
+ }
+ if (searchTerm !== null && searchTerm.length > 0) {
+ eventFields["searchTerm"] = searchTerm;
+ }
+ }
+ if (objectGraph.client.isVision) {
+ const pageUrl = serverData.asString(eventFields, "pageUrl");
+ if (isSome(pageUrl) && isNothing(eventFields["searchTerm"])) {
+ const searchTerm = util.searchTermFromProductURL(pageUrl);
+ if (isSome(searchTerm)) {
+ eventFields["searchTerm"] = searchTerm;
+ }
+ }
+ }
+ }
+ /**
+ * Apply the rules specific to the `pageChange` event. The pageChange should have the same treatment
+ * as page event.
+ *
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _decoratePageChange(objectGraph, eventFields) {
+ this._decoratePage(objectGraph, eventFields);
+ }
+ /**
+ * Apply the rules specific to the `search` event.
+ *
+ * @param objectGraph Current object graph
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _decorateSearch(eventFields) {
+ eventFields["eventVersion"] = 3;
+ util.adjustGhostHintFieldsForSearch(eventFields);
+ }
+ /**
+ * Apply the rules specific to the `pageExit` event.
+ *
+ * @param eventFields The fields which will be used to construct a built event.
+ */
+ _decoratePageExit(eventFields) {
+ MetricsReferralContext.shared.endReferralContextIfNecessaryForPageEvent(eventFields);
+ }
+ // endsection
+ // section Filter
+ _filterExtraneous(eventFields) {
+ util.removeExtraGhostHintFields(eventFields);
+ MetricsReferralContext.shared.removeReferralContextInfoFromMetricsEvent(eventFields);
+ }
+ // endsection
+ // section Building Events
+ /**
+ * Create a metrics event by applying the business rules of App Store to a given fields blob.
+ *
+ * @param eventFields The fields to use to construct an event.
+ * @returns A built event ready for posting.
+ */
+ makeEvent(objectGraph, eventFields) {
+ var _a, _b;
+ if (preprocessor.GAMES_TARGET) {
+ // Until we get further with Privacy/Legal, the decision has been made to disable instrumentation
+ // for Game Overlay. the pre-consent fields provider is only active for Game Overlay. Other options
+ // for more fine-tuned control (_gameCenterPreConsent and _crossUsePreConsent) have been added to
+ // provide JS flexibility.
+ const isPreConsentFieldsProviderEnabled = eventFields["_isPreConsentFieldsProviderEnabled"];
+ const gameCenterPreConsent = eventFields["_gameCenterPreConsent"];
+ const crossUsePreConsent = eventFields["_crossUsePreConsent"];
+ if (isPreConsentFieldsProviderEnabled === true ||
+ gameCenterPreConsent === true ||
+ crossUsePreConsent === true) {
+ return new LintedMetricsEvent({});
+ }
+ delete eventFields["_isPreConsentFieldsProviderEnabled"];
+ delete eventFields["_gameCenterPreConsent"];
+ delete eventFields["_crossUsePreConsent"];
+ }
+ const eventType = serverData.asString(eventFields, "eventType");
+ if (this._options.isLoggingEnabled) {
+ objectGraph.console.log(`Building event for topic: ${eventType}`);
+ }
+ // rdar://135738684 (TLF: Off Store Lockups Removal)
+ // Currently all events from App Store Components are suppressed.
+ // Delete all fields to leave an empty event.
+ const app = eventFields["app"];
+ if (app === "com.apple.appstorecomponentsd") {
+ return new LintedMetricsEvent({});
+ }
+ const topic = serverData.asString(eventFields, "topic") || this._options.defaultTopic;
+ this._decorateAll(objectGraph, eventFields, topic);
+ // rdar://148554411 (Metrics: Disable PII collection from U13 accounts)
+ let isUnderThirteenAccount;
+ if (preprocessor.GAMES_TARGET && objectGraph.props.enabled("157263806-add-playerBridge-askGlobal")) {
+ isUnderThirteenAccount = (_b = (_a = objectGraph.player) === null || _a === void 0 ? void 0 : _a.isUnderThirteen) !== null && _b !== void 0 ? _b : false;
+ }
+ else {
+ isUnderThirteenAccount = objectGraph.user.isUnderThirteen;
+ }
+ if (isUnderThirteenAccount) {
+ delete eventFields["dsId"];
+ delete eventFields["userId"];
+ delete eventFields["canonicalAccountIdentifierOverride"];
+ }
+ const extRefUrl = eventFields["extRefUrl"];
+ if (extRefUrl && extRefUrl === "") {
+ delete eventFields["extRefUrl"];
+ }
+ switch (eventType) {
+ case "click":
+ this._decorateClick(eventFields);
+ break;
+ case "exit":
+ break;
+ case "impressions":
+ const isValidEvent = this._decorateImpressions(objectGraph, eventFields);
+ if (!isValidEvent) {
+ // We want to filter out empty impressions events,
+ // and passing an empty event will contractually drop it from the metrics recorder
+ return new LintedMetricsEvent({});
+ }
+ break;
+ case "media":
+ this._decorateMedia(eventFields);
+ break;
+ case "page":
+ this._decoratePage(objectGraph, eventFields);
+ break;
+ case "pageChange":
+ this._decoratePageChange(objectGraph, eventFields);
+ break;
+ case "pageExit":
+ this._decoratePageExit(eventFields);
+ break;
+ case "search":
+ this._decorateSearch(eventFields);
+ break;
+ default:
+ break;
+ }
+ this._filterExtraneous(eventFields);
+ if (objectGraph.bag.metricsIdMigrationEnabled) {
+ util.removeDSIDFields(eventFields);
+ }
+ return new LintedMetricsEvent(eventFields);
+ }
+}
+/**
+ * The length of a client generated user ID.
+ */
+EventLinter.clientGeneratedUserIdLength = 24;
+/**
+ * Key whose value indicates if event fields contain iAd data.
+ */
+EventLinter.hasIAdData = "hasiAdData";
+//# sourceMappingURL=event-linter.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/buy.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/buy.js
new file mode 100644
index 0000000..b39f9cf
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/buy.js
@@ -0,0 +1,331 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { BuyParameters } from "../../../foundation/metrics/buy-parameters";
+import { URL } from "../../../foundation/network/urls";
+import * as productVariants from "../../product-page/product-page-variants";
+import { MetricsReferralContext } from "../metrics-referral-context";
+import * as metricsPosting from "../posting";
+import * as misc from "./misc";
+import { IAdSearchInformation } from "./models";
+import * as metricsUtil from "./util";
+//* *************************
+//* Buy Metrics
+//* *************************
+/**
+ * Adds bag driven metrics fields.
+ * @param objectGraph
+ * @returns
+ */
+function addBagMetricsToBuyParams(objectGraph, baseBuyParams) {
+ const buyParams = new BuyParameters(baseBuyParams);
+ const metricsConfiguration = objectGraph.bag.metricsConfiguration;
+ const language = serverData.asString(metricsConfiguration, "metricsBase.language");
+ buyParams.set("languageId", language);
+ return buyParams.toString();
+}
+export function addPageMetricsToBuyParams(objectGraph, baseBuyParams, adamId, pageInformation, targetType, kind, metricsPlatformDisplayStyle, productVariantData, inAppEventId, excludeCrossfireAttribution, extRefApp2, extRefUrl2) {
+ const buyParams = new BuyParameters(baseBuyParams);
+ addPageMetricsToBuyParamsObject(objectGraph, buyParams, adamId, pageInformation, targetType, kind, metricsPlatformDisplayStyle, productVariantData, inAppEventId, excludeCrossfireAttribution, extRefApp2, extRefUrl2);
+ addExtraInfoToBuyParamsObject(objectGraph, buyParams, pageInformation);
+ return buyParams.toString();
+}
+export function addPageMetricsToBuyParamsObject(objectGraph, buyParams, adamId, pageInformation, targetType, kind, metricsPlatformDisplayStyle, productVariantData, inAppEventId, excludeCrossfireAttribution, extRefApp2, extRefUrl2) {
+ var _a, _b, _c;
+ const fields = misc.fieldsFromPageInformation(pageInformation);
+ if (targetType) {
+ buyParams.set("impressionType", targetType); // impressionType == targetType == locationType.
+ }
+ if (kind) {
+ buyParams.set("kind", kind);
+ }
+ const pageId = serverData.asString(serverData.asJSONData(fields), "pageId");
+ buyParams.set("pageId", pageId);
+ const pageType = serverData.asString(serverData.asJSONData(fields), "pageType");
+ buyParams.set("pageType", pageType);
+ // Ad container id
+ const iAdContainerId = serverData.asString(serverData.asJSONData(fields), "iAdContainerId");
+ if (isSome(iAdContainerId) && iAdContainerId.length > 0) {
+ buyParams.set(adBuyParamKeys.containerId, iAdContainerId, null);
+ }
+ // we add search terms to the page if the page is for the item we are buying, or if
+ // its for a streamlined contingent offer
+ const pageIds = (_a = pageId === null || pageId === void 0 ? void 0 : pageId.split("_")) !== null && _a !== void 0 ? _a : [];
+ const pageMatchesProduct = pageIds.includes(adamId);
+ const isProductPage = pageType === "Software";
+ const isStreamlinedContingentOffer = ((_b = buyParams.get("contingentItemId", null)) === null || _b === void 0 ? void 0 : _b.length) > 0;
+ if (!isProductPage || pageMatchesProduct || isStreamlinedContingentOffer) {
+ // If there is a native search term, it will overwrite this
+ // initial value below in `addNativeMetricsToBuyParams`.
+ let searchTerm = serverData.asString(serverData.asJSONData(pageInformation), "searchTermContext.resultsTerm");
+ // A search term may be attached to the product URL as a means of associating buys with searches.
+ if (serverData.isNullOrEmpty(searchTerm)) {
+ searchTerm = metricsUtil.searchTermFromProductURL(pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.pageUrl);
+ }
+ if (!serverData.isNull(searchTerm)) {
+ buyParams.set("searchTerm", searchTerm);
+ }
+ }
+ if (isProductPage && isSome(pageInformation) && isSome(pageInformation.pageUrl)) {
+ const pageUrl = new URL(pageInformation.pageUrl);
+ if (((_c = pageUrl.query) === null || _c === void 0 ? void 0 : _c["context"]) === "browserChoice") {
+ buyParams.set("prevPage", "BrowserChoice");
+ buyParams.set("browserChoiceScreenBuy", "1", null);
+ }
+ }
+ productVariants.addProductPageVariantMetricsToBuyParams(buyParams, adamId, pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.productVariantData, productVariantData !== null && productVariantData !== void 0 ? productVariantData : undefined);
+ if (!serverData.isNull(metricsPlatformDisplayStyle) && metricsPlatformDisplayStyle.length > 0) {
+ buyParams.set("platformDisplayStyle", metricsPlatformDisplayStyle);
+ }
+ // App Event ID
+ buyParams.set("inAppEventId", inAppEventId);
+ // Referrer
+ if (!excludeCrossfireAttribution) {
+ if (serverData.isDefinedNonNull(MetricsReferralContext.shared.activeReferralData)) {
+ buyParams.set("extRefApp2", MetricsReferralContext.shared.activeReferralData.extRefApp2, null);
+ buyParams.set("extRefUrl2", MetricsReferralContext.shared.activeReferralData.extRefUrl2, null);
+ if (isSome(MetricsReferralContext.shared.activeReferralData.kind)) {
+ const extRefAppKindName = MetricsReferralContext.shared.activeReferralData.kind.name;
+ if (extRefAppKindName === "clip" || extRefAppKindName === "appClip") {
+ buyParams.set("hostApp", "com.apple.AppStore.clipOverlay");
+ }
+ }
+ }
+ else {
+ buyParams.set("extRefApp2", extRefApp2, null);
+ buyParams.set("extRefUrl2", extRefUrl2, null);
+ }
+ }
+}
+function addExtraInfoToBuyParamsObject(objectGraph, buyParams, pageInformation) {
+ var _a, _b;
+ if (isNothing(pageInformation)) {
+ return;
+ }
+ const extraInfo = [];
+ const hasiAdData = isSome((_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.clickFields["hasiAdData"]);
+ if (hasiAdData) {
+ const iAdExtraInfoKey = isIAdFromCurrentPage(objectGraph, pageInformation) ? "iAdSponsored" : "iAdOriginated";
+ extraInfo.push({ key: iAdExtraInfoKey, value: "true" });
+ }
+ if (serverData.isDefinedNonNullNonEmpty(extraInfo)) {
+ const extraInfoParamValue = extraInfo
+ .map((extraInfoValue) => `${extraInfoValue.key}=${extraInfoValue.value}`)
+ .join(";");
+ const encodedValue = (_b = objectGraph.cryptography) === null || _b === void 0 ? void 0 : _b.base64Encode(extraInfoParamValue);
+ if (isSome(encodedValue)) {
+ buyParams.set("extraInfo", encodedValue);
+ }
+ }
+}
+/**
+ * Determines if an iAd is from the current page by comparing the iAd's placement type with the page type.
+ *
+ * This function validates whether an advertisement is correctly placed on the appropriate page type.
+ * Different ad placement types are designed for specific page contexts, and this function ensures
+ * that the placement matches the current page context.
+ *
+ * @param objectGraph - The AppStore object graph providing access to app services and utilities
+ * @param pageInformation - Information about the current page, including its type and iAd information
+ * @returns True if the iAd's placement type matches the current page type, false otherwise
+ * or if pageInformation or iAdInfo is missing
+ */
+function isIAdFromCurrentPage(objectGraph, pageInformation) {
+ const iAdInfo = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo;
+ if (isNothing(pageInformation) || isNothing(iAdInfo)) {
+ return false;
+ }
+ const pageType = pageInformation.baseFields["pageType"];
+ const iAdPlacementType = IAdSearchInformation.placementTypeFromPlacementId(objectGraph, iAdInfo.placementId);
+ switch (iAdPlacementType) {
+ case "searchLanding":
+ return pageType === "SearchLanding";
+ case "today":
+ return pageType === "Today";
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ return pageType === "Software" || pageType === "SoftwareBundle";
+ default:
+ return pageType === "Search";
+ }
+}
+/**
+ * Returns a copy of the specified buy parameters enriched with native metrics fields.
+ *
+ * @param baseBuyParams The buy parameters of an app which have previously been
+ * decorated with page metrics. Passing buy parameters that were not decorated with page
+ * metrics can result in incorrect search terms being reported.
+ * @param adamId The identifier of the app whose buy parameters are being decorated.
+ * @param excludeCrossfireAttribution Specifies whether crossfire attribution should be
+ * excluded from the decorated buy params.
+ * @param nativeMetrics Metrics fields provided by native code.
+ * @param osMetrics Metrics fields describing the device's OS provided by native code.
+ * @returns A copy of `baseBuyParams` enriched with native metrics fields.
+ */
+export function addNativeValuesToBuyParams(objectGraph, baseBuyParams, adamId, excludeCrossfireAttribution, isAppInstalled, nativeMetrics, osMetrics) {
+ const baseBuyParamsObject = new BuyParameters(baseBuyParams);
+ addNativeValuesToBuyParamsObject(objectGraph, baseBuyParamsObject, adamId, excludeCrossfireAttribution, isAppInstalled, nativeMetrics, osMetrics);
+ return baseBuyParamsObject.toString();
+}
+/**
+ * Returns a copy of the specified buy parameters enriched with native metrics fields.
+ *
+ * @param baseBuyParams The buy parameters of an app which have previously been
+ * decorated with page metrics. Passing buy parameters that were not decorated with page
+ * metrics can result in incorrect search terms being reported.
+ * @param adamId The identifier of the app whose buy parameters are being decorated.
+ * @param excludeCrossfireAttribution Specifies whether crossfire attribution should be
+ * excluded from the decorated buy params.
+ * @param nativeMetrics Metrics fields provided by native code.
+ * @param osMetrics Metrics fields describing the device's OS provided by native code.
+ * @returns A copy of `baseBuyParams` enriched with native metrics fields.
+ */
+export function addNativeValuesToBuyParamsObject(objectGraph, buyParams, adamId, excludeCrossfireAttribution, isAppInstalled, nativeMetrics, osMetrics) {
+ var _a, _b;
+ const pageContext = serverData.asString(nativeMetrics, "pageContext");
+ buyParams.set("pageContext", pageContext);
+ const paymentTopic = objectGraph.props.enabled("paymentTopicFromBag")
+ ? objectGraph.bag.metricsPaymentTopic
+ : undefined;
+ buyParams.set("topic", paymentTopic !== null && paymentTopic !== void 0 ? paymentTopic : objectGraph.bag.metricsTopic);
+ metricsPosting.buyDecorator.useNativeValues(nativeMetrics);
+ const decoratorParams = metricsPosting.buyDecorator.params;
+ for (const key of Object.keys(decoratorParams)) {
+ if (key === "prevPage" && isSome(buyParams.get("prevPage"))) {
+ // We may have added a `prevPage` param earlier, specifically don't override this value if we have.
+ continue;
+ }
+ const value = serverData.asString(decoratorParams, key);
+ buyParams.set(key, value);
+ }
+ if (!serverData.isNull(osMetrics)) {
+ for (const key of Object.keys(osMetrics)) {
+ const value = serverData.asString(osMetrics, key);
+ buyParams.set(key, value);
+ buyParams.set(key, value, null);
+ }
+ }
+ if (!nativeMetrics) {
+ // ^^ Is this actually needed?
+ buyParams.set("searchTerm", null);
+ buyParams.set("platformDisplayStyle", null);
+ return;
+ }
+ const hostApp = serverData.asString(nativeMetrics, "hostApp");
+ if (isSome(hostApp) && hostApp.length > 0) {
+ buyParams.set("hostApp", hostApp);
+ }
+ const isContingentOffer = ((_a = buyParams.get("contingentItemId", null)) === null || _a === void 0 ? void 0 : _a.length) > 0;
+ const app = serverData.asString(nativeMetrics, "app");
+ if (isContingentOffer) {
+ // Contingent Offer Streamline buy app install flag
+ buyParams.set("app", objectGraph.host.clientIdentifier);
+ }
+ else if (isSome(app) && app.length > 0) {
+ buyParams.set("app", app);
+ }
+ if (!excludeCrossfireAttribution && !MetricsReferralContext.shared.shouldUseJSReferralData) {
+ const extRefUrl = serverData.asString(nativeMetrics, "extRefUrl2");
+ const extractedSiriRefApp = metricsUtil.extractSiriRefAppFromRefURL(extRefUrl);
+ if (extRefUrl && extractedSiriRefApp) {
+ nativeMetrics["refApp"] = extractedSiriRefApp;
+ }
+ // ^^ Is this actually needed?
+ const usageContext = serverData.asString(nativeMetrics, "usageContext");
+ if (isSome(usageContext)) {
+ switch (usageContext) {
+ case "overlay":
+ buyParams.set("hostApp", "com.apple.AppStore.overlay");
+ break;
+ case "overlayClip":
+ buyParams.set("hostApp", "com.apple.AppStore.clipOverlay");
+ break;
+ default:
+ break;
+ }
+ buyParams.set("extRefApp2", hostApp, null);
+ }
+ else {
+ const extRefApp2 = serverData.asString(nativeMetrics, "extRefApp2");
+ buyParams.set("extRefApp2", extRefApp2, null);
+ const extRefUrl2 = serverData.asString(nativeMetrics, "extRefUrl2");
+ buyParams.set("extRefUrl2", extRefUrl2, null);
+ const extRefAppType = serverData.asString(nativeMetrics, "extRefAppType");
+ if (extRefAppType === "clip") {
+ buyParams.set("hostApp", "com.apple.AppStore.clipOverlay");
+ }
+ }
+ }
+ // we add search terms to the page if the page is for the item we are buying
+ const pageId = buyParams.get("pageId");
+ const pageType = buyParams.get("pageType");
+ const pageIds = (_b = pageId === null || pageId === void 0 ? void 0 : pageId.split("_")) !== null && _b !== void 0 ? _b : [];
+ const pageMatchesProduct = pageIds.includes(adamId);
+ const isProductPage = pageType === "Software";
+ if (!isProductPage || pageMatchesProduct) {
+ const searchTerm = metricsUtil.searchTermFromRefURL(serverData.asString(nativeMetrics, "refUrl"));
+ if (serverData.isDefinedNonNull(searchTerm)) {
+ buyParams.set("searchTerm", searchTerm);
+ }
+ }
+ // Remove ownerDsid from buyParams
+ buyParams.set("ownerDsid", null, null);
+}
+/**
+ *
+ * @param adamId The identifier of the app whose buy parameters are being decorated.
+ * @param buyParams The buy parameters of an app from a Media API response.
+ * @param pageInformation The purchase configuration page metrics configuration
+ * from an offer action.
+ * @param excludeCrossfireAttribution Specifies whether crossfire attribution should be
+ * excluded from the decorated buy params.
+ * @param targetType The target type that buy occured on
+ * @param kind The Kind that buy occured on, if any.
+ * @param metricsPlatformDisplayStyle The platform display style from an offer action.
+ * @param nativeMetrics Metrics fields provided by native code.
+ * @param osMetrics Metrics fields describing the device's OS provided by native code.
+ * @returns A copy of `buyParams` decorated with all metrics.
+ */
+export function addMetricsToBuyParams(objectGraph, adamId, buyParams, pageInformation, excludeCrossfireAttribution, isAppInstalled, targetType, kind, metricsPlatformDisplayStyle, nativeMetrics, osMetrics, productVariantData, inAppEventId, extRefApp2, extRefUrl2) {
+ // Future: Build BuyParameters once, instead of creating it per modification.
+ const bagMetricsBuyParams = addBagMetricsToBuyParams(objectGraph, buyParams);
+ const pageMetricsBuyParams = addPageMetricsToBuyParams(objectGraph, bagMetricsBuyParams, adamId, pageInformation, targetType, kind, metricsPlatformDisplayStyle, productVariantData, inAppEventId, excludeCrossfireAttribution, extRefApp2, extRefUrl2);
+ const nativeMetricsBuyParams = addNativeValuesToBuyParams(objectGraph, pageMetricsBuyParams, adamId, excludeCrossfireAttribution, isAppInstalled, nativeMetrics, osMetrics);
+ return nativeMetricsBuyParams;
+}
+/**
+ * Keys used for ad-related buy params.
+ */
+export const adBuyParamKeys = {
+ containerId: "mtContainerId",
+ placementId: "mtIadPlacementId",
+ templateType: "mtIadTemplateType",
+};
+/**
+ * Copy ad-related buy params to the override buy params.
+ * Override buy params are used for updated and redownloads, and in those cases we lose iAd download attribution.
+ * If the original buy params had ad fields, we should copy them over here.
+ * @param originalBuyParamsString The original buy params attached to the offerAction.
+ * @param overrideBuyParamsString The override buy params from the purchase.
+ * @returns An updated buy params string with any ad-related fields copied over.
+ */
+export function copyAdBuyParamsToOverrideBuyParams(originalBuyParamsString, overrideBuyParamsString) {
+ const originalBuyParams = new BuyParameters(originalBuyParamsString);
+ const overrideBuyParams = new BuyParameters(overrideBuyParamsString);
+ const originalPlacementId = originalBuyParams.get(adBuyParamKeys.placementId, null);
+ if ((originalPlacementId === null || originalPlacementId === void 0 ? void 0 : originalPlacementId.length) > 0 &&
+ serverData.isNullOrEmpty(overrideBuyParams.get(adBuyParamKeys.placementId, null))) {
+ overrideBuyParams.set(adBuyParamKeys.placementId, originalPlacementId, null);
+ }
+ const originalContainerId = originalBuyParams.get(adBuyParamKeys.containerId, null);
+ if ((originalContainerId === null || originalContainerId === void 0 ? void 0 : originalContainerId.length) > 0 &&
+ serverData.isNullOrEmpty(overrideBuyParams.get(adBuyParamKeys.containerId, null))) {
+ overrideBuyParams.set(adBuyParamKeys.containerId, originalContainerId, null);
+ }
+ const originalTemplateType = originalBuyParams.get(adBuyParamKeys.templateType, null);
+ if ((originalTemplateType === null || originalTemplateType === void 0 ? void 0 : originalTemplateType.length) > 0 &&
+ serverData.isNullOrEmpty(overrideBuyParams.get(adBuyParamKeys.templateType, null))) {
+ overrideBuyParams.set(adBuyParamKeys.templateType, originalTemplateType, null);
+ }
+ return overrideBuyParams.toString();
+}
+//# sourceMappingURL=buy.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/clicks.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/clicks.js
new file mode 100644
index 0000000..cc7b6f2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/clicks.js
@@ -0,0 +1,458 @@
+import * as metricsBuilder from "../builder";
+import * as metricsConstants from "./constants";
+import * as metricsLocation from "./location";
+import * as metricsModels from "./models";
+import * as metricsUtil from "./util";
+import * as metricsMisc from "./misc";
+import * as validation from "@jet/environment/json/validation";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as dateUtil from "../../../foundation/util/date-util";
+//* *************************
+//* Click Metrics
+//* *************************
+export function clickOptionsForLockup(objectGraph, data, baseOptions, clickOptions) {
+ return validation.context("clickOptionsForLockup", () => {
+ const contextualAdamId = data.id.slice();
+ let id = data.id;
+ if (baseOptions.anonymizationOptions !== undefined &&
+ baseOptions.anonymizationOptions.anonymizationString.length > 0) {
+ id = baseOptions.anonymizationOptions.anonymizationString;
+ }
+ const metricsOptions = {
+ ...baseOptions,
+ ...clickOptions,
+ id: id,
+ contextualAdamId: contextualAdamId,
+ softwareType: metricsUtil.softwareTypeForData(objectGraph, data),
+ };
+ if (serverData.isNullOrEmpty(metricsOptions.targetType)) {
+ metricsOptions.targetType = objectGraph.client.isVision ? "lockupSmall" : "lockup";
+ }
+ // There's a delicate false split from `baseOptions` and click options interface. Some paths pre-populate an unexpected `kind`, but we don't want to change that suddenly.
+ // Derive `kind` from data if missing.
+ if (serverData.isNull(metricsOptions.kind)) {
+ metricsOptions.kind = metricsUtil.metricsKindFromData(objectGraph, data);
+ }
+ // Include offerType for pre-order impressions
+ const isPreorder = mediaAttributes.attributeAsBoolean(data, "isPreorder");
+ if (isPreorder) {
+ metricsOptions.offerType = "preorder";
+ }
+ return metricsOptions;
+ });
+}
+export function addBuyEventToOfferActionOnPage(objectGraph, action, options, isPreorder, isDefaultBrowser) {
+ var _a, _b, _c, _d, _e;
+ const pageInformation = options.pageInformation;
+ const buttonMetricsOptions = {
+ ...options,
+ targetType: "button",
+ };
+ const metricsLocations = metricsLocation.createContentLocation(objectGraph, buttonMetricsOptions, (_a = action.title) !== null && _a !== void 0 ? _a : "");
+ let targetId = (_c = (_b = options.anonymizationOptions) === null || _b === void 0 ? void 0 : _b.anonymizationString) !== null && _c !== void 0 ? _c : action.adamId;
+ if (isSome(options.targetId) && ((_d = options.targetId) === null || _d === void 0 ? void 0 : _d.length) > 0) {
+ targetId = options.targetId;
+ }
+ addBuyEventToOfferAction(objectGraph, action, targetId, isPreorder, pageInformation, metricsLocations, (_e = options.isAdvert) !== null && _e !== void 0 ? _e : false, options.recoMetricsData, isDefaultBrowser);
+}
+export function addBuyEventToOfferActionInheritingMetrics(objectGraph, newAction, originalAction, isPreorder) {
+ const originalPageInformation = originalAction.purchaseConfiguration.pageInformation;
+ // Find some event to inherit location event field from.
+ let metricsLocations;
+ if (isSome(originalAction.actionMetrics)) {
+ for (const event of originalAction.actionMetrics.data) {
+ metricsLocations = serverData.asArrayOrEmpty(serverData.asJSONValue(event.fields), "location");
+ if (metricsLocations) {
+ break;
+ }
+ }
+ }
+ // Fine for now - CMC doesn't use anonymization.
+ addBuyEventToOfferAction(objectGraph, newAction, newAction.adamId, isPreorder, originalPageInformation, metricsLocations, false);
+}
+function addBuyEventToOfferAction(objectGraph, action, targetId, isPreorder, pageInformation, metricsLocations, isAdvert, recoMetricsData, isDefaultBrowser) {
+ var _a, _b, _c, _d, _e;
+ const eventFields = {};
+ if (pageInformation) {
+ // We can't always check that `pageInformation` is an `instanceof MetricsPageInformation` here, because sometimes
+ // `pageInformation` is reconstituted from JSON which means it doesn't get the underlying type information.
+ // Instead, cast it and check for the existence of individual properties on the cast object.
+ const metricsPageInformation = pageInformation;
+ if (isAdvert &&
+ ((_c = (_b = (_a = metricsPageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.iAdAdamId) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) > 0 &&
+ isSome(metricsPageInformation.iAdInfo) &&
+ metricsPageInformation.iAdInfo.iAdAdamId === action.adamId) {
+ Object.assign(eventFields, metricsPageInformation.iAdInfo.clickFields);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(metricsPageInformation.searchTermContext)) {
+ eventFields["searchTerm"] = metricsPageInformation.searchTermContext.term;
+ }
+ }
+ // Add the reco metrics data if available
+ if (isSome(recoMetricsData)) {
+ Object.assign(eventFields, recoMetricsData);
+ }
+ eventFields["actionDetails"] = { buyParams: action.purchaseConfiguration.buyParams };
+ if (metricsLocations !== undefined) {
+ eventFields["location"] = metricsLocations;
+ }
+ // This hack where we add the adamId to the eventFields can be removed when `ActionDispatcher`
+ // is fully enabled and old ActionRunner code is removed. The custom data below replaces this.
+ eventFields[metricsConstants.contextualAdamIdKey] = action.adamId;
+ action.actionMetrics.custom[metricsConstants.contextualAdamIdKey] = action.adamId;
+ // Add Pre-order fields
+ if (isPreorder) {
+ eventFields["offerType"] = "preorder";
+ if (serverData.isDefinedNonNull(action.expectedReleaseDate)) {
+ eventFields["offerReleaseDate"] = dateUtil.millisecondsToUTCMidnightFromLocalDate(action.expectedReleaseDate);
+ }
+ }
+ const clickEvent = metricsBuilder.createMetricsClickData(objectGraph, targetId, "button", eventFields, undefined, isDefaultBrowser);
+ // This hack where we add the adamId to the eventFields can be removed when `ActionDispatcher`
+ // is fully enabled and old ActionRunner code is removed. The custom data below replaces this.
+ eventFields[metricsConstants.contextualAdamIdKey] = action.adamId; // needed for `appState`
+ action.actionMetrics.custom[metricsConstants.contextualAdamIdKey] = action.adamId;
+ clickEvent.includingFields.push("appState");
+ if (action.purchaseConfiguration.isArcadeApp) {
+ // Include button names in arcade buy action metrics
+ clickEvent.includingFields.push("buttonName");
+ }
+ const shouldIncludeAdRotationFields = (_e = (_d = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) === null || _d === void 0 ? void 0 : _d.shouldIncludeAdRotationFields) !== null && _e !== void 0 ? _e : false;
+ if (isAdvert && shouldIncludeAdRotationFields) {
+ clickEvent.includingFields.push("advertRotation");
+ }
+ const shouldIncludeAdWindowFields = isAdvert && objectGraph.client.isPad;
+ if (shouldIncludeAdWindowFields) {
+ clickEvent.includingFields.push("advertDeviceWindow");
+ }
+ action.actionMetrics.addMetricsData(clickEvent);
+}
+export function addClickEventToArcadeBuyInitiateAction(objectGraph, action, options) {
+ var _a;
+ addClickEventToAction(objectGraph, action, {
+ ...options,
+ actionType: "buyInitiate",
+ subscriptionSKU: (_a = objectGraph.bag.arcadeProductId) !== null && _a !== void 0 ? _a : undefined,
+ actionContext: "Arcade",
+ targetType: "button",
+ });
+}
+export function addClickEventToAction(objectGraph, action, options, addIAdFields = false, targetType) {
+ var _a, _b, _c, _d, _e, _f, _g;
+ let actionType = options.actionType;
+ if (!actionType) {
+ actionType = "navigate";
+ }
+ const eventFields = {
+ actionType: actionType,
+ };
+ // Ad click events will be wrapped in a CompoundAction. We still want to handle attaching `actionUrl` properly
+ // below if a `FlowAction` or `ExternalUrlAction` are inside the `CompoundAction`, so we create an array of actions
+ // to check.
+ let actions;
+ if (action instanceof models.CompoundAction) {
+ actions = action.actions;
+ }
+ else {
+ actions = [action];
+ }
+ actions.forEach((subAction) => {
+ // Set the action URL if appropriate
+ if (subAction instanceof models.FlowAction) {
+ const flowAction = subAction;
+ eventFields["actionUrl"] = flowAction.pageUrl;
+ }
+ else if (subAction instanceof models.ExternalUrlAction) {
+ const flowAction = subAction;
+ eventFields["actionUrl"] = flowAction.url;
+ }
+ });
+ if (options.actionDetails) {
+ eventFields["actionDetails"] = options.actionDetails;
+ }
+ if (options.actionContext) {
+ eventFields["actionContext"] = options.actionContext;
+ }
+ // Add offer type for pre-orders
+ if (serverData.isDefinedNonNull(options.offerType)) {
+ eventFields["offerType"] = options.offerType;
+ }
+ // Add release date for pre-orders
+ if (serverData.isDefinedNonNull(options.offerReleaseDate)) {
+ eventFields["offerReleaseDate"] = dateUtil.millisecondsToUTCMidnightFromLocalDate(options.offerReleaseDate);
+ }
+ const title = (_c = (_b = (_a = options.anonymizationOptions) === null || _a === void 0 ? void 0 : _a.anonymizationString) !== null && _b !== void 0 ? _b : action === null || action === void 0 ? void 0 : action.title) !== null && _c !== void 0 ? _c : "";
+ eventFields["location"] = metricsLocation.createContentLocation(objectGraph, options, title);
+ // Search Term
+ if (options.pageInformation && options.pageInformation.searchTermContext) {
+ eventFields["searchTerm"] = options.pageInformation.searchTermContext.term;
+ }
+ if (serverData.isDefinedNonNull(options.softwareType)) {
+ eventFields["softwareType"] = options.softwareType;
+ }
+ // Advert Metrics
+ let additionalIncludingFields;
+ // Do we need both `options.isAdvert` and `addIAdFields`?
+ if ((options.isAdvert || options.isAdEligible) && addIAdFields && ((_d = options.pageInformation) === null || _d === void 0 ? void 0 : _d.iAdInfo)) {
+ Object.assign(eventFields, options.pageInformation.iAdInfo.clickFields);
+ if (objectGraph.client.isPad) {
+ additionalIncludingFields = ["advertDeviceWindow"];
+ }
+ }
+ if (options.mercuryMetricsData) {
+ Object.assign(eventFields, options.mercuryMetricsData);
+ }
+ if (isSome(options.subjectIds)) {
+ eventFields["subjectIds"] = options.subjectIds;
+ }
+ const event = metricsBuilder.createMetricsClickData(objectGraph, options.id, targetType !== null && targetType !== void 0 ? targetType : metricsUtil.targetTypeForMetricsOptions(objectGraph, options), eventFields, additionalIncludingFields);
+ // Include button names in arcade buy action metrics
+ const isArcadeBuyAction = options.actionContext === "Arcade" && (options.actionType === "buy" || options.actionType === "buyInitiate");
+ if (isArcadeBuyAction) {
+ event.includingFields.push("buttonName");
+ // This hack where we add the adamId to the eventFields can be removed when `ActionDispatcher`
+ // is fully enabled and old ActionRunner code is removed. The custom data below replaces this.
+ event.fields[metricsConstants.contextualAdamIdKey] = options.contextualAdamId;
+ if (isSome(action.adamId)) {
+ action.actionMetrics.custom[metricsConstants.contextualAdamIdKey] = action.adamId;
+ }
+ }
+ // Include ad rotation metrics and click events if enabled for placement.
+ const shouldIncludeAdRotationFields = (_g = (_f = (_e = options.pageInformation) === null || _e === void 0 ? void 0 : _e.iAdInfo) === null || _f === void 0 ? void 0 : _f.shouldIncludeAdRotationFields) !== null && _g !== void 0 ? _g : false;
+ if (options.isAdvert && shouldIncludeAdRotationFields) {
+ event.includingFields.push("advertRotation");
+ }
+ action.actionMetrics.addMetricsData(event);
+}
+/**
+ *
+ * @param objectGraph The App Store Object Graph
+ * @param action The search cancel or dismiss action
+ * @param options The metrics click options
+ * @param targetType The metrics click target type
+ * @param searchTerm The current search term
+ */
+export function addClickEventToSearchCancelOrDismissAction(objectGraph, action, options, targetType, searchTerm) {
+ const eventFields = {
+ searchTerm: searchTerm,
+ actionType: options.actionType,
+ };
+ if (options.actionDetails) {
+ eventFields["actionDetails"] = options.actionDetails;
+ }
+ if (options.actionContext) {
+ eventFields["actionContext"] = options.actionContext;
+ }
+ // Search Term
+ if (searchTerm) {
+ eventFields["searchTerm"] = searchTerm;
+ }
+ if (serverData.isDefinedNonNull(options.softwareType)) {
+ eventFields["softwareType"] = options.softwareType;
+ }
+ if (options.mercuryMetricsData) {
+ Object.assign(eventFields, options.mercuryMetricsData);
+ }
+ const event = metricsBuilder.createMetricsClickData(objectGraph, options.id, targetType !== null && targetType !== void 0 ? targetType : metricsUtil.targetTypeForMetricsOptions(objectGraph, options), eventFields);
+ action.actionMetrics.addMetricsData(event);
+}
+export function addClickEventsToAdLockup(objectGraph, lockup, clickOptions) {
+ var _a, _b, _c, _d, _e;
+ const searchAd = (_b = (_a = lockup.searchAdOpportunity) === null || _a === void 0 ? void 0 : _a.searchAd) !== null && _b !== void 0 ? _b : lockup.searchAd;
+ if (serverData.isNull(searchAd)) {
+ return;
+ }
+ // Theoretically, we would probably be fine to just overwrite the click event action metrics below in `addClickEventToAction`.
+ // But we'll keep the call to clear the metrics just to provide continuity with older code that ran this full path.
+ (_c = lockup.clickAction) === null || _c === void 0 ? void 0 : _c.actionMetrics.clearAll();
+ if (lockup.clickAction) {
+ addClickEventToAction(objectGraph, lockup.clickAction, clickOptions, true);
+ }
+ const pageInformation = clickOptions.pageInformation;
+ const eventFields = {
+ actionType: "ad_transparency",
+ };
+ if (pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) {
+ Object.assign(eventFields, pageInformation.iAdInfo.clickFields);
+ }
+ const figaroEvent = metricsBuilder.createMetricsClickData(objectGraph, lockup.adamId, "button", eventFields);
+ const shouldIncludeAdRotationFields = (_e = (_d = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) === null || _d === void 0 ? void 0 : _d.shouldIncludeAdRotationFields) !== null && _e !== void 0 ? _e : false;
+ if (shouldIncludeAdRotationFields) {
+ figaroEvent.includingFields.push("advertRotation");
+ }
+ searchAd.transparencyAction.actionMetrics.addMetricsData(figaroEvent);
+}
+export function addClickEventToSeeAllAction(objectGraph, action, url, options) {
+ const eventFields = {};
+ if (serverData.isDefinedNonNull(url)) {
+ eventFields["actionUrl"] = url;
+ }
+ if (!options.targetType) {
+ options.targetType = "button";
+ }
+ eventFields["location"] = metricsLocation.createBasicLocation(objectGraph, options, action.title);
+ eventFields["actionType"] = "navigate";
+ eventFields["target"] = "button_See All";
+ const event = metricsBuilder.createMetricsClickData(objectGraph, "See All", "button", eventFields);
+ action.actionMetrics.addMetricsData(event);
+}
+export function addClickEventToClearSearchHistoryAction(objectGraph, clearAction) {
+ // MAINTAINER'S NOTE:
+ // We intentionally use an unlocalized targetId, instead of localized `clearAction.title`,
+ // so metrics are consistent regardless of language.
+ const clickMetrics = metricsBuilder.createMetricsClickData(objectGraph, "Clear Searches", "button", {
+ actionType: "confirm",
+ });
+ clearAction.actionMetrics.addMetricsData(clickMetrics);
+}
+export function addClickEventToActivityFeedMetrics(objectGraph, actionMetrics, title, targetId, options) {
+ const eventFields = {
+ actionType: "navigate",
+ id: targetId,
+ idType: "static",
+ location: metricsLocation.createBasicLocation(objectGraph, options, title),
+ };
+ const clickEvent = metricsBuilder.createMetricsClickData(objectGraph, targetId, "link", eventFields);
+ actionMetrics.addMetricsData(clickEvent);
+}
+export function addClickEventToPageFacetsChangeAction(objectGraph, action, filterParameter) {
+ const eventFields = {};
+ eventFields["actionType"] = "filter";
+ const event = metricsBuilder.createMetricsClickData(objectGraph, `filter_${filterParameter}`, "button", eventFields);
+ event.includingFields.push("selectedPageFacets");
+ action.actionMetrics.addMetricsData(event);
+}
+//* *************************
+//* Search Metrics
+//* *************************
+/**
+ * Adds a given `SearchAction` with the metrics data based on the Search it will fire, i.e data for:
+ * - Click Event
+ * - Search Event
+ *
+ * @param action Action to add metrics to
+ * @param target Target of this action. This should correspond to the UI this action is attached to.
+ * @param locationTracker Location tracker.
+ */
+export function addEventsToSearchAction(objectGraph, action, target, locationTracker, pageInformation) {
+ var _a, _b, _c, _d;
+ const actionType = metricsActionTypeForSearchOrigin(action.origin);
+ if (isNothing(pageInformation)) {
+ pageInformation = new metricsModels.MetricsPageInformation({
+ page: "Search",
+ pageType: "Search",
+ pageId: "Search",
+ pageDetails: "Apps", // Legacy. Note sure why this is Apps.
+ });
+ }
+ const options = {
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ targetType: target,
+ };
+ // Click-Only Fields
+ const clickFields = {
+ ...metricsMisc.fieldsFromPageInformation(pageInformation),
+ actionType: actionType,
+ actionUrl: metricsUtil.emptyStringIfNullOrUndefined(action.url),
+ location: metricsLocation.createBasicLocation(objectGraph, options, action.term),
+ searchTerm: action.term,
+ };
+ // Search-Only Fields
+ const searchFields = {
+ targetId: action.term,
+ };
+ const searchActionDetails = {};
+ if ((_a = action.prefixTerm) === null || _a === void 0 ? void 0 : _a.length) {
+ // Keep `searchPrefix` in `actionDetails` for search events
+ searchActionDetails["searchPrefix"] = action.prefixTerm;
+ }
+ if ((_b = action.entity) === null || _b === void 0 ? void 0 : _b.length) {
+ searchActionDetails["hintsEntity"] = action.entity;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(searchActionDetails)) {
+ searchFields["actionDetails"] = searchActionDetails;
+ }
+ // Shared Fields
+ if ((_c = action.originatingTerm) === null || _c === void 0 ? void 0 : _c.length) {
+ clickFields["searchOriginatingTerm"] = action.originatingTerm;
+ searchFields["searchOriginatingTerm"] = action.originatingTerm;
+ }
+ // SSS: Clicks must be before Search
+ const clickData = metricsBuilder.createMetricsClickData(objectGraph, action.term, target, clickFields, [
+ "searchGhostHint",
+ ]);
+ action.actionMetrics.addMetricsData(clickData);
+ const searchData = metricsBuilder.createMetricsSearchData(objectGraph, action.term, target, actionType, (_d = action.url) !== null && _d !== void 0 ? _d : null, searchFields, ["searchGhostHint"]);
+ action.actionMetrics.addMetricsData(searchData);
+}
+/**
+ * Returns the mapped action type for given search origin.
+ * @param origin Origin to resolve action type for
+ */
+function metricsActionTypeForSearchOrigin(origin) {
+ // actionType based on search origin.
+ switch (origin) {
+ case "trending":
+ return "trending";
+ case "suggested":
+ return "suggested";
+ case "recents":
+ return "recentQuery";
+ case "hints":
+ return "hint";
+ case "undoSpellCorrection":
+ return "searchInsteadFor";
+ case "applySpellCorrection":
+ return "didYouMean";
+ case "userTypedHint":
+ return "userTypedHint";
+ // It is unexpected to see other search origins here. These are built in native:
+ default:
+ return "submit";
+ }
+}
+// region Segmented Search
+/**
+ * Add click metrics to segment change action
+ * @param action Action to add click event to.
+ * @param locationTracker Location tracker
+ */
+export function addEventsToSegmentChangeAction(objectGraph, action, targetId, locationTracker) {
+ // Click-Only Fields
+ const targetType = "link";
+ const clickFields = {
+ actionType: "navigate",
+ location: metricsLocation.createBasicLocation(objectGraph, {
+ pageInformation: null,
+ locationTracker: locationTracker,
+ targetType: targetType,
+ }, action.title),
+ };
+ const clickData = metricsBuilder.createMetricsClickData(objectGraph, targetId, targetType, clickFields);
+ action.actionMetrics.addMetricsData(clickData);
+}
+/**
+ * Add click metrics to segment change action
+ * @param action Action to add click event to.
+ * @param locationTracker Location tracker
+ */
+export function addClickEventToSearchPageSegmentChangeAction(objectGraph, action, targetId, locationTracker) {
+ // Click-Only Fields
+ const targetType = "SearchResults";
+ const clickFields = {
+ actionType: "navigate",
+ location: metricsLocation.createBasicLocation(objectGraph, {
+ pageInformation: null,
+ locationTracker: locationTracker,
+ targetType: targetType,
+ }, "searchPageSegmentChange"),
+ };
+ const clickData = metricsBuilder.createMetricsClickData(objectGraph, targetId, targetType, clickFields);
+ action.actionMetrics.addMetricsData(clickData);
+}
+// endregion
+//# sourceMappingURL=clicks.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/constants.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/constants.js
new file mode 100644
index 0000000..c23084b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/constants.js
@@ -0,0 +1,11 @@
+/**
+ * HACK: <rdar://problem/65687333> Tech Debt: Metrics: Adam ID attribution should be explicit
+ * This is an incremental step out of a hack that used `targetId` as `adamId` for decorating adamId-related fields for a given `MetricsData`.
+ * It should be leveraged for paths that use field providers that have an adam id dependency.
+ *
+ * As of 2022E we are using this key in the `custom` field of `actionMetrics`, which is a recommended method of sending down
+ * additional data to augment metrics events but shouldn't be included in the event itself. `custom` fields are automatically
+ * added to the `MetricsFieldsContext` by `ActionDispatcher`, allowing `MetricsFieldsProvider`s to access and use this value.
+ */
+export const contextualAdamIdKey = "jet_adamId";
+//# sourceMappingURL=constants.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/impressions.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/impressions.js
new file mode 100644
index 0000000..edc1d96
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/impressions.js
@@ -0,0 +1,419 @@
+import * as metricsLocation from "./location";
+import * as metricsUtil from "./util";
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as productVariants from "../../product-page/product-page-variants";
+import * as lockups from "../../../common/lockups/lockups";
+//* *************************
+//* Impression Metrics
+//* *************************
+function generateImpressionFields(objectGraph, options) {
+ /// please note this is not the metrics event itself but the fields for the items that impress
+ /// there is a very strict spec for these items so please do not modify this unless you know
+ /// exactly what you are doing.
+ var _a, _b, _c, _d;
+ let id = options.id;
+ let title = options.title;
+ if (serverData.isDefinedNonNullNonEmpty(options.anonymizationOptions)) {
+ const anonymizationString = (_b = (_a = options.anonymizationOptions) === null || _a === void 0 ? void 0 : _a.anonymizationString) !== null && _b !== void 0 ? _b : "ANONYMOUS";
+ id = anonymizationString;
+ title = anonymizationString;
+ }
+ else if (id && options.createUniqueImpressionId && !objectGraph.client.isWatch) {
+ id = createUniqueImpressionId(objectGraph, id);
+ }
+ const impressionData = {
+ id: metricsUtil.emptyStringIfNullOrUndefined(id),
+ name: metricsUtil.emptyStringIfNullOrUndefined(title),
+ impressionType: metricsUtil.targetTypeForMetricsOptions(objectGraph, options),
+ };
+ const idType = metricsUtil.idTypeForMetricsOptions(options);
+ if (isSome(idType)) {
+ impressionData["idType"] = idType;
+ }
+ if (options && options.kind) {
+ impressionData["kind"] = options.kind;
+ }
+ if (options && options.softwareType) {
+ impressionData["softwareType"] = options.softwareType;
+ }
+ if (options && options.recoMetricsData) {
+ Object.assign(impressionData, options.recoMetricsData);
+ }
+ if (options && options.mercuryMetricsData) {
+ Object.assign(impressionData, options.mercuryMetricsData);
+ }
+ if (options && options.lockupDisplayStyle) {
+ impressionData["platformDisplayStyle"] = options.lockupDisplayStyle;
+ }
+ const shouldOmitImpressionIndex = (_c = options.shouldOmitImpressionIndex) !== null && _c !== void 0 ? _c : false;
+ if (options && options.locationTracker && !shouldOmitImpressionIndex) {
+ const currentPosition = metricsLocation.currentPosition(options.locationTracker);
+ impressionData["impressionIndex"] = currentPosition;
+ if (impressionData.id === "") {
+ impressionData.id = currentPosition.toString();
+ impressionData["idType"] = "sequential";
+ }
+ }
+ if (options && options.modelSource) {
+ impressionData["modelSource"] = options.modelSource;
+ }
+ // Add offerType if available
+ if (serverData.isDefinedNonNull(options.offerType)) {
+ impressionData["offerType"] = options.offerType;
+ }
+ // Arcade Upsell Tracking
+ if (options && serverData.isDefinedNonNull(options.displaysArcadeUpsell)) {
+ impressionData["displaysArcadeUpsell"] = options.displaysArcadeUpsell;
+ }
+ // Preorder Tracking
+ if (options && serverData.isDefinedNonNull(options.isPreorder)) {
+ impressionData["isPreorder"] = options.isPreorder;
+ }
+ // Add adamId if available
+ if (serverData.isDefinedNonNull(options.adamId) && serverData.isNullOrEmpty(options.anonymizationOptions)) {
+ impressionData["adamId"] = options.adamId;
+ }
+ // Badges
+ if (options && serverData.isDefinedNonNull(options.badges)) {
+ impressionData["badges"] = options.badges;
+ }
+ // In App Event ID
+ if (options && serverData.isDefinedNonNull(options.inAppEventId)) {
+ impressionData["inAppEventId"] = options.inAppEventId;
+ }
+ // Related subject IDs
+ if (options && serverData.isDefinedNonNull(options.relatedSubjectIds)) {
+ impressionData["relatedSubjectIds"] = options.relatedSubjectIds;
+ }
+ /// Hints entity
+ if ((_d = options === null || options === void 0 ? void 0 : options.hintsEntity) === null || _d === void 0 ? void 0 : _d.length) {
+ impressionData["hintsEntity"] = options.hintsEntity;
+ }
+ // autoAdvanceInterval for auto scrolling Views
+ if (options && serverData.isDefinedNonNull(options.autoAdvanceInterval)) {
+ impressionData["autoAdvanceInterval"] = options.autoAdvanceInterval;
+ }
+ // Add fcKind if available
+ if (serverData.isDefinedNonNull(options === null || options === void 0 ? void 0 : options.fcKind)) {
+ impressionData["fcKind"] = options.fcKind;
+ }
+ if (serverData.isDefinedNonNull(options === null || options === void 0 ? void 0 : options.canonicalId)) {
+ impressionData["canonicalId"] = options.canonicalId;
+ }
+ if (serverData.isDefinedNonNull(options === null || options === void 0 ? void 0 : options.displayStyle)) {
+ impressionData["displayStyle"] = options.displayStyle;
+ }
+ // Add product variant fields if available
+ if (serverData.isDefinedNonNull(options.productVariantData)) {
+ Object.assign(impressionData, productVariants.contentFieldsForProductVariantData(options.productVariantData));
+ }
+ if (isSome(options.contentRating)) {
+ impressionData["contentRating"] = options.contentRating;
+ }
+ if (isSome(options.bundleId)) {
+ impressionData["bundleId"] = options.bundleId;
+ }
+ if (impressionData.id === "") {
+ objectGraph.console.log(`impressionId missing. Tracking broken for ${impressionData.name} of ${impressionData.impressionType}`);
+ }
+ return impressionData;
+}
+export function addImpressionFields(objectGraph, impressionable, options) {
+ if (!impressionable) {
+ return;
+ }
+ impressionable.impressionMetrics = new models.ImpressionMetrics(generateImpressionFields(objectGraph, options));
+}
+export function addImpressionFieldsToTagRoomHeader(objectGraph, impressionable, options) {
+ if (!impressionable) {
+ return;
+ }
+ const impressionMetrics = new models.ImpressionMetrics(generateImpressionFields(objectGraph, options));
+ delete impressionMetrics.fields.impressionIndex;
+ impressionable.impressionMetrics = impressionMetrics;
+}
+/**
+ * Adds impression fields to the search metadata ribbon item.
+ *
+ * @param objectGraph - The object graph.
+ * @param metadataRibbonItem - The metadata ribbon item.
+ * @param options - The metrics impression options.
+ */
+export function addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, metadataRibbonItem, options) {
+ var _a;
+ if (!metadataRibbonItem) {
+ return;
+ }
+ addImpressionFields(objectGraph, metadataRibbonItem, options);
+ if ((options.isAdvert || options.isAdEligible) &&
+ ((_a = options.pageInformation) === null || _a === void 0 ? void 0 : _a.iAdInfo) &&
+ metadataRibbonItem.impressionMetrics) {
+ metadataRibbonItem.impressionMetrics = new models.FastImpressionMetrics(metadataRibbonItem.impressionMetrics, true);
+ }
+}
+/**
+ * Add impressions fields to a Today card.
+ * @param objectGraph The object graph.
+ * @param card The Today card to apply the impressions fields to.
+ * @param options A set of metrics options to gather data from.
+ * @param franchise The card franchise.
+ * @param cardType The card type.
+ * @param isOnboardingCard Whether the card is an onboarding card.
+ * @param coerceNullToEmptyStrings Whether `null` values for `franchise` and `cardType` should be coerced into empty strings. This
+ * was a behaviour that was always enabled by default, but as of the implementation of Chainlink ads, we don't necessarily want empty strings.
+ * Defaults to true to maintain legacy behaviour for other cards that don't want to opt out.
+ * @returns
+ */
+export function addImpressionsFieldsToTodayCard(objectGraph, card, options, franchise, cardType, isOnboardingCard, coerceNullToEmptyStrings = true) {
+ var _a, _b, _c, _d, _e, _f;
+ if (!card) {
+ return;
+ }
+ const impressionData = generateImpressionFields(objectGraph, options);
+ if (coerceNullToEmptyStrings) {
+ impressionData["franchise"] = metricsUtil.emptyStringIfNullOrUndefined(franchise);
+ impressionData["cardType"] = metricsUtil.emptyStringIfNullOrUndefined(cardType);
+ }
+ else {
+ if (franchise) {
+ impressionData["franchise"] = franchise;
+ }
+ if (cardType) {
+ impressionData["cardType"] = cardType;
+ }
+ }
+ if (isOnboardingCard) {
+ impressionData["isOnboardingCard"] = isOnboardingCard;
+ }
+ if (((_b = (_a = options === null || options === void 0 ? void 0 : options.optimizationEntityId) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0) {
+ impressionData["optimizationEntityId"] = options.optimizationEntityId;
+ }
+ if (((_d = (_c = options === null || options === void 0 ? void 0 : options.optimizationId) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0) {
+ impressionData["optimizationId"] = options.optimizationId;
+ }
+ if (isSome(options === null || options === void 0 ? void 0 : options.rowIndex)) {
+ impressionData["rowIndex"] = options.rowIndex;
+ }
+ card.impressionMetrics = new models.ImpressionMetrics(impressionData);
+ if ((options.isAdvert || options.isAdEligible) && ((_e = options.pageInformation) === null || _e === void 0 ? void 0 : _e.iAdInfo)) {
+ const sanitizedIAdData = metricsUtil.sanitizedMetricsDictionary(options.pageInformation.iAdInfo.fastImpressionsFieldsForCurrentItem(options.locationTracker, options.adSlotOverride));
+ Object.assign(card.impressionMetrics.fields, sanitizedIAdData);
+ card.impressionMetrics = new models.FastImpressionMetrics(card.impressionMetrics, true);
+ if (options.isAdvert) {
+ (_f = card.impressionMetrics) === null || _f === void 0 ? true : delete _f.fields["cardType"];
+ }
+ }
+}
+export function addImpressionsFieldsToAd(objectGraph, impressionable, options, iAdData) {
+ if (!impressionable || !iAdData) {
+ return;
+ }
+ addImpressionFields(objectGraph, impressionable, options);
+ const sanitizedIAdData = metricsUtil.sanitizedMetricsDictionary(iAdData.fastImpressionsFieldsForCurrentItem(options.locationTracker, options.adSlotOverride));
+ if (isSome(impressionable.impressionMetrics)) {
+ Object.assign(impressionable.impressionMetrics.fields, sanitizedIAdData);
+ const disableFastImpressions = serverData.asBooleanOrFalse(options.disableFastImpressionsForAds);
+ impressionable.impressionMetrics = new models.FastImpressionMetrics(impressionable.impressionMetrics, !disableFastImpressions);
+ }
+ /**
+ * This is an longstanding hack to prevent ad and organic impression showing same content (i.e. AdamId)
+ * to properly have nonclashing identifiers, and have separate impression objects.
+ * Even with impression item ids' hasing on `impressionIndex` - this is still needed in case the Nth rotated ad is the same as Nth organic.
+ * This is orthogonal to "ad_container" in location stack when building ads for ad-rotation, which is a separate metrics trick.
+ */
+ if (isSome(impressionable.impressionMetrics)) {
+ impressionable.impressionMetrics.fields["parentId"] = "ad_container";
+ }
+}
+export function addImpressionFieldsToInAppPurchaseLockup(objectGraph, lockup, options) {
+ if (!lockup) {
+ return;
+ }
+ // The code that calls this method currently does this indirectly,
+ // this is just to guard against that code changing in the future.
+ if (!lockup.impressionMetrics) {
+ addImpressionFields(objectGraph, lockup, options);
+ }
+ if (lockup.parent && lockup.parent.adamId && isSome(lockup.impressionMetrics)) {
+ lockup.impressionMetrics.fields["parentAdamId"] = metricsUtil.emptyStringIfNullOrUndefined(lockup.parent.adamId);
+ }
+}
+export function impressionOptionsForLockup(objectGraph, data, lockup, displayStyle, baseOptions, canDisplayArcadeOfferButton, attributePlatformOverride = undefined) {
+ var _a;
+ const options = impressionOptions(objectGraph, data, lockup.title, baseOptions);
+ options.lockupDisplayStyle = displayStyle;
+ options.contentRating = (_a = lockup.offerDisplayProperties) === null || _a === void 0 ? void 0 : _a.contentRating;
+ options.bundleId = lockup.bundleId;
+ // If no targetType is provided, set the correct value for the platform.
+ if (serverData.isNullOrEmpty(options.targetType)) {
+ options.targetType = objectGraph.client.isVision ? "lockupSmall" : "lockup";
+ }
+ if (canDisplayArcadeOfferButton && content.isArcadeSupported(objectGraph, data)) {
+ options.displaysArcadeUpsell = true;
+ }
+ // If it has a discounted offer then use the options already set
+ const parentID = baseOptions["id"];
+ if (serverData.isDefinedNonNullNonEmpty(lockups.discountedOfferFromData(data)) &&
+ serverData.isDefinedNonNull(parentID) &&
+ parentID.length > 0) {
+ options.id = parentID;
+ }
+ return options;
+}
+export function impressionOptions(objectGraph, data, title, baseOptions, attributePlatformOverride = undefined) {
+ return validation.context("impressionOptions", () => {
+ const kind = metricsUtil.metricsKindFromData(objectGraph, data);
+ const softwareType = metricsUtil.softwareTypeForData(objectGraph, data);
+ const metricsOptions = {
+ ...baseOptions,
+ kind: kind,
+ softwareType: softwareType,
+ title: title,
+ id: data.id,
+ };
+ // Include offerType for pre-order impressions
+ // NOTE: Even though metricsOptions.isPreorder may be true, we don't key off that here because
+ // offerType implies an offer exists, which is not always true (e.g. an Arcade Coming Soon breakout).
+ const containsPreorderOffer = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isPreorder", attributePlatformOverride);
+ if (containsPreorderOffer) {
+ metricsOptions.offerType = "preorder";
+ }
+ return metricsOptions;
+ });
+}
+/// Returns impression options for Arcade See All Games ribbon item.
+export function impressionOptionsForArcadeSeeAllGamesRibbonItem(baseOptions) {
+ return validation.context("impressionOptionsForArcadeSeeAllGamesRibbonItem", () => {
+ const metricsOptions = {
+ ...baseOptions,
+ id: "AllGames",
+ idType: "none",
+ kind: null,
+ softwareType: null,
+ title: "All Games",
+ };
+ return metricsOptions;
+ });
+}
+/// Returns impression options for tag ribbon item on product page.
+export function impressionOptionsForTagRibbonItem(objectGraph, data, title, baseOptions) {
+ return validation.context("impressionOptions", () => {
+ const kind = metricsUtil.metricsKindFromData(objectGraph, data);
+ const softwareType = metricsUtil.softwareTypeForData(objectGraph, data);
+ const metricsOptions = {
+ ...baseOptions,
+ kind: kind,
+ softwareType: softwareType,
+ title: title,
+ id: data.id,
+ idType: "its_id",
+ displayStyle: "textOnly",
+ };
+ return metricsOptions;
+ });
+}
+/// Returns impression options for tag ribbon item on product page.
+export function impressionOptionsForTagHeader(objectGraph, data, title, baseOptions) {
+ return validation.context("impressionOptions", () => {
+ const kind = metricsUtil.metricsKindFromData(objectGraph, data);
+ const softwareType = metricsUtil.softwareTypeForData(objectGraph, data);
+ const metricsOptions = {
+ ...baseOptions,
+ kind: kind,
+ softwareType: softwareType,
+ title: title,
+ id: data.id,
+ idType: "its_contentId",
+ targetType: "tagHeader",
+ };
+ return metricsOptions;
+ });
+}
+/// Returns impression options for Arcade Choose Your Favorites brick.
+export function impressionOptionsForArcadeChooseYourFavoritesBrick(baseOptions) {
+ return validation.context("impressionOptionsForArcadeChooseYourFavoritesBrick", () => {
+ const metricsOptions = {
+ ...baseOptions,
+ id: "",
+ kind: null,
+ softwareType: null,
+ title: "choose_your_games_brick",
+ };
+ return metricsOptions;
+ });
+}
+export function impressionOptionsForMetadataRibbonItem(baseOptions, id, name, idType) {
+ return validation.context("impressionOptionsForMetadataRibbonItem", () => {
+ const metricsOptions = {
+ ...baseOptions,
+ id: id,
+ kind: null,
+ softwareType: null,
+ title: name,
+ idType: idType,
+ targetType: "tag",
+ };
+ return metricsOptions;
+ });
+}
+// region Hints Impressions
+export function impressionOptionsForSearchHint(objectGraph, hintTerm, baseOptions, searchEntity, hintSource) {
+ return validation.context("impressionOptionsForSearchHint", () => {
+ const metricsOptions = {
+ ...baseOptions,
+ id: "",
+ kind: null,
+ softwareType: null,
+ title: hintTerm,
+ hintsEntity: searchEntity,
+ modelSource: hintSource,
+ };
+ return metricsOptions;
+ });
+}
+export function addImpressionMetricsToHintsSearchAction(objectGraph, searchAction, metricsOptions) {
+ const options = impressionOptionsForSearchHint(objectGraph, searchAction.term, metricsOptions, searchAction.entity, searchAction.source);
+ const impressionFields = generateImpressionFields(objectGraph, options);
+ searchAction.impressionMetrics = new models.ImpressionMetrics(impressionFields);
+}
+const uniqueIdDelimiter = "::";
+function createUniqueImpressionId(objectGraph, baseId) {
+ return `${baseId}${uniqueIdDelimiter}${objectGraph.random.nextUUID()}`;
+}
+/**
+ * Strips the unique impression ID from the event fields if necessary.
+ *
+ * @param eventFields - The event fields containing impressions.
+ */
+export function stripUniqueImpressionIdsIfNecessary(eventFields) {
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ for (const impression of impressions) {
+ const impressionId = serverData.asString(impression, "id");
+ if (isNothing(impressionId)) {
+ continue;
+ }
+ impression["id"] = stripUniqueImpressionIdIfNecessary(impressionId);
+ }
+}
+/**
+ * Strips the unique impression ID from the given impression ID if necessary.
+ * If the impression ID contains a unique ID delimiter, it splits the string
+ * and returns the part before the delimiter. Otherwise, it returns the original
+ * impression ID.
+ *
+ * @param impressionId - The impression ID to process.
+ * @returns The processed impression ID.
+ */
+function stripUniqueImpressionIdIfNecessary(impressionId) {
+ if (impressionId.includes(uniqueIdDelimiter)) {
+ return impressionId.split(uniqueIdDelimiter)[0];
+ }
+ return impressionId;
+}
+// endregion
+//# sourceMappingURL=impressions.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/legacy-metrics-identifier-fields-opt-out.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/legacy-metrics-identifier-fields-opt-out.js
new file mode 100644
index 0000000..4232a33
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/legacy-metrics-identifier-fields-opt-out.js
@@ -0,0 +1,18 @@
+/**
+ * Hack for injecting clientId and metrics data if needed.
+ */
+import { AppStoreMetricsData } from "../../../api/models";
+/**
+ * Opt out of legacy metrics id fields provider, the `AMSMetricsIdentifierFieldsProvider` instead
+ * we'll rely solely on `MetricsIdFieldsProvider` added for Katana
+ * @param objectGraph - The object graph.
+ * @param metricsData - The metrics data.
+ * @returns The metrics data, with the `amsMetricsID` field excluded.
+ */
+export function optOutOfLegacyMetricsIdFieldsProvider(objectGraph, metricsData) {
+ var _a;
+ const excludingFields = (_a = metricsData.excludingFields) !== null && _a !== void 0 ? _a : [];
+ excludingFields.push("amsMetricsID");
+ return new AppStoreMetricsData(metricsData.fields, metricsData.includingFields, excludingFields, metricsData.topic, metricsData.shouldFlush);
+}
+//# sourceMappingURL=legacy-metrics-identifier-fields-opt-out.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/location.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/location.js
new file mode 100644
index 0000000..f7ffd4e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/location.js
@@ -0,0 +1,188 @@
+import * as validation from "@jet/environment/json/validation";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import { asJSONData } from "../../../foundation/json-parsing/server-data";
+import * as productVariant from "../../product-page/product-page-variants";
+import * as metricsUtil from "./util";
+class MetricsLocationStackItem {
+ constructor() {
+ this.position = 0;
+ }
+}
+export function newLocationTracker() {
+ return {
+ rootPosition: 0,
+ locationStack: [],
+ };
+}
+/**
+ * @param locationTracker The location tracker to copy
+ * @returns A copy of the location tracker
+ */
+export function createLocationTrackerCopy(locationTracker) {
+ const locationStackCopy = [];
+ for (const locationStackEntry of locationTracker.locationStack) {
+ locationStackCopy.push({
+ ...locationStackEntry,
+ });
+ }
+ return {
+ rootPosition: locationTracker.rootPosition,
+ locationStack: locationStackCopy,
+ };
+}
+export function createContentLocation(objectGraph, options, title) {
+ const locations = stackItemsToLocationStack(options.locationTracker);
+ const contentLocation = newContentLocation(objectGraph, options, title);
+ return [contentLocation, ...locations];
+}
+export function createBasicLocation(objectGraph, options, title) {
+ const locations = stackItemsToLocationStack(options.locationTracker);
+ const basicLocation = newBasicLocation(objectGraph, options, title);
+ return [basicLocation, ...locations];
+}
+export function pushContentLocation(objectGraph, options, title) {
+ const stackItem = new MetricsLocationStackItem();
+ stackItem.location = newContentLocation(objectGraph, options, title);
+ options.locationTracker.locationStack.unshift(stackItem);
+}
+export function pushBasicLocation(objectGraph, options, title) {
+ const stackItem = new MetricsLocationStackItem();
+ stackItem.location = newBasicLocation(objectGraph, options, title);
+ options.locationTracker.locationStack.unshift(stackItem);
+}
+export function popLocation(tracker) {
+ if (tracker.locationStack.length === 0) {
+ validation.unexpectedType("ignoredValue", "non-empty location stack", "empty location stack");
+ return;
+ }
+ tracker.locationStack.shift();
+}
+export function currentPosition(tracker) {
+ const stackItem = lastStackItemAdded(tracker);
+ if (stackItem) {
+ return stackItem.position;
+ }
+ else {
+ return tracker.rootPosition;
+ }
+}
+export function previousPosition(tracker) {
+ if (tracker.locationStack.length < 2) {
+ return null;
+ }
+ return tracker.locationStack[1].position;
+}
+export function currentLocation(tracker) {
+ const stackItem = lastStackItemAdded(tracker);
+ if (stackItem) {
+ return stackItem.location;
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Set current position of tracker. This is necessary when large today modules are broken apart into multipart shelves.
+ * We need to preserve the position of content within server-response, not our logical shelves.
+ * @param tracker Tracker update position of.
+ * @param position Position to set to.
+ */
+export function setCurrentPosition(tracker, position) {
+ const stackItem = lastStackItemAdded(tracker);
+ if (stackItem) {
+ stackItem.position = position;
+ }
+ else {
+ tracker.rootPosition = position;
+ }
+}
+export function nextPosition(tracker) {
+ const stackItem = lastStackItemAdded(tracker);
+ if (stackItem) {
+ stackItem.position++;
+ }
+ else {
+ tracker.rootPosition++;
+ }
+}
+function newContentLocation(objectGraph, options, title) {
+ var _a;
+ const base = newBasicLocation(objectGraph, options, title);
+ // Use the location tracker if there is no id override
+ if (!options.id && options.locationTracker) {
+ base.idType = "sequential";
+ base.id = currentPosition(options.locationTracker).toString();
+ }
+ else {
+ // If there is a id specified, use that
+ const idType = metricsUtil.idTypeForMetricsOptions(options);
+ if (isSome(idType)) {
+ base.idType = idType;
+ }
+ let id = options.id;
+ if ((_a = options.anonymizationOptions) === null || _a === void 0 ? void 0 : _a.anonymizationString) {
+ id = options.anonymizationOptions.anonymizationString;
+ }
+ base.id = isNothing(id) ? "" : id;
+ }
+ if (options.fcKind) {
+ base.fcKind = options.fcKind;
+ }
+ if (options.displayStyle) {
+ base.displayStyle = options.displayStyle;
+ }
+ if (options.inAppEventId) {
+ base.inAppEventId = options.inAppEventId;
+ }
+ if (options.relatedSubjectIds) {
+ base.relatedSubjectIds = options.relatedSubjectIds;
+ }
+ if (options.canonicalId) {
+ base.canonicalId = options.canonicalId;
+ }
+ if (options.optimizationEntityId) {
+ base.optimizationEntityId = options.optimizationEntityId;
+ }
+ if (options.optimizationId) {
+ base.optimizationId = options.optimizationId;
+ }
+ if (isSome(options.rowIndex)) {
+ base.rowIndex = options.rowIndex;
+ }
+ if (options.productVariantData) {
+ Object.assign(base, productVariant.contentFieldsForProductVariantData(options.productVariantData));
+ }
+ return base;
+}
+function newBasicLocation(objectGraph, options, title) {
+ var _a, _b;
+ let name = title;
+ if ((_a = options.anonymizationOptions) === null || _a === void 0 ? void 0 : _a.anonymizationString) {
+ name = options.anonymizationOptions.anonymizationString;
+ }
+ const location = {
+ locationPosition: currentPosition(options.locationTracker),
+ locationType: metricsUtil.targetTypeForMetricsOptions(objectGraph, options),
+ name: isNothing(name) ? "" : name,
+ };
+ if (isSome(options.badges)) {
+ location.badges = (_b = asJSONData(options.badges)) !== null && _b !== void 0 ? _b : undefined;
+ }
+ if (options.recoMetricsData) {
+ Object.assign(location, options.recoMetricsData);
+ }
+ return location;
+}
+function stackItemsToLocationStack(tracker) {
+ return tracker.locationStack.map((stackItem) => {
+ return stackItem.location;
+ });
+}
+function lastStackItemAdded(tracker) {
+ const length = tracker.locationStack.length;
+ if (length === 0) {
+ return null;
+ }
+ return tracker.locationStack[0];
+}
+//# sourceMappingURL=location.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/media.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/media.js
new file mode 100644
index 0000000..0b12cf9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/media.js
@@ -0,0 +1,34 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as objects from "../../../foundation/util/objects";
+import * as metricsBuilder from "../builder";
+import * as metricsLocation from "./location";
+import * as misc from "./misc";
+import * as metricsUtil from "./util";
+//* *************************
+//* Media Metrics
+//* *************************
+export function addMetricsEventsToVideo(objectGraph, video, options) {
+ if (isNothing(video)) {
+ return;
+ }
+ const mediaEventFields = misc.fieldsFromPageInformation(options.pageInformation);
+ if (mediaEventFields === null) {
+ return;
+ }
+ mediaEventFields["id"] = metricsUtil.emptyStringIfNullOrUndefined(options.id);
+ const idType = metricsUtil.idTypeForMetricsOptions(options);
+ if (isSome(idType)) {
+ mediaEventFields["idType"] = idType;
+ }
+ mediaEventFields["type"] = "video";
+ mediaEventFields["typeDetails"] = "iTunesStoreContent";
+ mediaEventFields["location"] = metricsLocation.createContentLocation(objectGraph, options, "");
+ if (options.actionDetails) {
+ mediaEventFields["actionDetails"] = options.actionDetails;
+ }
+ video.templateMediaEvent = metricsBuilder.createMetricsMediaData(objectGraph, mediaEventFields);
+ const clickEventFields = objects.shallowCopyOf(mediaEventFields);
+ clickEventFields["actionUrl"] = video.videoUrl;
+ video.templateClickEvent = metricsBuilder.createMetricsMediaClickData(objectGraph, null, "button", clickEventFields);
+}
+//# sourceMappingURL=media.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/misc.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/misc.js
new file mode 100644
index 0000000..ffb0b77
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/misc.js
@@ -0,0 +1,46 @@
+import { isDefinedNonNull } from "../../../foundation/json-parsing/server-data";
+import { pageFieldsForPageInfoProductVariantData } from "../../product-page/product-page-variants";
+import { EventLinter } from "../event-linter";
+import * as metricsUtil from "./util";
+// region Page Information
+/**
+ * Returns a set of pageFields from a given `pageInformation` - i.e. fields that are included on metrics data with `pageField` IncludingFields, and buyParams.
+ * It is not recommended to use this function for instrumentation that involves field providers, as these page fields will overwrite them.
+ * @param pageInformation Page information to create page fields for.
+ */
+export function fieldsFromPageInformation(pageInformation) {
+ var _a;
+ const fields = {};
+ if (!pageInformation) {
+ return fields;
+ }
+ Object.assign(fields, pageInformation.baseFields);
+ if (pageInformation.pageUrl) {
+ fields["pageUrl"] = pageInformation.pageUrl;
+ }
+ else if (pageInformation.timingMetrics && pageInformation.timingMetrics.pageURL) {
+ fields["pageUrl"] = pageInformation.timingMetrics.pageURL;
+ }
+ if (pageInformation.recoMetricsData) {
+ Object.assign(fields, pageInformation.recoMetricsData);
+ }
+ if (pageInformation.mercuryMetricsData) {
+ Object.assign(fields, pageInformation.mercuryMetricsData);
+ }
+ if (pageInformation.productVariantData) {
+ Object.assign(fields, pageFieldsForPageInfoProductVariantData(pageInformation.productVariantData));
+ }
+ if (pageInformation.iAdInfo && isDefinedNonNull(pageInformation.iAdInfo.pageFields[EventLinter.hasIAdData])) {
+ fields[EventLinter.hasIAdData] = pageInformation.iAdInfo.pageFields[EventLinter.hasIAdData];
+ }
+ const iAdId = (_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.pageFields["iAdId"];
+ if (isDefinedNonNull(iAdId)) {
+ fields["iAdId"] = iAdId;
+ }
+ return metricsUtil.sanitizedMetricsDictionary(fields);
+}
+// endregion
+// region Network Performance
+// @see `JSNetworkPerformanceMetrics.metrics(fromResult:)` in native.
+// endregion
+//# sourceMappingURL=misc.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/models.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/models.js
new file mode 100644
index 0000000..3456e9e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/models.js
@@ -0,0 +1,671 @@
+import * as metricsLocation from "./location";
+import * as metricsUtil from "./util";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { attributeAsDictionary } from "../../../foundation/media/attributes";
+import { shallowCopyOf } from "../../../foundation/util/objects";
+import { productVariantDataForData, productVariantDataHasVariant } from "../../product-page/product-page-variants";
+import { EventLinter } from "../event-linter";
+import { eligibleSlotPositionsForAdPlacement } from "../../ads/ad-common";
+import { FlattenedTodayItemType } from "../../today/today-parse-util";
+import { getSelectedCustomCreativeId } from "../../search/custom-creative";
+export const iAdURLParameterStringToken = "X-AppStore-iAdClickToken";
+export const iAdURLLineItemParameterStringToken = "X-AppStore-iAdLineItem";
+export const iAdDismissAdActionMetricsParameterStringToken = "X-AppStore-iAdDismissAdActionMetrics";
+// User defined type guard for determining if an object conforms to ContentMetricsOptions interface.
+export function isContentMetricsOptions(object) {
+ return object && Object.prototype.hasOwnProperty.call(object, "id");
+}
+export class IAdSearchInformation {
+ /**
+ * Initialise a new `IAdSearchInformation`
+ * @param objectGraph The Object Graph.
+ * @param placementType The placement type for the ad this object is tracking.
+ * @param baseSlotInformation The initial list of slotInfos for this ad placement
+ * @param iAdId The unique id for the ad instance.
+ * @param appStoreClientRequestId The unique id for the client requesting the ad.
+ * @param wasOdmlSuccessful Whether native ODML processing was successful.
+ * @param positionInfo The position info data describing the requested position of this ad.
+ */
+ constructor(objectGraph, placementType, baseSlotInformation, iAdId, appStoreClientRequestId, wasOdmlSuccessful, positionInfo) {
+ this.placementType = placementType;
+ this.placementId = placementType === null ? null : this.placementIdFromType(placementType);
+ this.pageFields = {};
+ this.clickFields = {};
+ this.impressionsFields = {};
+ this.fastImpressionFields = {};
+ this.iAdClickEventFields = {};
+ this._iAdApplied = false;
+ this._iAdAdamId = undefined;
+ this.positionInfo = positionInfo;
+ this.slotInfo = baseSlotInformation;
+ this.setInitialAdData(objectGraph, iAdId, appStoreClientRequestId);
+ if (serverData.isDefinedNonNull(wasOdmlSuccessful)) {
+ this.pageFields["iAdOdmlSuccess"] = wasOdmlSuccessful;
+ }
+ this.fastImpressionFields["iAdEligible"] = true;
+ }
+ /**
+ * Construct an IAdSearchInformation from the given JSON representation.
+ * This is necessary over and above standard JSON parsing to preserve our ability to call functions on this object.
+ * @param objectGraph The Object Graph.
+ * @param json The JSON representation of the object.
+ * @returns A constructed IAdSearchInformation from the JSON.
+ */
+ static from(objectGraph, json) {
+ var _a, _b, _c, _d;
+ const iAdInfo = new IAdSearchInformation(objectGraph, serverData.asString(json.placementType), serverData.asArrayOrEmpty(json.slotInfo), (_a = serverData.asString(json.iAdId)) !== null && _a !== void 0 ? _a : undefined, (_b = serverData.asString(json.appStoreClientRequestId)) !== null && _b !== void 0 ? _b : undefined, (_c = serverData.asBoolean(json.wasOdmlSuccessful)) !== null && _c !== void 0 ? _c : undefined, serverData.asInterface(json.positionInfo));
+ iAdInfo._iAdApplied = serverData.asBooleanOrFalse(json._iAdApplied);
+ iAdInfo._iAdAdamId = (_d = serverData.asString(json._iAdAdamId)) !== null && _d !== void 0 ? _d : undefined;
+ Object.assign(iAdInfo.pageFields, json.pageFields);
+ Object.assign(iAdInfo.clickFields, json.clickFields);
+ Object.assign(iAdInfo.impressionsFields, json.impressionsFields);
+ Object.assign(iAdInfo.fastImpressionFields, json.fastImpressionFields);
+ Object.assign(iAdInfo.iAdClickEventFields, json.iAdClickEventFields);
+ iAdInfo.updateContainerId(serverData.asString(json.containerId));
+ return iAdInfo;
+ }
+ /**
+ * Create an array of `IAdSlotInformation` objects based on the current ad information available.
+ * This isn't ideal, but we need to understand the available ad slots so we can report
+ * on all slots, whether they have ads in them or not.
+ */
+ static createInitialSlotInfos(objectGraph, placementType, positionInfo, flattenedTodayFeed) {
+ var _a;
+ switch (placementType) {
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ const productPageContainerId = IAdSearchInformation.containerIdFromType(placementType);
+ const slotIndex = (_a = positionInfo === null || positionInfo === void 0 ? void 0 : positionInfo.slot) !== null && _a !== void 0 ? _a : 0;
+ const productPageSlot = {
+ slotId: `${productPageContainerId}_${slotIndex}`,
+ slotIndex: slotIndex,
+ hasAdData: false,
+ };
+ return [
+ {
+ containerId: productPageContainerId,
+ slots: [productPageSlot],
+ },
+ ];
+ break;
+ case "today":
+ const placementEligibleSlotPositions = eligibleSlotPositionsForAdPlacement(objectGraph, placementType);
+ const eligibleAdSlots = isNothing(placementEligibleSlotPositions)
+ ? []
+ : placementEligibleSlotPositions.map((slotPosition) => slotPosition.slot);
+ const containerSlotInfoMapping = {};
+ /// iAd provides slots that are not zero based, adjust the slot
+ const adjustedAdSlot = isSome(positionInfo) ? positionInfo.slot - 1 : null;
+ let adPositionEncountered = false;
+ eligibleAdSlots.forEach((eligibleIndex) => {
+ var _a;
+ // If we've reached the ad we need to subtract 1 from the index, to act as if the ad ad was
+ // actually part of the feed.
+ const augmentedFeedIndexIndex = adPositionEncountered ? eligibleIndex - 1 : eligibleIndex;
+ const todayItem = flattenedTodayFeed === null || flattenedTodayFeed === void 0 ? void 0 : flattenedTodayFeed.find((item) => item.containedAdSlots.includes(augmentedFeedIndexIndex));
+ const isAdSlotIndex = adjustedAdSlot === eligibleIndex;
+ const todayContainerId = iAdContainerIdForSlotInTodayItem(augmentedFeedIndexIndex, isAdSlotIndex, todayItem);
+ const containerSlotInformation = (_a = containerSlotInfoMapping[todayContainerId]) !== null && _a !== void 0 ? _a : {
+ containerId: todayContainerId,
+ slots: [],
+ };
+ containerSlotInfoMapping[todayContainerId] = containerSlotInformation;
+ const todaySlot = {
+ slotId: `${todayContainerId}_${eligibleIndex}`,
+ slotIndex: eligibleIndex,
+ hasAdData: false,
+ };
+ containerSlotInformation.slots.push(todaySlot);
+ adPositionEncountered = adPositionEncountered || isAdSlotIndex;
+ });
+ return Object.values(containerSlotInfoMapping);
+ default:
+ return null;
+ }
+ }
+ get iAdIsPresent() {
+ return this._iAdApplied;
+ }
+ get iAdAdamId() {
+ return this._iAdAdamId;
+ }
+ /**
+ * Update our information with a new ad response.
+ * This is primarily used for asynchronous ad requests, where we need to update the metrics info subsequent to the initial page load.
+ * @param objectGraph The Object Graph.
+ * @param adResponse An ad response to use to update some fields.
+ */
+ updateForAdResponse(objectGraph, adResponse) {
+ var _a;
+ if (serverData.isNull(adResponse)) {
+ return;
+ }
+ this.placementType = adResponse.placementType;
+ this.placementId = this.placementIdFromType(this.placementType);
+ this.positionInfo = (_a = adResponse.onDeviceAd) === null || _a === void 0 ? void 0 : _a.positionInfo;
+ this.setInitialAdData(objectGraph, adResponse.iAdId, adResponse.clientRequestId);
+ }
+ /**
+ * Set the initial ad data, if available.
+ * This function requires that an ad fetch has been made, and can be called from two paths:
+ * 1. Via the constructor, where the ad fetch is made at page load time, or
+ * 2. Via `updateForAdResponse`, where the ad fetch was made asynchronously after the page load.
+ * @param objectGraph The Object Graph.
+ * @param iAdId The unique id provided for the ad instance.
+ * @param appStoreClientRequestId The unique id representing the App Store ad request client.
+ */
+ setInitialAdData(objectGraph, iAdId, appStoreClientRequestId) {
+ if (isNothing(appStoreClientRequestId)) {
+ return;
+ }
+ // ToroID Suppression expects this to be replaced with -1 for figaro events
+ const iAdIdValue = isNothing(iAdId) ? "-1" : iAdId;
+ this.pageFields[EventLinter.hasIAdData] = true;
+ switch (this.placementType) {
+ case "today":
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ // Only set `hasIAdData` on impressions fields initially if it's a Chainlink placement.
+ // This covers the "ad eligible" cases where we still want to check ad metrics when ads are not present.
+ this.impressionsFields[EventLinter.hasIAdData] = true;
+ break;
+ default:
+ break;
+ }
+ this.pageFields["iAdAppStoreClientRequestId"] = appStoreClientRequestId;
+ switch (this.placementType) {
+ case "today":
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ this.clickFields["iAdAppStoreClientRequestId"] = appStoreClientRequestId;
+ this.impressionsFields["iAdAppStoreClientRequestId"] = appStoreClientRequestId;
+ break;
+ default:
+ break;
+ }
+ this.pageFields["iAdId"] = iAdIdValue;
+ this.impressionsFields["iAdId"] = iAdIdValue;
+ this.clickFields["iAdId"] = iAdIdValue;
+ this.updateContainerId(null);
+ // Update slot info with data
+ this.updateSlotInfo();
+ if (serverData.isDefinedNonNullNonEmpty(this.slotInfo)) {
+ this.pageFields["iAdSlotInfo"] = this.slotInfo;
+ this.clickFields["iAdSlotInfo"] = this.slotInfo;
+ this.impressionsFields["iAdSlotInfo"] = this.slotInfo;
+ }
+ if (this.placementId !== null && this.placementId.length > 0) {
+ this.pageFields["iAdPlacementId"] = this.placementId;
+ this.clickFields["iAdPlacementId"] = this.placementId;
+ // Attach the `iAdPlacementId` to top-level impressions for v4 impressions.
+ this.impressionsFields["iAdPlacementId"] = this.placementId;
+ // For Chainlink placements, the `iAdPlacementId` sits within the impressions array.
+ // We manually remove `iAdPlacementId` from the top-level fields for v5 impressions in `createMetricsFastImpressionsData`.
+ switch (this.placementType) {
+ case "today":
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ this.fastImpressionFields["iAdPlacementId"] = this.placementId;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ /**
+ * Update the containerId based on the current placementType.
+ */
+ updateContainerId(containerId) {
+ if (this.placementType === "today") {
+ this.containerId = containerId !== null && containerId !== void 0 ? containerId : null;
+ if (serverData.isDefinedNonNull(this.containerId)) {
+ this.clickFields["iAdContainerId"] = this.containerId;
+ this.fastImpressionFields["iAdContainerId"] = this.containerId;
+ }
+ }
+ else {
+ this.containerId =
+ this.placementType === null ? null : IAdSearchInformation.containerIdFromType(this.placementType);
+ if (serverData.isDefinedNonNull(this.containerId)) {
+ this.pageFields["iAdContainerId"] = this.containerId;
+ this.clickFields["iAdContainerId"] = this.containerId;
+ this.fastImpressionFields["iAdContainerId"] = this.containerId;
+ }
+ }
+ }
+ /**
+ * @param slotIndex The slot index to look for a container Id for
+ * @returns The container Id for the given slot index, based off the slot infos
+ */
+ containerIdForSlotIndex(slotIndex) {
+ if (isNothing(slotIndex) || isNothing(this.slotInfo)) {
+ return null;
+ }
+ for (const slotInfo of this.slotInfo) {
+ for (const slot of slotInfo.slots) {
+ if (slot.slotIndex === slotIndex) {
+ return slotInfo.containerId;
+ }
+ }
+ }
+ return this.containerId;
+ }
+ apply(objectGraph, adData) {
+ if (isNothing(adData) || serverData.isNullOrEmpty(adData)) {
+ return;
+ }
+ const iAdAdamId = adData.id;
+ const iAdConfigurationDictionary = attributeAsDictionary(adData, "iad");
+ this._iAdAdamId = iAdAdamId;
+ if (iAdConfigurationDictionary) {
+ this.impressionsFields[EventLinter.hasIAdData] = true;
+ this.clickFields[EventLinter.hasIAdData] = true;
+ const impressionId = metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["impressionId"]);
+ this.fastImpressionFields["iAdImpressionId"] = impressionId;
+ this.clickFields["iAdImpressionId"] = impressionId;
+ const metadata = metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["metadata"]);
+ this.clickFields["iAdMetadata"] = metadata;
+ this.fastImpressionFields["iAdMetadata"] = metadata;
+ // Ads boldly populate `adamId` on pages. This is correct.
+ this.pageFields["adamId"] = iAdAdamId;
+ this.pageFields["iAd"] = {
+ iAdFormat: metricsUtil.sanitizedMetricsDictionary(serverData.asInterface(serverData.asJSONValue(iAdConfigurationDictionary), "format")),
+ iAdAlgoId: metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["algoId"]),
+ iAdImpressionId: metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["impressionId"]),
+ iAdMetadata: metricsUtil.emptyStringIfNullOrUndefined(iAdConfigurationDictionary["metadata"]),
+ };
+ const productVariantData = productVariantDataForData(objectGraph, adData);
+ this.updateIAdMetricsFieldsForProductVariantData(productVariantData, this.clickFields);
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ if (objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) {
+ const selectedCustomCreativeId = getSelectedCustomCreativeId(adData);
+ if (this.placementType === "today") {
+ this.updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, this.fastImpressionFields);
+ }
+ else {
+ this.updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, this.impressionsFields);
+ }
+ this.updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, this.clickFields);
+ }
+ }
+ Object.assign(this.iAdClickEventFields, iAdConfigurationDictionary);
+ this._iAdApplied = true;
+ // Clear out any prior missed opportunity reason if we have an ad.
+ this.setMissedOpportunity(objectGraph, undefined, this.placementType);
+ }
+ // Update slot info after ad is applied.
+ this.updateSlotInfo();
+ if (serverData.isDefinedNonNullNonEmpty(this.slotInfo)) {
+ this.pageFields["iAdSlotInfo"] = this.slotInfo;
+ this.clickFields["iAdSlotInfo"] = this.slotInfo;
+ this.impressionsFields["iAdSlotInfo"] = this.slotInfo;
+ }
+ }
+ /**
+ * Apply the click fields that were attached to the page request we're following.
+ * This will generally be following on from click on a lockup with ad data attached, where we want to pull
+ * some of the click fields from ad to be applied to this new page data.
+ *
+ * The goal here is to attach enough metadata into the incoming page that we can attribute any offer action
+ * to the ad that was tapped on to reach this page. Whilst doing this we don't want to attach too much
+ * ad data to the page, as this can start to interfere with metrics for other ad placements. This results in
+ * us manually inserting and removing fields to achieve the right balance.
+ * @param iAdAdamId The adamId for the ad.
+ * @param fields The set of fields to apply to our clickFields.
+ */
+ applyClickFieldsFromPageRequest(iAdAdamId, fields) {
+ this._iAdApplied = true;
+ this._iAdAdamId = iAdAdamId;
+ Object.assign(this.clickFields, fields);
+ // We don't want to assign any of the fields to the page event, as this looks like an ad is being shown
+ // on the page we're navigating to.
+ Object.keys(this.pageFields).forEach((field) => delete this.pageFields[field]);
+ }
+ setSpecifiedAlignedRegionUsed(didUseSpecifiedCreative) {
+ this.fastImpressionFields["iAdIsSpecifiedCreativeUsed"] = didUseSpecifiedCreative;
+ this.clickFields["iAdIsSpecifiedCreativeUsed"] = didUseSpecifiedCreative;
+ }
+ /**
+ * Set the template type for the page.
+ */
+ setTemplateType(templateType) {
+ this.pageFields["iAdTemplateType"] = templateType;
+ this.impressionsFields["iAdTemplateType"] = templateType;
+ this.clickFields["iAdTemplateType"] = templateType;
+ }
+ setMissedOpportunity(objectGraph, reason, placementType) {
+ this.missedOpportunityReason = reason;
+ // Only set in page and impressions fields if reason is non-null.
+ if (serverData.isDefinedNonNull(reason)) {
+ this.clickFields["iAdMissedOpportunityReason"] = reason;
+ // Chainlink placements don't expect "iAdMissedOpportunityReason" at the top level of impressions or page events.
+ switch (this.placementType) {
+ case "today":
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ break;
+ default:
+ this.pageFields["iAdMissedOpportunityReason"] = reason;
+ this.impressionsFields["iAdMissedOpportunityReason"] = reason;
+ break;
+ }
+ }
+ else {
+ delete this.clickFields["iAdMissedOpportunityReason"];
+ // Generally, we just want to remove the missed opportunity reason if one isn't set.
+ // The 'productPageYMALDuringDownload' placement is a special case where it's placed on the page
+ // subsequent to a previous placement, and metrics is updated via the pageChange metrics.
+ // This means that we need to send `null` as the value for this placement, in order to clear
+ // any possible missed opportunity value from the placement it's replacing.
+ switch (placementType) {
+ case "productPageYMALDuringDownload":
+ this.pageFields["iAdMissedOpportunityReason"] = null;
+ this.impressionsFields["iAdMissedOpportunityReason"] = null;
+ break;
+ default:
+ delete this.pageFields["iAdMissedOpportunityReason"];
+ delete this.impressionsFields["iAdMissedOpportunityReason"];
+ break;
+ }
+ }
+ // Only set in page and impressions fields if reason is non-null.
+ if (serverData.isDefinedNonNull(reason)) {
+ this.pageFields["iAdMissedOpportunityReason"] = reason;
+ this.impressionsFields["iAdMissedOpportunityReason"] = reason;
+ }
+ else {
+ delete this.pageFields["iAdMissedOpportunityReason"];
+ delete this.impressionsFields["iAdMissedOpportunityReason"];
+ }
+ // Update slotInfo with missed opportunity reason.
+ this.updateSlotInfo();
+ if (serverData.isDefinedNonNullNonEmpty(this.slotInfo)) {
+ this.pageFields["iAdSlotInfo"] = this.slotInfo;
+ this.clickFields["iAdSlotInfo"] = this.slotInfo;
+ this.impressionsFields["iAdSlotInfo"] = this.slotInfo;
+ }
+ }
+ placementIdFromType(type) {
+ switch (type) {
+ case "searchLanding":
+ return "APPSTORE_SEARCH_LANDING_PAGE";
+ case "searchResults":
+ return "APPSTORE_SEARCH_RESULT_PAGE";
+ case "today":
+ return "APPSTORE_TODAY_TAB";
+ case "productPageYMAL":
+ return "APPSTORE_PRODUCT_PAGE";
+ case "productPageYMALDuringDownload":
+ return "APPSTORE_PRODUCT_PAGE_DOWNLOAD";
+ default:
+ throw new Error(`This method should never be called with value: ${type}`);
+ }
+ }
+ static placementTypeFromPlacementId(objectGraph, id) {
+ switch (id) {
+ case "APPSTORE_SEARCH_LANDING_PAGE":
+ return "searchLanding";
+ case "APPSTORE_SEARCH_RESULT_PAGE":
+ return "searchResults";
+ case "APPSTORE_TODAY_TAB":
+ return "today";
+ case "APPSTORE_PRODUCT_PAGE":
+ return "productPageYMAL";
+ case "APPSTORE_PRODUCT_PAGE_DOWNLOAD":
+ return "productPageYMALDuringDownload";
+ default:
+ objectGraph.console.log(`Failed to get placementType from placementId ${id}. Defaulting to searchResults`);
+ // For legacy reasons we fall back to `seachResults` when nothing is provided.
+ // Being the first ad slot, in the past this has been assumed as the "default".
+ return "searchResults";
+ }
+ }
+ /**
+ * Get a containerId value for a given placement type to attach to the ad's metrics.
+ * @param type The AdPlacementType for the current ad.
+ * @returns A string value for containerId, if the placement has one. Otherwise null.
+ */
+ static containerIdFromType(type) {
+ switch (type) {
+ case "productPageYMAL":
+ return "customers-also-bought-apps";
+ case "productPageYMALDuringDownload":
+ return "customers-also-bought-apps-download";
+ case "today":
+ return null; // Today page has variable containerIds that are updated programatically
+ default:
+ return null;
+ }
+ }
+ /**
+ * Return the fast impressions metrics fields for an item using the location tracker.
+ *
+ * `fastImpressionFields` are the items inside the impressions array of metrics events. Although some fields are stable,
+ * these are unique to a given position, so we must use that position information to generate the right fields.
+ *
+ * @param locationTracker A LocationTracker used to identify the relevant information to attach to the metrics fields.
+ * @param adSlotOverride The ad slot to use for the metrics fields. If not provided, the ad slot will be inferred from
+ * the location tracker. This is needed for today where the ad can span different shelves so the position is going to be 0 for multiple ads
+ * @returns Relevant metrics fields for the item in an impressions array.
+ */
+ fastImpressionsFieldsForCurrentItem(locationTracker, adSlotOverride) {
+ switch (this.placementType) {
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ case "today":
+ let position;
+ if (isSome(adSlotOverride)) {
+ position = adSlotOverride;
+ }
+ else {
+ const location = metricsLocation.currentLocation(locationTracker);
+ // If the current location is within a todayCard, we want to extract the location of the card, not the location
+ // of this lockup within the card.
+ if (location !== null && location.locationType === "todayCard") {
+ position = metricsLocation.previousPosition(locationTracker);
+ }
+ else {
+ position = metricsLocation.currentPosition(locationTracker);
+ }
+ }
+ const sharedFields = shallowCopyOf(this.fastImpressionFields);
+ sharedFields["iAdSlotId"] = `${this.containerIdForSlotIndex(position)}_${position}`;
+ if (position !== this.adjustedSlotIndex) {
+ // If the current position is not where the ad is, we only want a subset of the tracked fields.
+ const allowedFields = ["iAdEligible", "iAdPlacementId", "iAdContainerId", "iAdSlotId"];
+ Object.keys(sharedFields).forEach((key) => {
+ if (!allowedFields.includes(key)) {
+ delete sharedFields[key];
+ }
+ });
+ }
+ return sharedFields;
+ default:
+ return this.fastImpressionFields;
+ }
+ }
+ /**
+ * Get the adjusted slot index of the ad we're tracking.
+ * This is zero-based - Ad Platforms returns slot info to us one-based but we always adjust it before using it anywhere.
+ */
+ get adjustedSlotIndex() {
+ var _a;
+ const positionInfoSlotIndex = (_a = this.positionInfo) === null || _a === void 0 ? void 0 : _a.slot;
+ // The slot as provided by ad platforms is one-based - adjust it so we're working with zero-based numbers.
+ if (serverData.isDefinedNonNull(positionInfoSlotIndex)) {
+ return positionInfoSlotIndex - 1;
+ }
+ return null;
+ }
+ /**
+ * Update the existing slot information now that new data has been applied.
+ */
+ updateSlotInfo() {
+ if (isNothing(this.slotInfo)) {
+ return;
+ }
+ switch (this.placementType) {
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ for (const containerSlotInfo of this.slotInfo) {
+ for (const slot of containerSlotInfo.slots) {
+ slot.hasAdData = this.iAdIsPresent;
+ if (serverData.isDefinedNonNull(this.missedOpportunityReason)) {
+ slot.missedOpportunityReason = this.missedOpportunityReason;
+ }
+ }
+ }
+ break;
+ case "today":
+ const adjustedSlotIndex = this.adjustedSlotIndex;
+ for (const containerSlotInfo of this.slotInfo) {
+ for (const slot of containerSlotInfo.slots) {
+ // Check whether the current index matches our slot information.
+ // If so, an ad is intended to be placed here.
+ const isAdInCurrentIndex = adjustedSlotIndex === slot.slotIndex;
+ // In order to understand whether a given slot has ad data, we ensure that a usable ad is present,
+ // and the current slot is the one an ad is placed in.
+ const hasAdData = this.iAdIsPresent && isAdInCurrentIndex;
+ // By default, use the missed opportunity reason, if any, stored here.
+ let missedOpportunityReason = this.missedOpportunityReason;
+ // If:
+ // - the ad is not in the current index we're building slot info for, and
+ // - there is a valid slot index
+ // we override the reason to 'NOAD', as it means another eligible slot was filled.
+ if (!isAdInCurrentIndex && serverData.isDefinedNonNull(adjustedSlotIndex)) {
+ missedOpportunityReason = "NOAD";
+ }
+ slot.hasAdData = hasAdData;
+ if (serverData.isDefinedNonNull(missedOpportunityReason)) {
+ slot.missedOpportunityReason = missedOpportunityReason;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ /**
+ * Modifies the provided MetricsFields for the ProductVariantData specific to fields required for iAds.
+ * @param productVariantData The variant data of impressionable data to apply.
+ * @param metricsFields An existing set of MetricsFields to modify.
+ */
+ updateIAdMetricsFieldsForProductVariantData(productVariantData, metricsFields) {
+ if (isSome(productVariantData) && productVariantDataHasVariant(productVariantData, "customProductPage")) {
+ metricsFields["iAdPageCustomId"] = productVariantData.productPageId;
+ }
+ else {
+ // If a custom product page doesn't exist, we remove the fields.
+ delete metricsFields["iAdPageCustomId"];
+ }
+ }
+ /**
+ * Modifies the provided MetricsFields to include the custom id for iAds.
+ * @param selectedCustomCreativeId The id for the custom creative that we want to set as custom id.
+ * @param metricsFields An existing set of MetricsFields to modify.
+ */
+ updateIAdMetricsFieldsForAlignedRegion(selectedCustomCreativeId, metricsFields) {
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ if (isSome(selectedCustomCreativeId)) {
+ metricsFields["iAdCustomId"] = selectedCustomCreativeId;
+ }
+ else {
+ delete metricsFields["iAdCustomId"];
+ }
+ }
+ }
+ /**
+ * Return the event version for fast impressions based on the placement of the ad.
+ * Impressions v5 is being rolled out first for Chainlink. Once all impressions have adopted it, this can be removed.
+ */
+ get fastImpressionsEventVersion() {
+ switch (this.placementType) {
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ case "today":
+ return 5;
+ default:
+ return 4;
+ }
+ }
+ /**
+ * Return whether ad rotation fields should be included on metrics events, based on the current placement type.
+ */
+ get shouldIncludeAdRotationFields() {
+ switch (this.placementType) {
+ case "productPageYMAL":
+ case "productPageYMALDuringDownload":
+ case "today":
+ return false;
+ case "searchLanding":
+ case "searchResults":
+ return true;
+ default:
+ return true;
+ }
+ }
+}
+/**
+ * Return the container id to use for a today feed item
+ * @param slotIndex The slot index that we found a TodayItem for
+ * @param isAdSlot Whether this slotIndex is the slot the ad is in
+ * @param todayItem The today item that this card is contained in, this could be a single item or a story group item
+ * @returns The configuration to use when parsing a today card
+ */
+export function iAdContainerIdForSlotInTodayItem(slotIndex, isAdSlot, todayItem) {
+ if (isNothing(todayItem)) {
+ return "0";
+ }
+ switch (todayItem.type) {
+ case FlattenedTodayItemType.EditorialItemGroup:
+ const storyGroupHasMultipleItems = todayItem.containedAdSlots.length > 1;
+ const slotIndexIsFirstOrLastInStoryGroup = !storyGroupHasMultipleItems ||
+ slotIndex === todayItem.containedAdSlots[0] ||
+ slotIndex === todayItem.containedAdSlots[todayItem.containedAdSlots.length - 1];
+ if (isAdSlot && slotIndexIsFirstOrLastInStoryGroup) {
+ // If the story group has fewer than 2 items, that means theres not valid slot **in** the actual story group
+ // to place the ad, so its going to be at the top level. Or if there are multiple items, but the slot is the
+ // first or last item there is no reason to pull the ad into the story group, it should be top level.
+ return "0";
+ }
+ else {
+ // Using the `data` from the todayItem here and not the todayCardData, because
+ // this will be the data for the containing story gorup
+ return "0";
+ }
+ default:
+ return "0";
+ }
+}
+/**
+ * Create the iAdInformation for a given page if necessary
+ * @param objectGraph The dependency graph for the app store
+ * @param pageId The id of the page this iAd info is being used for
+ * @param response The MAPI response for this page
+ * @param positionInfo The position information on where the ad will attempted to be inserted
+ * @param flattenedTodayFeed The flattened to today feed if this is for a today page
+ * @returns The iAdInformation to be used in a metrics page information object
+ */
+export function iAdInformationFromMediaApiResponse(objectGraph, pageId, response, positionInfo = null, flattenedTodayFeed = null) {
+ var _a;
+ /** <rdar://problem/33764430> Toro: iAd is missing AD_OPEN figaro event when tapping on ad and pressing OPEN through the product page */
+ const iAdClickInfoString = serverData.asString(response, iAdURLParameterStringToken);
+ if (isNothing(iAdClickInfoString)) {
+ return null;
+ }
+ const iAdClickInfo = JSON.parse(iAdClickInfoString);
+ // rdar://72607206 (Gibraltar: Tech Debt: Population of iAd fields in Product Page)
+ const placementType = IAdSearchInformation.placementTypeFromPlacementId(objectGraph, serverData.asString(iAdClickInfo, "iAdPlacementId"));
+ const iAdInfo = new IAdSearchInformation(objectGraph, placementType, IAdSearchInformation.createInitialSlotInfos(objectGraph, placementType, positionInfo, flattenedTodayFeed), (_a = serverData.asString(iAdClickInfo, "iAdId")) !== null && _a !== void 0 ? _a : undefined, undefined, undefined, positionInfo);
+ iAdInfo.applyClickFieldsFromPageRequest(pageId !== null && pageId !== void 0 ? pageId : undefined, iAdClickInfo);
+ return iAdInfo;
+}
+/** @public */
+export class MetricsPageInformation {
+ constructor(base = {}) {
+ this.baseFields = base;
+ }
+}
+//# sourceMappingURL=models.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/page.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/page.js
new file mode 100644
index 0000000..0c088b1
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/page.js
@@ -0,0 +1,482 @@
+import { PageInvocationPoint } from "@jet/environment/types/metrics";
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import { ResponseMetadata } from "../../../foundation/network/network";
+import { Parameters } from "../../../foundation/network/url-constants";
+import * as urls from "../../../foundation/network/urls";
+import * as dateUtil from "../../../foundation/util/date-util";
+import * as objects from "../../../foundation/util/objects";
+import * as content from "../../content/content";
+import { productVariantDataForData } from "../../product-page/product-page-variants";
+import * as guidedSearch from "../../search/guided-search/guided-search-metrics";
+import * as searchAds from "../../search/search-ads";
+import * as metricsBuilder from "../builder";
+import * as misc from "./misc";
+import * as metricsModels from "./models";
+import * as metricsUtil from "./util";
+import { searchMetricsDataSetID } from "../../search/search-common";
+//* *************************
+//* Page Metrics
+//* *************************
+export function metricsPageInformationFromMarketingItemMediaApiResponse(objectGraph, pageType, pageId, marketingItemData) {
+ var _a;
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, pageType, pageId, marketingItemData);
+ if (serverData.isDefinedNonNull(marketingItemData)) {
+ pageInformation.mercuryMetricsData =
+ (_a = metricsUtil.marketingItemTopLevelBaseFieldsFromData(objectGraph, marketingItemData)) !== null && _a !== void 0 ? _a : undefined;
+ }
+ return pageInformation;
+}
+export function metricsPageInformationFromMediaApiResponse(objectGraph, pageType, pageId, response, pageDetails, overrideIAdInfo) {
+ var _a, _b, _c;
+ const pageInformation = (_a = serverData.asInterface(response, ResponseMetadata.pageInformation, {})) !== null && _a !== void 0 ? _a : {};
+ const timingMetrics = serverData.asInterface(response, ResponseMetadata.timingValues);
+ const pageUrl = serverData.asString(response, ResponseMetadata.requestedUrl);
+ let productVariantData;
+ const responsePageBaseFields = pageInformation;
+ responsePageBaseFields.pageType = pageType;
+ responsePageBaseFields.pageId = pageId;
+ if (pageDetails) {
+ responsePageBaseFields["pageDetails"] = pageDetails;
+ }
+ if (pageType === "Software") {
+ const softwareData = mediaDataStructure.dataFromDataContainer(objectGraph, response);
+ if (isSome(softwareData)) {
+ const name = mediaAttributes.attributeAsString(softwareData, "name");
+ const artistName = mediaAttributes.attributeAsString(softwareData, "artistName");
+ responsePageBaseFields["pageDetails"] = `${artistName}_${name}`;
+ // To distinguish normal apps from Arcade apps
+ if (content.isArcadeSupported(objectGraph, softwareData)) {
+ responsePageBaseFields["softwareType"] = "Arcade";
+ }
+ productVariantData = productVariantDataForData(objectGraph, softwareData);
+ }
+ }
+ else if (pageType === "Genre") {
+ responsePageBaseFields["pageDetails"] = `${pageType}_${pageId}`;
+ }
+ else if (pageType === "Search") {
+ responsePageBaseFields["pageDetails"] = "Apps";
+ }
+ else if (pageType === "SearchLanding" && pageId === "SearchLanding") {
+ responsePageBaseFields["pageDetails"] = `${pageType}_${pageId}`;
+ }
+ const pageInfo = new metricsModels.MetricsPageInformation((_b = metricsUtil.sanitizedMetricsDictionary(responsePageBaseFields)) !== null && _b !== void 0 ? _b : {});
+ if (timingMetrics !== null) {
+ pageInfo.timingMetrics = timingMetrics;
+ if (isSome(pageUrl)) {
+ pageInfo.pageUrl = pageUrl;
+ }
+ }
+ // For product pages, every shelf is creating it's a new page information. There should be one top-level one, shared for page.
+ // Revisit in: rdar://77227964 (Metrics: ProductPage: Shelves should share page information instead of building it per-shelf.)
+ if (serverData.isDefinedNonNull(productVariantData)) {
+ pageInfo.productVariantData = productVariantData;
+ }
+ const iAdInfo = overrideIAdInfo !== null && overrideIAdInfo !== void 0 ? overrideIAdInfo : metricsModels.iAdInformationFromMediaApiResponse(objectGraph, pageId, response);
+ if (isSome(iAdInfo)) {
+ pageInfo.iAdInfo = iAdInfo;
+ }
+ pageInfo.recoMetricsData = (_c = mediaDataStructure.metricsFromMediaApiObject(response)) !== null && _c !== void 0 ? _c : undefined;
+ return pageInfo;
+}
+export function fakeMetricsPageInformation(objectGraph, pageType, pageId, pageDetails, iAdClickInfo) {
+ var _a;
+ const page = new metricsModels.MetricsPageInformation({
+ pageType: pageType,
+ pageId: pageId,
+ page: `${pageType}_${pageId}`,
+ pageDetails: pageDetails,
+ });
+ if (iAdClickInfo) {
+ // rdar://72607206 (Gibraltar: Tech Debt: Population of iAd fields in Product Page)
+ const placementType = metricsModels.IAdSearchInformation.placementTypeFromPlacementId(objectGraph, serverData.asString(iAdClickInfo, "iAdPlacementId"));
+ page.iAdInfo = new metricsModels.IAdSearchInformation(objectGraph, placementType, metricsModels.IAdSearchInformation.createInitialSlotInfos(objectGraph, placementType, null, null), (_a = serverData.asString(iAdClickInfo, "iAdId")) !== null && _a !== void 0 ? _a : undefined);
+ page.iAdInfo.applyClickFieldsFromPageRequest(pageId, iAdClickInfo);
+ }
+ return page;
+}
+export function addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation, fieldsModifier, isDefaultBrowser) {
+ if (serverData.isNull(pageInformation)) {
+ return;
+ }
+ page.pageMetrics.pageFields = misc.fieldsFromPageInformation(pageInformation);
+ page.pageMetrics.addManyInstructions(impressionsInstructionsFromPageData(objectGraph, pageInformation, fieldsModifier));
+ page.pageMetrics.addData(pageEventFromPageData(objectGraph, pageInformation, fieldsModifier, isDefaultBrowser), [
+ PageInvocationPoint.pageEnter,
+ ]);
+ page.pageMetrics.addData(pageExitEventFromPageData(objectGraph, pageInformation, fieldsModifier), [
+ PageInvocationPoint.pageExit,
+ ]);
+ page.pageMetrics.pageRenderFields = pageRenderFromPageData(objectGraph, pageInformation, fieldsModifier);
+ page.pageRenderMetrics = pageRenderFromPageData(objectGraph, pageInformation, fieldsModifier);
+ if (pageRequiresBackEvent(page)) {
+ page.pageMetrics.addData(backEventFromPageData(objectGraph, pageInformation, fieldsModifier), [
+ PageInvocationPoint.backButton,
+ ]);
+ }
+ const fetchTimingMetricsBuilder = objectGraph.fetchTimingMetricsBuilder;
+ if (isSome(fetchTimingMetricsBuilder)) {
+ fetchTimingMetricsBuilder.decorate(page);
+ }
+}
+/**
+ * @param page The page to check
+ * @returns Whether this page requires a back event to be sent when the user navigates back from it.
+ */
+function pageRequiresBackEvent(page) {
+ const isSearchResultsPage = page instanceof models.SearchResults;
+ const isContingentOfferDetailPage = page instanceof models.ContingentOfferDetailPage;
+ const isWinbackOfferDetailPage = page instanceof models.OfferItemDetailPage;
+ return !isSearchResultsPage && !isContingentOfferDetailPage && !isWinbackOfferDetailPage;
+}
+export function copyMetricsEventsIntoSidepackedPagewithInformation(objectGraph, page, pageInformation) {
+ if (serverData.isNull(pageInformation)) {
+ return;
+ }
+ page.pageMetrics.addData(backEventFromPageData(objectGraph, pageInformation, undefined), [
+ PageInvocationPoint.backButton,
+ ]);
+ if (serverData.isNull(page.pageMetrics.pageFields)) {
+ page.pageMetrics.pageFields = {};
+ }
+}
+/**
+ * Constructs metrics page information for a page navigated to from the action links on the product page.
+ * @param productId The adamId of the product for which these action links are displayed.
+ * @param pageType The metrics pageType for the page the links navigates to.
+ */
+export function pageInformationForActionLinkPage(objectGraph, productId, pageType) {
+ const base = {
+ pageId: productId || "",
+ pageType: pageType,
+ };
+ return new metricsModels.MetricsPageInformation(base);
+}
+/**
+ * Create metrics page information for rooms that may not have an its id.
+ * @param pageId The identifier to differentiate room
+ */
+export function pageInformationForRoom(objectGraph, pageId) {
+ const pageType = "Room";
+ const page = new metricsModels.MetricsPageInformation({
+ pageType: pageType,
+ pageId: pageId,
+ page: `${pageType}_${pageId}`,
+ });
+ return page;
+}
+/**
+ * Constructs empty page information for search hints 'page' for given prefix term and optional dataSetId.
+ */
+export function pageInformationForSearchHintsPage(objectGraph, prefixTerm, pageUrl, dataSetId) {
+ const base = {
+ pageId: "hints",
+ pageType: "Search",
+ };
+ if (dataSetId) {
+ base[searchMetricsDataSetID] = dataSetId;
+ }
+ const pageInformation = new metricsModels.MetricsPageInformation(base);
+ pageInformation.pageUrl = pageUrl;
+ return pageInformation;
+}
+export function pageInformationForIAPPage(objectGraph, parentId, iapId) {
+ const base = {
+ pageId: iapId || "",
+ pageType: "IAPInstallPage",
+ parentId: metricsUtil.emptyStringIfNullOrUndefined(parentId),
+ };
+ const pageInformation = new metricsModels.MetricsPageInformation(base);
+ return pageInformation;
+}
+/**
+ * Build the pageInformation to use for search pages.
+ */
+export function pageInformationForSearchPage(objectGraph, request, response, termContext, searchRequestUrl, pageId, sponsoredSearchRequestData, wasOdmlSuccessful, guidedSearchData) {
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "Search", pageId, response);
+ pageInformation.searchTermContext = termContext;
+ pageInformation.pageUrl = searchRequestUrl; // For Search, pageUrl is request url
+ if (guidedSearchData) {
+ pageInformation.guidedSearch = guidedSearch.guidedSearchPageInformationFields(objectGraph, request, guidedSearchData);
+ }
+ if (searchAds.platformSupportsAdverts(objectGraph) && sponsoredSearchRequestData != null) {
+ pageInformation.iAdInfo = new metricsModels.IAdSearchInformation(objectGraph, "searchResults", metricsModels.IAdSearchInformation.createInitialSlotInfos(objectGraph, "searchResults", null, null), sponsoredSearchRequestData.iAdId, sponsoredSearchRequestData.appStoreClientRequestId, wasOdmlSuccessful);
+ }
+ return pageInformation;
+}
+export function pageInformationForSegmentedSearchPage(objectGraph, response, termContext, searchRequestUrl, groupId) {
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "Search", groupId, response);
+ pageInformation.searchTermContext = termContext;
+ pageInformation.pageUrl = searchRequestUrl;
+ return pageInformation;
+}
+export function pageInformationForAppPromotionDetailPage(objectGraph, appPromotionType, appPromotionId, parentAppAdamId, referrerData, recoMetricsData) {
+ let pageId = "";
+ let pageType = "";
+ switch (appPromotionType) {
+ case models.AppPromotionType.AppEvent:
+ pageId = `${appPromotionId}_${parentAppAdamId}`;
+ pageType = "EventDetails";
+ break;
+ case models.AppPromotionType.ContingentOffer:
+ case models.AppPromotionType.OfferItem:
+ pageId = `${appPromotionId}`;
+ pageType = "OfferDetails";
+ break;
+ default:
+ break;
+ }
+ const base = {
+ pageId: pageId,
+ pageType: pageType,
+ };
+ if (serverData.isDefinedNonNull(referrerData)) {
+ base["refApp"] = referrerData["app"];
+ base["extRefUrl"] = referrerData["externalUrl"];
+ }
+ const pageInformation = new metricsModels.MetricsPageInformation(base);
+ pageInformation.recoMetricsData = recoMetricsData !== null && recoMetricsData !== void 0 ? recoMetricsData : undefined;
+ return pageInformation;
+}
+export function addPageEventsToInAppPurchasePage(objectGraph, page, pageInformation) {
+ addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation);
+}
+export function makePageReferralEligible(objectGraph, page) {
+ if (serverData.isNull(page) || serverData.isNull(page.pageMetrics)) {
+ return;
+ }
+ const pageInstructions = page.pageMetrics.instructions;
+ if (!serverData.isNull(pageInstructions)) {
+ for (const instruction of pageInstructions) {
+ // We shoudl only be requesting crossfire information for page events.
+ // if we dont, and add it to all of the instructions then we end up clearing out
+ // the referral information on the native side during any other event that happens on this page
+ // before a buy can happen, for instance an impressions event that occurs on page exit when
+ // displaying an upsell sheet.
+ if (instruction.data.fields["eventType"] !== "page") {
+ continue;
+ }
+ instruction.data.includingFields.push("crossfireReferral");
+ }
+ }
+ // Ensure that for referral eligible pages, the attribution is included in the purchase configuration
+ // <rdar://problem/45420867> Crossfire: Metrics: extRefApp2/extRefUrl2 buyparam are being persisted too strongly, being sent on buys of other app pages
+ let productLockup = null;
+ if (page instanceof models.ProductPage) {
+ productLockup = page;
+ }
+ else if (page instanceof models.ShelfBasedProductPage) {
+ productLockup = page.lockup;
+ }
+ if (productLockup) {
+ const eligibleActions = [];
+ if (productLockup.buttonAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction);
+ }
+ else if (productLockup.buttonAction instanceof models.OfferConfirmationAction &&
+ productLockup.buttonAction.buyAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.buyAction);
+ }
+ else if (productLockup.buttonAction instanceof models.OfferAlertAction &&
+ productLockup.buttonAction.completionAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.completionAction);
+ }
+ else if (productLockup.buttonAction instanceof models.OfferStateAction) {
+ if (productLockup.buttonAction.buyAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.buyAction);
+ }
+ if (productLockup.buttonAction.defaultAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.defaultAction);
+ }
+ if (productLockup.buttonAction.openAction instanceof models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.openAction);
+ }
+ if (productLockup.buttonAction.subscribePageAction instanceof models.FlowAction &&
+ productLockup.buttonAction.subscribePageAction.page === "arcadeSubscribe" &&
+ isSome(productLockup.buttonAction.subscribePageAction.pageUrl) &&
+ productLockup.buttonAction.subscribePageAction.pageUrl.length > 0) {
+ const pageUrl = urls.URL.from(productLockup.buttonAction.subscribePageAction.pageUrl);
+ pageUrl.param(Parameters.includePostSubscribeAttribution, "true");
+ productLockup.buttonAction.subscribePageAction.pageUrl = pageUrl.build();
+ }
+ if (productLockup.buttonAction.subscribePageAction instanceof models.FlowAction &&
+ productLockup.buttonAction.subscribePageAction.pageData instanceof models.MarketingItemRequestInfo &&
+ productLockup.buttonAction.subscribePageAction.pageData.purchaseSuccessAction instanceof
+ models.OfferAction) {
+ eligibleActions.push(productLockup.buttonAction.subscribePageAction.pageData.purchaseSuccessAction);
+ }
+ }
+ for (const eligibleAction of eligibleActions) {
+ if (eligibleAction.purchaseConfiguration) {
+ eligibleAction.purchaseConfiguration.excludeAttribution = false;
+ }
+ }
+ }
+}
+/**
+ * Add an updated set of page metrics to the shelf that will be used to update the existing page metrics,
+ * as well as be sent as a `pageChange` event.
+ * @param objectGraph The object graph.
+ * @param shelf The shelf to attach the pageChange fields to
+ * @param pageInformation The modified page information for the containing page.
+ * @param fieldsModifier A field modifier, if required.
+ * @returns
+ */
+export function addPageChangeFieldsToShelfWithInformation(objectGraph, shelf, pageInformation, fieldsModifier) {
+ if (serverData.isNull(pageInformation)) {
+ return;
+ }
+ let updatedEvents = [];
+ // Page
+ const pageFields = misc.fieldsFromPageInformation(pageInformation);
+ const pageEvent = pageEventFromPageData(objectGraph, pageInformation, fieldsModifier);
+ updatedEvents.push(pageEvent);
+ // Impressions
+ const impressionsInstructions = impressionsInstructionsFromPageData(objectGraph, pageInformation, fieldsModifier);
+ const impressionsEvents = impressionsInstructions.map((instruction) => instruction.data);
+ updatedEvents = updatedEvents.concat(impressionsEvents);
+ filterPageMetricsFieldsForPageChange(pageFields);
+ updatedEvents.forEach((event) => filterPageMetricsFieldsForPageChange(event.fields));
+ shelf.pageChangeMetrics = {
+ pageFields: pageFields,
+ updatedEvents: updatedEvents,
+ };
+}
+/// An allow list for the fields that can change in a `pageChange` event.
+const allowedPageChangeKeys = new Set([
+ "iAdAppStoreClientRequestId",
+ "iAdId",
+ "iAdSlotInfo",
+ "iAdOdmlSuccess",
+ "iAdEligible",
+ "iAdContainerId",
+ "iAdImpressionId",
+ "iAdMetadata",
+ "adamId",
+ "iAd",
+ "iAdPlacementId",
+ "iAdMissedOpportunityReason",
+ "hasiAdData",
+ "iAdTemplateType",
+ // These are required to merge with existing events
+ "eventType",
+ "impressionQueue",
+]);
+/**
+ * Filter some fields for the allowed content for a pageChange event.
+ * @param metricsFields The MetricsFields to filter.
+ */
+function filterPageMetricsFieldsForPageChange(metricsFields) {
+ Object.keys(metricsFields)
+ .filter((key) => !allowedPageChangeKeys.has(key))
+ .forEach((key) => delete metricsFields[key]);
+}
+function pageEventFromPageData(objectGraph, pageInformation, fieldsModifier, isDefaultBrowser) {
+ var _a, _b, _c, _d, _e;
+ const base = baseFieldsForPageEventsFromPageInformation(pageInformation, fieldsModifier);
+ if (pageInformation.iAdInfo) {
+ // Always copy this, even if no ad is present.
+ Object.assign(base, pageInformation.iAdInfo.pageFields);
+ }
+ // Include pre-order release date for page events.
+ if (serverData.isDefinedNonNull(pageInformation.offerReleaseDate)) {
+ base["offerReleaseDate"] = dateUtil.millisecondsToUTCMidnightFromLocalDate(pageInformation.offerReleaseDate);
+ }
+ const searchTermContext = pageInformation.searchTermContext;
+ if (searchTermContext) {
+ base["searchTerm"] = searchTermContext.term;
+ if (searchTermContext.suggestedTerm) {
+ base["searchSuggestedTerm"] = searchTermContext.suggestedTerm;
+ }
+ if (searchTermContext.correctedTerm) {
+ base["searchCorrectedTerm"] = searchTermContext.correctedTerm;
+ }
+ if (searchTermContext.originatingTerm) {
+ base["searchOriginatingTerm"] = searchTermContext.originatingTerm;
+ }
+ }
+ if (pageInformation.guidedSearch) {
+ Object.assign(base, pageInformation.guidedSearch);
+ }
+ const pageEvent = metricsBuilder.createMetricsPageData(objectGraph, false, (_a = pageInformation.isCrossfireReferralCandidate) !== null && _a !== void 0 ? _a : false, pageInformation.timingMetrics, base, isDefaultBrowser);
+ const hasAdverts = (_c = (_b = pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.iAdIsPresent) !== null && _c !== void 0 ? _c : false;
+ const shouldIncludeAdRotationFields = (_e = (_d = pageInformation.iAdInfo) === null || _d === void 0 ? void 0 : _d.shouldIncludeAdRotationFields) !== null && _e !== void 0 ? _e : false;
+ if (hasAdverts && shouldIncludeAdRotationFields) {
+ pageEvent.includingFields.push("advertRotation");
+ }
+ return pageEvent;
+}
+/**
+ * Creates a pageExitEvent which has all of the same fields as a pageEvent but with an eventType of pageExit.
+ * Noted this event is an event that is primarily meant to be used so the JS can get a hook into when a page is
+ * left so we can clear out associated referralContexts
+ */
+function pageExitEventFromPageData(objectGraph, pageInformation, fieldsModifier) {
+ const pageExitEvent = pageEventFromPageData(objectGraph, pageInformation, fieldsModifier);
+ pageExitEvent.fields["eventType"] = "pageExit";
+ return pageExitEvent;
+}
+function backEventFromPageData(objectGraph, pageInformation, fieldsModifier) {
+ const base = baseFieldsForPageEventsFromPageInformation(pageInformation, fieldsModifier);
+ return metricsBuilder.createMetricsBackClickData(objectGraph, base);
+}
+function pageRenderFromPageData(objectGraph, pageInformation, fieldsModifier) {
+ const base = baseFieldsForPageEventsFromPageInformation(pageInformation, fieldsModifier);
+ if (pageInformation.searchTermContext) {
+ base["searchTerm"] = pageInformation.searchTermContext.term;
+ }
+ // Splice in page fields until all native clients have fix for <rdar://problem/68879825>
+ if (pageInformation.baseFields) {
+ Object.assign(base, pageInformation.baseFields);
+ }
+ return metricsBuilder.createMetricsPageRenderFields(objectGraph, pageInformation.timingMetrics, base);
+}
+function impressionsInstructionsFromPageData(objectGraph, pageInformation, fieldsModifier) {
+ var _a, _b, _c;
+ const base = baseFieldsForPageEventsFromPageInformation(pageInformation, fieldsModifier);
+ if (pageInformation.searchTermContext) {
+ base["searchTerm"] = pageInformation.searchTermContext.term;
+ }
+ const impressionBase = objects.shallowCopyOf(base);
+ if (pageInformation.iAdInfo) {
+ Object.assign(impressionBase, pageInformation.iAdInfo.impressionsFields);
+ }
+ if (pageInformation.guidedSearch) {
+ Object.assign(impressionBase, pageInformation.guidedSearch);
+ }
+ // Regular Impressions
+ const shouldIncludeAdMetrics = serverData.isDefinedNonNull(pageInformation.iAdInfo);
+ const iAdEligibleForWindowCollection = serverData.isNullOrEmpty((_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.missedOpportunityReason) && objectGraph.client.isPad;
+ const shouldIncludeAdRotationFields = (_c = (_b = pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.shouldIncludeAdRotationFields) !== null && _c !== void 0 ? _c : false;
+ const impressionsData = metricsBuilder.createMetricsImpressionsData(objectGraph, impressionBase, shouldIncludeAdMetrics && iAdEligibleForWindowCollection, shouldIncludeAdRotationFields, true);
+ const instruction = {
+ data: impressionsData,
+ invocationPoints: [PageInvocationPoint.appExit, PageInvocationPoint.pageExit],
+ };
+ // Fast Impressions
+ const impressionsArray = [instruction];
+ if (shouldIncludeAdMetrics) {
+ const fastImpressions = metricsBuilder.createMetricsFastImpressionsData(objectGraph, impressionBase, pageInformation);
+ impressionsArray.push({
+ data: fastImpressions,
+ invocationPoints: [PageInvocationPoint.appExit, PageInvocationPoint.pageExit, PageInvocationPoint.timer],
+ });
+ }
+ return impressionsArray;
+}
+function baseFieldsForPageEventsFromPageInformation(pageInformation, fieldsModifier) {
+ const base = {};
+ // Add offer type for pre-orders (this should be added to everything, including impressions)
+ if (serverData.isDefinedNonNull(pageInformation.offerType)) {
+ base["offerType"] = pageInformation.offerType;
+ }
+ if (fieldsModifier !== undefined && base) {
+ fieldsModifier(base);
+ }
+ return base;
+}
+//# sourceMappingURL=page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-focus-impressions.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-focus-impressions.js
new file mode 100644
index 0000000..0bd030a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-focus-impressions.js
@@ -0,0 +1,57 @@
+import * as serverData from "../../../foundation/json-parsing/server-data";
+/**
+ * This is a workaround for Search Focus Page based on existing metrics tech debt in Search Results Page.
+ *
+ * # Context
+ * - In CrystalB, impressions for SFP now includes a recent searches shelf which needs to update dynamically as searches are performed.
+ *
+ * # What is the Workaround?
+ * The JetEngine Metrics API is designed to work s.t.:
+ * 1. JS populates `parentId`, which JetEngine Metric APIs uses **internally** to refer to between `ImpressionMetrics`.
+ * 2. Native generates `impressionParentId`, which is refers `impressionId` of parent assigned during serialization -
+ *
+ * Here, we instead:
+ * 1. JS populates `parentId` as normal
+ * 2. `event-linter` iterates through the impressions, finds the parent containers, and adds parent impression ids.
+ * 3. Native generates `impressionParentId` as normal where possible.
+ *
+ * # Why Workaround?
+ * 1. There are existing hacks for impression parents, e.g. fake `ad_container` and native child trackers w/ parent ID attribution.
+ * 2. AppStore is stuck between old ASK metrics and new JE metrics APIs. Our JS builders and existing native metrics are intertwined in a way that makes using JS defined `parentId` attribution nontrivial.
+ * 3. We need to dynamically update the recent searches shelf every time a search is performed.
+ *
+ * See `search-results-impressions` for more background.
+ */
+/**
+ * Update the `impression` field, attributing impressionParentIds per workaround above.
+ * @param eventFields Event fields to modify **in place**.
+ */
+export function decorateImpressionParentId(eventFields) {
+ var _a;
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ // Find result parent id.
+ let recentsParentImpressionId;
+ for (const impression of impressions) {
+ const canonicalId = serverData.asString(impression, "canonicalId");
+ if (canonicalId === "R8804") {
+ recentsParentImpressionId = (_a = serverData.asString(impression, "impressionId")) !== null && _a !== void 0 ? _a : undefined; // *NOT* id.
+ break;
+ }
+ }
+ if (!recentsParentImpressionId) {
+ return;
+ }
+ // Update impressions for search results
+ eventFields["impressions"] = impressions.map((impression) => {
+ const canonicalId = serverData.asString(impression, "canonicalId");
+ const impressionType = serverData.asString(impression, "impressionType");
+ if (serverData.isNullOrEmpty(canonicalId) &&
+ impressionType === "link" &&
+ impression != null &&
+ serverData.asString(impression, "impressionParentId") == null) {
+ impression["impressionParentId"] = recentsParentImpressionId;
+ }
+ return impression;
+ });
+}
+//# sourceMappingURL=search-focus-impressions.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-result-impressions.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-result-impressions.js
new file mode 100644
index 0000000..6874724
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search-result-impressions.js
@@ -0,0 +1,56 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+/**
+ * This is a workaround for existing metrics tech debt in Search Results Page.
+ *
+ * # Context
+ * - In AzulC, impressions for SRP now includes 'containers' for search results and guided search tokens.
+ * - The containers were added so tokens don't affect impression index, which search uses as a signal for ranking.
+ *
+ * # What is the Workaround?
+ * The JetEngine Metrics API is designed to work s.t.:
+ * 1. JS populates `parentId`, which JetEngine Metric APIs uses **internally** to refer to between `ImpressionMetrics`.
+ * 2. Native generates `impressionParentId`, which is refers `impressionId` of parent assigned during serialization -
+ *
+ * Here, we instead:
+ * 1. JS *doesn't* populate `parentId`
+ * 2. `event-linter` iterates through the impressions, finds the parent containers, and adds parent impression ids.
+ *
+ * # Why Workaround?
+ * 1. There are existing hacks for impression parents, e.g. fake `ad_container` and native child trackers w/ parent ID attribution.
+ * 2. AppStore is stuck between old ASK metrics and new JE metrics APIs. Our JS builders and existing native metrics are intertwined in a way that makes using JS defined `parentId` attribution nontrivial.
+ *
+ * Note that we **don't** use this for attributing parent id workaround for `search-revisions`, i.e. guided search tokens.
+ * This is because that codepath began from a clean slate, and uses JE metrics as-designed
+ */
+/**
+ * Update the `impression` field, attributing impressionParentIds per workaround above.
+ * @param eventFields Event fields to modify **in place**.
+ */
+export function decorateImpressionParentId(eventFields) {
+ const impressions = serverData.asArrayOrEmpty(eventFields, "impressions");
+ // Find result parent id.
+ let resultsParentImpressionId;
+ for (const impression of impressions) {
+ const impressionType = serverData.asString(impression, "impressionType");
+ if (isSome(impression) && impressionType === "SearchResults") {
+ resultsParentImpressionId = impression["impressionId"]; // *NOT* id.
+ break;
+ }
+ }
+ if (!resultsParentImpressionId) {
+ return;
+ }
+ // Update impressions for search results
+ eventFields["impressions"] = impressions.map((impression) => {
+ const impressionType = serverData.asString(impression, "impressionType");
+ const isCardResult = impressionType === "card";
+ const isEventModuleResult = impressionType === "eventModule";
+ const isSearchResult = isCardResult || isEventModuleResult; // search results are cards and app events.
+ if (isSome(impression) && isSearchResult) {
+ impression["impressionParentId"] = resultsParentImpressionId;
+ }
+ return impression;
+ });
+}
+//# sourceMappingURL=search-result-impressions.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search/search-shelves.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search/search-shelves.js
new file mode 100644
index 0000000..486fc9d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/search/search-shelves.js
@@ -0,0 +1,98 @@
+import * as mediaDataStructure from "../../../../foundation/media/data-structure";
+import * as models from "../../../../api/models";
+import * as searchShelves from "../../../search/content/search-shelves";
+import { impressionOptions } from "../impressions";
+import { asString } from "../../../../foundation/json-parsing/server-data";
+import { relationship } from "../../../../foundation/media/relationships";
+import { isNothing } from "@jet/environment/types/optional";
+/**
+ * Generates the metrics impressions options for the search shelf
+ * @param objectGraph The App Store Object Graph
+ * @param data The shelf data object
+ * @param shelfAttributes The shelf's attributes
+ * @param searchPageContext The context for the page containing the shelf
+ * @returns The metrics options for the shelf
+ */
+export function createMetricsOptionsForGenericSearchPageShelf(objectGraph, data, shelfAttributes, searchPageContext) {
+ var _a, _b, _c;
+ /// On shelves, the actual reco metrics are on the content and not the data blob itself
+ const recoMetricsDataContainer = relationship(data, "contents");
+ const recoMetricsData = recoMetricsDataContainer === null
+ ? undefined
+ : (_a = mediaDataStructure.metricsFromMediaApiObject(recoMetricsDataContainer)) !== null && _a !== void 0 ? _a : undefined;
+ let impressionsIdType = "its_contentId";
+ if (searchPageContext.pageType === searchShelves.SearchPageType.ChartsAndCategories) {
+ impressionsIdType = "static";
+ }
+ const shelfMetricsOptions = {
+ id: shelfAttributes.id,
+ kind: null,
+ softwareType: null,
+ targetType: "swoosh",
+ title: (_b = shelfAttributes.title) !== null && _b !== void 0 ? _b : "",
+ pageInformation: searchPageContext.metricsPageInformation,
+ locationTracker: searchPageContext.metricsLocationTracker,
+ idType: impressionsIdType,
+ fcKind: undefined,
+ canonicalId: (_c = asString(data.meta, "canonicalId")) !== null && _c !== void 0 ? _c : undefined,
+ recoMetricsData: recoMetricsData,
+ };
+ return shelfMetricsOptions;
+}
+/**
+ * Generates the impressions metrics options for the search chart or category
+ * @param objectGraph The App Store Object Graph
+ * @param model The chart or category model
+ * @param modelData The chart or category model data object
+ * @param searchShelfContext The context for the shelf containing the chart or category
+ * @returns The metrics options for the chart or category model
+ */
+export function createMetricsOptionsForChartOrCategory(objectGraph, model, modelData, searchShelfContext) {
+ var _a;
+ const chartModelMetricsOptions = {
+ ...searchShelfContext.metricsOptions,
+ ...impressionOptions(objectGraph, modelData, model.title, searchShelfContext.metricsOptions),
+ recoMetricsData: (_a = mediaDataStructure.metricsFromMediaApiObject(modelData)) !== null && _a !== void 0 ? _a : undefined,
+ targetType: metricsTargetTypeForChartOrCategory(model.density),
+ idType: "its_id",
+ };
+ return chartModelMetricsOptions;
+}
+/**
+ * Generates the click metrics options for the search chart or category
+ * @param objectGraph The App Store Object Graph
+ * @param model The chart or category model
+ * @param modelData The chart or category model data object
+ * @param searchShelfContext The context for the shelf containing the chart or category
+ * @returns The metrics options for the chart or category model
+ */
+export function createClickMetricsOptionsForChartOrCategory(objectGraph, modelDensity, modelData, searchShelfContext) {
+ var _a;
+ const chartModelMetricsOptions = {
+ pageInformation: searchShelfContext.metricsOptions.pageInformation,
+ locationTracker: searchShelfContext.metricsOptions.locationTracker,
+ recoMetricsData: (_a = mediaDataStructure.metricsFromMediaApiObject(modelData)) !== null && _a !== void 0 ? _a : undefined,
+ targetType: metricsTargetTypeForChartOrCategory(modelDensity),
+ id: modelData.id,
+ };
+ return chartModelMetricsOptions;
+}
+/**
+ * Gets the matching metrics target type for the chart or category model
+ * @param model The model we want the target type for
+ * @returns The target type for the model
+ */
+function metricsTargetTypeForChartOrCategory(modelDensity) {
+ if (isNothing(modelDensity)) {
+ return "tile";
+ }
+ switch (modelDensity) {
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density1:
+ return "tile";
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density2:
+ return "pill";
+ default:
+ return "tile";
+ }
+}
+//# sourceMappingURL=search-shelves.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/util.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/util.js
new file mode 100644
index 0000000..8be872f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/helpers/util.js
@@ -0,0 +1,407 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as urls from "../../../foundation/network/urls";
+import * as errors from "../../../foundation/util/errors";
+import * as objects from "../../../foundation/util/objects";
+import * as content from "../../content/content";
+import * as deviceFamily from "../../content/device-family";
+export function targetTypeForMetricsOptions(objectGraph, options) {
+ let type = options.targetType;
+ if (!type) {
+ type = objectGraph.client.isVision ? "lockupSmall" : "lockup";
+ }
+ return type;
+}
+export function idTypeForMetricsOptions(options) {
+ let type = options.idType;
+ if (type === "none") {
+ type = null;
+ }
+ else if (!type) {
+ type = "its_id";
+ }
+ return type;
+}
+export function softwareTypeForData(objectGraph, data) {
+ return content.isArcadeSupported(objectGraph, data) ? "Arcade" : null;
+}
+export function metricsKindFromData(objectGraph, data, attributePlatformOverride = undefined) {
+ const type = serverData.asString(data, "type");
+ const isMacType = deviceFamily.dataHasDeviceFamily(objectGraph, data, "mac", true);
+ const isOnlyMacType = deviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "mac", true);
+ const isIOSType = deviceFamily.dataHasAnyDeviceFamilies(objectGraph, data, ["iphone", "ipad", "ipod", "tvos", "watch"], true);
+ const isOnlyIOSType = deviceFamily.dataOnlyHasDeviceFamilies(objectGraph, data, ["iphone", "ipad", "ipod", "tvos", "watch"], true);
+ const isIOSDeviceType = objectGraph.client.isiOS || objectGraph.client.isTV || objectGraph.client.isWatch;
+ const isAppleSiliconDeviceType = objectGraph.client.isMac && objectGraph.appleSilicon.isSupportEnabled;
+ const isVisionDeviceType = objectGraph.client.isVision;
+ const isVisionType = deviceFamily.dataHasDeviceFamily(objectGraph, data, "realityDevice", true);
+ const isOnlyVisionType = deviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "realityDevice", true);
+ // If:
+ // - this is a mac only app, or
+ // - it has multiple types but we're currently on the Mac, or
+ // - it has multiple types and the plaform override is for Mac,
+ // use the 'macSoftware' or 'macSoftwareBundle' kinds
+ if (isOnlyMacType ||
+ (isMacType && objectGraph.client.isMac) ||
+ (isMacType && attributePlatformOverride === "osx")) {
+ switch (type) {
+ case "apps":
+ return "macSoftware";
+ case "app-bundles":
+ return "macSoftwareBundle";
+ default:
+ break;
+ }
+ }
+ // If:
+ // - this is a Vision only app, or
+ // - it has multiple types but we're currently on a Vision device, or
+ // - it has multiple types and the platform override is for Vision,
+ // use the 'visionSoftware' kind.
+ if (isOnlyVisionType ||
+ (isVisionType && objectGraph.client.isVision) ||
+ (isVisionType && attributePlatformOverride === "xros")) {
+ switch (type) {
+ case "apps":
+ return "visionSoftware";
+ case "app-bundles":
+ // To add if/when vision supports app bundles.
+ break;
+ default:
+ break;
+ }
+ }
+ // If this is an iOS only app or it has multiple types but we're currently:
+ // - on an iOS device, or
+ // - on an Apple Silicon Mac, or
+ // - on a Vision device, or
+ // - the platform override is for an iOS-like device
+ // use the 'iosSoftware' or 'mobileSoftwareBundle' kinds
+ if (isOnlyIOSType ||
+ (isIOSType && isIOSDeviceType) ||
+ (isIOSType && isAppleSiliconDeviceType) ||
+ (isIOSType && isVisionDeviceType) ||
+ (isIOSType && attributePlatformOverride === "ios") ||
+ (isIOSType && attributePlatformOverride === "watch") ||
+ (isIOSType && attributePlatformOverride === "appletvos")) {
+ switch (type) {
+ case "apps":
+ return "iosSoftware";
+ case "app-bundles":
+ return "mobileSoftwareBundle";
+ default:
+ break;
+ }
+ }
+ switch (type) {
+ case "in-apps":
+ return "softwareAddOn";
+ case "groupings":
+ return "grouping";
+ case "editorial-elements":
+ case "editorial-items":
+ return "editorialItem";
+ case "developers":
+ return "artist";
+ default:
+ return null;
+ }
+}
+export function emptyStringIfNullOrUndefined(object) {
+ if (object === null || object === undefined) {
+ return "";
+ }
+ return object;
+}
+export function extractSiriRefAppFromRefURL(urlString) {
+ if (!urlString) {
+ return null;
+ }
+ const refUrl = new urls.URL(urlString);
+ let extracteRefApp = null;
+ const query = refUrl.query;
+ if (isSome(query)) {
+ for (const key of Object.keys(query)) {
+ if (key === "referrer") {
+ if (query[key] === "siri") {
+ extracteRefApp = "com.apple.siri";
+ }
+ break;
+ }
+ }
+ }
+ return extracteRefApp;
+}
+export function sanitizedMetricsDictionary(dict) {
+ var _a;
+ if (isNothing(dict)) {
+ return {};
+ }
+ return (_a = serverData.asInterface(sanitizeJson(serverData.asJSONData(dict)))) !== null && _a !== void 0 ? _a : {};
+}
+function sanitizeJson(json) {
+ if (serverData.isNull(json)) {
+ return null;
+ }
+ else if (json instanceof Array) {
+ const arrayCopy = [];
+ for (const value of json) {
+ const sanitizedValue = sanitizeJson(value);
+ if (serverData.isDefinedNonNull(sanitizedValue)) {
+ arrayCopy.push(sanitizedValue);
+ }
+ }
+ return arrayCopy;
+ }
+ else if (json instanceof Object) {
+ const objectCopy = {};
+ Object.keys(json).forEach((key, index, array) => {
+ const value = json[key];
+ const sanitizedValue = sanitizeJson(value);
+ if (serverData.isDefinedNonNull(sanitizedValue)) {
+ objectCopy[key] = sanitizedValue;
+ }
+ });
+ return objectCopy;
+ }
+ return json;
+}
+export function searchTermFromRefURL(refUrlString) {
+ if (!refUrlString) {
+ return null;
+ }
+ const refUrl = new urls.URL(refUrlString);
+ const queryItems = refUrl.query;
+ const searchTerm = queryItems === null || queryItems === void 0 ? void 0 : queryItems["term"];
+ const path = refUrl.pathname;
+ if (serverData.isNull(searchTerm) || serverData.isNull(path)) {
+ return null;
+ }
+ if (!path.endsWith("/search")) {
+ return null;
+ }
+ // the url object has already urldecoded this query parameter
+ const plainTerm = searchTerm;
+ return plainTerm;
+}
+/**
+ * Get a search term from a product URL, if one has been added.
+ * @param productUrlString The URL of a product
+ * @returns A string, if a search term exists.
+ */
+export function searchTermFromProductURL(productUrlString) {
+ if (isNothing(productUrlString)) {
+ return null;
+ }
+ const productUrl = new urls.URL(productUrlString);
+ const queryItems = productUrl.query;
+ const searchTerm = queryItems === null || queryItems === void 0 ? void 0 : queryItems["searchTerm"];
+ const path = productUrl.pathname;
+ if (isNothing(searchTerm) || isNothing(path)) {
+ return null;
+ }
+ if (!path.includes("/app")) {
+ return null;
+ }
+ const plainTerm = searchTerm;
+ return plainTerm;
+}
+/**
+ * Convert a product's data `type` and top lockup icon into a `MetricsPlatformDisplayStyle` object.
+ * This is used to determine how an app icon is presented (i.e. as watch, as atv) for metrics.
+ * @param objectGraph Current object graph
+ * @param data Server data for the app
+ * @param artwork The product's top lockup icon.
+ * @param clientIdentifierOverride The preferred client identifier, if any.
+ * @returns a MetricsPlatformDisplayStyle object.
+ */
+// <rdar://problem/47715014> Metrics: Send editorial intent buy param to finance for watch apps
+export function metricsPlatformDisplayStyleFromData(objectGraph, data, artwork, clientIdentifierOverride) {
+ if (!data || !artwork) {
+ return "unknown";
+ }
+ if (data.type === "app-bundles") {
+ return "bundle";
+ }
+ const artworkStyle = artwork.style;
+ if (isNothing(artworkStyle)) {
+ return "unknown";
+ }
+ switch (artworkStyle) {
+ case "roundedRect":
+ case "roundedRectPrerendered": {
+ return "ios";
+ }
+ case "unadorned": {
+ return "mac";
+ }
+ case "tvRect": {
+ return "tv";
+ }
+ case "round":
+ case "roundPrerendered": {
+ const attributePlatform = content.iconAttributePlatform(objectGraph, data, clientIdentifierOverride !== null && clientIdentifierOverride !== void 0 ? clientIdentifierOverride : undefined);
+ if (attributePlatform === "xros") {
+ return "vision";
+ }
+ else {
+ return "watch";
+ }
+ }
+ case "pill": {
+ return "messages";
+ }
+ case "iap": {
+ return "iap";
+ }
+ default: {
+ errors.unreachable(artworkStyle);
+ return "unknown";
+ }
+ }
+}
+// region Search GhostHint
+/**
+ * Move ghostHint fields for click events
+ * @param eventFields Fields of event to modify in place.
+ */
+export function adjustGhostHintFieldsForClick(eventFields) {
+ /**
+ * Copy `searchGhostHintPrefix` to `searchPrefix` if no prefix is present.
+ * - JS-built search actions specify searchPrefix (matches prefixTerm of hint request).
+ * - Native-built search actions don't specify searchPrefix (dynamic).
+ */
+ const existingSearchPrefix = serverData.asString(eventFields, "searchPrefix");
+ const ghostHintPrefix = serverData.asString(eventFields, "searchGhostHintPrefix");
+ if (serverData.isNull(existingSearchPrefix) && isSome(ghostHintPrefix) && (ghostHintPrefix === null || ghostHintPrefix === void 0 ? void 0 : ghostHintPrefix.length) > 0) {
+ eventFields["searchPrefix"] = ghostHintPrefix;
+ }
+ /**
+ * Delete `searchGhostHintTerm` if phase is pending (i.e. only send if displayed or rejected) per POR.
+ */
+ const ghostHintTermPhase = serverData.asString(eventFields, "searchGhostHintTermPhase");
+ if (ghostHintTermPhase === "pending") {
+ delete eventFields["searchGhostHintTerm"];
+ }
+}
+/**
+ * Move ghostHint fields for seach events
+ * @param eventFields Fields of event to modify in place.
+ */
+export function adjustGhostHintFieldsForSearch(eventFields) {
+ var _a;
+ /**
+ * Copy `searchGhostHintPrefix` to `searchPrefix` if no prefix is present.
+ * - JS-built search actions specify actionDetails.searchPrefix (matches prefixTerm of hint request).
+ * - Native-built search actions don't specify actionDetails.searchPrefix (dynamic).
+ */
+ const actionDetails = (_a = eventFields["actionDetails"]) !== null && _a !== void 0 ? _a : {};
+ const existingSearchPrefix = actionDetails["searchPrefix"];
+ const ghostHintPrefix = serverData.asString(eventFields, "searchGhostHintPrefix");
+ if (serverData.isNull(existingSearchPrefix) && isSome(ghostHintPrefix) && (ghostHintPrefix === null || ghostHintPrefix === void 0 ? void 0 : ghostHintPrefix.length) > 0) {
+ actionDetails["searchPrefix"] = ghostHintPrefix;
+ eventFields["actionDetails"] = actionDetails;
+ }
+ /**
+ * Delete `searchGhostHintTerm` if phase is pending (i.e. only send if displayed or rejected) per POR.
+ */
+ const ghostHintTermPhase = serverData.asString(eventFields, "searchGhostHintTermPhase");
+ if (ghostHintTermPhase === "pending") {
+ delete eventFields["searchGhostHintTerm"];
+ }
+ /**
+ * Prune `searchGhostHintTerm` if `actionType` is `input`, i.e. is from hints page loading.
+ * This is a side-effect of when the event is fired, and otherwise doesn't belong there.
+ */
+ if (eventFields["actionType"] === "input") {
+ delete eventFields["searchGhostHintTerm"];
+ }
+}
+/**
+ * Clean up extraneous generated fields. These extra fields are speculative
+ * to allow some additional JS customization if needed for SSS.
+ * @param eventFields Event fields to modify in place.
+ */
+export function removeExtraGhostHintFields(eventFields) {
+ // Prune prefix annotation.
+ delete eventFields["searchGhostHintPrefix"];
+ // Prune phase annotation.
+ delete eventFields["searchGhostHintTermPhase"];
+ // Prune historical annotation.
+ delete eventFields["searchGhostHintTermLastDisplayed"];
+}
+// endregion
+// region Arcade Upsell Marketing Items
+/**
+ * Returns a dictionary of fields pulled out from the meta.metrics dictionary associated with a marketing item response.
+ * This data comes from Mercury, and we simply pull out relevant fields to be hoisted into the top-level base field on
+ * metrics events (page/impression/click).
+ * @param marketingItemData The marketing item response data.
+ */
+export function marketingItemTopLevelBaseFieldsFromData(objectGraph, marketingItemData) {
+ if (!serverData.isDefinedNonNull(marketingItemData)) {
+ return null;
+ }
+ const fieldsData = {};
+ const marketingDictionary = serverData.asDictionary(marketingItemData, "meta.metrics");
+ if (!serverData.isDefinedNonNullNonEmpty(marketingDictionary)) {
+ return null;
+ }
+ const channelPartner = serverData.asString(marketingDictionary, "channelPartner");
+ if (isSome(channelPartner) && (channelPartner === null || channelPartner === void 0 ? void 0 : channelPartner.length) > 0) {
+ fieldsData["channelPartner"] = channelPartner;
+ }
+ const eligibilityType = serverData.asString(marketingDictionary, "eligibilityType");
+ if (isSome(eligibilityType) && (eligibilityType === null || eligibilityType === void 0 ? void 0 : eligibilityType.length) > 0) {
+ fieldsData["eligibilityType"] = eligibilityType;
+ }
+ const upsellScenario = serverData.asString(marketingDictionary, "upsellScenario");
+ if (isSome(upsellScenario) && (upsellScenario === null || upsellScenario === void 0 ? void 0 : upsellScenario.length) > 0) {
+ fieldsData["upsellScenario"] = upsellScenario;
+ }
+ fieldsData["marketing"] = {
+ marketingItemId: marketingItemData.id,
+ };
+ return fieldsData;
+}
+// endregion
+// region On Device Personalization
+/**
+ * Merges the provided reco metrics data with the on device personalization metrics data.
+ * @param metricsData The input reco metrics data
+ * @param onDevicePersonalizationProcessingType The type of processing that occurred on the data
+ * @param onDevicePersonalizationMetricsData The metrics data provided by the on device personalization framework
+ * @returns Reco metrics data, or null
+ */
+export function combinedRecoMetricsDataFromMetricsData(metricsData, onDevicePersonalizationProcessingType, onDevicePersonalizationMetricsData) {
+ let combinedMetricsData = null;
+ if (serverData.isDefinedNonNull(metricsData)) {
+ combinedMetricsData = objects.shallowCopyOf(metricsData);
+ }
+ if (serverData.isDefinedNonNull(onDevicePersonalizationProcessingType)) {
+ if (serverData.isNull(combinedMetricsData)) {
+ combinedMetricsData = {};
+ }
+ combinedMetricsData["odpModuleUpdate"] = onDevicePersonalizationProcessingType.toString();
+ }
+ if (serverData.isDefinedNonNullNonEmpty(onDevicePersonalizationMetricsData)) {
+ if (serverData.isNull(combinedMetricsData)) {
+ combinedMetricsData = {};
+ }
+ combinedMetricsData["userSegment"] = onDevicePersonalizationMetricsData;
+ }
+ return combinedMetricsData;
+}
+// endregion
+// region Metrics ID
+/**
+ * Clean up dsid fields. We want this as a last line of defense for removing dsid.
+ * @param eventFields Event fields to modify in place.
+ */
+export function removeDSIDFields(eventFields) {
+ // Prune dsid
+ delete eventFields["dsid"];
+ delete eventFields["DSID"];
+}
+// endregion
+//# sourceMappingURL=util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js b/node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js
new file mode 100644
index 0000000..2e0a81f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/metrics/metrics-referral-context.js
@@ -0,0 +1,370 @@
+import { isNothing } from "@jet/environment";
+import { asInterface, asJSONData, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, traverse, } from "../../foundation/json-parsing/server-data";
+/**
+ * This class is used as a singleton to track whether we should use the crossfire attribution from the JS purchase
+ * configuration, this is to remedy the following issue:
+ *
+ * rdar://96291594 ([Sydney][Regression][App Store][Clickstream][iTMS11] RefApp missing from page event - (iOS) Sydney20A312a)
+ *
+ * The flow of how this object is used is as follows:
+ *
+ * 1. Deeplink comes in and is routed to the product page controller, containing the crossfire referrer data
+ * 2. We build the product page model and pass the referrer data along
+ * 3. As we're building the page model for this product page, we check to see if we have valid referrer data. If we do
+ * we call into `MetricsReferralContext.beginReferralContextForProduct(appProductId)`, which will create store a unique
+ * referral context identifier for this product page. This identifier will be used to make sure we're only every dealing
+ * with the original product page.
+ * 4. When we create the pageEvent for this product page we modify all the event fields to include unique referral context id,
+ * using `MetricsReferralContext.addReferralContextToMetricsFieldsIfNecessary(metricsFields)`
+ * 5. The referrer data is then added to the purchase configuration for the product lockup's buy button, which comes back to us
+ * at time of purchase decoration
+ * 6. Later when the user taps ont the product buy button and we enter the purchase decoration flow, we check to see if we should
+ * skip adding the native referrer data to the purchase buy params, which would clear out the referrer data since the page change,
+ * event has cleared it out natively. We call `MetricsReferralContext.shouldUseJSReferralData` for this check.
+ * 7. If any page event comes in to the metrics event linter, we check to see if it has a referral context id that is not the
+ * current one, if it does we clear the current metrics referrer contents using `MetricsReferralContext.endReferralContextIfNecessaryForPageExitEvent(pageEvent)`
+ * 8. Additionally in the event linter we always call into `MetricsReferralContext.removeReferralContextInfoFromMetricsEvent(metricsFields)`
+ * which deletes the added referralContextId field.
+ */
+export class MetricsReferralContext {
+ static createSharedMetricsReferralContext(objectGraph) {
+ if (MetricsReferralContext.shared) {
+ return;
+ }
+ MetricsReferralContext.shared = new MetricsReferralContext(objectGraph);
+ }
+ /**
+ * Initializers
+ */
+ /**
+ * @param objectGraph The object graph to use to determine if the referral context is needed.
+ */
+ constructor(objectGraph) {
+ /**
+ * Properties
+ */
+ /**
+ * Identifier denoting that we should use the extRefUrl2 and extRefApp2 from the original purchase configuration
+ * found in the purchase token, rather than using the native metrics data which would clear out the ref data
+ * due to pageChange events.
+ */
+ this.currentReferral = null;
+ if (objectGraph.host.isiOS) {
+ this.isMetricsReferralContextRequired = true;
+ this.isEventDetailClickEventOverrideNecessary = !objectGraph.host.isOSAtLeast(16, 2, 0);
+ }
+ else if (objectGraph.host.isMac) {
+ this.isMetricsReferralContextRequired = objectGraph.host.isOSAtLeast(13, 0, 0);
+ this.isEventDetailClickEventOverrideNecessary = false;
+ }
+ else {
+ this.isMetricsReferralContextRequired = false;
+ this.isEventDetailClickEventOverrideNecessary = false;
+ }
+ }
+ /**
+ * Returns whether we should use the JS referral data or not.
+ */
+ get shouldUseJSReferralData() {
+ return this.isMetricsReferralContextRequired && isDefinedNonNull(this.currentReferral);
+ }
+ /**
+ * Returns the current referral data for the active context
+ */
+ get activeReferralData() {
+ if (!this.shouldUseJSReferralData) {
+ return null;
+ }
+ if (this.currentReferral === null || !this.currentReferral.isActive) {
+ return null;
+ }
+ return this.currentReferral.data;
+ }
+ /**
+ * Setting Referral Data
+ */
+ /**
+ * Called when we get a deep link into the product page and need to make sure we track
+ * the referral data for this page.
+ *
+ * @param productId The id of the product the referral context is for.
+ * @param referrerData The referral data for this product page.
+ */
+ setReferralDataForProduct(productId, referrerData) {
+ var _a, _b, _c;
+ if (!this.isMetricsReferralContextRequired || isNull(referrerData)) {
+ return;
+ }
+ const extRefApp2 = (_a = asString(referrerData, "app")) !== null && _a !== void 0 ? _a : null;
+ const extRefUrl2 = (_b = asString(referrerData, "externalUrl")) !== null && _b !== void 0 ? _b : null;
+ const kind = (_c = asInterface(referrerData, "kind")) !== null && _c !== void 0 ? _c : null;
+ this.currentReferral = {
+ id: `${productId}_${Date.now()}`,
+ data: {
+ extRefApp2,
+ extRefUrl2,
+ kind,
+ refUrl: null,
+ },
+ isActive: false,
+ productPageExtensionInfo: null,
+ };
+ }
+ /**
+ * Called when we are linting a page event, if this page event is for a product page extension, and
+ * we don't yet have an active crossfire referral context, we need to make sure we start one, since there
+ * should always be one in this case. The fact that there is not means that the pageChange event cleared it out
+ * natively.
+ *
+ * Additionally we're going to add the referral context id to the page event so we can track it later, and know
+ * when to end the referral context.
+ *
+ * @param pageEvent The page event that is currently being linted, so we can check to see if we're on a product page,
+ * in the product page extension.
+ */
+ setReferralDataForProductPageExtensionIfNecessary(pageEvent) {
+ var _a, _b;
+ if (!this.isMetricsReferralContextRequired) {
+ return;
+ }
+ const productId = asString(pageEvent, "pageId");
+ const refApp = asString(pageEvent, "refApp");
+ if (!MetricsReferralContextUtil.isProductPageExtension(pageEvent) ||
+ !MetricsReferralContextUtil.isValidPageEvent(pageEvent) ||
+ isNull(productId) ||
+ isNull(refApp)) {
+ return;
+ }
+ const extRefUrl = (_a = asString(pageEvent, "extRefUrl")) !== null && _a !== void 0 ? _a : null;
+ const refAppKindName = asString(pageEvent, "refAppType");
+ let refAppKindContext;
+ switch (refAppKindName) {
+ case "trampoline":
+ refAppKindContext = asJSONData(traverse(pageEvent, "trampolineContext"));
+ break;
+ case "widget":
+ refAppKindContext = asJSONData(traverse(pageEvent, "widgetContext"));
+ break;
+ default:
+ refAppKindContext = {};
+ }
+ const refUrl = (_b = asString(pageEvent, "refUrl")) !== null && _b !== void 0 ? _b : null;
+ this.currentReferral = {
+ id: `${productId}_${Date.now()}`,
+ data: {
+ extRefApp2: refApp,
+ extRefUrl2: extRefUrl,
+ refUrl: refUrl,
+ kind: {
+ name: refAppKindName,
+ context: refAppKindContext,
+ },
+ },
+ isActive: false,
+ productPageExtensionInfo: {
+ productId,
+ },
+ };
+ this.addReferralContextToMetricsFieldsIfNecessary(pageEvent);
+ }
+ /**
+ * Begin / End Metrics Referral Context
+ */
+ /**
+ * Called when we get a deep link into the product page and need to make sure we track
+ * whether the referral data should be used from the js configuration.
+ *
+ * @param pageEvent Some page event that may be associated with the current referral context.
+ */
+ beginReferralContextForPageIfNecessary(pageEvent) {
+ if (!this.isMetricsReferralContextRequired || !MetricsReferralContextUtil.isValidPageEvent(pageEvent)) {
+ return;
+ }
+ if (!MetricsReferralContextUtil.isReferralForEvent(this.currentReferral, pageEvent)) {
+ return;
+ }
+ if (this.currentReferral !== null) {
+ this.currentReferral.isActive = true;
+ }
+ }
+ /**
+ * Called when we get a pageExit event after the page event for the current deeplinked
+ * product page, if there is one. This will reset the flag to use the native metrics. This should
+ * always be the next pageExit event after the page enter event
+ */
+ endReferralContextIfNecessaryForPageEvent(pageExitEvent) {
+ if (!this.isMetricsReferralContextRequired || !MetricsReferralContextUtil.isValidPageEvent(pageExitEvent)) {
+ return;
+ }
+ if (!MetricsReferralContextUtil.isReferralForEvent(this.currentReferral, pageExitEvent)) {
+ return;
+ }
+ this.currentReferral = null;
+ }
+ /**
+ * Setting / Clearing Page Fields
+ */
+ /**
+ * Called when we're building the metrics events for a product page, this way we can tag the events with the current
+ * referral context id if there is one.
+ *
+ * @param pageMetricsFields The page event fields we can modify to track the current product page.
+ */
+ addReferralContextToMetricsFieldsIfNecessary(pageMetricsFields) {
+ var _a;
+ if (!this.isMetricsReferralContextRequired) {
+ return;
+ }
+ pageMetricsFields[MetricsReferralContext.referralContextEventField] = (_a = this.currentReferral) === null || _a === void 0 ? void 0 : _a.id;
+ }
+ /**
+ * Called when linting our metrics events so we can make sure to remove the referral context id, so its not sent to the server
+ *
+ * @param metricsEvent The metrics event we're currently linting.
+ */
+ removeReferralContextInfoFromMetricsEvent(metricsEvent) {
+ if (!this.isMetricsReferralContextRequired) {
+ return;
+ }
+ delete metricsEvent[MetricsReferralContext.referralContextEventField];
+ }
+ /**
+ * Event Attribution
+ */
+ /**
+ * If we have an active referral context, we need to make sure we add the referral data to the event.
+ *
+ * @param metricsEvent The metrics event we're currently linting.
+ */
+ addReferralDataToEventIfNecessary(metricsEvent) {
+ if (isNull(this.activeReferralData)) {
+ return;
+ }
+ if (!MetricsReferralContextUtil.shouldAddReferralDataToEvent(metricsEvent)) {
+ return;
+ }
+ if (MetricsReferralContextUtil.isEventDetailsClickEvent(metricsEvent) &&
+ !this.isEventDetailClickEventOverrideNecessary) {
+ return;
+ }
+ if (MetricsReferralContextUtil.isEventDetailsClickEvent(metricsEvent)) {
+ // Correct the `pageType` of this event for rdar://101302008 ([Sydney][App Store] [Clickstream][iTMS11] click event on EventDetails page from an app referral has incorrect pageType)
+ // Then continue on and apply referral data.
+ metricsEvent["pageType"] = "EventDetails";
+ }
+ metricsEvent["refApp"] = this.activeReferralData.extRefApp2;
+ metricsEvent["extRefUrl"] = this.activeReferralData.extRefUrl2;
+ if (isDefinedNonNullNonEmpty(this.activeReferralData.refUrl)) {
+ metricsEvent["refUrl"] = this.activeReferralData.refUrl;
+ }
+ if (this.activeReferralData !== null && this.activeReferralData.kind !== null) {
+ metricsEvent["refAppType"] = this.activeReferralData.kind.name;
+ switch (metricsEvent["refAppType"]) {
+ case "trampoline":
+ metricsEvent["trampolineContext"] = this.activeReferralData.kind.context;
+ break;
+ case "widget":
+ metricsEvent["widgetContext"] = this.activeReferralData.kind.context;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+/**
+ * They event field to use on a page event so we can determine later if this page event belongs to
+ * the same deeplinked product page.
+ */
+MetricsReferralContext.referralContextEventField = "referralContextId";
+// eslint-disable-next-line @typescript-eslint/no-extraneous-class
+class MetricsReferralContextUtil {
+ /**
+ * Check to see if the pageEvent is within the ProductPageExtension
+ *
+ * @param pageEvent The page event we're checking to see if its in an extension.
+ * @returns Whether "app" for this event is a valid type
+ */
+ static isProductPageExtension(pageEvent) {
+ const app = asString(pageEvent, "app");
+ return app === MetricsReferralContextUtil.productPageExtensionAppId;
+ }
+ /**
+ * Check to see if the current page event is for a product page.
+ *
+ * @param pageEvent The page event we're checking to see if its a product page.
+ * @returns Whether "pageType" for this event is a valid type
+ */
+ static isValidPageEvent(pageEvent) {
+ const pageType = asString(pageEvent, "pageType");
+ if (isNothing(pageType)) {
+ return false;
+ }
+ return MetricsReferralContextUtil.validPageEventTypes.has(pageType);
+ }
+ /**
+ * This method will check the `referralContextEventField` to see if it matches the current referral.
+ *
+ * @param referral The current metrics referral taken from the referral context
+ * @param event Some event to test whether there is an associated referral context. And if so
+ * if that referral context matches.
+ */
+ static isReferralForEvent(referral, event) {
+ var _a;
+ if (isNull(referral)) {
+ return false;
+ }
+ const referralContextId = event[MetricsReferralContext.referralContextEventField];
+ const productId = asString(event, "pageId");
+ if (isDefinedNonNull(referralContextId)) {
+ return referralContextId === referral.id;
+ }
+ else if (MetricsReferralContextUtil.isProductPageExtension(event) && isDefinedNonNull(productId)) {
+ // For product page extensions we do not get a chance to add the referralContextId to the
+ // pageExit event so we need to check the productId to see if it matches the current referral.
+ return productId === ((_a = referral === null || referral === void 0 ? void 0 : referral.productPageExtensionInfo) === null || _a === void 0 ? void 0 : _a.productId);
+ }
+ else {
+ return false;
+ }
+ }
+ static shouldAddReferralDataToEvent(event) {
+ // Generally, we don't want to force referral data onto click events, but this is not true for In-App Events (IAE):
+ // rdar://101399254 ([Sydney] [App Store][Clickstream][iTMS11] Missing refApp and extRefURL on IAE Click Open events through App/Web Referrals)
+ // This only applies prior to SydneyC, as this was fixed natively there.
+ if (event.eventType === "click") {
+ return this.isEventDetailsClickEvent(event);
+ }
+ return true;
+ }
+ /**
+ * Check whether the event is for a click on an In-App Events (IAE) page.
+ *
+ * @param event The event we're checking to see if it's on a an IAE page.
+ */
+ static isEventDetailsClickEvent(event) {
+ if (event.eventType !== "click") {
+ return false;
+ }
+ const location = event.location;
+ const currentLocation = location === null || location === void 0 ? void 0 : location[0];
+ return isDefinedNonNull(currentLocation) && currentLocation.locationType === "EventDetails";
+ }
+}
+/**
+ * The identifier for the product page extension in a metrics page event
+ */
+MetricsReferralContextUtil.productPageExtensionAppId = "com.apple.AppStore.ProductPageExtension";
+/**
+ * The identifier used for the pageType field of an app event page event.
+ */
+MetricsReferralContextUtil.eventDetailsPageType = "EventDetails";
+/**
+ * The set of valid page types for a product page, page event
+ */
+MetricsReferralContextUtil.validPageEventTypes = new Set([
+ "Software",
+ "SoftwareBundle",
+ MetricsReferralContextUtil.eventDetailsPageType,
+]);
+//# sourceMappingURL=metrics-referral-context.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/offers/external-purchases.js b/node_modules/@jet-app/app-store/tmp/src/common/offers/external-purchases.js
new file mode 100644
index 0000000..92e3d9d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/offers/external-purchases.js
@@ -0,0 +1,65 @@
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as contentAttributes from "../content/attributes";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import { makeRoutableArticlePageCanonicalUrl } from "../today/routable-article-page-url-utils";
+import { makeRoutableArticlePageIntent } from "../../api/intents/routable-article-page-intent";
+import { getPlatform } from "../preview-platform";
+import { getLocale } from "../locale";
+/**
+ * Determines whether the product has external purchases.
+ * @param objectGraph Current object graph
+ * @param data The product data
+ * @returns True if the product has external purchases
+ */
+export function hasExternalPurchasesForData(objectGraph, data) {
+ const usesExternalPurchase = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "usesExternalPurchase");
+ const usesExternalLinkPurchase = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "usesExternalLinkPurchase");
+ return usesExternalPurchase || usesExternalLinkPurchase;
+}
+/**
+ * Determines whether external purchases should be indicated for the given client / bag / placement combination.
+ * @param objectGraph Current object graph
+ * @param placement Indicates where the external purchases indicator will be placed
+ * @returns True if the indicator should be displayed in the given placement.
+ */
+export function externalPurchasesPlacementIsEnabled(objectGraph, placement) {
+ return (objectGraph.bag.enableExternalPurchases &&
+ objectGraph.bag.enabledExternalPurchasesPlacements.includes(placement));
+}
+/**
+ * Creates a flow action for the external purchases story.
+ * @param objectGraph Current object graph
+ * @param title The title to use for the action
+ * @param metricsPageInformation Current metrics page information
+ * @param metricsLocationTracker Current metrics location tracker
+ * @returns A flow action for the story, or null
+ */
+export function externalPurchasesLearnMoreAction(objectGraph, title, metricsPageInformation, metricsLocationTracker) {
+ const editorialItemId = objectGraph.bag.externalPurchasesLearnMoreEditorialItemId;
+ if (serverData.isNullOrEmpty(editorialItemId) || !objectGraph.bag.enableExternalPurchases) {
+ return null;
+ }
+ const flowAction = new models.FlowAction("article");
+ flowAction.title = title;
+ flowAction.pageUrl = `https://apps.apple.com/story/id${editorialItemId}`;
+ if (editorialItemId && objectGraph.client.isWeb) {
+ const destination = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: editorialItemId,
+ });
+ const pageUrlString = makeRoutableArticlePageCanonicalUrl(objectGraph, destination);
+ flowAction.pageUrl = pageUrlString;
+ flowAction.destination = destination;
+ }
+ metricsHelpersClicks.addClickEventToAction(objectGraph, flowAction, {
+ id: "LearnMore",
+ targetType: "link",
+ actionType: "navigate",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ });
+ return flowAction;
+}
+//# sourceMappingURL=external-purchases.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/offers/offer-formatting.js b/node_modules/@jet-app/app-store/tmp/src/common/offers/offer-formatting.js
new file mode 100644
index 0000000..c085065
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/offers/offer-formatting.js
@@ -0,0 +1,231 @@
+//
+// offer-formatting.ts
+// AppStoreKit
+//
+// Created by Dersu Abolfathi on 8/8/19.
+// Copyright (c) 2019 Apple Inc. All rights reserved.
+//
+import { isNothing } from "@jet/environment";
+import * as serverData from "../../foundation/json-parsing/server-data";
+export class SubscriptionRecurrence {
+ constructor(periodDuration, periodCount, type) {
+ this.periodDuration = periodDuration;
+ this.periodCount = periodCount;
+ this.type = type;
+ }
+ isEqualTo(otherRecurrence) {
+ return (otherRecurrence.periodDuration === this.periodDuration &&
+ otherRecurrence.periodCount === this.periodCount &&
+ otherRecurrence.type === this.type);
+ }
+}
+const DAYS_PER_WEEK = 7;
+/**
+ * Determines the recurrence type for the server-vended subscription recurrence string.
+ * @param serverRecurrence The string denoting the recurring subscription recurrence (i.e. 'P1M', 'P1Y', etc.).
+ * @param numberOfPeriods - The number of periods for the subscription.
+ * @returns {SubscriptionRecurrence} The recurrence value.
+ */
+export function subscriptionRecurrenceForServerRecurrence(objectGraph, serverRecurrence, numberOfPeriods) {
+ if (isNothing(serverRecurrence)) {
+ return null;
+ }
+ const match = serverRecurrence.match(/P(\d+)([A-Z]+)/);
+ if (!match || match.length !== 3) {
+ return null;
+ }
+ let periodDuration = serverData.asNumber(match[1]);
+ let type = match[2];
+ if (!periodDuration || !type) {
+ return null;
+ }
+ // Unfortunately, the server will give us weekly recurrences in days. So, we need to transform a days-based
+ // recurrence that lands on week boundaries to a week-based recurrence.
+ if (type === "D" && periodDuration > 0 && periodDuration % DAYS_PER_WEEK === 0) {
+ type = "W";
+ periodDuration = periodDuration / DAYS_PER_WEEK;
+ }
+ return new SubscriptionRecurrence(periodDuration, numberOfPeriods !== null && numberOfPeriods !== void 0 ? numberOfPeriods : 1, type);
+}
+// endregion
+// region IAP Subscription Trials
+/**
+ * The pre-install description for a subscription trial offer.
+ * @param inAppOfferData The overall data for the subscription, including the discount.
+ * @returns {string} The fully-localized formatted string for the subscription trial description.
+ */
+export function installPagePreInstallTrialDescription(objectGraph, inAppOfferData) {
+ const discountData = serverData.asArrayOrEmpty(inAppOfferData, "discounts")[0];
+ if (!discountData) {
+ return null;
+ }
+ const trialRecurrencePeriod = serverData.asString(discountData, "recurringSubscriptionPeriod");
+ const trialNumberOfPeriods = serverData.asNumber(discountData, "numOfPeriods");
+ const postTrialRecurrencePeriod = serverData.asString(inAppOfferData, "recurringSubscriptionPeriod");
+ const postTrialNumberOfPeriods = serverData.asNumber(inAppOfferData, "numOfPeriods");
+ if (!trialRecurrencePeriod || !postTrialRecurrencePeriod) {
+ return null;
+ }
+ const trialType = serverData.asString(discountData, "modeType");
+ const trialRecurrence = subscriptionRecurrenceForServerRecurrence(objectGraph, trialRecurrencePeriod, trialNumberOfPeriods);
+ const postTrialRecurrence = subscriptionRecurrenceForServerRecurrence(objectGraph, postTrialRecurrencePeriod, postTrialNumberOfPeriods);
+ // Replace any spaces within the formatted prices with non-breaking spaces to avoid prices breaking across multiple lines
+ const trialPriceFormatted = serverData.asString(discountData, "priceFormatted").replace(/ /g, "\u00a0");
+ const postTrialPriceFormatted = serverData.asString(inAppOfferData, "priceFormatted").replace(/ /g, "\u00a0");
+ let postTrialPriceDuration = priceDurationString(objectGraph, postTrialRecurrence.type, postTrialRecurrence.periodDuration, postTrialPriceFormatted);
+ // Update any slash (/) characters to be non-breaking by wrapping with u2060 word-joiner unicode characters.
+ postTrialPriceDuration = postTrialPriceDuration.replace(/\//g, "\u2060/\u2060");
+ switch (trialType) {
+ case "FreeTrial":
+ // template: "Free for @@durationCount@@, then @@postTrialPriceDuration@@ after trial."
+ // result: "Free for 6 months, then $4.99/month after trial."
+ const freeTrialDurationCount = durationCountString(objectGraph, trialRecurrence.type, trialRecurrence.periodDuration * trialRecurrence.periodCount);
+ if (freeTrialDurationCount && postTrialPriceDuration) {
+ return objectGraph.loc
+ .string("InAppOfferPage.Description.FreeTrialTemplate")
+ .replace("@@durationCount@@", tokenReplacer(freeTrialDurationCount))
+ .replace("@@postTrialPriceDuration@@", tokenReplacer(postTrialPriceDuration));
+ }
+ break;
+ case "PayUpFront":
+ // template: "@@durationCount@@ for @@trialPrice@@, then @@postTrialPriceDuration@@ after trial."
+ // result: "3 months for $9.99, then $19.99/year after trial."
+ const payUpFrontDurationCount = durationCountString(objectGraph, trialRecurrence.type, trialRecurrence.periodDuration * trialRecurrence.periodCount);
+ if (payUpFrontDurationCount && postTrialPriceDuration) {
+ return objectGraph.loc
+ .string("InAppOfferPage.Description.PaidUpFrontTemplate")
+ .replace("@@durationCount@@", tokenReplacer(payUpFrontDurationCount))
+ .replace("@@trialPrice@@", tokenReplacer(trialPriceFormatted))
+ .replace("@@postTrialPriceDuration@@", tokenReplacer(postTrialPriceDuration));
+ }
+ break;
+ case "PayAsYouGo":
+ // template: "@@trialPriceDuration@@ for @@durationCount@@, then @@postTrialPriceDuration@@ after trial."
+ // result: "$1.99/week for 3 weeks, then $9.99/month after trial."
+ const trialPriceDuration = priceDurationString(objectGraph, trialRecurrence.type, trialRecurrence.periodDuration, trialPriceFormatted);
+ const durationCount = durationCountString(objectGraph, trialRecurrence.type, trialRecurrence.periodDuration * trialRecurrence.periodCount);
+ if (durationCount && postTrialPriceDuration) {
+ return objectGraph.loc
+ .string("InAppOfferPage.Description.PaidTrialTemplate")
+ .replace("@@trialPriceDuration@@", tokenReplacer(trialPriceDuration))
+ .replace("@@durationCount@@", tokenReplacer(durationCount))
+ .replace("@@postTrialPriceDuration@@", tokenReplacer(postTrialPriceDuration));
+ }
+ break;
+ default:
+ return null;
+ }
+ return null;
+}
+// <rdar://problem/55389195> Arcade grouping upsell text showing pricing token
+// We use a function replacer instead of a string replacer, since `$` is interpreted as a special substitution token.
+// Function replacers have less special semantics.
+function tokenReplacer(replacementString) {
+ return replacementString;
+}
+/**
+ * Returns a string representing a formatted price per duration of time.
+ * e.g. "$3.99/day" if the recurrenceType is `days` and durationCount is `1`.
+ * e.g. "$3.99 every 3 months" the recurrenceType is `months` and durationCount is `3`.
+ * @param recurrenceType: The recurrence type - e.g. days, weeks, months, years.
+ * @param durationCount The count of the duration that will be represented in the string.
+ * @param formattedPrice The formatted price that will be substituted in the returned string.
+ * @returns {string} The localized string for the price per duration.
+ */
+export function priceDurationString(objectGraph, recurrenceType, durationCount, formattedPrice) {
+ // template: "@@price@@ every @@count@@ days"
+ // result: "$1.99 every 3 days"
+ let template;
+ switch (recurrenceType) {
+ // NOTE: Below we have added a workaround to enforce use of the .one plural string variation when count is exactly 1, as jet localizer doesn't do this by default for all languages.
+ // In rdar://113586253 we will adopt newer API from jet that allows us to do this globally, and then this workaround can be removed.
+ case "D":
+ if (durationCount === 1) {
+ template = objectGraph.loc
+ .string("InAppOfferPage.Description.PriceDuration.Days.one")
+ .replace("@@count@@", objectGraph.loc.formattedCount(durationCount));
+ }
+ else {
+ template = objectGraph.loc.stringWithCount("InAppOfferPage.Description.PriceDuration.Days", durationCount);
+ }
+ break;
+ case "W":
+ if (durationCount === 1) {
+ template = objectGraph.loc
+ .string("InAppOfferPage.Description.PriceDuration.Weeks.one")
+ .replace("@@count@@", objectGraph.loc.formattedCount(durationCount));
+ }
+ else {
+ template = objectGraph.loc.stringWithCount("InAppOfferPage.Description.PriceDuration.Weeks", durationCount);
+ }
+ break;
+ case "M":
+ if (durationCount === 1) {
+ template = objectGraph.loc
+ .string("InAppOfferPage.Description.PriceDuration.Months.one")
+ .replace("@@count@@", objectGraph.loc.formattedCount(durationCount));
+ }
+ else {
+ template = objectGraph.loc.stringWithCount("InAppOfferPage.Description.PriceDuration.Months", durationCount);
+ }
+ break;
+ case "Y":
+ if (durationCount === 1) {
+ template = objectGraph.loc
+ .string("InAppOfferPage.Description.PriceDuration.Years.one")
+ .replace("@@count@@", objectGraph.loc.formattedCount(durationCount));
+ }
+ else {
+ template = objectGraph.loc.stringWithCount("InAppOfferPage.Description.PriceDuration.Years", durationCount);
+ }
+ break;
+ default:
+ break;
+ }
+ return template.replace("@@price@@", tokenReplacer(formattedPrice));
+}
+/**
+ * Returns a string representing the duration count of the specified recurrence type.
+ * e.g. "3 days" if recurrenceType is `days` and durationCount is `3`.
+ * @param recurrenceType: The recurrence type - e.g. days, weeks, months, years.
+ * @param durationCount The count of the duration that will be represented in the string.
+ * @returns {string} The localized duration count string.
+ */
+export function durationCountString(objectGraph, recurrenceType, durationCount) {
+ switch (recurrenceType) {
+ // NOTE: Below we have added a workaround to enforce use of the .one plural string variation when count is exactly 1, as jet localizer doesn't do this by default for all languages.
+ // In rdar://113586253 we will adopt newer API from jet that allows us to do this globally, and then this workaround can be removed.
+ case "D":
+ if (durationCount === 1) {
+ return objectGraph.loc
+ .string("InAppOfferPage.Description.DurationCount.Days.one")
+ .replace("@@count@@", objectGraph.loc.formattedCount(durationCount));
+ }
+ return objectGraph.loc.stringWithCount("InAppOfferPage.Description.DurationCount.Days", durationCount);
+ case "W":
+ if (durationCount === 1) {
+ return objectGraph.loc
+ .string("InAppOfferPage.Description.DurationCount.Weeks.one")
+ .replace("@@count@@", objectGraph.loc.formattedCount(durationCount));
+ }
+ return objectGraph.loc.stringWithCount("InAppOfferPage.Description.DurationCount.Weeks", durationCount);
+ case "M":
+ if (durationCount === 1) {
+ return objectGraph.loc
+ .string("InAppOfferPage.Description.DurationCount.Months.one")
+ .replace("@@count@@", objectGraph.loc.formattedCount(durationCount));
+ }
+ return objectGraph.loc.stringWithCount("InAppOfferPage.Description.DurationCount.Months", durationCount);
+ case "Y":
+ if (durationCount === 1) {
+ return objectGraph.loc
+ .string("InAppOfferPage.Description.DurationCount.Years.one")
+ .replace("@@count@@", objectGraph.loc.formattedCount(durationCount));
+ }
+ return objectGraph.loc.stringWithCount("InAppOfferPage.Description.DurationCount.Years", durationCount);
+ default:
+ break;
+ }
+ return null;
+}
+//# sourceMappingURL=offer-formatting.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/offers/offers.js b/node_modules/@jet-app/app-store/tmp/src/common/offers/offers.js
new file mode 100644
index 0000000..2cb2986
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/offers/offers.js
@@ -0,0 +1,1735 @@
+//
+// offers.ts
+// AppStoreKit
+//
+// Created by Kevin MacWhinnie on 8/15/16.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import * as validation from "@jet/environment/json/validation";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as dateUtil from "../../foundation/util/date-util";
+import * as objects from "../../foundation/util/objects";
+import { MetricsIdentifierType } from "../../foundation/metrics/metrics-identifiers-cache";
+import * as client from "../../foundation/wrappers/client";
+import * as ageRatings from "../content/age-ratings";
+import * as artworkBuilder from "../content/artwork/artwork";
+import * as contentAttributes from "../content/attributes";
+import * as content from "../content/content";
+import * as contentDeviceFamily from "../content/device-family";
+import * as gameController from "../content/game-controller";
+import * as sad from "../content/sad";
+import * as filtering from "../filtering";
+import * as links from "../linking/os-update-links";
+import * as lockups from "../lockups/lockups";
+import { adBuyParamKeys } from "../metrics/helpers/buy";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsUtil from "../metrics/helpers/util";
+import * as productVariant from "../product-page/product-page-variants";
+import { externalPurchasesPlacementIsEnabled, hasExternalPurchasesForData } from "./external-purchases";
+/**
+ * Create a purchase configuration (aka purchase token) from the provided product and other data.
+ * @param objectGraph The App Store object graph.
+ * @param product The data for the product.
+ * @param buyParams The buy params being passed through the offer.
+ * @param isPreorder Whether the purchase is a pre-order.
+ * @param excludeAttribution Whether attribution should be excluded for this purchase.
+ * @param pageInformation The information for the page the purchase will take place on.
+ * @param metricsPlatformDisplayStyle The platform display style for the purchase.
+ * @param options The MetricsClickOptions.
+ * @param referrerData Referrer data for the purchase.
+ * @param isDefaultBrowser Whether the install is a default browser install.
+ * @returns A complete PurchaseConfiguration.
+ */
+export function purchaseConfigurationFromProduct(objectGraph, product, buyParams, isPreorder, excludeAttribution, pageInformation, metricsPlatformDisplayStyle, options, referrerData, isDefaultBrowser) {
+ return validation.context("purchaseConfigurationFromProduct", () => {
+ const appTitle = mediaAttributes.attributeAsString(product, "name");
+ let vendor = mediaAttributes.attributeAsString(product, "artistName");
+ if (!vendor) {
+ vendor = "test";
+ }
+ const bundleId = sad.systemApps(objectGraph).bundleIdFromData(product);
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, product);
+ const lineItem = mediaAttributes.attributeAsString(product, "iad.lineItem");
+ const preflightPackageUrl = contentAttributes.contentAttributeAsString(objectGraph, product, "preflightPackageUrl");
+ const supportsArcade = content.isArcadeSupported(objectGraph, product);
+ const supportsMacOSCompatibleIOSBinary = content.supportsMacOSCompatibleIOSBinaryFromData(objectGraph, product, objectGraph.appleSilicon.isSupportEnabled);
+ const supportsVisionOSCompatibleIOSBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, product);
+ const extRefApp2 = serverData.asString(referrerData, "app");
+ const extRefUrl2 = serverData.asString(referrerData, "externalUrl");
+ // Get the Serial Numbers from the devices we want to download from
+ const remoteDownloadIdentifiers = supportsVisionDownloadFromVisionCompanion(objectGraph, product)
+ ? objectGraph.client.remoteDownloadIdentifiers
+ : [];
+ const hasMacIPAPackage = hasMacIPAPackageForData(objectGraph, product);
+ const contentRating = ageRatings.value(objectGraph, product, true);
+ const purchaseConfiguration = new models.PurchaseConfiguration(buyParams, vendor, appTitle, bundleId, appPlatforms, isPreorder, excludeAttribution, metricsPlatformDisplayStyle, lineItem, false, preflightPackageUrl, supportsArcade, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary, options.inAppEventId, extRefApp2, extRefUrl2, undefined, content.appBinaryTraitsFromData(objectGraph, product), isDefaultBrowser, remoteDownloadIdentifiers, hasMacIPAPackage, contentRating);
+ purchaseConfiguration.pageInformation = { ...pageInformation };
+ purchaseConfiguration.productVariantData = productVariant.productVariantDataForData(objectGraph, product);
+ purchaseConfiguration.targetType = options.targetType;
+ purchaseConfiguration.metricsKind = options.kind;
+ return purchaseConfiguration;
+ });
+}
+/**
+ * Extract the offer data from the provided data object.
+ * @param objectGraph The App Store object graph.
+ * @param data The data blob from which to extract the offer data.
+ * @param attributePlatformOverride An override platform, from which to fetch the offer data.
+ * @returns A `JSONData` with offer data.
+ */
+export function offerDataFromData(objectGraph, data, attributePlatformOverride = undefined) {
+ return validation.context("offerDataFromData", () => {
+ const offers = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, data, "offers", attributePlatformOverride);
+ if (offers.length === 0) {
+ return null;
+ }
+ return offers[0];
+ });
+}
+export function offerDataFromMarketingItem(objectGraph, marketingItemData) {
+ const offersArray = mediaAttributes.attributeAsArrayOrEmpty(marketingItemData, "offers");
+ if (offersArray.length === 0) {
+ return null;
+ }
+ return offersArray[0];
+}
+export function updateOfferDataFromData(objectGraph, data) {
+ return validation.context("updateOfferDataFromData", () => {
+ const offers = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, data, "offers");
+ if (offers.length === 0) {
+ return null;
+ }
+ for (const offerDict of offers) {
+ const type = serverData.asString(offerDict, "type");
+ if (type === "update") {
+ return offerDict;
+ }
+ }
+ return null;
+ });
+}
+/**
+ * Create an offer action using the provided offer data.
+ * @param objectGraph The App Store object graph.
+ * @param offerData The offer data from the original data.
+ * @param data The data for the product.
+ * @param isPreorder Whether the purchase is a pre-order.
+ * @param includeBetaApps Whether beta apps should be included.
+ * @param metricsPlatformDisplayStyle The platform display style for the purchase.
+ * @param options The MetricsClickOptions.
+ * @param context The context in which the offer will be placed.
+ * @param referrerData Referrer data for the purchase.
+ * @param isDefaultBrowser Whether the install is a default browser install.
+ * @returns A complete OfferAction.
+ */
+export function offerActionFromOfferData(objectGraph, offerData, data, isPreorder, includeBetaApps, metricsPlatformDisplayStyle, options, context = "default", referrerData, isDefaultBrowser, parentAdamId) {
+ return validation.context(`offerActionFromOfferData: ${data.id}`, () => {
+ var _a, _b, _c, _d, _e, _f, _g;
+ /* Buy Params */
+ let buyParams = serverData.asString(offerData, "buyParams");
+ if (serverData.isNull(buyParams)) {
+ validation.unexpectedNull("ignoredValue", "string", "item.offer.buyParams");
+ return null;
+ }
+ // This was added as a workaround for:
+ // <rdar://problem/33567931> Bundles: Suppress CMB dialog
+ //
+ // TODO: We need to suppress this dialog until CmB is completed:
+ // <rdar://problem/32007058> Bundles: Implement complete my bundle
+ //
+ // TODO: Unsuppress when commerce has new intrim alerts ready:
+ // <rdar://problem/33732045> Bundles: Remove Supressing CMB dialog
+ if (data.type === "app-bundles") {
+ if (buyParams.indexOf("rebuy") >= 0) {
+ buyParams = buyParams.replace("rebuy=false", "rebuy=true");
+ }
+ else {
+ if (buyParams.length > 0) {
+ buyParams += "&";
+ }
+ buyParams += "rebuy=true";
+ }
+ }
+ if (serverData.isDefinedNonNullNonEmpty(options.inAppEventId)) {
+ if (buyParams.length > 0) {
+ buyParams += "&";
+ }
+ buyParams += `mtInAppEventId=${options.inAppEventId}`;
+ }
+ if (options.isAdvert) {
+ const iAdPlacementId = (_b = (_a = options.pageInformation) === null || _a === void 0 ? void 0 : _a.iAdInfo) === null || _b === void 0 ? void 0 : _b.placementId;
+ if (serverData.isDefinedNonNull(iAdPlacementId)) {
+ if (buyParams.length > 0) {
+ buyParams += "&";
+ }
+ buyParams += `${adBuyParamKeys.placementId}=${iAdPlacementId}`;
+ }
+ const iAdContainerId = (_d = (_c = options.pageInformation) === null || _c === void 0 ? void 0 : _c.iAdInfo) === null || _d === void 0 ? void 0 : _d.containerId;
+ if (serverData.isDefinedNonNull(iAdContainerId)) {
+ if (buyParams.length > 0) {
+ buyParams += "&";
+ }
+ buyParams += `${adBuyParamKeys.containerId}=${iAdContainerId}`;
+ }
+ const iAdTemplateType = (_f = (_e = options.pageInformation) === null || _e === void 0 ? void 0 : _e.iAdInfo) === null || _f === void 0 ? void 0 : _f.clickFields["iAdTemplateType"];
+ if (serverData.isDefinedNonNull(iAdTemplateType)) {
+ if (buyParams.length > 0) {
+ buyParams += "&";
+ }
+ buyParams += `${adBuyParamKeys.templateType}=${iAdTemplateType}`;
+ }
+ }
+ /* Adam Id */
+ const adamId = data.id;
+ if (serverData.isNull(adamId)) {
+ validation.unexpectedNull("ignoredValue", "string", "item.offer.id");
+ return null;
+ }
+ /* Purchase Configuration */
+ const purchaseConfiguration = purchaseConfigurationFromProduct(objectGraph, data, buyParams, isPreorder, options.excludeAttribution, options.pageInformation, metricsPlatformDisplayStyle, options, referrerData, isDefaultBrowser);
+ const action = internalOfferActionFromOfferData(objectGraph, offerData, adamId, purchaseConfiguration, includeBetaApps, context, (_g = options.isAdvert) !== null && _g !== void 0 ? _g : false, parentAdamId);
+ metricsHelpersClicks.addBuyEventToOfferActionOnPage(objectGraph, action, options, isPreorder, isDefaultBrowser);
+ return action;
+ });
+}
+/**
+ * Determines whether a given offer action is a free.
+ * @param offerAction The offer action to check.
+ * @returns true if it's a free
+ */
+export function isFreeFromOfferAction(objectGraph, offerAction) {
+ return serverData.isNull(offerAction) || serverData.isNull(offerAction.price) || offerAction.price === 0;
+}
+export function expectedReleaseDateFromData(objectGraph, data) {
+ return validation.context("expectedReleaseDateFromData", () => {
+ const expectedReleaseDateString = mediaAttributes.attributeAsString(data, "offers.0.expectedReleaseDate");
+ return dateUtil.parseDateOmittingTimeFromString(expectedReleaseDateString);
+ });
+}
+/**
+ * Determines the price for the offer data.
+ * @param {JSONData} offer The offer data from which to determine the price.
+ * @returns {number} The price for the offer.
+ */
+export function priceFromOfferData(objectGraph, offer) {
+ const type = serverData.asString(offer, "type");
+ if (type === "buy" || type === "complete" || type === "preorder") {
+ return serverData.asNumber(offer, "price");
+ }
+ return null;
+}
+/**
+ * Common builder for `OfferAction` model object. Metrics events should be added by calling function.
+ * @param offer JSONData of offer from platform data.
+ * @param adamId AdamId of offered product.
+ * @param purchaseConfiguration Configuration to use in offer.
+ * @param includeBetaApps Whether to include beta apps in the offer button configuration.
+ * @param context The context within which this offer button is visible.
+ * @param isAd Whether offer button is for an ad.
+ * @returns An `OfferAction` with given parameters.
+ */
+function internalOfferActionFromOfferData(objectGraph, offer, adamId, purchaseConfiguration, includeBetaApps, context = "default", isAd = false, parentAdamId) {
+ return validation.context("offerActionFromOfferData", () => {
+ // Localize action title
+ const type = serverData.asString(offer, "type");
+ const useAdsLocale = isAd && context === "default" && isSome(objectGraph.bag.adsOverrideLanguage);
+ const adsOverrideLocalizer = useAdsLocale ? objectGraph.adsLoc : objectGraph.loc;
+ let actionTitle;
+ switch (type) {
+ case "get":
+ if (context === "flowPreview") {
+ actionTitle = objectGraph.loc.string("OfferButton.FlowPreview.Get", "Get");
+ }
+ else {
+ const watchOSActionTitleKey = "OfferButton.Title.Get.TitleCase";
+ const actionTitleKey = objectGraph.client.isWatch ? watchOSActionTitleKey : "OfferButton.Title.Get";
+ actionTitle = adsOverrideLocalizer.string(actionTitleKey);
+ }
+ break;
+ case "preorder":
+ if (context === "flowPreview") {
+ actionTitle = objectGraph.loc.string("OfferButton.FlowPreview.Preorder", "Pre-Order");
+ }
+ else {
+ actionTitle = adsOverrideLocalizer.string("OfferButton.Title.Get");
+ }
+ break;
+ default:
+ actionTitle = type;
+ }
+ let actionPrice = null;
+ let actionPriceFormatted = null;
+ const price = priceFromOfferData(objectGraph, offer);
+ if (price > 0) {
+ actionPrice = price;
+ actionPriceFormatted = serverData.asString(offer, "priceFormatted");
+ }
+ const expectedReleaseDateString = serverData.asString(offer, "expectedReleaseDate");
+ const expectedReleaseDate = dateUtil.parseDateOmittingTimeFromString(expectedReleaseDateString);
+ const action = new models.OfferAction(actionTitle, adamId, purchaseConfiguration, parentAdamId);
+ action.price = actionPrice;
+ action.priceFormatted = actionPriceFormatted;
+ action.expectedReleaseDate = expectedReleaseDate;
+ action.includeBetaApps = includeBetaApps;
+ return action;
+ });
+}
+function wrapOfferActionForPreorder(objectGraph, action, data, options, context) {
+ if (serverData.isNull(action)) {
+ return null;
+ }
+ const preorderStateAction = cancellablePreorderOfferStateAction(objectGraph, data, action, false, options);
+ preorderStateAction.buyAction = action;
+ return preorderStateAction;
+}
+export function wrapOfferActionIfNeeded(objectGraph, action, data, isPreorder, metricsOptions, context = "default", clientIdentifierOverride = null, shouldNavigateToProductPage = false) {
+ if (isNothing(action)) {
+ return null;
+ }
+ // Emit update links for macOS installers
+ if (content.isMacOSInstaller(objectGraph, data)) {
+ if (context === "flowPreview") {
+ return null;
+ }
+ // When `default`, i.e. not product page, show "VIEW".
+ if (context === "default") {
+ return lockups.actionFromData(objectGraph, data, metricsOptions, null);
+ }
+ // Otherwise punt to Software Update
+ const matchingOSBundle = contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId");
+ if (serverData.isDefinedNonNullNonEmpty(matchingOSBundle)) {
+ const installUpdateUrl = links.osUpdateUrl("mac", matchingOSBundle);
+ if (isSome(installUpdateUrl)) {
+ const updateAction = new models.ExternalUrlAction(installUpdateUrl);
+ return new models.OfferStateAction(action.adamId, updateAction);
+ }
+ }
+ }
+ if (context === "default") {
+ if (data.type === "app-bundles" ||
+ content.isUnsupportedByCurrentCompanion(objectGraph, data) ||
+ shouldNavigateToProductPage) {
+ return lockups.actionFromData(objectGraph, data, metricsOptions, null);
+ }
+ }
+ // Wrap non-Arcade preorders to enable view to drill into product page (except for tvOS)
+ // Note: Arcade pre-orders are handled by `wrapArcadeAppOfferActionIfNeeded`.
+ if (isPreorder && objectGraph.client.deviceType !== "tv" && !content.isArcadeSupported(objectGraph, data)) {
+ const wrappedPreorderAction = wrapOfferActionForPreorder(objectGraph, action, data, metricsOptions, context);
+ if (wrappedPreorderAction !== null) {
+ return wrappedPreorderAction;
+ }
+ }
+ // Configure chain of dialogs for vision-only purchase.
+ // Note: Arcade purchases are handled by `wrapArcadeAppOfferActionIfNeeded`.
+ const isVisionOnlyApp = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "realityDevice");
+ const isCompanion = objectGraph.client.isCompanionVisionApp;
+ if (!content.isArcadeSupported(objectGraph, data) &&
+ (isCompanion || (isVisionOnlyApp && objectGraph.client.deviceType !== "vision"))) {
+ const isFree = isFreeFromOfferAction(objectGraph, action);
+ const isArcade = content.isArcadeSupported(objectGraph, data);
+ return visionAppActionForBuyAction(objectGraph, action, data, isFree, isArcade);
+ }
+ // Configure chain of dialogs for tvOS-only purchase.
+ const isTvOnlyApp = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "tvos");
+ if (isTvOnlyApp && objectGraph.client.deviceType !== "tv") {
+ const requiresGameController = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "requiresGameController");
+ return tvOnlyAppActionForBuyAction(objectGraph, action, requiresGameController);
+ }
+ // Configure dialogs for watchOS-only purchase.
+ const isWatchOnlyApp = !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isDeliveredInIOSAppForWatchOS") &&
+ contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS");
+ if (isWatchOnlyApp && objectGraph.client.deviceType !== "watch") {
+ return watchOnlyAppActionForBuyAction(objectGraph, action);
+ }
+ // App is not supported on paired watch OS version
+ const minimumWatchOSVersionString = contentAttributes.contentAttributeAsString(objectGraph, data, "minimumWatchOSVersion");
+ if (serverData.isDefinedNonNullNonEmpty(clientIdentifierOverride) &&
+ clientIdentifierOverride === client.watchIdentifier &&
+ isSome(minimumWatchOSVersionString) &&
+ content.isActivePairedWatchOSBelowVersion(objectGraph, minimumWatchOSVersionString)) {
+ return watchUpdateRequiredActionForBuyAction(objectGraph, action, minimumWatchOSVersionString);
+ }
+ if (shouldWrapOffer(objectGraph, data)) {
+ // Handle all Arcade offers.
+ if (content.isArcadeSupported(objectGraph, data)) {
+ return wrapArcadeAppOfferActionIfNeeded(objectGraph, action, data, isPreorder, context, arcadeSubscribeContextFromOfferContext(objectGraph, context, isPreorder), metricsOptions);
+ }
+ // Wrap offers on tvOS in `OfferAlertAction` in order to check requirements and confirm.
+ const offerActionIndex = createOfferAlertActionIfNeeded(objectGraph, action, data, isPreorder, metricsOptions);
+ const offerAlertAction = offerActionIndex.startAction;
+ // Handle all NON-Arcade pre-orders.
+ if (isPreorder) {
+ const preorderStateAction = cancellablePreorderOfferStateAction(objectGraph, data, offerActionIndex.underlyingOfferAction, false, metricsOptions);
+ preorderStateAction.buyAction = offerAlertAction;
+ return preorderStateAction;
+ }
+ // Handle NON-Arcade NON-pre-orders.
+ return offerAlertAction;
+ }
+ else {
+ // Arcade offers do *not* get wrapped in a two-phase confirmation.
+ return wrapOfferInTwoPhasedConfirmationActionIfNeeded(objectGraph, action, isPreorder, metricsOptions);
+ }
+}
+function shouldWrapOffer(objectGraph, data) {
+ if (content.isArcadeSupported(objectGraph, data)) {
+ return true;
+ }
+ if (objectGraph.client.isTV || objectGraph.client.isVision) {
+ return true;
+ }
+ return false;
+}
+function arcadeSubscribeContextFromOfferContext(objectGraph, offerContext, isArcadePreorder) {
+ if (isArcadePreorder) {
+ return models.marketingItemContextFromString("arcadeComingSoon");
+ }
+ switch (offerContext) {
+ case "productPage":
+ return models.marketingItemContextFromString("productPage");
+ case "default":
+ case "flowPreview":
+ return models.marketingItemContextFromString("groupingLockup");
+ default:
+ return models.marketingItemContextFromString("generic");
+ }
+}
+export function appInstallActionFromAppData(objectGraph, data, offerContext, marketingItemContext, isPreorder, metricsOptions, clientIdentifierOverride = null) {
+ switch (marketingItemContext) {
+ case models.marketingItemContextFromString("productPage"):
+ case models.marketingItemContextFromString("groupingLockup"):
+ const primaryIcon = content.iconFromData(objectGraph, data, {
+ useCase: 3 /* content.ArtworkUseCase.LockupIconLarge */,
+ });
+ const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, data, metricsOptions);
+ const offerData = offerDataFromData(objectGraph, data);
+ const metricsPlatformDisplayStyle = metricsUtil.metricsPlatformDisplayStyleFromData(objectGraph, data, primaryIcon, clientIdentifierOverride);
+ return offerActionFromOfferData(objectGraph, offerData, data, isPreorder, false, metricsPlatformDisplayStyle, metricsClickOptions, offerContext);
+ default:
+ return null;
+ }
+}
+/**
+ * Ensures that the provided action is properly wrapped if it is for an Arcade app.
+ * @param objectGraph The object graph.
+ * @param action The naked offer action.
+ * @param underlyingAction The buy action for tvOS.
+ * @param data The data for the product.
+ * @param isPreorder Indicates whether or not this offer action is for a preorder.
+ * @param offerContext Contextual information about this offer.
+ * @param marketingItemContext Contextual information about the marketing item.
+ * @param metricsOptions Contextual information about metrics.
+ */
+function wrapArcadeAppOfferActionIfNeeded(objectGraph, offerAction, data, isPreorder, offerContext, marketingItemContext, metricsOptions) {
+ if (!content.isArcadeSupported(objectGraph, data)) {
+ return offerAction;
+ }
+ switch (objectGraph.client.deviceType) {
+ case "tv":
+ return wrapArcadeAppOfferActionForTV(objectGraph, offerAction, data, isPreorder, marketingItemContext, metricsOptions);
+ case "vision":
+ return wrapArcadeAppOfferActionForVision(objectGraph, offerAction, data, isPreorder, marketingItemContext, metricsOptions);
+ default:
+ return wrapArcadeAppOfferActionForOtherPlatforms(objectGraph, offerAction, data, isPreorder, offerContext, marketingItemContext, metricsOptions);
+ }
+}
+/**
+ * Wrap the offer action for Arcade app in TV to check requirements, presenting upsell and confirm.
+ *
+ * If the app is preorder app:
+ * - It should show `OfferAlertAction` first to check requirements (eg: game controller)
+ * - After alert is shown, perform the preorder action to subscribe/unsubscribe coming soon order.
+ * - Then show the upsell with rate limit applied.
+ * - If user purchase subscription success, perform the buy action.
+ *
+ * Otherwise:
+ * - Show the `OfferAlertAction` first to check requirements (eg: game controller)
+ * - After alert is shown, show the upsell action.
+ * - If user purchase subscription success, perform the buy action.
+ *
+ * @param objectGraph The object graph.
+ * @param action The offer action that perform requirements check before buy.
+ * @param underlyingAction The buy action for tvOS.
+ * @param data The data for the product.
+ * @param isPreorder Indicates whether or not this offer action is for a preorder.
+ * @param marketingItemContext Contextual information about the marketing item.
+ * @param metricsOptions Contextual information about metrics.
+ */
+function wrapArcadeAppOfferActionForTV(objectGraph, offerAction, data, isPreorder, marketingItemContext, metricsOptions) {
+ var _a, _b, _c, _d;
+ // Wrap offers on tvOS in `OfferAlertAction` in order to check requirements and confirm.
+ const offerActionIndex = createTVOfferAlertActionIfNeeded(objectGraph, offerAction, null, data, isPreorder, metricsOptions);
+ const offerAlertAction = offerActionIndex.startAction;
+ const buyAction = offerActionIndex.underlyingOfferAction;
+ const isContextual = models.isContextualUpsellContext(marketingItemContext);
+ const upsellRequestInfo = new models.MarketingItemRequestInfo("arcade", marketingItemContext, objectGraph.bag.metricsTopic, data.id);
+ if (isSome((_b = (_a = metricsOptions.pageInformation) === null || _a === void 0 ? void 0 : _a.searchTermContext) === null || _b === void 0 ? void 0 : _b.term)) {
+ upsellRequestInfo.metricsOverlay["searchTerm"] = (_c = metricsOptions.pageInformation.searchTermContext) === null || _c === void 0 ? void 0 : _c.term;
+ }
+ const metricsIdentifierFields = (_d = objectGraph.metricsIdentifiersCache) === null || _d === void 0 ? void 0 : _d.getMetricsFieldsForTypes([
+ MetricsIdentifierType.user,
+ MetricsIdentifierType.client,
+ MetricsIdentifierType.canonical,
+ ]);
+ if (isSome(metricsIdentifierFields)) {
+ upsellRequestInfo.metricsOverlay = {
+ ...upsellRequestInfo.metricsOverlay,
+ ...metricsIdentifierFields,
+ };
+ }
+ if (isContextual) {
+ upsellRequestInfo.purchaseSuccessAction = buyAction;
+ upsellRequestInfo.carrierLinkSuccessAction = buyAction;
+ }
+ const upsellAction = new models.FlowAction("upsellMarketingItem");
+ upsellAction.pageData = upsellRequestInfo;
+ if (metricsOptions && metricsOptions.pageInformation) {
+ upsellAction.referrerUrl = metricsOptions.pageInformation.pageUrl;
+ }
+ // Always add buyParams to actionDetails of offer actions so that we have them when reporting events for subscription buys
+ if (offerAlertAction instanceof models.OfferAction) {
+ metricsOptions.actionDetails = {
+ buyParams: offerAlertAction.purchaseConfiguration.buyParams,
+ ...metricsOptions.actionDetails,
+ };
+ }
+ metricsHelpersClicks.addClickEventToArcadeBuyInitiateAction(objectGraph, upsellAction, metricsOptions);
+ // Arcade Pre-order logic
+ if (isPreorder) {
+ const preorderStateAction = cancellablePreorderOfferStateAction(objectGraph, data, buyAction, true, metricsOptions); // For subscribers
+ preorderStateAction.buyAction = offerAlertAction;
+ const preorderOrCancelSubscribePageAction = cancellablePreorderOfferStateAction(objectGraph, data, buyAction, true, metricsOptions); // For non-subscribers
+ preorderOrCancelSubscribePageAction.buyAction = offerAlertAction;
+ preorderStateAction.subscribePageAction = preorderOrCancelSubscribePageAction;
+ buyAction.buyCompletedAction = arcadePreOrderBuyCompleteActionForTV(objectGraph, upsellAction);
+ return preorderStateAction;
+ }
+ else {
+ // If user is not subscribed to Arcade: show OfferAlertAction then display upsell
+ // Otherwise: we want to show OfferAlertAction without upsell
+ const stateAction = new models.OfferStateAction(data.id, offerAlertAction);
+ const wrappedUpsellAction = createTVOfferAlertActionIfNeeded(objectGraph, offerAction, upsellAction, data, isPreorder, metricsOptions).startAction;
+ stateAction.subscribePageAction = wrappedUpsellAction;
+ return stateAction;
+ }
+}
+/**
+ * Wrap the offer action for Arcade app in Vision to check requirements, presenting upsell and confirm.
+ *
+ * If the app is preorder app:
+ * - It should show `OfferAlertAction` first to check requirements (eg: game controller)
+ * - After alert is shown, perform the preorder action to subscribe/unsubscribe coming soon order.
+ * - Then show the upsell with rate limit applied.
+ * - If user purchase subscription success, perform the buy action.
+ *
+ * Otherwise:
+ * - Show the `OfferAlertAction` first to check requirements (eg: game controller)
+ * - After alert is shown, show the upsell action.
+ * - If user purchase subscription success, perform the buy action.
+ *
+ * @param objectGraph The object graph.
+ * @param action The offer action that perform requirements check before buy.
+ * @param underlyingAction The buy action for tvOS.
+ * @param data The data for the product.
+ * @param isPreorder Indicates whether or not this offer action is for a preorder.
+ * @param marketingItemContext Contextual information about the marketing item.
+ * @param metricsOptions Contextual information about metrics.
+ */
+function wrapArcadeAppOfferActionForVision(objectGraph, offerAction, data, isPreorder, marketingItemContext, metricsOptions) {
+ var _a, _b, _c, _d;
+ // Wrap offers on tvOS in `OfferAlertAction` in order to check requirements and confirm.
+ const offerActionIndex = createVisionOfferAlertActionIfNeeded(objectGraph, offerAction, null, data, isPreorder, metricsOptions);
+ const offerAlertAction = offerActionIndex.startAction;
+ const buyAction = offerActionIndex.underlyingOfferAction;
+ const isContextual = models.isContextualUpsellContext(marketingItemContext);
+ const upsellRequestInfo = new models.MarketingItemRequestInfo("arcade", marketingItemContext, objectGraph.bag.metricsTopic, data.id);
+ if (isSome((_b = (_a = metricsOptions.pageInformation) === null || _a === void 0 ? void 0 : _a.searchTermContext) === null || _b === void 0 ? void 0 : _b.term)) {
+ upsellRequestInfo.metricsOverlay["searchTerm"] = (_c = metricsOptions.pageInformation.searchTermContext) === null || _c === void 0 ? void 0 : _c.term;
+ }
+ const metricsIdentifierFields = (_d = objectGraph.metricsIdentifiersCache) === null || _d === void 0 ? void 0 : _d.getMetricsFieldsForTypes([
+ MetricsIdentifierType.user,
+ MetricsIdentifierType.client,
+ MetricsIdentifierType.canonical,
+ ]);
+ if (isSome(metricsIdentifierFields)) {
+ upsellRequestInfo.metricsOverlay = {
+ ...upsellRequestInfo.metricsOverlay,
+ ...metricsIdentifierFields,
+ };
+ }
+ if (isContextual) {
+ upsellRequestInfo.purchaseSuccessAction = buyAction;
+ upsellRequestInfo.carrierLinkSuccessAction = buyAction;
+ }
+ const upsellAction = new models.FlowAction("upsellMarketingItem");
+ upsellAction.pageData = upsellRequestInfo;
+ if (metricsOptions && metricsOptions.pageInformation) {
+ upsellAction.referrerUrl = metricsOptions.pageInformation.pageUrl;
+ }
+ // Always add buyParams to actionDetails of offer actions so that we have them when reporting events for subscription buys
+ if (offerAlertAction instanceof models.OfferAction) {
+ metricsOptions.actionDetails = {
+ buyParams: offerAlertAction.purchaseConfiguration.buyParams,
+ ...metricsOptions.actionDetails,
+ };
+ }
+ metricsHelpersClicks.addClickEventToArcadeBuyInitiateAction(objectGraph, upsellAction, metricsOptions);
+ // Arcade Pre-order logic
+ if (isPreorder && buyAction !== null) {
+ const preorderStateAction = cancellablePreorderOfferStateAction(objectGraph, data, buyAction, true, metricsOptions); // For subscribers
+ preorderStateAction.buyAction = offerAlertAction;
+ const preorderOrCancelSubscribePageAction = cancellablePreorderOfferStateAction(objectGraph, data, buyAction, true, metricsOptions); // For non-subscribers
+ preorderOrCancelSubscribePageAction.buyAction = offerAlertAction;
+ preorderStateAction.subscribePageAction = preorderOrCancelSubscribePageAction;
+ buyAction.buyCompletedAction = arcadePreOrderBuyCompleteAction(objectGraph, upsellAction);
+ return preorderStateAction;
+ }
+ else {
+ // If user is not subscribed to Arcade: show OfferAlertAction then display upsell
+ // Otherwise: we want to show OfferAlertAction without upsell
+ const stateAction = new models.OfferStateAction(data.id, offerAlertAction);
+ const wrappedUpsellAction = createVisionOfferAlertActionIfNeeded(objectGraph, offerAction, upsellAction, data, isPreorder, metricsOptions).startAction;
+ stateAction.subscribePageAction = wrappedUpsellAction;
+ return stateAction;
+ }
+}
+/**
+ * Wrap the offer action for Arcade app in MacOS, iOS, WatchOS to present the upsell or subscribe/unsubscribe preorder
+ *
+ * If the app is preorder app:
+ * - Wraps the offer action with `cancellablePreorderOfferStateAction` for subscribe/unsubscribe coming soon order.
+ * - After the preorder offer action completed, it will call the Upsell FlowAction with rate limited.
+ *
+ * Otherwise:
+ * - Create a new OfferState action
+ * - Create an Upsell FlowAction, and add the upsell action as `subscribePageAction`.
+ * - Add the offer action as `purchaseSuccessAction` or `carrierLinkSuccessAction` to the Upsell FlowAction.
+ *
+ * @param objectGraph The object graph.
+ * @param action The offer action that perform requirements check before buy.
+ * @param underlyingAction The buy action for tvOS.
+ * @param data The data for the product.
+ * @param isPreorder Indicates whether or not this offer action is for a preorder.
+ * @param offerContext Contextual information about this offer.
+ * @param marketingItemContext Contextual information about the marketing item.
+ * @param metricsOptions Contextual information about metrics.
+ * @returns the final action for offer button.
+ */
+function wrapArcadeAppOfferActionForOtherPlatforms(objectGraph, offerAction, data, isPreorder, offerContext, marketingItemContext, metricsOptions) {
+ var _a, _b, _c, _d;
+ const isContextual = models.isContextualUpsellContext(marketingItemContext);
+ const upsellRequestInfo = new models.MarketingItemRequestInfo("arcade", marketingItemContext, objectGraph.bag.metricsTopic, data.id);
+ if (isSome((_b = (_a = metricsOptions.pageInformation) === null || _a === void 0 ? void 0 : _a.searchTermContext) === null || _b === void 0 ? void 0 : _b.term)) {
+ upsellRequestInfo.metricsOverlay["searchTerm"] = (_c = metricsOptions.pageInformation.searchTermContext) === null || _c === void 0 ? void 0 : _c.term;
+ }
+ const metricsIdentifierFields = (_d = objectGraph.metricsIdentifiersCache) === null || _d === void 0 ? void 0 : _d.getMetricsFieldsForTypes([
+ MetricsIdentifierType.user,
+ MetricsIdentifierType.client,
+ MetricsIdentifierType.canonical,
+ ]);
+ if (isSome(metricsIdentifierFields)) {
+ upsellRequestInfo.metricsOverlay = {
+ ...upsellRequestInfo.metricsOverlay,
+ ...metricsIdentifierFields,
+ };
+ }
+ // If the host app is not set, and this is the isCompanionVisionApp and the supportsCompanionCheck is permitted,
+ // update the host app to ClientIdentifier.VisionCompanion.
+ if (objectGraph.props.enabled("supportsCompanionCheck") &&
+ objectGraph.client.isCompanionVisionApp &&
+ isNothing(upsellRequestInfo.metricsOverlay["hostApp"])) {
+ upsellRequestInfo.metricsOverlay["hostApp"] = "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */;
+ }
+ if (isContextual) {
+ upsellRequestInfo.purchaseSuccessAction = offerAction;
+ upsellRequestInfo.carrierLinkSuccessAction = offerAction;
+ }
+ const upsellAction = new models.FlowAction("upsellMarketingItem");
+ upsellAction.pageData = upsellRequestInfo;
+ if (metricsOptions && metricsOptions.pageInformation) {
+ upsellAction.referrerUrl = metricsOptions.pageInformation.pageUrl;
+ }
+ // Always add buyParams to actionDetails of offer actions so that we have them when reporting events for subscription buys
+ metricsOptions.actionDetails = {
+ buyParams: offerAction.purchaseConfiguration.buyParams,
+ ...metricsOptions.actionDetails,
+ };
+ metricsHelpersClicks.addClickEventToArcadeBuyInitiateAction(objectGraph, upsellAction, metricsOptions);
+ // Arcade Pre-order logic
+ if (isPreorder) {
+ // iOS and macOS
+ if (offerAction instanceof models.OfferAction) {
+ const preorderStateAction = cancellablePreorderOfferStateAction(objectGraph, data, offerAction, true, metricsOptions); // For subscribers
+ preorderStateAction.buyAction = offerAction;
+ const preorderOrCancelSubscribePageAction = cancellablePreorderOfferStateAction(objectGraph, data, offerAction, true, metricsOptions); // For non-subscribers
+ preorderOrCancelSubscribePageAction.buyAction = offerAction;
+ preorderStateAction.subscribePageAction = preorderOrCancelSubscribePageAction;
+ offerAction.buyCompletedAction = arcadePreOrderBuyCompleteAction(objectGraph, upsellAction);
+ return preorderStateAction;
+ }
+ }
+ // Vision only, Arcade purchase
+ const isVisionOnlyApp = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "realityDevice");
+ if (supportsVisionDownloadFromVisionCompanion(objectGraph, data) ||
+ (isVisionOnlyApp && objectGraph.client.deviceType !== "vision")) {
+ // Alert action for subscribed user with offer action
+ const alertActionForSubscribed = visionAppAlertForBuyAction(objectGraph, offerAction, data, true, true);
+ // Alert action for unsubscribed user with upsell action
+ const alertActionForUnsubscribed = visionAppAlertForBuyAction(objectGraph, upsellAction, data, true, true);
+ // Offer state action
+ const offerStateAction = new models.OfferStateAction(data.id, alertActionForSubscribed);
+ offerStateAction.subscribePageAction = new models.OfferStateAction(data.id, alertActionForUnsubscribed);
+ return offerStateAction;
+ }
+ // No change
+ const stateAction = new models.OfferStateAction(data.id, offerAction);
+ stateAction.subscribePageAction = upsellAction;
+ return stateAction;
+}
+/**
+ * An offer state action for a preorder that is cancellable.
+ * @param objectGraph The object graph.
+ * @param data The app data.
+ * @param action An offer action that will either purchase the app, or present a cancellation dialog.
+ * @param isArcade Indicates if this app is an Arcade app.
+ * @param metricsClickOptions The metrics click options used for this interaction.
+ */
+function cancellablePreorderOfferStateAction(objectGraph, data, action, isArcade, metricsClickOptions) {
+ let defaultAction;
+ if (isArcade) {
+ const subscribedAction = wrappedCancellablePreorderAction(objectGraph, data, action.purchaseConfiguration.appName, isArcade, true, metricsClickOptions);
+ const notSubscribedAction = wrappedCancellablePreorderAction(objectGraph, data, action.purchaseConfiguration.appName, isArcade, false, metricsClickOptions);
+ defaultAction = new models.ArcadeSubscriptionStateAction(notSubscribedAction, notSubscribedAction, subscribedAction, notSubscribedAction);
+ }
+ else {
+ defaultAction = wrappedCancellablePreorderAction(objectGraph, data, action.purchaseConfiguration.appName, false, false, metricsClickOptions);
+ }
+ return new models.OfferStateAction(data.id, defaultAction);
+}
+/**
+ * A cancellable preorder action that is wrapped in an alert or sheet.
+ * @param objectGraph The object graph
+ * @param data The app data
+ * @param appName The name of the app that will be cancelled.
+ * @param isArcade Indicates if this app is an Arcade app.
+ * @param isSubscribedToArcade Indicates if this action is for an Arcade subscriber or not.
+ * @param metricsClickOptions The metrics click options used for this interaction.
+ */
+function wrappedCancellablePreorderAction(objectGraph, data, appName, isArcade, isSubscribedToArcade, metricsClickOptions) {
+ const cancelPreorderAction = new models.CancelPreorderAction(data.id, isArcade);
+ let title;
+ let cancelTitle;
+ let body;
+ // rdar://144833292 (Remove Cancel Preorder Native Loc)
+ // Once the service strings are localised and deployed, we should remove the native loc strings.
+ if (preprocessor.GAMES_TARGET) {
+ title = objectGraph.loc.string("PreOrder.Cancel.Title");
+ if (objectGraph.client.isAutomaticDownloadingEnabled() && ((isArcade && isSubscribedToArcade) || !isArcade)) {
+ cancelPreorderAction.title = objectGraph.loc.string("PreOrder.Cancel.Button.Download");
+ body = objectGraph.loc.string("PreOrder.Cancel.Body.Download").replace("{appName}", appName);
+ }
+ else {
+ cancelPreorderAction.title = objectGraph.loc.string("PreOrder.Cancel.Button");
+ body = objectGraph.loc.string("PreOrder.Cancel.Body").replace("{appName}", appName);
+ }
+ cancelTitle = objectGraph.loc.string("PreOrder.Cancel.NotNow");
+ }
+ else {
+ title = objectGraph.loc.string("CANCEL_COMING_SOON_TITLE");
+ if (objectGraph.client.isAutomaticDownloadingEnabled() && ((isArcade && isSubscribedToArcade) || !isArcade)) {
+ cancelPreorderAction.title = objectGraph.loc.string("CANCEL_COMING_SOON_BUTTON_DOWNLOAD");
+ body = objectGraph.loc.string("COMING_SOON_BODY_DOWNLOAD").replace("{appName}", appName);
+ }
+ else {
+ cancelPreorderAction.title = objectGraph.loc.string("CANCEL_COMING_SOON_BUTTON");
+ body = objectGraph.loc.string("COMING_SOON_BODY").replace("{appName}", appName);
+ }
+ cancelTitle = objectGraph.loc.string("CANCEL_COMING_SOON_CANCEL");
+ }
+ let wrappedCancellationAction;
+ if (objectGraph.client.deviceType === "mac" || objectGraph.client.deviceType === "tv") {
+ const alertAction = new models.AlertAction("default");
+ alertAction.title = title;
+ alertAction.message = body;
+ alertAction.buttonActions = [cancelPreorderAction];
+ alertAction.isCancelable = true;
+ alertAction.cancelTitle = cancelTitle;
+ alertAction.destructiveActionIndex = 0;
+ wrappedCancellationAction = alertAction;
+ }
+ else if (objectGraph.client.deviceType === "vision" || preprocessor.GAMES_TARGET) {
+ const visionAlertAction = new models.AlertAction("default");
+ visionAlertAction.title = title;
+ visionAlertAction.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://bell.slash.fill", 95, 90);
+ visionAlertAction.message = body;
+ visionAlertAction.buttonActions = [cancelPreorderAction];
+ visionAlertAction.isCancelable = true;
+ visionAlertAction.cancelTitle = cancelTitle;
+ wrappedCancellationAction = visionAlertAction;
+ }
+ else {
+ const sheetAction = new models.SheetAction([cancelPreorderAction]);
+ sheetAction.title = title;
+ sheetAction.message = body;
+ sheetAction.isCancelable = true;
+ sheetAction.cancelTitle = cancelTitle;
+ sheetAction.isCustom = false;
+ sheetAction.destructiveActionIndex = 0;
+ wrappedCancellationAction = sheetAction;
+ }
+ metricsHelpersClicks.addClickEventToAction(objectGraph, cancelPreorderAction, {
+ ...metricsClickOptions,
+ actionType: "cancelPreorder",
+ });
+ // Promo codes allow users to install preorder apps before they are available, so we need to
+ // allow the app to be opened without showing any alert if it is installed.
+ const openAppAction = new models.OpenAppAction(data.id, "app");
+ const offerStateAction = new models.OfferStateAction(data.id, wrappedCancellationAction);
+ offerStateAction.openAction = openAppAction;
+ return offerStateAction;
+}
+function arcadePreOrderBuyCompleteActionForTV(objectGraph, subscribePageAction) {
+ if (!objectGraph.client.isTV) {
+ // Use `arcadePreOrderBuyCompleteAction` instead.
+ return null;
+ }
+ // For preorders before Coming Soon Enhancements:
+ // On TV, we show the pre-order legal terms as part of the confirmation. On all other supported
+ // platforms, the legal terms are visible on the product page itself, so we don't need to show
+ // them again.
+ const subscriberOfferAlertAction = new models.OfferAlertAction();
+ if (objectGraph.client.isAutomaticDownloadingEnabled()) {
+ subscriberOfferAlertAction.title = objectGraph.loc.string("PREORDER_NOTIFY_AUTOMATIC_DOWNLOAD_MESSAGE");
+ }
+ else {
+ subscriberOfferAlertAction.title = objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
+ }
+ const unsubscriberOfferAlertAction = new models.OfferAlertAction();
+ unsubscriberOfferAlertAction.title = objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
+ // Configure both actions
+ for (const alertAction of [subscriberOfferAlertAction, unsubscriberOfferAlertAction]) {
+ alertAction.isCancelable = false;
+ alertAction.shouldCheckForAvailableDiskSpace = false;
+ alertAction.remoteControllerRequirement = null;
+ alertAction.shouldCheckForGameController = false;
+ alertAction.shouldPromptForConfirmation = true;
+ alertAction.shouldIncludeActiveAccountInFooterMessage = true;
+ alertAction.completionAction = new models.BlankAction();
+ alertAction.completionAction.title = objectGraph.loc.string("Action.OK");
+ }
+ // For those not subscribed, show an upsell (rate limited) and a toast.
+ const preorderSubscribePageAction = new models.RateLimitedAction("arcade-preorder", new models.CompoundAction([subscribePageAction, unsubscriberOfferAlertAction]));
+ preorderSubscribePageAction.rateLimit = objectGraph.bag.arcadePreOrderUpsellLimitSeconds;
+ preorderSubscribePageAction.fallbackAction = unsubscriberOfferAlertAction;
+ return new models.ArcadeSubscriptionStateAction(preorderSubscribePageAction, subscriberOfferAlertAction, subscriberOfferAlertAction, subscriberOfferAlertAction);
+}
+function arcadePreOrderBuyCompleteAction(objectGraph, subscribePageAction) {
+ if (objectGraph.client.isTV) {
+ // Use `arcadePreOrderBuyCompleteActionForTV` instead.
+ return null;
+ }
+ const checkmarkArtwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://checkmark", 95, 90);
+ const bellArtwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://bell.fill", 95, 90);
+ // On completion, we will show a toast (and maybe an upsell if you're unsubscribed from Arcade).
+ const isVisionOS = objectGraph.client.isVision;
+ let subscribedAction;
+ if (isVisionOS) {
+ subscribedAction = new models.AlertAction("default");
+ subscribedAction.title = objectGraph.loc.string("ARCADE_PREORDER_LOCKUP_COMING_SOON");
+ subscribedAction.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://bell.badge.fill", 95, 90);
+ subscribedAction.isCancelable = true;
+ if (objectGraph.client.isAutomaticDownloadingEnabled()) {
+ subscribedAction.message = objectGraph.loc.string("PREORDER_NOTIFY_AUTOMATIC_DOWNLOAD_MESSAGE");
+ }
+ else {
+ subscribedAction.message = objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
+ }
+ }
+ else {
+ subscribedAction = new models.AlertAction("toast");
+ subscribedAction.title = "";
+ subscribedAction.artwork = checkmarkArtwork;
+ // rdar://144833292 (Remove Cancel Preorder Native Loc)
+ // Once the service strings are localised and deployed, we should remove the native loc strings.
+ if (preprocessor.GAMES_TARGET) {
+ if (objectGraph.client.isAutomaticDownloadingEnabled()) {
+ subscribedAction.message = objectGraph.loc.string("PreOrder.Notify.Message.Download");
+ subscribedAction.toastDuration = 2.5;
+ }
+ else {
+ subscribedAction.message = objectGraph.loc.string("PreOrder.Notify.Message");
+ subscribedAction.toastDuration = 1.5;
+ }
+ }
+ else {
+ if (objectGraph.client.isAutomaticDownloadingEnabled()) {
+ subscribedAction.message = objectGraph.loc.string("PREORDER_NOTIFY_AUTOMATIC_DOWNLOAD_MESSAGE");
+ subscribedAction.toastDuration = 2.5;
+ }
+ else {
+ subscribedAction.message = objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
+ subscribedAction.toastDuration = 1.5;
+ }
+ }
+ }
+ // Users who are not subscribed should never see the 'auto download' message.
+ let unsubscribedAction;
+ if (isVisionOS) {
+ const preorderNotifyAlert = new models.AlertAction("default");
+ preorderNotifyAlert.title = objectGraph.loc.string("ARCADE_PREORDER_LOCKUP_COMING_SOON");
+ preorderNotifyAlert.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://bell.badge.fill", 95, 90);
+ preorderNotifyAlert.message = objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
+ preorderNotifyAlert.isCancelable = true;
+ const subscribePageRateLimitedAction = new models.RateLimitedAction("arcade-preorder", new models.CompoundAction([subscribePageAction]));
+ subscribePageRateLimitedAction.title = objectGraph.loc.string("ACTION_OK");
+ subscribePageRateLimitedAction.rateLimit = objectGraph.bag.arcadePreOrderUpsellLimitSeconds;
+ subscribePageRateLimitedAction.fallbackAction = null;
+ // For those not subscribed, show an alert to confirm preorder first, wait for it dismissed then show an upsell (rate limited).
+ preorderNotifyAlert.cancelAction = subscribePageRateLimitedAction;
+ unsubscribedAction = preorderNotifyAlert;
+ }
+ else {
+ const preorderNotifyToast = new models.AlertAction("toast");
+ preorderNotifyToast.title = "";
+ preorderNotifyToast.artwork = bellArtwork;
+ // rdar://144833292 (Remove Cancel Preorder Native Loc)
+ // Once the service strings are localised and deployed, we should remove the native loc strings.
+ preorderNotifyToast.message = preprocessor.GAMES_TARGET
+ ? objectGraph.loc.string("PreOrder.Notify.Message")
+ : objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
+ preorderNotifyToast.toastDuration = 1.5;
+ // For those not subscribed, show an upsell (rate limited) and a toast.
+ const compoundRateLimitedAction = new models.RateLimitedAction("arcade-preorder", new models.CompoundAction([subscribePageAction, preorderNotifyToast]));
+ compoundRateLimitedAction.rateLimit = objectGraph.bag.arcadePreOrderUpsellLimitSeconds;
+ compoundRateLimitedAction.fallbackAction = preorderNotifyToast;
+ unsubscribedAction = compoundRateLimitedAction;
+ }
+ // Users who have not opted into notifications on their device will be shown a full screen prompt to turn notifications on. To facilitate this we swap out toast and upsell actions for a blank action
+ // - For Arcade subscribed users we don't want to overlay the toast in this situation
+ // - For Arcade unsubscribed users it was decided that the notifications prompt should take precedence and we shouldn't show that either
+ // - To help keep co-ordination of prompts/toasts/sheets simpler this toast and arcade upsell should/will ultimately be moved to be part of an ODJ
+ // Until rdar://144839099 (TCC Notification for Moltres), we assume the user is authorized for notifications.
+ const isUnauthorizedForUserNotifications = !(preprocessor.GAMES_TARGET || objectGraph.client.isAuthorizedForUserNotifications());
+ if (objectGraph.bag.newEventsForODJAreEnabled && isUnauthorizedForUserNotifications) {
+ return new models.ArcadeSubscriptionStateAction(new models.BlankAction(), new models.BlankAction(), new models.BlankAction(), new models.BlankAction());
+ }
+ else {
+ return new models.ArcadeSubscriptionStateAction(unsubscribedAction, subscribedAction, subscribedAction, subscribedAction);
+ }
+}
+/**
+ * Wrap the offer in an `OfferAlertAction` for tvOS in order to check for requirements and confirm purchase.
+ */
+function createOfferAlertActionIfNeeded(objectGraph, offerAction, data, isPreorder, metricsOptions) {
+ if (objectGraph.client.deviceType === "tv") {
+ return createTVOfferAlertActionIfNeeded(objectGraph, offerAction, null, data, isPreorder, metricsOptions);
+ }
+ else if (objectGraph.client.isVision) {
+ return createVisionOfferAlertActionIfNeeded(objectGraph, offerAction, null, data, isPreorder, metricsOptions);
+ }
+ else {
+ return {
+ startAction: serverData.isNull(offerAction) ? null : offerAction,
+ underlyingOfferAction: null,
+ };
+ }
+}
+function createTVOfferAlertActionIfNeeded(objectGraph, offerAction, alertCompletionActionOverride, data, isPreorder, metricsOptions) {
+ if (serverData.isNull(offerAction)) {
+ return {
+ startAction: null,
+ underlyingOfferAction: null,
+ };
+ }
+ const offerAlertAction = new models.OfferAlertAction();
+ const isFree = isFreeFromOfferAction(objectGraph, offerAction);
+ const appName = offerAction.purchaseConfiguration.appName;
+ // Configure requirement checks
+ offerAlertAction.shouldCheckForAvailableDiskSpace = !isPreorder;
+ if (objectGraph.host.isTV) {
+ offerAlertAction.remoteControllerRequirement = gameController.controllerRequirementFromData(objectGraph, data);
+ offerAlertAction.shouldCheckForGameController = false;
+ }
+ else {
+ offerAlertAction.remoteControllerRequirement = "NO_BADGE";
+ offerAlertAction.shouldCheckForGameController = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "requiresGameController");
+ }
+ // Configure restrictions check
+ const contentRating = ageRatings.value(objectGraph, data, true);
+ if (serverData.isDefinedNonNull(contentRating)) {
+ offerAlertAction.checkRestrictionsForContentRating = contentRating;
+ }
+ // Configure confirmation alert title
+ if (isFree) {
+ const titleFormat = isPreorder
+ ? objectGraph.loc.string("OfferAlert.TV.Title.PredorderFree")
+ : objectGraph.loc.string("OfferAlert.TV.Title.Free");
+ offerAlertAction.title = titleFormat.replace("{title}", appName);
+ }
+ else {
+ const titleFormat = isPreorder
+ ? objectGraph.loc.string("OfferAlert.TV.Title.PreorderPaid")
+ : objectGraph.loc.string("OfferAlert.TV.Title.Paid");
+ offerAlertAction.title = titleFormat.replace("{title}", appName).replace("{price}", offerAction.priceFormatted);
+ }
+ // Configure confirmation action title
+ const buyAction = objects.shallowCopyOf(offerAction);
+ if (isPreorder) {
+ buyAction.title = objectGraph.loc.string("OfferButton.Title.Preorder");
+ }
+ else if (isFree) {
+ buyAction.title = objectGraph.loc.string("OfferButton.Title.Get");
+ }
+ else {
+ buyAction.title = objectGraph.loc.string("OfferButton.Title.Buy");
+ }
+ // Configure completion action
+ const alertCompletionAction = isNothing(alertCompletionActionOverride)
+ ? buyAction
+ : alertCompletionActionOverride;
+ offerAlertAction.completionAction = alertCompletionAction;
+ // Configure footer message
+ offerAlertAction.shouldIncludeActiveAccountInFooterMessage = true;
+ const footerMessage = [];
+ const hasInAppPurchases = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "hasInAppPurchases");
+ if (hasInAppPurchases) {
+ footerMessage.push(objectGraph.loc.string("OFFERS_IN_APP_PURCHASES", "Offers In-App Purchases"));
+ }
+ if (footerMessage.length > 0) {
+ offerAlertAction.footerMessage = footerMessage.join(objectGraph.loc.string("TV_OFFER_ALERT_FOOTER_LINE_BREAK"));
+ }
+ // Add metrics event for initiating purchase
+ offerAlertAction.impressionMetrics = buyAction.impressionMetrics;
+ // Create an instance of offer alert with requirements checks,
+ // but no confirmation prompt for redownloads and updates
+ const offerAlertActionWithoutConfirmationPrompt = objects.shallowCopyOf(offerAlertAction);
+ offerAlertActionWithoutConfirmationPrompt.shouldPromptForConfirmation = false;
+ offerAlertActionWithoutConfirmationPrompt.title = null;
+ offerAlertActionWithoutConfirmationPrompt.footerMessage = null;
+ // By default, perform requirements checks, but avoid confirmation prompt
+ const offerStateAction = new models.OfferStateAction(offerAction.adamId, offerAlertActionWithoutConfirmationPrompt);
+ // Perform requirements checks and show confirmation for buys
+ if (!content.isArcadeSupported(objectGraph, data)) {
+ offerStateAction.buyAction = offerAlertAction;
+ }
+ // Run offer action directly for opens, bypassing all prompts
+ offerStateAction.openAction = offerAction;
+ return {
+ startAction: offerStateAction,
+ underlyingOfferAction: buyAction,
+ };
+}
+function createVisionOfferAlertActionIfNeeded(objectGraph, offerAction, alertCompletionActionOverride, data, isPreorder, metricsOptions) {
+ if (serverData.isNull(offerAction)) {
+ return {
+ startAction: null,
+ underlyingOfferAction: null,
+ };
+ }
+ const offerAlertAction = new models.OfferAlertAction();
+ const isFree = isFreeFromOfferAction(objectGraph, offerAction);
+ offerAlertAction.remoteControllerRequirement = gameController.controllerRequirementFromData(objectGraph, data);
+ offerAlertAction.spatialControllerRequirement = gameController.spatialControllerRequirementFromData(objectGraph, data);
+ // Configure confirmation action title
+ const buyAction = objects.shallowCopyOf(offerAction);
+ if (isPreorder) {
+ buyAction.title = objectGraph.loc.string("OFFER_BUTTON_TITLE_PREORDER");
+ }
+ else if (isFree) {
+ buyAction.title = objectGraph.loc.string("OFFER_BUTTON_TITLE_GET");
+ }
+ else {
+ buyAction.title = objectGraph.loc.string("OFFER_BUTTON_TITLE_BUY");
+ }
+ // Configure completion action
+ const alertCompletionAction = isNothing(alertCompletionActionOverride)
+ ? buyAction
+ : alertCompletionActionOverride;
+ offerAlertAction.completionAction = alertCompletionAction;
+ // Add metrics event for initiating purchase
+ offerAlertAction.impressionMetrics = buyAction.impressionMetrics;
+ // Create an instance of offer alert with requirements checks,
+ // but no confirmation prompt for redownloads and updates
+ const offerAlertActionWithoutConfirmationPrompt = objects.shallowCopyOf(offerAlertAction);
+ offerAlertActionWithoutConfirmationPrompt.shouldPromptForConfirmation = false;
+ offerAlertActionWithoutConfirmationPrompt.title = null;
+ offerAlertActionWithoutConfirmationPrompt.footerMessage = null;
+ /**
+ * - If the app is buyable or updatable: perform requirement checks, then perform the `buyAction`
+ * - If the app is cancellable: perform the `buyAction` which will cancel the downloading
+ * - If the app is openable: perform the `buyAction` which will opening the app
+ * - Otherwise by default perform the offer alert action without confirmation prompt
+ */
+ const offerStateAction = new models.OfferStateAction(offerAction.adamId, offerAlertActionWithoutConfirmationPrompt);
+ offerStateAction.buyAction = offerAlertAction;
+ offerStateAction.cancelAction = buyAction;
+ offerStateAction.openAction = buyAction;
+ return {
+ startAction: offerStateAction,
+ underlyingOfferAction: buyAction,
+ };
+}
+/**
+ * Add two-phased confirmation to the buy action.
+ * @note This function should only be used on clients that need two-phased confirmation, at the moment, macOS.
+ * @param buyAction The action to perform buy.
+ * @param isPreorder Whether the buy is for a preorder.
+ * @param metricsOptions The metrics options to use for reporting metrics actions.
+ * @returns {Action} A configured confirmation action, or the original action if wrapping was not needed.
+ */
+export function wrapOfferInTwoPhasedConfirmationActionIfNeeded(objectGraph, buyAction, isPreorder, metricsOptions) {
+ if (serverData.isNull(buyAction)) {
+ return null;
+ }
+ if (preprocessor.GAMES_TARGET) {
+ // There's a confirmation built-in to the flow in Cheer,
+ // thus two-phased confirmation is not necessary.
+ return buyAction;
+ }
+ // There's a confirmation built-in to the flow for the new payment method as well as pre-orders,
+ // thus two-phased confirmation is not necessary.
+ if (!objectGraph.bag.enableTwoPhaseOfferConfirmation || isPreorder) {
+ return buyAction;
+ }
+ // Create metrics event for initiating confirmation
+ const confirmationInitiationAction = new models.BlankAction();
+ confirmationInitiationAction.impressionMetrics = buyAction.impressionMetrics;
+ const confirmationMetricsOptions = objects.shallowCopyOf(metricsOptions);
+ if (!serverData.isNull(confirmationMetricsOptions)) {
+ confirmationMetricsOptions.actionType = "buyInitiate";
+ confirmationMetricsOptions.targetType = "button";
+ metricsHelpersClicks.addClickEventToAction(objectGraph, confirmationInitiationAction, confirmationMetricsOptions);
+ }
+ // Create confirmation action
+ const confirmationAction = new models.OfferConfirmationAction(buyAction, confirmationInitiationAction);
+ // Add accessibility confirmation action
+ confirmationAction.confirmationAccessibilityAction = wrapOfferActionInConfirmAlertAction(objectGraph, buyAction);
+ return confirmationAction;
+}
+/**
+ * Configures a tvOS-only intermediate action for an offer action.
+ * @param offerAction The underlying offer action to perform the buy with.
+ * @param requiresGameController Whether or not the product requires a game controller.
+ * @returns {OfferStateAction} The tvOS-only-wrapped action.
+ */
+function tvOnlyAppActionForBuyAction(objectGraph, offerAction, requiresGameController) {
+ const tvOnlyBuyAlert = new models.AlertAction("default");
+ tvOnlyBuyAlert.title = objectGraph.loc.string("Alert.Buy.TvOnly.Title");
+ tvOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.TvOnly.Message");
+ tvOnlyBuyAlert.isCancelable = true;
+ if (requiresGameController) {
+ tvOnlyBuyAlert.buttonActions = [requiresGameControllerActionForBuyAction(objectGraph, offerAction)];
+ }
+ else {
+ tvOnlyBuyAlert.buttonActions = [offerAction];
+ }
+ tvOnlyBuyAlert.buttonTitles = [objectGraph.loc.string("Alert.Buy.TvOnly.ButtonTitle")];
+ const offerStateAction = new models.OfferStateAction(offerAction.adamId, tvOnlyBuyAlert);
+ offerStateAction.title = offerAction.title;
+ return offerStateAction;
+}
+/**
+ * Configures a visionOS intermediate action for an offer action.
+ * @param {AppStoreObjectGraph} objectGraph The object graph associated with this purchase.
+ * @param {models.OfferAction} offerAction The underlying offer action to perform the buy with.
+ * @param {mediaDataStructure.Data} data MAPI data blob describing the content model which offer belongs to.
+ * @param {boolean} isFree Whether the buy is for a free product.
+ * @param {boolean} isArcade Whether the buy is for an Arcade app.
+ * @returns {OfferStateAction} The vision-only-wrapped action.
+ */
+function visionAppActionForBuyAction(objectGraph, offerAction, data, isFree, isArcade) {
+ const visionOnlyBuyAlert = visionAppAlertForBuyAction(objectGraph, offerAction, data, isFree, isArcade);
+ const offerStateAction = new models.OfferStateAction(offerAction.adamId, visionOnlyBuyAlert);
+ offerStateAction.title = offerAction.title;
+ return offerStateAction;
+}
+/**
+ * Configures an alert action for vision-only intermediate action.
+ * @param {AppStoreObjectGraph} objectGraph The object graph associated with this purchase.
+ * @param {models.Action} buttonAction The underlying action to perform.
+ * @param {mediaDataStructure.Data} data MAPI data blob describing the content model which offer belongs to.
+ * @param {boolean} isFree Whether the buy is for a free product.
+ * @param {boolean} isArcade Whether the buy is for an Arcade app.
+ * @returns {AlertAction} The alert action for the vision-only buy action.
+ */
+function visionAppAlertForBuyAction(objectGraph, buttonAction, data, isFree, isArcade) {
+ const visionOnlyBuyAlert = new models.AlertAction("default");
+ visionOnlyBuyAlert.isCancelable = true;
+ visionOnlyBuyAlert.buttonActions = [buttonAction];
+ visionOnlyBuyAlert.imageName = "vision.pro";
+ if (supportsVisionDownloadFromVisionCompanion(objectGraph, data)) {
+ // Shows the purchase details from the Companion app
+ if (isFree) {
+ visionOnlyBuyAlert.title = objectGraph.loc.string("Alert.Buy.VisionOnly.Free.RemoteDownloads.Title");
+ visionOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.VisionOnly.Free.RemoteDownloads.Message");
+ visionOnlyBuyAlert.buttonTitles = [
+ objectGraph.loc.string("Alert.Buy.VisionOnly.Free.RemoteDownloads.ButtonTitle"),
+ ];
+ }
+ else {
+ visionOnlyBuyAlert.title = objectGraph.loc.string("Alert.Buy.VisionOnly.Paid.RemoteDownloads.Title");
+ visionOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.VisionOnly.Paid.RemoteDownloads.Message");
+ visionOnlyBuyAlert.buttonTitles = [
+ objectGraph.loc.string("Alert.Buy.VisionOnly.Paid.RemoteDownloads.ButtonTitle"),
+ ];
+ }
+ }
+ else {
+ // Show the default vision only purchase.
+ visionOnlyBuyAlert.title = objectGraph.loc.string("Alert.Buy.VisionOnly.Title");
+ if (isArcade) {
+ visionOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.VisionOnly.Message.Arcade");
+ }
+ else {
+ visionOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.VisionOnly.Message");
+ }
+ visionOnlyBuyAlert.buttonTitles = [objectGraph.loc.string("Alert.Buy.VisionOnly.ButtonTitle")];
+ }
+ return visionOnlyBuyAlert;
+}
+/**
+ * This will return true if we are running in the companion app and the purchase we are working with
+ * will successfully download and run on a vision pro.
+ * @param {AppStoreObjectGraph} objectGraph The object graph associated with this purchase.
+ * @param {mediaDataStructure.Data} data MAPI data blob describing the content model which offer belongs to.
+ * @returns {boolean} true if the data/product can be downloaded onto vision pro from within companion.
+ */
+export function supportsVisionPlatformForVisionCompanion(objectGraph, data) {
+ if (!objectGraph.client.isCompanionVisionApp) {
+ return false;
+ }
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ const purchaseSupportsVision = appPlatforms.includes("vision") || content.supportsVisionOSCompatibleIOSBinaryOnAnyClient(data);
+ if (!purchaseSupportsVision) {
+ return false;
+ }
+ return true;
+}
+/**
+ * This will return true if we are running in the Vision companion app and the purchase we are working with
+ * will successfully download and run on a vision pro and the user has associated vision pros to download
+ * this purchase to.
+ * @param {AppStoreObjectGraph} objectGraph The object graph associated with this purchase.
+ * @param {mediaDataStructure.Data} data MAPI data blob describing the content model which offer belongs to.
+ * @returns {boolean} true if the data/product can be downloaded onto vision pro from within companion.
+ */
+export function supportsVisionDownloadFromVisionCompanion(objectGraph, data) {
+ if (!supportsVisionPlatformForVisionCompanion(objectGraph, data)) {
+ return false;
+ }
+ return hasRemoteDownloadIdentifiers(objectGraph);
+}
+/**
+ * This will return true if the client passed in any remoteDownloadIdentifiers and false otherwise.
+ * @param {AppStoreObjectGraph} objectGraph The object graph associated with this purchase.
+ * @returns {boolean} true if remoteDownloadIdentifiers contains anything.
+ */
+export function hasRemoteDownloadIdentifiers(objectGraph) {
+ if (isNothing(objectGraph.client.remoteDownloadIdentifiers)) {
+ return false;
+ }
+ const hasNoDevicesForRemoteDownload = objectGraph.client.remoteDownloadIdentifiers.length === 0;
+ if (hasNoDevicesForRemoteDownload) {
+ return false;
+ }
+ return true;
+}
+/**
+ * Determines whether the product contains a macOS IPA install
+ * @param objectGraph Current object graph
+ * @param data The product data
+ * @returns True if the product has a macOS IPA installer
+ */
+export function hasMacIPAPackageForData(objectGraph, data) {
+ return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "hasMacIPAPackage");
+}
+/**
+ * Configures a game-controller-required action.
+ * @param action The action to carry out on the alert confirmation.
+ * @returns {AlertAction} The alert action to use for the game-controller-required dialog.
+ */
+function requiresGameControllerActionForBuyAction(objectGraph, action) {
+ const requiresControllerAlert = new models.AlertAction("default");
+ requiresControllerAlert.title = objectGraph.loc.string("Alert.Buy.TvGameControllerRequired.Title");
+ requiresControllerAlert.message = objectGraph.loc.string("Alert.Buy.TvGameControllerRequired.Message");
+ requiresControllerAlert.buttonTitles = [objectGraph.loc.string("Alert.Buy.TvGameControllerRequired.ButtonTitle")];
+ requiresControllerAlert.isCancelable = true;
+ requiresControllerAlert.buttonActions = [action];
+ return requiresControllerAlert;
+}
+/**
+ * Configures a watchOS-only intermediate action for an offer action.
+ * @param offerAction The underlying offer action to perform the buy with.
+ * @returns {OfferStateAction} The watchOS-only-wrapped action.
+ */
+function watchOnlyAppActionForBuyAction(objectGraph, offerAction) {
+ const tvOnlyBuyAlert = new models.AlertAction("default");
+ tvOnlyBuyAlert.title = objectGraph.loc.string("Alert.Buy.WatchOnly.Title");
+ tvOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.WatchOnly.Message");
+ tvOnlyBuyAlert.isCancelable = true;
+ tvOnlyBuyAlert.buttonActions = [offerAction];
+ tvOnlyBuyAlert.buttonTitles = [objectGraph.loc.string("Alert.Buy.WatchOnly.ButtonTitle")];
+ const offerStateAction = new models.OfferStateAction(offerAction.adamId, tvOnlyBuyAlert);
+ offerStateAction.title = offerAction.title;
+ return offerStateAction;
+}
+/**
+ * Configures a watchOS update required intermediate action for an offer action.
+ * @param offerAction The underlying offer action to perform the buy with.
+ * @returns {OfferStateAction} The watchOS update required wrapped action.
+ */
+function watchUpdateRequiredActionForBuyAction(objectGraph, offerAction, minOSVersion) {
+ const alert = new models.AlertAction("default");
+ alert.title = objectGraph.loc
+ .string("ProductPage.WatchOSUpdateRequired.Title")
+ .replace("{osVersion}", minOSVersion);
+ alert.message = objectGraph.loc
+ .string("ProductPage.WatchOSUpdateRequired.Message")
+ .replace("{osVersion}", minOSVersion);
+ alert.buttonActions = [offerAction];
+ alert.buttonTitles = [objectGraph.loc.string("Action.OK")];
+ const offerStateAction = new models.OfferStateAction(offerAction.adamId, offerAction);
+ offerStateAction.buyAction = alert;
+ return offerStateAction;
+}
+function wrapOfferActionInConfirmAlertAction(objectGraph, action) {
+ if (serverData.isNull(action)) {
+ return null;
+ }
+ const alert = new models.AlertAction("default");
+ if (serverData.isNull(action.priceFormatted)) {
+ alert.title = objectGraph.loc.string("GET");
+ alert.message = "Are you sure you want to get " + action.purchaseConfiguration.appName;
+ }
+ else {
+ alert.title = "Buy App";
+ alert.message = `Are you sure you want to buy ${action.purchaseConfiguration.appName} for ${action.priceFormatted}`;
+ }
+ alert.isCancelable = true;
+ alert.buttonActions = [action];
+ const offerStateAction = new models.OfferStateAction(action.adamId, action);
+ offerStateAction.buyAction = alert;
+ return offerStateAction;
+}
+/**
+ * Create object describing the visual aspects of an offer action.
+ *
+ * @param {AppStoreObjectGraph} objectGraph Object graph.
+ * @param {OfferAction} action The offer action to create display properties for.
+ * @param {OfferType} type The type of offer to display.
+ * @param {Data} data MAPI data blob describing the content model which offer belongs to.
+ * @param {boolean} isPreorder Whether the buy is for a preorder.
+ * @param {boolean} isContainedInPreorderExclusiveShelf Whether this offer is displayed in a coming soon shelf.
+ * @param {OfferStyle} style The style of offer button. Returned display property may have different style, depending on filters.
+ * @param {OfferEnvironment} environment The environment which offer button will be displayed in.
+ * @param {JSONData} discountData The JSON blob describing discounts to visualize.
+ * @param {boolean} isParentAppFree Flag indicating whether parent app was free app.
+ * @param {OfferContext} context Contextual info about offer.
+ * @param {boolean} shouldNavigateToProductPage Whether this offer should navigate to the product page.
+ * @param {boolean} isAd Whether this offer is for an ad.
+ * @param {ClientIdentifier} An optional override of the client identifier
+ * @param {Data} An optional fallback of the parent App Data used when constructing IAP offers
+ * @returns {OfferDisplayProperties} The display properties for offer button for given product and offer.
+ *
+ * @seealso `personalizedCMCDisplayPropertiesFromBuyButtonMetadata`, the display properties created from the old-school `buyButtonMetadataUrl` endpoint.
+ */
+export function displayPropertiesFromOfferAction(objectGraph, action, type, data, isPreorder, isContainedInPreorderExclusiveShelf, style, environment, discountData, isParentAppFree, context = "default", shouldNavigateToProductPage = false, isAd = false, clientIdentifierOverride = undefined, parentAppData = undefined, isBuyDisallowed = false) {
+ if (serverData.isNull(action)) {
+ return null;
+ }
+ return validation.context("displayPropertiesFromOfferAction", () => {
+ var _a;
+ let derivedStyle = style;
+ // We can't prevent deep links into apps that are filtered,
+ // but we should disable the buy button (if present) to prevent purchases
+ if (!isBuyDisallowed && filtering.shouldFilter(objectGraph, data, 77238 /* filtering.Filter.Offers */)) {
+ derivedStyle = "disabled";
+ }
+ // Disable preorders on unsupported OS' (if we have a buy button)
+ if (!isBuyDisallowed && !lockups.deviceHasCapabilitiesFromData(objectGraph, data)) {
+ derivedStyle = "disabled";
+ }
+ // Disable buy button when we are in companion but cannot install app onto visionOS (if we have a buy button)
+ if (!isBuyDisallowed &&
+ objectGraph.client.isCompanionVisionApp &&
+ !supportsVisionDownloadFromVisionCompanion(objectGraph, data)) {
+ derivedStyle = "disabled";
+ }
+ const parentData = (_a = lockups.parentDataFromInAppData(objectGraph, data)) !== null && _a !== void 0 ? _a : parentAppData;
+ let parentAdamId;
+ if (parentData) {
+ parentAdamId = parentData.id;
+ }
+ let displayProperties = new models.OfferDisplayProperties(type, action.adamId, action.bundleId, derivedStyle, parentAdamId, environment);
+ // Configure complementary offer label for preorders
+ displayProperties.isPreorder = isPreorder;
+ // If this is for an ad allow localization overrides if one is in the bag.
+ const useAdsLocale = isAd && context === "default" && isSome(objectGraph.bag.adsOverrideLanguage);
+ const adsOverrideLocalizer = useAdsLocale ? objectGraph.adsLoc : objectGraph.loc;
+ displayProperties.useAdsLocale = useAdsLocale;
+ // Arcade apps that are "Coming Soon" do not use the `preorder` label style.
+ if (isPreorder) {
+ if (content.isArcadeSupported(objectGraph, data)) {
+ displayProperties.offerLabelStyle = "arcadeComingSoon";
+ if (objectGraph.client.isVision && !isContainedInPreorderExclusiveShelf) {
+ // In visionOS, we would like lockup always display `Coming Soon` instead of release date
+ // Except for lockup within Coming Soon shelf
+ displayProperties.subtitles["expectedReleaseDate"] = objectGraph.loc.string("ARCADE_PREORDER_LOCKUP_COMING_SOON");
+ }
+ else {
+ displayProperties.subtitles["expectedReleaseDate"] = content.dynamicPreorderDateFromData(objectGraph, data, objectGraph.loc.string("ARCADE_PREORDER_LOCKUP_COMING_SOON"));
+ }
+ displayProperties.titleSymbolNames["preorderedSubscribed"] = "checkmark";
+ displayProperties.titleSymbolNames["preorderedNotSubscribed"] = "bell.fill";
+ // If the server-side value is an uppercase "COMING SOON" replace it with the titlecase variant for lockups.
+ if (displayProperties.subtitles["expectedReleaseDate"] ===
+ objectGraph.loc.string("ARCADE_PREORDER_COMING_SOON")) {
+ displayProperties.subtitles["expectedReleaseDate"] = objectGraph.loc.string("ARCADE_PREORDER_LOCKUP_COMING_SOON");
+ }
+ }
+ else {
+ displayProperties.offerLabelStyle = "preorder";
+ displayProperties.titleSymbolNames["standard"] = "checkmark";
+ const expectedReleaseDate = content.dynamicPreorderDateFromData(objectGraph, data, "");
+ if (isSome(expectedReleaseDate)) {
+ displayProperties.subtitles["expectedReleaseDate"] = expectedReleaseDate;
+ }
+ }
+ }
+ // Configure offer titles
+ const isFree = isFreeFromOfferAction(objectGraph, action);
+ displayProperties.isFree = isFree;
+ let standardTitle = null;
+ if (context === "default" &&
+ (data.type === "app-bundles" ||
+ content.isMacOSInstaller(objectGraph, data) ||
+ content.isUnsupportedByCurrentCompanion(objectGraph, data) ||
+ shouldNavigateToProductPage)) {
+ standardTitle = objectGraph.loc.string("OfferButton.Title.View");
+ }
+ else if (context === "flowPreview" &&
+ (data.type === "app-bundles" ||
+ content.isMacOSInstaller(objectGraph, data) ||
+ content.isUnsupportedByCurrentCompanion(objectGraph, data))) {
+ // We do not want to support "View" in flow preview actions
+ return null;
+ }
+ else if (context === "productPageBrowserChoice") {
+ standardTitle = objectGraph.loc.string("OfferButton.Title.Select");
+ }
+ else if (isFree) {
+ if (context === "flowPreview") {
+ if (isPreorder) {
+ standardTitle = objectGraph.loc.string("OfferButton.FlowPreview.Preorder");
+ }
+ else {
+ standardTitle = objectGraph.loc.string("OfferButton.FlowPreview.Get");
+ }
+ }
+ else {
+ standardTitle = action.title;
+ }
+ }
+ else if (objectGraph.client.isTV && (environment === "productPage" || environment === "arcadeProductPage")) {
+ standardTitle = objectGraph.loc
+ .string("OfferButton.Title.BuyWithPrice")
+ .replace("{price}", action.priceFormatted);
+ }
+ else if (context === "flowPreview") {
+ if (isPreorder) {
+ standardTitle = objectGraph.loc
+ .string("OfferButton.FlowPreview.PreorderWithPrice")
+ .replace("{price}", action.priceFormatted);
+ }
+ else {
+ standardTitle = objectGraph.loc
+ .string("OfferButton.FlowPreview.BuyWithPrice")
+ .replace("{price}", action.priceFormatted);
+ }
+ }
+ else {
+ standardTitle = action.priceFormatted;
+ }
+ displayProperties.titles["standard"] = standardTitle;
+ displayProperties.priceFormatted = action.priceFormatted;
+ // Confirmation buys are not supported in flow preview
+ if (objectGraph.bag.enableTwoPhaseOfferConfirmation && context !== "flowPreview") {
+ displayProperties.titles["confirmation"] = isFree
+ ? objectGraph.loc.string("OfferButton.Title.ConfirmGet")
+ : objectGraph.loc.string("OfferButton.Title.ConfirmBuy");
+ }
+ if (content.isArcadeSupported(objectGraph, data)) {
+ if (context === "flowPreview") {
+ const standardText = objectGraph.loc.string("OfferButton.FlowPreview.Arcade.Standard");
+ displayProperties.titles["standard"] = standardText;
+ displayProperties.titles["trial"] = standardText;
+ displayProperties.titles["open"] = objectGraph.loc.string("OfferButton.FlowPreview.Arcade.Open");
+ displayProperties.titles["notSubscribed"] = standardText;
+ if (isPreorder) {
+ displayProperties.titles["preorderSubscribed"] = objectGraph.loc.string("OfferButton.FlowPreview.Arcade.PreorderSubscribed");
+ displayProperties.titles["preorderNotSubscribed"] = objectGraph.loc.string("OfferButton.FlowPreview.Arcade.PreorderNotSubscribed");
+ }
+ }
+ else {
+ const standardText = adsOverrideLocalizer.string("OfferButton.Arcade.Title.Standard");
+ displayProperties.titles["standard"] = standardText;
+ displayProperties.titles["trial"] = standardText;
+ displayProperties.titles["open"] = adsOverrideLocalizer.string("OfferButton.Arcade.Title.Open");
+ displayProperties.titles["notSubscribed"] = standardText;
+ if (isPreorder) {
+ displayProperties.titles["preorderSubscribed"] = adsOverrideLocalizer.string("OfferButton.Arcade.Title.PreorderSubscribed");
+ displayProperties.titles["preorderNotSubscribed"] = adsOverrideLocalizer.string("OfferButton.Arcade.Title.PreorderNotSubscribed");
+ displayProperties.titles["preorderedSubscribed"] = adsOverrideLocalizer.string("OfferButton.Arcade.Title.PreorderedSubscribed");
+ displayProperties.titles["preorderedNotSubscribed"] = adsOverrideLocalizer.string("OfferButton.Arcade.Title.PreorderedNotSubscribed");
+ }
+ }
+ }
+ // Introductory Pricing
+ // Not supported in flow preview
+ if (discountData && context !== "flowPreview") {
+ const discountType = serverData.asString(discountData, "modeType");
+ const discountPriceFormatted = serverData.asString(discountData, "priceFormatted");
+ if (serverData.isDefinedNonNull(discountPriceFormatted) && serverData.isDefinedNonNull(discountType)) {
+ let discountOwnedParentTitle = null;
+ let discountUnownedParentTitle = null;
+ switch (discountType) {
+ case "FreeTrial":
+ if (isParentAppFree) {
+ discountOwnedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.FreeTrial");
+ discountUnownedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.FreeTrial");
+ }
+ else {
+ discountOwnedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.FreeTrial");
+ discountUnownedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.Trial");
+ }
+ break;
+ case "PayUpFront":
+ const payUpfrontPrice = objectGraph.loc
+ .string("OfferButton.IntroPrice.PaidUpfront.Trial")
+ .replace("{price}", discountPriceFormatted);
+ if (isParentAppFree) {
+ discountOwnedParentTitle = payUpfrontPrice;
+ discountUnownedParentTitle = payUpfrontPrice;
+ }
+ else {
+ discountOwnedParentTitle = payUpfrontPrice;
+ discountUnownedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.Trial");
+ }
+ break;
+ case "PayAsYouGo":
+ discountOwnedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.Trial");
+ discountUnownedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.Trial");
+ break;
+ default:
+ break;
+ }
+ displayProperties.titles["discountOwnedParent"] = discountOwnedParentTitle;
+ displayProperties.titles["discountUnownedParent"] = discountUnownedParentTitle;
+ displayProperties.subtitles["discountOwnedParent"] =
+ objectGraph.loc.string("INTRO_PRICE_OFFER_SUBTITLE");
+ displayProperties.subtitles["discountUnownedParent"] =
+ objectGraph.loc.string("INTRO_PRICE_OFFER_SUBTITLE");
+ // Determine whether the button title is too long and the subtitle should be pushed below.
+ const maxCharacterCount = 10;
+ let isWidthConstrained = false;
+ for (const titleKey of Object.keys(displayProperties.titles)) {
+ const title = displayProperties.titles[titleKey];
+ if (title.length > maxCharacterCount) {
+ isWidthConstrained = true;
+ break;
+ }
+ }
+ if (isWidthConstrained) {
+ displayProperties = displayProperties.newOfferDisplayPropertiesChangingAppearance(false, null, "widthConstrainedLockup");
+ }
+ }
+ }
+ // iAPs
+ const hasInAppPurchases = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "hasInAppPurchases");
+ const hasExternalPurchases = hasExternalPurchasesForData(objectGraph, data);
+ const externalPurchasesEnabled = externalPurchasesPlacementIsEnabled(objectGraph, "lockup");
+ const showExternalPurchasesSubtitle = hasExternalPurchases && externalPurchasesEnabled;
+ displayProperties.hasInAppPurchases = hasInAppPurchases;
+ displayProperties.hasExternalPurchases = showExternalPurchasesSubtitle;
+ if (hasInAppPurchases || showExternalPurchasesSubtitle) {
+ const inAppPurchasesKey = "Offer.InlineInAppPurchases";
+ const subtitleKey = showExternalPurchasesSubtitle
+ ? "OfferButton.ExternalPurchases.Subtitle"
+ : inAppPurchasesKey;
+ const standardSubtitle = adsOverrideLocalizer.string(subtitleKey);
+ displayProperties.subtitles["standard"] = standardSubtitle;
+ // Confirmation buys are not supported in flow preview
+ if (objectGraph.bag.enableTwoPhaseOfferConfirmation && context !== "flowPreview") {
+ displayProperties.subtitles["confirmation"] = standardSubtitle;
+ }
+ }
+ // System Apps
+ displayProperties.isDeletableSystemApp =
+ sad.systemApps(objectGraph).isSystemAppFromData(data) &&
+ !content.isUnsupportedByCurrentCompanion(objectGraph, data);
+ // Content Restrictions
+ const contentRating = ageRatings.value(objectGraph, data, true);
+ displayProperties.contentRating = contentRating !== null && contentRating !== void 0 ? contentRating : undefined;
+ // OS Eligibility
+ displayProperties.appCapabilities = action.purchaseConfiguration.appCapabilities;
+ // App bundles
+ // Not supported in flow preview
+ if (data.type === "app-bundles" && context !== "flowPreview") {
+ displayProperties.offerToken = {
+ offerAction: action,
+ offerDisplayProperties: objects.shallowCopyOf(displayProperties),
+ };
+ }
+ return displayProperties;
+ });
+}
+export function macFileSizeInBytesFromData(objectGraph, data) {
+ const offerData = offerDataFromData(objectGraph, data);
+ if (serverData.isNull(offerData)) {
+ return null;
+ }
+ const allAssets = serverData.asArrayOrEmpty(offerData, "assets");
+ if (!allAssets.length) {
+ return null;
+ }
+ for (const assetData of allAssets) {
+ const flavor = serverData.asString(assetData, "flavor");
+ if (flavor === "macSoftware") {
+ return serverData.asNumber(assetData, "size");
+ }
+ }
+ return null;
+}
+// region CMC Personalization
+/**
+ * Determine the type of offer type given offer metadata from `buyButtonMetadata` endpoint.
+ *
+ * @param {JSONData} buyButtonMetadata Metadata for offer returned from personalized offer endpoint.
+ * @returns {PersonalizedOfferType} Type of personalized offer.
+ */
+export function personalizedOfferTypeFromBuyButtonMetadata(objectGraph, buyButtonMetadata) {
+ return validation.context("personalizedOfferTypeFromBuyButtonMetadata", () => {
+ const offers = serverData.asArrayOrEmpty(buyButtonMetadata, "offers");
+ if (offers.length === 0) {
+ return null;
+ }
+ for (const offer of offers) {
+ const type = serverData.asString(offer, "type");
+ if (type) {
+ return type;
+ }
+ }
+ return "none";
+ });
+}
+/**
+ * Create a personalized offer action specific for CMC given personalized offer metadata and original offer.
+ *
+ * @param {JSONData} buyButtonMetadata The personalized offer metadata to build offer with.
+ * @param {OfferAction} originalOfferAction The original offer action to borrow metrics from.
+ * @returns {OfferAction} A offer action with complete my bundle personalization applied.
+ */
+export function personalizedCMCOfferActionFromBuyButtonMetadata(objectGraph, buyButtonMetadata, originalOfferAction, isPreorder) {
+ return validation.context("personalizedCMCOfferActionFromBuyButtonMetadata", () => {
+ const offers = serverData.asArrayOrEmpty(buyButtonMetadata, "offers");
+ if (offers.length === 0) {
+ return null;
+ }
+ let type;
+ let personalizedOfferData = null;
+ for (const offer of offers) {
+ type = serverData.asString(offer, "type");
+ switch (type) {
+ case "complete":
+ case "purchased":
+ personalizedOfferData = offer;
+ break;
+ default:
+ personalizedOfferData = null;
+ break;
+ }
+ }
+ if (!personalizedOfferData) {
+ return null;
+ }
+ /* Buy Params */
+ let buyParams = serverData.asString(personalizedOfferData, "buyParams");
+ if (type === "complete" && serverData.isNull(buyParams)) {
+ validation.unexpectedNull("ignoredValue", "string", "item.offer.buyParams");
+ return null;
+ }
+ else if (!buyParams) {
+ buyParams = "";
+ }
+ /* Purchase Configuration */
+ const originalConfiguration = originalOfferAction.purchaseConfiguration;
+ const purchaseConfiguration = new models.PurchaseConfiguration(buyParams, originalConfiguration.vendor, originalConfiguration.appName, originalConfiguration.bundleId, originalConfiguration.appPlatforms, originalConfiguration.isPreorder, originalConfiguration.excludeAttribution, originalConfiguration.metricsPlatformDisplayStyle, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, originalConfiguration.appCapabilities, originalConfiguration.isDefaultBrowser, originalConfiguration.remoteDownloadIdentifiers, originalConfiguration.hasMacIPAPackage, originalConfiguration.contentRating);
+ purchaseConfiguration.pageInformation = originalConfiguration.pageInformation;
+ const action = internalOfferActionFromOfferData(objectGraph, personalizedOfferData, originalOfferAction.adamId, purchaseConfiguration, false);
+ metricsHelpersClicks.addBuyEventToOfferActionInheritingMetrics(objectGraph, action, originalOfferAction, isPreorder);
+ return action;
+ });
+}
+/**
+ * Create offer display properties specific for CMC offers from personalized action and original display properties.
+ *
+ * @param {OfferAction} personalizedAction The personalized CMC offer action created via `personalizedCMCOfferActionFromBuyButtonMetadata`.
+ * @param {OfferType} type The type of offer to display.
+ * @param {OfferDisplayProperties} originalOfferDisplayProperties The original offer display property to inherit some values from.
+ * @param {OfferStyle} style The style of offer button. Returned display property may have different style, depending on filters.
+ * @returns {OfferDisplayProperties} Offer display properties for a personalized CMC action.
+ *
+ * @note This code is similar to `media_displayPropertiesFromOfferAction`, but inserts on old-school non-MAPI data from
+ * `buyButtonMetadataUrl` endpoint to original offer display properties. It assumes that bundles cannot:
+ * - Be preordered
+ * - Have introductory pricing
+ * - Be filtered
+ * - Have parent apps
+ * - Be a deletable system app
+ *
+ * @seealso `media_displayPropertiesFromOfferAction`, for how display properties are created through MAPI data.
+ */
+export function personalizedCMCDisplayPropertiesFromBuyButtonMetadata(objectGraph, personalizedAction, type, originalOfferDisplayProperties, style) {
+ if (serverData.isNull(personalizedAction)) {
+ return null;
+ }
+ return validation.context("personalizedCMCDisplayPropertiesFromBuyButtonMetadata", () => {
+ const displayProperties = new models.OfferDisplayProperties(type, personalizedAction.adamId, personalizedAction.bundleId, style);
+ // Configure offer titles
+ const isFree = isFreeFromOfferAction(objectGraph, personalizedAction);
+ displayProperties.isFree = isFree;
+ let standardTitle = null;
+ if (isFree) {
+ standardTitle = personalizedAction.title;
+ }
+ else {
+ standardTitle = personalizedAction.priceFormatted;
+ }
+ displayProperties.titles["standard"] = standardTitle;
+ displayProperties.priceFormatted = personalizedAction.priceFormatted;
+ // iAPs
+ const hasInAppPurchases = originalOfferDisplayProperties.hasInAppPurchases;
+ const hasExternalPurchases = originalOfferDisplayProperties.hasExternalPurchases;
+ displayProperties.hasInAppPurchases = hasInAppPurchases;
+ displayProperties.hasExternalPurchases = hasExternalPurchases;
+ if (hasExternalPurchases) {
+ const standardSubtitle = objectGraph.loc.string("OfferButton.ExternalPurchases.Subtitle");
+ displayProperties.subtitles["standard"] = standardSubtitle;
+ }
+ else if (hasInAppPurchases) {
+ const standardSubtitle = objectGraph.loc.string("Offer.InlineInAppPurchases");
+ displayProperties.subtitles["standard"] = standardSubtitle;
+ }
+ // Content Restrictions
+ displayProperties.contentRating = originalOfferDisplayProperties.contentRating;
+ // OS Eligibility
+ displayProperties.appCapabilities = personalizedAction.purchaseConfiguration.appCapabilities;
+ return displayProperties;
+ });
+}
+// endregion
+//# sourceMappingURL=offers.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-impression-demotion.js b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-impression-demotion.js
new file mode 100644
index 0000000..f2430e9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-impression-demotion.js
@@ -0,0 +1,73 @@
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { demoteByEngagements } from "@amp/amd-apps";
+// The AMD key used to retrieve the engagement events from the app store.
+export const AMSEngagementAppStoreEventKey = "appStore.getEngagementEvents";
+/**
+ * personalize the dataItems based on the impression data for that shelf.
+ *
+ * @param objectGraph The object graph.
+ * @param dataItems The data items to personalize.
+ * @param impressionData The impression data to use for personalization.
+ * @returns The personalized data items.
+ */
+export function personalizeDataItems(dataItems, impressionData, shelfRecoMetricsData) {
+ const shelfAlgoId = shelfRecoMetricsData["reco_algoId"];
+ if (serverData.isNullOrEmpty(shelfAlgoId) ||
+ serverData.isNullOrEmpty(dataItems) ||
+ serverData.isNullOrEmpty(impressionData[shelfAlgoId])) {
+ return dataItems;
+ }
+ // First create Candidate objects from the data items.
+ const candidates = dataItems.map((dataItem) => {
+ var _a;
+ const adamId = serverData.asNumber(dataItem.id);
+ const score = (_a = serverData.asNumber(dataItem, "meta.personalizationData.score")) !== null && _a !== void 0 ? _a : 0;
+ const candidate = { identifier: adamId, score: score };
+ return candidate;
+ });
+ // Demote the candidates based on the impression data.
+ const shelfRecoData = impressionData[shelfAlgoId];
+ const rerankedCandidates = demoteByEngagements(candidates, shelfRecoData);
+ // Create a lookup map from dataItems to allow faster rearranging
+ const dictionary = dataItems.reduce((acc, item) => ({ ...acc, [item.id]: item }), {});
+ // create a new array based on the rearranged Candidate array
+ const rearranged = rerankedCandidates.map((candidate) => dictionary[candidate.identifier.toString()]);
+ return rearranged;
+}
+/**
+ * Takes the AMD response and creates a map of EngagementEvent per shelf.
+ *
+ * @param objectGraph The object graph.
+ * @param data The data to convert.
+ * @returns The map of EngagementEvent per shelf.
+ */
+export function impressionEventsFromData(objectGraph, data) {
+ // go through all the impressionData and return the EgagementData
+ if (!serverData.isDefinedNonNullNonEmpty(data)) {
+ return {};
+ }
+ // Impression data is a map of shelf ids to impression arrays.
+ // We want to keep this relationship so only impressions for a given shelf are returned.
+ const impressionData = serverData.asDictionary(data, "data.engagementEvents.impression");
+ const convertedMap = {};
+ // Iterate over each key in the original map and convert the data into EngagementEvents which can be passed to the demoteByEngagements function.
+ for (const key in impressionData) {
+ if (serverData.isDefinedNonNullNonEmpty(key)) {
+ const impressions = serverData.asArrayOrEmpty(impressionData, key);
+ convertedMap[key] = impressions.map((impression) => {
+ const adamId = serverData.asNumber(impression["adamId"]);
+ const timestamp = serverData.asNumber(impression["eventTimeMillis"]);
+ const event = { adamIdentifier: adamId, timestamp: timestamp };
+ return event;
+ });
+ }
+ }
+ return convertedMap;
+}
+/**
+ * Convenience function for determining if data personalization is available.
+ */
+export function isImpressionDemotionAvailable(objectGraph) {
+ return objectGraph.client.isiOS && objectGraph.bag.enableRecoOnDeviceReordering;
+}
+//# sourceMappingURL=on-device-impression-demotion.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-personalization-processing.js b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-personalization-processing.js
new file mode 100644
index 0000000..538e708
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-personalization-processing.js
@@ -0,0 +1,370 @@
+import { isNothing } from "@jet/environment";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { diversifyDataItems, getOrderedAppIds, getUpdatedScoreAfterBoosting, PersonalizedData, } from "./on-device-recommendations-common";
+/**
+ * This utility class simplifies processing the raw data, by decorating with some key properties.
+ * */
+class PersonalizedDataDefault extends PersonalizedData {
+ constructor(rawData) {
+ super();
+ this.rawData = rawData;
+ this.isExactMatch = false;
+ this.isWildcardMatch = false;
+ this.isUnpersonalizedMatch = false;
+ this.isFallbackMatch = false;
+ this.appId = null;
+ this.groupId = null;
+ this.score = 0;
+ this.modifiedScore = 0;
+ this.onDeviceScore = 0;
+ }
+}
+// Represents a "match all" wildcard segment. Any data items that have this segment are always considered a match.
+const alwaysMatchUserSegment = "-1";
+/**
+ * Converts a list of raw data blobs into a list that has been personalized for the user, based upon on device personalization data.
+ *
+ * If use_segment_scores is true, the rules we follow here are:
+ * 1. Choose the data items that have personalization segments which match the user
+ * 2. Remove some data items so that there is only one per group
+ * 3. Bring any data items where the user exactly matches the personalization segment to the front of the list
+ *
+ * If needed, we may also include fallback results to reach a preferred number of results. For any group where no matches are found, the last
+ * item in that group can be used as a fallback. We can only ever have one item per group, so it may not always be possible to reach the
+ * preferred number of results.
+ *
+ * If use_signals is true, we rerank content using the on-device scores
+ *
+ * @param dataItems The raw data blobs.
+ * @param onDevicePersonalizationDataContainer The on device personalization data container for the user, used for matching segments against the dataItems.
+ * @param includeItemsWithNoPersonalizationData Whether dataItems without any valid personalization data should always be included in the results.
+ * @param allowUnmatchedFallbackResults Whether to allow fallback results to be included in the results. This will only be utilised in order to reach a preferredResultCount.
+ * @param preferredResultCount The preferred number of items to be included in the results.
+ * @param parentAppId An optional appID, which is the parent for all the dataItems. Currently only used for search.
+ * @param diversify An optional flag that determines if we should diverse the personalized results on the basis of server side apps ranking
+ * @returns The personalized set of data. This will be a subset (or all) of the original dataItems, and metrics data.
+ */
+export function personalizeDataItems(objectGraph, dataItems, onDevicePersonalizationDataContainer, includeItemsWithNoPersonalizationData, allowUnmatchedFallbackResults, preferredResultCount, parentAppId, diversify) {
+ var _a;
+ let sortResult = { sortedDataItems: [], processingType: 0 };
+ const useSignals = (_a = onDevicePersonalizationDataContainer === null || onDevicePersonalizationDataContainer === void 0 ? void 0 : onDevicePersonalizationDataContainer.metricsData["use_signals"]) !== null && _a !== void 0 ? _a : false;
+ if (!useSignals) {
+ // First decorate our raw dataItems with segment and group information
+ const personalizedDataItems = personalizedDataItemsFromDataItems(objectGraph, dataItems, onDevicePersonalizationDataContainer === null || onDevicePersonalizationDataContainer === void 0 ? void 0 : onDevicePersonalizationDataContainer.personalizationData, includeItemsWithNoPersonalizationData, parentAppId);
+ // Get server side ordering of app Ids to be used for diversification
+ const serverSideAppIdsOrdering = getOrderedAppIds(personalizedDataItems);
+ // Now iterate through the list of personalizedDataItems, and choose one per group
+ const matchedDataItemsIncludingFallback = filterDataItemsIntoOnePerGroup(objectGraph, personalizedDataItems);
+ // Now sort the data items, respecting our preferredResultCount if needed
+ sortResult = sortDataItems(objectGraph, matchedDataItemsIncludingFallback, allowUnmatchedFallbackResults, serverSideAppIdsOrdering, preferredResultCount, diversify);
+ }
+ else {
+ // First decorate our raw dataItems with frequency, recency, usage information
+ const personalizedDataItems = personalizedDataItemsFromDataItemsOnDeviceSignals(objectGraph, dataItems, onDevicePersonalizationDataContainer === null || onDevicePersonalizationDataContainer === void 0 ? void 0 : onDevicePersonalizationDataContainer.personalizationData, includeItemsWithNoPersonalizationData, parentAppId);
+ // Now sort the data items
+ const sortedDataItems = getUpdatedScoreAfterBoosting(personalizedDataItems, onDevicePersonalizationDataContainer === null || onDevicePersonalizationDataContainer === void 0 ? void 0 : onDevicePersonalizationDataContainer.metricsData);
+ const orderWasNotChanged = personalizedDataItems.every((dataItem, index) => {
+ return dataItem === sortedDataItems[index];
+ });
+ sortResult = {
+ sortedDataItems: sortedDataItems,
+ processingType: orderWasNotChanged
+ ? 0 /* onDevicePersonalization.ProcessingType.contentsNotChanged */
+ : 2 /* onDevicePersonalization.ProcessingType.contentsSorted */,
+ };
+ if (serverData.isDefinedNonNull(preferredResultCount) &&
+ sortResult.sortedDataItems.length >= preferredResultCount) {
+ sortResult.sortedDataItems = sortResult.sortedDataItems.slice(0, preferredResultCount);
+ }
+ }
+ // We only need to return the raw data blobs, so remove the personalization decoration
+ const finalDataItems = sortResult.sortedDataItems.map((personalizedDataItem) => personalizedDataItem.rawData);
+ // Generate the processing type value
+ const filterType = dataItems.length !== finalDataItems.length
+ ? 1 /* onDevicePersonalization.ProcessingType.contentsFiltered */
+ : 0 /* onDevicePersonalization.ProcessingType.contentsNotChanged */;
+ const processingType = filterType + sortResult.processingType;
+ return {
+ personalizedData: finalDataItems,
+ processingType: processingType,
+ };
+}
+/**
+ * Creates a list of `PersonalizedData` objects, based on the input raw data items.
+ *
+ * @param dataItems The raw data blobs.
+ * @param onDevicePersonalizationData The on device personalization data, used for matching personalization segments against the dataItems.
+ * @param includeItemsWithNoPersonalizationData Whether dataItems without any valid personalization data should be included in the results.
+ * @param parentAppId An optional appID, which is the parent for all the dataItems. Currently only used for search.
+ * @returns A list of PersonalizedData objects.
+ */
+function personalizedDataItemsFromDataItemsOnDeviceSignals(objectGraph, dataItems, onDevicePersonalizationData, includeItemsWithNoPersonalizationData, parentAppId) {
+ const personalizedDataItems = [];
+ for (const data of dataItems) {
+ const personalizedData = new PersonalizedDataDefault(data);
+ // Filter out invalid data
+ const score = serverData.asNumber(data, "meta.personalizationData.score");
+ let appId = serverData.asString(data, "meta.personalizationData.appId");
+ if ((isNothing(appId) || appId.length === 0) && (parentAppId === null || parentAppId === void 0 ? void 0 : parentAppId.length) > 0) {
+ // If we have a parentAppId this means we are coming from search, where `appId` is not provided.
+ appId = parentAppId;
+ }
+ if (isNothing(appId) || appId.length === 0) {
+ // Personalization data is missing or invalid. This may sometimes be valid, eg. evergreen today stories for when reco times out.
+ if (includeItemsWithNoPersonalizationData) {
+ personalizedData.isUnpersonalizedMatch = true;
+ personalizedDataItems.push(personalizedData);
+ }
+ continue;
+ }
+ if (serverData.isDefinedNonNull(onDevicePersonalizationData)) {
+ const onDevicePersonalizationDataForApp = onDevicePersonalizationData[appId];
+ if (serverData.isDefinedNonNull(onDevicePersonalizationDataForApp) &&
+ serverData.isDefinedNonNull(onDevicePersonalizationDataForApp.onDeviceSignals)) {
+ personalizedData.onDeviceScore = +onDevicePersonalizationDataForApp.onDeviceSignals;
+ }
+ }
+ personalizedData.appId = appId;
+ personalizedData.score = score !== null && score !== void 0 ? score : 0;
+ personalizedDataItems.push(personalizedData);
+ }
+ return personalizedDataItems;
+}
+/**
+ * Creates a list of `PersonalizedData` objects, based on the input raw data items.
+ *
+ * @param dataItems The raw data blobs.
+ * @param onDevicePersonalizationData The on device personalization data, used for matching personalization segments against the dataItems.
+ * @param includeItemsWithNoPersonalizationData Whether dataItems without any valid personalization data should be included in the results.
+ * @param parentAppId An optional appID, which is the parent for all the dataItems. Currently only used for search.
+ * @returns A list of PersonalizedData objects.
+ */
+function personalizedDataItemsFromDataItems(objectGraph, dataItems, onDevicePersonalizationData, includeItemsWithNoPersonalizationData, parentAppId) {
+ const personalizedDataItems = [];
+ for (const data of dataItems) {
+ const personalizedData = new PersonalizedDataDefault(data);
+ // Filter out invalid data
+ const rawDataUserSegments = serverData.asString(data, "meta.personalizationData.segId");
+ let appId = serverData.asString(data, "meta.personalizationData.appId");
+ let groupId = serverData.asString(data, "meta.personalizationData.grpId");
+ if ((isNothing(appId) || appId.length === 0) && (parentAppId === null || parentAppId === void 0 ? void 0 : parentAppId.length) > 0) {
+ // If we have a parentAppId this means we are coming from search, where `appId` and `grpId` are not provided.
+ // Normally we filter our data items to only allow one item per group, so in this case we allocate a random
+ // group ID, so that none of the data items get filtered out for that reason. Later on as part of search
+ // results processing we will pick the first (valid) result, but only after ODP has finished.
+ appId = parentAppId;
+ groupId = objectGraph.random.nextUUID();
+ }
+ if (serverData.isNullOrEmpty(rawDataUserSegments) ||
+ serverData.isNullOrEmpty(appId) ||
+ serverData.isNullOrEmpty(groupId)) {
+ // Personalization data is missing or invalid. This may sometimes be valid, eg. evergreen today stories for when reco times out.
+ if (includeItemsWithNoPersonalizationData) {
+ personalizedData.isUnpersonalizedMatch = true;
+ personalizedDataItems.push(personalizedData);
+ }
+ continue;
+ }
+ // Check if the data has the match all user segment
+ const dataUserSegments = rawDataUserSegments.split(",");
+ if (dataUserSegments.includes(alwaysMatchUserSegment)) {
+ personalizedData.isWildcardMatch = true;
+ }
+ // Check if any of the data segments match with the on device personalization data
+ if (serverData.isDefinedNonNull(onDevicePersonalizationData)) {
+ const onDevicePersonalizationDataForApp = onDevicePersonalizationData[appId];
+ if (serverData.isDefinedNonNull(onDevicePersonalizationDataForApp)) {
+ for (const dataUserSegment of dataUserSegments) {
+ if (onDevicePersonalizationDataForApp.userSegments.includes(dataUserSegment)) {
+ personalizedData.isExactMatch = true;
+ break;
+ }
+ }
+ }
+ }
+ personalizedData.appId = appId;
+ personalizedData.groupId = groupId;
+ personalizedDataItems.push(personalizedData);
+ }
+ return personalizedDataItems;
+}
+/**
+ * Iterates through the list of given data items, and ensures we only have one per group.
+ *
+ * @param dataItems The data items to processed.
+ * @returns A subset of dataItems, with only one dataItem per group.
+ */
+function filterDataItemsIntoOnePerGroup(objectGraph, dataItems) {
+ var _a;
+ const filledGroupIds = new Set();
+ const matchedDataItemsIncludingMultipleFallbacksPerGroup = [];
+ // Determine which groups have any exact matches
+ const groupIdsWithExactMatchesArray = dataItems
+ .filter((dataItem) => {
+ return dataItem.isExactMatch;
+ })
+ .map((dataItem) => {
+ return dataItem.groupId;
+ });
+ const groupIdsWithExactMatches = new Set(groupIdsWithExactMatchesArray);
+ // Now iterate through our data items, and filter out any we don't need
+ dataItems.forEach((dataItem, index) => {
+ // If an item has no group, we always include it. This would only happen for
+ // data which is missing valid personalization metadata, and we have specifically
+ // opted in to including these items in the results.
+ if (serverData.isNullOrEmpty(dataItem.groupId)) {
+ matchedDataItemsIncludingMultipleFallbacksPerGroup.push(dataItem);
+ return;
+ }
+ // We already have a match for this group, so move onto the next item
+ if (filledGroupIds.has(dataItem.groupId)) {
+ return;
+ }
+ // This item is an unpersonalized match, which will only occur if we permit this.
+ // These are always added to the result set.
+ if (dataItem.isUnpersonalizedMatch) {
+ matchedDataItemsIncludingMultipleFallbacksPerGroup.push(dataItem);
+ return;
+ }
+ // This item is the first exact match for this group, so add it into our result set
+ if (dataItem.isExactMatch) {
+ filledGroupIds.add(dataItem.groupId);
+ matchedDataItemsIncludingMultipleFallbacksPerGroup.push(dataItem);
+ return;
+ }
+ // If we know we have an exact match somewhere else for this group, we can just
+ // continue on to the next item, as the exact match will be picked later.
+ if (groupIdsWithExactMatches.has(dataItem.groupId)) {
+ return;
+ }
+ // We have no exact matches for this group, so we can now take wildcard matches.
+ if (dataItem.isWildcardMatch) {
+ filledGroupIds.add(dataItem.groupId);
+ matchedDataItemsIncludingMultipleFallbacksPerGroup.push(dataItem);
+ return;
+ }
+ // This item is not a match. As we don't have any matches for this group yet,
+ // we can mark it as a fallback. This does not necessarily mean it will be used,
+ // but it does mean it becomes available for use. groupIDs are not necessarily in
+ // sequential order, so we mark all of these as fallbacks, and filter them further below.
+ dataItem.isFallbackMatch = true;
+ matchedDataItemsIncludingMultipleFallbacksPerGroup.push(dataItem);
+ });
+ // We now need to remove all the fallback items except for the last one in each group, so iterate
+ // through in reverse order and filter out any duplicates
+ const matchedDataItemsWithOneFallbackPerGroup = [];
+ const reversedMatchedDataItems = matchedDataItemsIncludingMultipleFallbacksPerGroup.slice().reverse();
+ for (const dataItem of reversedMatchedDataItems) {
+ if (dataItem.isFallbackMatch) {
+ if (filledGroupIds.has(dataItem.groupId)) {
+ continue;
+ }
+ }
+ matchedDataItemsWithOneFallbackPerGroup.push(dataItem);
+ if (((_a = dataItem.groupId) === null || _a === void 0 ? void 0 : _a.length) > 0) {
+ filledGroupIds.add(dataItem.groupId);
+ }
+ }
+ // Return to our original order
+ matchedDataItemsWithOneFallbackPerGroup.reverse();
+ return matchedDataItemsWithOneFallbackPerGroup;
+}
+/**
+ * Sorts the given list of data items, and optionally restricts the list to a specified number of results.
+ *
+ * @param dataItems The data items to process.
+ * @param allowUnmatchedFallbackResults Whether to allow fallback results to be included in the results. This will only be utilised in order to reach a preferredResultCount.
+ * @param preferredResultCount? The preferrd number of results.
+ * @param serverSideAppIdsOrdering List of ordered app ids from server side
+ * @param diversify An optional flag that determines if we should diverse the personalized results on the basis of server side apps ranking
+ * @returns The sorted list of dataItems, optionally restricted in length,
+ */
+function sortDataItems(objectGraph, dataItems, allowUnmatchedFallbackResults, serverSideAppIdsOrdering, preferredResultCount, diversify) {
+ let sortResult;
+ // Excluding fallback results is the preferred route, but if the number of results is less than our preferredResultCount, we will need to use the fallback results.
+ const dataItemsWithoutFallback = dataItems.filter((data) => data.isExactMatch || data.isWildcardMatch || data.isUnpersonalizedMatch || serverData.isNull(data.groupId));
+ if (serverData.isNull(preferredResultCount)) {
+ // There is no preferred number of results, so simply perform our final sort and then return
+ sortResult = sortAndDiversify(dataItemsWithoutFallback, serverSideAppIdsOrdering, diversify);
+ }
+ else if (dataItemsWithoutFallback.length >= preferredResultCount || !allowUnmatchedFallbackResults) {
+ // There is a preferred number of results, but we either have enough items without needing to utilise
+ // any fallback matches, or we don't allow fallback results.
+ sortResult = sortAndDiversify(dataItemsWithoutFallback, serverSideAppIdsOrdering, diversify);
+ sortResult.sortedDataItems = sortResult.sortedDataItems.slice(0, preferredResultCount);
+ }
+ else {
+ // There is a preferred number of results, and we need to use fallback matches in order to
+ // meet this number. We may still fall short, but this gets us as close as possible.
+ sortResult = sortAndDiversify(dataItems, serverSideAppIdsOrdering, diversify);
+ sortResult.sortedDataItems = sortResult.sortedDataItems.slice(0, preferredResultCount);
+ }
+ return sortResult;
+}
+/**
+ * Rearranges a list of dataItems, so that any where there is an exact segment match are moved to the front of the list.
+ *
+ * @param dataItems The data items to process.
+ * @param serverSideAppIdsOrdering List of ordered app ids from server side
+ * @param diversify An optional flag that determines if we should diverse the personalized results on the basis of server side apps ranking
+ * @returns The sorted list of data items.
+ */
+function sortAndDiversify(dataItems, serverSideAppIdsOrdering, diversify) {
+ const exactMatchDataItems = dataItems.filter((value) => value.isExactMatch);
+ let otherDataItems = dataItems.filter((value) => !value.isExactMatch);
+ if (serverData.isDefinedNonNull(diversify) && diversify) {
+ otherDataItems = diversifyDataItems(otherDataItems, serverSideAppIdsOrdering);
+ }
+ const sortedDataItems = exactMatchDataItems.concat(otherDataItems);
+ const orderWasNotChanged = dataItems.every((dataItem, index) => {
+ return dataItem === sortedDataItems[index];
+ });
+ return {
+ sortedDataItems: sortedDataItems,
+ processingType: orderWasNotChanged
+ ? 0 /* onDevicePersonalization.ProcessingType.contentsNotChanged */
+ : 2 /* onDevicePersonalization.ProcessingType.contentsSorted */,
+ };
+}
+/**
+ * Filters a list of raw data blobs into a list which only includes non-personalized data, or data that is set to "match all".
+ *
+ * @param dataItems The raw data blobs.
+ * @param preferredResultCount The preferred number of items to be included in the results.
+ * @returns The filtered set of data blobs. This will be a subset (or all) of the original dataItems.
+ */
+export function removePersonalizedDataItems(objectGraph, dataItems, preferredResultCount) {
+ let filteredDataItems = [];
+ const filledGroupIds = new Set();
+ for (const data of dataItems) {
+ // If the personalization data is invalid or empty, we keep this in our result set.
+ const rawDataUserSegments = serverData.asString(data, "meta.personalizationData.segId");
+ const appId = serverData.asString(data, "meta.personalizationData.appId");
+ const groupId = serverData.asString(data, "meta.personalizationData.grpId");
+ if (serverData.isNullOrEmpty(rawDataUserSegments) ||
+ serverData.isNullOrEmpty(appId) ||
+ serverData.isNullOrEmpty(groupId)) {
+ filteredDataItems.push(data);
+ continue;
+ }
+ // We already have a match for this group, so move onto the next item
+ if (filledGroupIds.has(groupId)) {
+ continue;
+ }
+ // If the data has a match all user segment, we keep this in our result set.
+ const dataUserSegments = rawDataUserSegments.split(",");
+ if (dataUserSegments.includes(alwaysMatchUserSegment)) {
+ filteredDataItems.push(data);
+ filledGroupIds.add(groupId);
+ }
+ }
+ // Finally, if we have a preferredResultCount which is smaller than our result set, trim our results down to this count
+ if (serverData.isDefinedNonNull(preferredResultCount) && filteredDataItems.length > preferredResultCount) {
+ filteredDataItems = filteredDataItems.slice(0, preferredResultCount);
+ }
+ return {
+ personalizedData: filteredDataItems,
+ processingType: null,
+ };
+}
+//# sourceMappingURL=on-device-personalization-processing.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-personalization.js b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-personalization.js
new file mode 100644
index 0000000..02003e9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-personalization.js
@@ -0,0 +1,134 @@
+import * as onDevicePersonalizationGroupingProcessing from "./on-device-personalization-grouping-processing";
+import * as onDevicePersonalizationProcessing from "./on-device-personalization-processing";
+import * as serverData from "../../foundation/json-parsing/server-data";
+/**
+ * Accepts an array of data blobs, and returns a subset of those original data blobs which is personalized to the user.
+ * In the case where personalization is disabled, or any personalized data blobs will instead be filtered out.
+ *
+ * @param placement Placement of the personalized items for on-device personalization
+ * @param dataItems The input list of data blobs.
+ * @param includeItemsWithNoPersonalizationData Whether to include data which no personalizationData meta is present.
+ * @param personalizationDataContainer The data container to use for personalizing the data.
+ * @param allowUnmatchedFallbackResults Whether to allow fallback results to be included in the results. This will only be utilised in order to reach a preferredResultCount.
+ * @param preferredResultCount The preferred number of items to be included in the results.
+ * @param parentAppId An optional appID, which is the parent for all the dataItems. Currently only used for search.
+ * @param diversify An optional flag that determines if we should diverse the personalized results on the basis of server side apps ranking
+ * @returns The relevant list of data blobs.
+ */
+export function personalizeDataItems(objectGraph, placement, dataItems, includeItemsWithNoPersonalizationData, personalizationDataContainer, allowUnmatchedFallbackResults = false, preferredResultCount, parentAppId, diversify) {
+ if (isPersonalizationAvailable(objectGraph)) {
+ switch (placement) {
+ case "groupingAppEvent":
+ return personalizeGroupingDataItems(objectGraph, dataItems, includeItemsWithNoPersonalizationData, personalizationDataContainer, diversify);
+ default:
+ return onDevicePersonalizationProcessing.personalizeDataItems(objectGraph, dataItems, personalizationDataContainer, includeItemsWithNoPersonalizationData, allowUnmatchedFallbackResults, preferredResultCount, parentAppId, diversify);
+ }
+ }
+ else {
+ return onDevicePersonalizationProcessing.removePersonalizedDataItems(objectGraph, dataItems, preferredResultCount);
+ }
+}
+/**
+ * Accepts an array of data blobs, and returns a subset of those original data blobs which is personalized to the user.
+ * In the case where personalization is disabled, or any personalized data blobs will instead be filtered out.
+ *
+ * @param dataItems The input list of data blobs.
+ * @param includeItemsWithNoPersonalizationData Whether to include data which no personalizationData meta is present.
+ * @param personalizationDataContainer The data container to use for personalizing the data.
+ * @param diversify An optional flag that determines if we should diverse the personalized results on the basis of server side apps ranking
+ * @returns The relevant list of data blobs.
+ */
+function personalizeGroupingDataItems(objectGraph, dataItems, includeItemsWithNoPersonalizationData, personalizationDataContainer, diversify) {
+ var _a, _b;
+ // We must filter out non app-event items in order to avoid moving their positions
+ // when contingent_offers_personalization is turned off
+ let appEventsOnlyDataItems = dataItems;
+ let wereItemsRemoved = false;
+ const nonAppEventIndexes = [];
+ if (!objectGraph.featureFlags.isEnabled("contingent_offers_personalization")) {
+ appEventsOnlyDataItems = dataItems.filter((item, index) => {
+ if (serverData.isDefinedNonNullNonEmpty(item.type) && item.type !== "app-events") {
+ nonAppEventIndexes.push(index);
+ return false;
+ }
+ return true;
+ });
+ wereItemsRemoved = appEventsOnlyDataItems.length !== dataItems.length;
+ }
+ // We fetch the information regarding the segment optimizer flow from the personalization container
+ const personalizedMetricsData = personalizationDataContainer === null || personalizationDataContainer === void 0 ? void 0 : personalizationDataContainer.metricsData;
+ const useSegScores = (_a = personalizedMetricsData["use_segment_scores"]) !== null && _a !== void 0 ? _a : false;
+ const useOnDeviceSignals = (_b = personalizedMetricsData["use_signals"]) !== null && _b !== void 0 ? _b : false;
+ let personalizedResults;
+ if (useSegScores || useOnDeviceSignals) {
+ personalizedResults = onDevicePersonalizationGroupingProcessing.personalizeDataItems(objectGraph, appEventsOnlyDataItems, personalizationDataContainer, diversify);
+ }
+ else {
+ personalizedResults = onDevicePersonalizationProcessing.personalizeDataItems(objectGraph, appEventsOnlyDataItems, personalizationDataContainer, includeItemsWithNoPersonalizationData, null, null, null, diversify);
+ }
+ // We re-add non app-event items back into their original positions
+ if (wereItemsRemoved) {
+ const resultsArray = personalizedResults.personalizedData;
+ nonAppEventIndexes.forEach((index) => {
+ const item = dataItems[index];
+ if (index < resultsArray.length) {
+ resultsArray.splice(index, 0, item);
+ }
+ else {
+ resultsArray.push(item);
+ }
+ });
+ personalizedResults = {
+ personalizedData: resultsArray,
+ processingType: personalizedResults.processingType,
+ };
+ }
+ return personalizedResults;
+}
+/**
+ * Convenience function for determining if data personalization is available.
+ */
+export function isPersonalizationAvailable(objectGraph) {
+ return (objectGraph.client.isiOS &&
+ objectGraph.user.isOnDevicePersonalizationEnabled &&
+ objectGraph.bag.enableOnDevicePersonalization);
+}
+/**
+ * Reaches down to the native client to return the current set of on device personalization data,
+ * restricted to a set of app IDs.
+ *
+ * @param appIds A set of appIds to restrict the personalization data to.
+ * @returns The relevant set of personalization data
+ */
+export function personalizationDataContainerForAppIds(objectGraph, appIds) {
+ if (!isPersonalizationAvailable(objectGraph)) {
+ return null;
+ }
+ if (objectGraph.host.platform === "iOS") {
+ return objectGraph.user.onDevicePersonalizationDataContainerForAppIds(Array.from(appIds));
+ }
+ else {
+ return {
+ personalizationData: {},
+ metricsData: null,
+ };
+ }
+ return null;
+}
+/**
+ * Reaches down to the native client to return the current metrics data.
+ *
+ * @returns The current AMDClient metrics data
+ */
+export function metricsData(objectGraph) {
+ if (!isPersonalizationAvailable(objectGraph)) {
+ return null;
+ }
+ if (objectGraph.host.platform === "iOS") {
+ return objectGraph.user.onDevicePersonalizationDataContainerForAppIds([]).metricsData;
+ }
+ else {
+ return null;
+ }
+}
+//# sourceMappingURL=on-device-personalization.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-common.js b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-common.js
new file mode 100644
index 0000000..c03b729
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-common.js
@@ -0,0 +1,190 @@
+import * as validation from "@jet/environment/json/validation";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaDataFetching from "../../foundation/media/data-fetching";
+import * as mediaNetwork from "../../foundation/media/network";
+import * as groupingShelfControllerCommon from "../grouping/shelf-controllers/grouping-shelf-controller-common";
+import { Parameters } from "../../foundation/network/url-constants";
+export class PersonalizedData {
+}
+export async function recommendedAppsForUseCase(objectGraph, useCase, displayContext) {
+ const displayContextLogString = displayContext === "shelf" ? "OnDeviceRecommendationsShelfController" : "OnDeviceRecommendationsPageController";
+ return await new Promise((resolve, reject) => {
+ if (!objectGraph.host.isiOS) {
+ const errorMessage = `${displayContextLogString}: On device personalization is only enabled on iOS devices.`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ return;
+ }
+ if (serverData.isNullOrEmpty(objectGraph.user.dsid)) {
+ const errorMessage = `${displayContextLogString}: User is currently not signed in.`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ return;
+ }
+ if (serverData.isNullOrEmpty(useCase)) {
+ const errorMessage = `${displayContextLogString}: Missing valid useCase for ODP: ${useCase}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ return;
+ }
+ objectGraph.onDeviceRecommendationsManager
+ .performRequest({
+ type: "fetchRecommendations",
+ dsId: objectGraph.user.dsid,
+ useCase: useCase,
+ })
+ .then((recoResponse) => {
+ const candidates = serverData.asArrayOrEmpty(recoResponse["candidates"]);
+ const recoMetrics = serverData.asJSONData(recoResponse["metrics"]);
+ if (serverData.isNullOrEmpty(candidates)) {
+ const errorMessage = `${displayContextLogString}: ODP returned no candidate ids for useCase: ${useCase}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new NoODPCandidatesError(errorMessage));
+ return;
+ }
+ if (serverData.isNullOrEmpty(recoMetrics)) {
+ const errorMessage = `${displayContextLogString}: ODP returned no metrics for useCase: ${useCase}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ return;
+ }
+ const candidatesData = [];
+ for (const candidateId of candidates) {
+ if (serverData.isDefinedNonNullNonEmpty(candidateId)) {
+ candidatesData.push({
+ id: candidateId,
+ type: "apps",
+ });
+ }
+ }
+ // MAPI Request
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph, candidatesData)
+ .withFilter("apps:recommendable", "true")
+ .addingQuery(Parameters.onDevicePersonalizationUseCase, useCase);
+ groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, mediaApiRequest);
+ mediaNetwork
+ .fetchData(objectGraph, mediaApiRequest)
+ .then((recoDataContainer) => {
+ resolve({
+ candidates: candidates,
+ recoMetrics: recoMetrics,
+ dataContainer: recoDataContainer,
+ });
+ })
+ .catch((error) => {
+ const errorMessage = `${displayContextLogString}: Failed to fetch Media API data for candidates: ${candidates}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ });
+ })
+ .catch((error) => {
+ const errorMessage = `${displayContextLogString}: Failed to perform ODP for useCase: ${useCase}, ${error}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ });
+ });
+}
+/***
+ * Gives the list of app ids in the order in which they appear in the passed response
+ * @param personalizedDataItems The data items to be processed
+ * @return List of ordered app ids
+ */
+export function getOrderedAppIds(personalizedDataItems) {
+ const seenAppIds = new Set();
+ const orderedAppIds = [];
+ personalizedDataItems.forEach((dataItem, index) => {
+ if (!seenAppIds.has(dataItem.appId)) {
+ orderedAppIds.push(dataItem.appId);
+ seenAppIds.add(dataItem.appId);
+ }
+ });
+ return orderedAppIds;
+}
+/***
+ * This function reranks events based on a calculated on-device score
+ * that can factor in recency, frequency, or usage of an app
+ * @param personalizedDataItems List of personalized data items
+ * @param metricsData Metrics data with hyperparameter values to use
+ * @return Personalized data items after reranking using on-device signals
+ */
+export function getUpdatedScoreAfterBoosting(personalizedDataItems, metricsData) {
+ var _a;
+ const weightParam = Number((_a = metricsData["weight_parameter"]) !== null && _a !== void 0 ? _a : 0.0);
+ for (const personalizedDataItem of personalizedDataItems) {
+ const serverScore = personalizedDataItem.score;
+ const onDeviceScore = personalizedDataItem.onDeviceScore;
+ personalizedDataItem.modifiedScore = weightParam * onDeviceScore + (1 - weightParam) * serverScore;
+ }
+ personalizedDataItems.sort((a, b) => {
+ return b.modifiedScore - a.modifiedScore;
+ });
+ return personalizedDataItems;
+}
+/***
+ * This function will diversify the personalized items on the basis of ordering of apps passed.
+ * In case there are pinned items, we will remove them from diversification bucket and keep them in their position and add diversified items in order
+ * This can lead to duplicates next to the pinned items. But the pinning and duplicate app events case scenario is rare so we avoid complicated logic here.
+ * Explanation ->
+ * If the original data items list contains appId in this order -> [1(a), 1(b), 2(a), 3(a), 2(b), 2(c), 3(b), 4(a), 1(c), 4(b)]
+ * and the ordering of the apps used for relative ranking is -> [1, 2, 3, 4]
+ * Then the updated ordering of the data items should look like -> [1(a), 2(a), 3(a), 4(a), 1(b), 2(b), 3(b), 4(b), 1(c), 2(c)]
+ * @param personalizedDataItems List of personalized data items for diversification
+ * @param orderedAppIds List of ordered app ids used for relative ordering between in-app events
+ * @return The diversified list of data items.
+ */
+export function diversifyDataItems(personalizedDataItems, orderedAppIds) {
+ const pinnedItems = personalizedDataItems.filter((item) => serverData.isDefinedNonNull(item.pinnedPosition));
+ personalizedDataItems = personalizedDataItems.filter((item) => serverData.isNull(item.pinnedPosition));
+ const appIdGroups = new Map();
+ // Insert the elements in a map in reverse order of their appearance in dataItems so that we can later use pop() instead of shift()
+ // 1 -> [1(c), 1(b), 1(a)], 2 -> [2(c), 2(b), 2(a)], 3 -> [3(b), 3(a)], 4-> [4(b), 4(a)]
+ personalizedDataItems.reverse();
+ personalizedDataItems.forEach((dataItem, index) => {
+ if (dataItem.appId in appIdGroups) {
+ appIdGroups[dataItem.appId].push(dataItem);
+ }
+ else {
+ appIdGroups[dataItem.appId] = [dataItem];
+ }
+ });
+ const diversifiedDataItems = [];
+ // We find the max number of appItems for our appIds, to determine the iteration count
+ const maxAppEventsForAppId = Math.max(...Object.values(appIdGroups).map((a) => a.length));
+ for (let index = 0; index < maxAppEventsForAppId; index++) {
+ const reducedServerSideAppIdsOrdering = [];
+ orderedAppIds.forEach((appId) => {
+ if (appId in appIdGroups && appIdGroups[appId].length > 0) {
+ diversifiedDataItems.push(appIdGroups[appId].pop());
+ if (appIdGroups[appId].length !== 0) {
+ reducedServerSideAppIdsOrdering.push(appId);
+ }
+ }
+ });
+ orderedAppIds = reducedServerSideAppIdsOrdering;
+ }
+ // Merge the pinned items and diversified items
+ const finalizedResponse = new Array(pinnedItems.length + diversifiedDataItems.length);
+ // Sort pinned items by pinned position to be able to handle the pinned items that exceed the final list length
+ // For all the pinned items that have position greater than input list size, we just add them to end of the list
+ pinnedItems.sort((a, b) => a.pinnedPosition - b.pinnedPosition);
+ for (const dataItem of pinnedItems) {
+ if (dataItem.pinnedPosition < finalizedResponse.length) {
+ // Possible edgecase - Sanity check in case filtering reduces the length of items
+ finalizedResponse[dataItem.pinnedPosition] = dataItem;
+ }
+ else {
+ // Extremely rare case scenario: Push these items to diversifiedDataItemsList
+ diversifiedDataItems.push(dataItem);
+ }
+ }
+ diversifiedDataItems.reverse(); // To allow popping for first element fetch
+ for (const [index, finalizedItem] of finalizedResponse.entries()) {
+ if (serverData.isNull(finalizedItem) && diversifiedDataItems.length) {
+ finalizedResponse[index] = diversifiedDataItems.pop();
+ }
+ }
+ return finalizedResponse;
+}
+export class NoODPCandidatesError extends Error {
+}
+//# sourceMappingURL=on-device-recommendations-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-today.js b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-today.js
new file mode 100644
index 0000000..df2fa73
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-today.js
@@ -0,0 +1,329 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaDataFetching from "../../foundation/media/data-fetching";
+import * as mediaNetwork from "../../foundation/media/network";
+import { Parameters } from "../../foundation/network/url-constants";
+import { allOptional } from "../../foundation/util/promise-util";
+import * as groupingShelfControllerCommon from "../grouping/shelf-controllers/grouping-shelf-controller-common";
+import * as lottery from "../util/lottery";
+import { startPromiseWithAdditionalTimeout } from "../util/timeout-manager-util";
+import { isPersonalizationAvailable } from "./on-device-personalization";
+export const todayTabODPTimeoutUseCase = "todayTabPersonalization";
+const displayContextLogString = "OnDeviceRecommendationsTodayShelfController";
+/**
+ * Convenience function for determining if Today tab Arcade personalization is available.
+ */
+export function isTodayTabArcadePersonalizationAvailable(objectGraph) {
+ const isDataPersonalizationAvailable = isPersonalizationAvailable(objectGraph);
+ const isiOS = objectGraph.client.isiOS;
+ const isFeatureEnabledForCurrentUser = lottery.isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.todayTabArcadePersonalizationRate);
+ // Personalization is enabled only IF
+ // - Data personalization is available AND
+ // - Client is iOS AND
+ // - Feature is enabled for current user
+ return isDataPersonalizationAvailable && isiOS && isFeatureEnabledForCurrentUser;
+}
+/**
+ * Fetches and returns Today recommendations result using on-device recommendations manager with timeout.
+ * @param timeout: Optional timeout in seconds.
+ * @returns Promise<Opt<TodayRecommendationsResult>>: Today recommendations result.
+ */
+export async function fetchTodayRecommendationsWithTimeout(objectGraph, timeout) {
+ return await startPromiseWithAdditionalTimeout(objectGraph, fetchTodayRecommendations(objectGraph), timeout, todayTabODPTimeoutUseCase);
+}
+/**
+ * Fetches and returns Today recommendations result using on-device recommendations manager.
+ * @returns Promise<Opt<TodayRecommendationsResult>>: Today recommendations result.
+ */
+async function fetchTodayRecommendations(objectGraph) {
+ try {
+ const useCases = await fetchUseCasesForTab("today", objectGraph);
+ const recommendationPromises = useCases.map(async (useCase) => await fetchTodayRecommendationForUseCase(objectGraph, useCase));
+ const recommendationPromiseResults = await allOptional(recommendationPromises);
+ const recommendations = recommendationPromiseResults
+ .map((promiseResult) => {
+ if (promiseResult.success) {
+ return promiseResult.value;
+ }
+ else {
+ return undefined;
+ }
+ })
+ .filter(isSome);
+ return new TodayRecommendationsResult(recommendations);
+ }
+ catch (error) {
+ objectGraph.console.log(`${displayContextLogString}: Failed to perform ODP for Today recommendations: ${error}`);
+ return undefined;
+ }
+}
+/**
+ * Fetches Today Recommendation for a use case using on-device recommendations manager.
+ * - useCase: On-device personalization use case.
+ * @returns Promise<TodayRecommendation | undefined>: Today recommendation.
+ */
+async function fetchTodayRecommendationForUseCase(objectGraph, useCase) {
+ try {
+ const odrResponse = await fetchTodayRecommendation(useCase, objectGraph);
+ const recommendedCandidatesAndMetrics = await makeTodayRecommendedCandidatesAndMetrics(useCase, odrResponse);
+ const recoMetrics = recommendedCandidatesAndMetrics.metrics;
+ const recommendedCandidates = recommendedCandidatesAndMetrics.candidates;
+ if (recommendedCandidates.length === 0) {
+ return undefined;
+ }
+ // Select the first candidate as we support only one candidate for each use case.
+ const recommendedCandidate = recommendedCandidates[0];
+ const todayRecommendationPromise = await makeTodayRecommendation(useCase, recommendedCandidate, recoMetrics, objectGraph);
+ return todayRecommendationPromise;
+ }
+ catch (error) {
+ objectGraph.console.log(`${displayContextLogString}: Failed to perform ODP Today recommendation for useCase: ${useCase}, with error: ${error}`);
+ return undefined;
+ }
+}
+/**
+ * Fetches and returns use cases for given tab using on-device recommendations manager.
+ * - tab: Navigation tab.
+ * @returns Promise<string[]>: Use cases for given tab.
+ */
+export async function fetchUseCasesForTab(tab, objectGraph) {
+ if (serverData.isNullOrEmpty(objectGraph.user.dsid)) {
+ const errorMessage = `${displayContextLogString}: User is currently not signed in.`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ throw new Error(errorMessage);
+ }
+ try {
+ const odrResponse = await objectGraph.onDeviceRecommendationsManager.performRequest({
+ type: "fetchUseCases",
+ tab: tab,
+ dsId: objectGraph.user.dsid,
+ });
+ const useCases = serverData.asArrayOrEmpty(odrResponse["useCases"]);
+ if (serverData.isNullOrEmpty(useCases)) {
+ const errorMessage = `${displayContextLogString}: ODP returned no use cases for tab: ${tab}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ throw new Error(errorMessage);
+ }
+ return useCases;
+ }
+ catch (error) {
+ const errorMessage = `${displayContextLogString}: Failed to fetch ODP use cases for tab: ${tab}, with error: ${error}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ throw new Error(errorMessage);
+ }
+}
+/**
+ * Fetches and returns Today recommendation response for given use case using on-device recommendations manager.
+ * - useCase: Use case.
+ * @returns Promise<JSONData>: Today recommendation response.
+ */
+export async function fetchTodayRecommendation(useCase, objectGraph) {
+ if (serverData.isNullOrEmpty(objectGraph.user.dsid)) {
+ const errorMessage = `${displayContextLogString}: User is currently not signed in.`;
+ throw new Error(errorMessage);
+ }
+ try {
+ return await objectGraph.onDeviceRecommendationsManager.performRequest({
+ type: "fetchRecommendations",
+ dsId: objectGraph.user.dsid,
+ useCase: useCase,
+ });
+ }
+ catch (error) {
+ const errorMessage = `${displayContextLogString}: Failed to perform ODP Today recommendation for useCase: ${useCase}, with error: ${error}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ throw new Error(errorMessage);
+ }
+}
+/**
+ * Makes and returns Today recommended candidates and metrics from given useCase and on-device recommendation response.
+ * - useCase: Use case.
+ * - odrResponse: On-device recommendation response.
+ * @returns { candidates: TodayRecommendedCandidate[]; metrics: JSONData }: Today recommended candidates and reco metrics.
+ */
+export async function makeTodayRecommendedCandidatesAndMetrics(useCase, odrResponse) {
+ const recoCandidates = serverData.asArrayOrEmpty(odrResponse["candidates"]);
+ if (serverData.isNullOrEmpty(recoCandidates)) {
+ const errorMessage = `${displayContextLogString}: ODP returned no candidates for useCase: ${useCase}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ throw new Error(errorMessage);
+ }
+ const recoMetrics = serverData.asJSONData(odrResponse["metrics"]);
+ const recommendedCandidates = recoCandidates.map((candidate) => makeRecommendedCandidate(candidate)).filter(isSome);
+ if (serverData.isNull(recoMetrics) || serverData.isNullOrEmpty(recommendedCandidates)) {
+ const errorMessage = `${displayContextLogString}: ODP candidates could not be parsed for useCase: ${useCase}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ throw new Error(errorMessage);
+ }
+ return { candidates: recommendedCandidates, metrics: recoMetrics };
+}
+/**
+ * Makes and returns Today recommendation for given use case and candidates using MAPI response.
+ * - useCase: Use case.
+ * - recommendedCandidate: Recommended candidate.
+ * - recoMetrics: Reco metrics.
+ * @returns Promise<TodayRecommendation>: Today recommendation.
+ */
+export async function makeTodayRecommendation(useCase, recommendedCandidate, recoMetrics, objectGraph) {
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph, recommendedCandidate.data, true)
+ .addingQuery(Parameters.onDevicePersonalizationUseCase, useCase)
+ .addingQuery(Parameters.filterRecommendable, "true");
+ groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, mediaApiRequest);
+ try {
+ const mapiResponse = await mediaNetwork.fetchData(objectGraph, mediaApiRequest);
+ return new TodayRecommendation(useCase, [recommendedCandidate], recoMetrics, mapiResponse);
+ }
+ catch (error) {
+ const errorMessage = `${displayContextLogString}: Failed to fetch Media API data for: ${recommendedCandidate.data}, with error: ${error}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ throw new Error(errorMessage);
+ }
+}
+/**
+ * Makes and returns Today recommended candidate using given candidate data.
+ * - candidate: Candidate data.
+ * @returns TodayRecommendedCandidate: Today recommended candidate.
+ */
+export function makeRecommendedCandidate(candidate) {
+ // Extract candidate ID and type from candidate data.
+ const candidateID = serverData.asString(candidate.id);
+ const candidateType = serverData.asString(candidate.type);
+ if (serverData.isNull(candidateID)) {
+ return undefined;
+ }
+ let storyIDs = [];
+ let mediaType;
+ switch (candidateType) {
+ case "editorialItemGroup":
+ // Exract story candidates to use within the story group.
+ const storyCandidates = serverData.asArrayOrEmpty(candidate.candidates);
+ // Create story IDs from story candidates.
+ storyIDs = storyCandidates
+ .map((storyCandidate) => serverData.asString(storyCandidate.id))
+ .filter((storyID) => isSome(storyID));
+ mediaType = "editorial-item-groups";
+ break;
+ case "editorialItem":
+ mediaType = "editorial-items";
+ break;
+ default:
+ return undefined;
+ }
+ let candidatesData = [];
+ candidatesData.push({
+ id: candidateID,
+ type: mediaType,
+ });
+ // If there are any story IDs, add story candidates to candidates data.
+ if (serverData.isDefinedNonNullNonEmpty(storyIDs)) {
+ const storyCandidates = storyIDs.map((storyID) => ({
+ id: storyID,
+ type: "editorial-items",
+ }));
+ candidatesData = candidatesData.concat(storyCandidates);
+ }
+ return new TodayRecommendedCandidate(candidateID, mediaType, storyIDs, candidatesData);
+}
+/**
+ * A container for Today recommendations that also makes story data and story group data.
+ */
+export class TodayRecommendationsResult {
+ /**
+ * Initializes today recommendations results.
+ * @param recommendations - Today recommendations.
+ */
+ constructor(recommendations) {
+ this.recommendations = recommendations;
+ }
+ /**
+ * Returns story data from recommendations that has the given use case.
+ * @param useCase - Use case that is used to match a story.
+ * @returns Story data for given use case and type, or null if not found.
+ */
+ storyData(useCase) {
+ var _a;
+ const recommendation = this.recommendationForUseCase(useCase);
+ const candidate = recommendation === null || recommendation === void 0 ? void 0 : recommendation.candidate("editorial-items");
+ if (isNothing(recommendation) || isNothing(candidate)) {
+ return undefined;
+ }
+ return (_a = recommendation === null || recommendation === void 0 ? void 0 : recommendation.dataContainer) === null || _a === void 0 ? void 0 : _a.data.find((item) => item.id === candidate.id);
+ }
+ /**
+ * Returns story group data from recommendations that has the given use case.
+ * @param useCase - Use case that is used to match a story group.
+ * @returns Story group data for given use case and type, or null if not found.
+ */
+ storyGroupData(useCase) {
+ var _a, _b;
+ const recommendation = this.recommendationForUseCase(useCase);
+ const candidate = recommendation === null || recommendation === void 0 ? void 0 : recommendation.candidate("editorial-item-groups");
+ if (isNothing(recommendation) || isNothing(candidate)) {
+ return undefined;
+ }
+ const storyGroupData = (_a = recommendation === null || recommendation === void 0 ? void 0 : recommendation.dataContainer) === null || _a === void 0 ? void 0 : _a.data.find((item) => item.id === (candidate === null || candidate === void 0 ? void 0 : candidate.id));
+ const storiesData = (_b = recommendation === null || recommendation === void 0 ? void 0 : recommendation.dataContainer) === null || _b === void 0 ? void 0 : _b.data.filter((item) => candidate.candidateIDs.includes(item.id));
+ if (isNothing(storyGroupData) || isNothing(storiesData)) {
+ return undefined;
+ }
+ storyGroupData["meta"] = {
+ associations: {
+ recommendations: {
+ data: storiesData,
+ },
+ },
+ };
+ return storyGroupData;
+ }
+ /**
+ * Returns the first recommendation that has the given use case.
+ * @param useCase - Use case that is used to match a recommendation.
+ * @returns Recommendation for given use case or null if not found.
+ */
+ recommendationForUseCase(useCase) {
+ return this.recommendations.find((recommendation) => recommendation.useCase === useCase);
+ }
+}
+/**
+ * Today recommendation with a use case, candidates, metrics and a data container.
+ */
+export class TodayRecommendation {
+ /**
+ * Initializes today recommendation.
+ * @param useCase - Use case that is used to match recommendations.
+ * @param candidates - Recommended candidates to use as replacements.
+ * @param recoMetrics - Metrics for given candidates.
+ * @param dataContainer - Data returned from MAPI for recommended candidates.
+ */
+ constructor(useCase, candidates, recoMetrics, dataContainer) {
+ this.useCase = useCase;
+ this.candidates = candidates;
+ this.recoMetrics = recoMetrics;
+ this.dataContainer = dataContainer;
+ }
+ /// Returns the first candidate that has the given type.
+ candidate(type) {
+ return this.candidates.find((candidate) => candidate.type === type);
+ }
+}
+/**
+ * Today recommended candidate with its id, type, data
+ * and candidate IDs (only if it is a candidate for a story group).
+ */
+export class TodayRecommendedCandidate {
+ /**
+ * Initializes today recommended candidate.
+ * @param id - Candidate id i.e. story ID or story group ID.
+ * @param type - Candidate type i.e. "editorial-items" or "editorial-item-groups".
+ * @param candidateIDs - Candidate IDs.
+ * @param data - Data to use while fetching from MAPI.
+ */
+ constructor(id, type, candidateIDs, data) {
+ this.id = id;
+ this.type = type;
+ this.candidateIDs = candidateIDs;
+ this.data = data;
+ }
+}
+//# sourceMappingURL=on-device-recommendations-today.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/placeholders/placeholders.js b/node_modules/@jet-app/app-store/tmp/src/common/placeholders/placeholders.js
new file mode 100644
index 0000000..8110a4a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/placeholders/placeholders.js
@@ -0,0 +1,229 @@
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as types from "../grouping/grouping-types";
+import { isSome } from "@jet/environment/types/optional";
+/**
+ * Determines if placeholders are enabled for the current device
+ * @param objectGraph Current object graph
+ * @returns True if the current device supports placeholders
+ */
+export function placeholdersEnabled(objectGraph) {
+ return objectGraph.client.isiOS || objectGraph.client.isVision;
+}
+/**
+ * Whether a given shelf content type supports placeholders
+ * @param objectGraph Current object graph
+ * @param contentType Content type for the shelf
+ * @returns True if the content type supports placeholders
+ */
+function contentTypeSupportsPlaceholders(objectGraph, contentType) {
+ if (preprocessor.GAMES_TARGET) {
+ switch (contentType) {
+ case "smallLockup":
+ case "mediumLockup":
+ case "mediumImageLockup":
+ case "largeImageLockup":
+ case "smallStoryCard":
+ case "mediumStoryCard":
+ case "largeStoryCard":
+ case "miniTodayCard":
+ return true;
+ default:
+ return false;
+ }
+ }
+ if (objectGraph.client.isVision) {
+ switch (contentType) {
+ case "smallBrick":
+ case "brick":
+ case "largeBrick":
+ case "smallLockup":
+ case "mediumLockup":
+ case "largeLockup":
+ case "smallVerticalLockup":
+ case "mediumVerticalLockup":
+ case "posterLockup":
+ case "action":
+ case "smallImageLockup":
+ case "mediumImageLockup":
+ case "largeImageLockup":
+ case "smallStoryCard":
+ case "mediumStoryCard":
+ case "largeHeroBreakout":
+ return true;
+ default:
+ return false;
+ }
+ }
+ else {
+ switch (contentType) {
+ case "smallLockup":
+ case "mediumLockup":
+ case "largeLockup":
+ case "brick":
+ case "categoryBrick":
+ case "videoCard":
+ case "posterLockup":
+ case "appTrailerLockup":
+ case "screenshotsLockup":
+ case "appPromotion":
+ case "appEvent":
+ case "smallStoryCard":
+ case "tagBrick":
+ case "miniTodayCard":
+ return true;
+ default:
+ return false;
+ }
+ }
+}
+/**
+ * The number of placeholders to use, if the hydration count is unknown
+ * @param objectGraph Current object graph
+ * @param itemCount The number of items
+ * @param featuredContentID An optional FCID
+ * @returns The number of placeholds to use
+ */
+function placeholderCountForItemCount(objectGraph, itemCount, featuredContentID) {
+ if (itemCount > 0) {
+ return itemCount;
+ }
+ // In some cases mediaAPI can return is a shelf with no items. This is valid for personalised shelves but not for
+ // editorially controlled shelves and something is probably wrong with the configuration of that shelf shelf.
+ // In that case we return 0 placeholders so the shelf doesn't get modified (and placeholders don't get inserted), staying hidden from the user
+ if (isSome(featuredContentID) && !types.isRecommendationsShelf(featuredContentID)) {
+ return 0;
+ }
+ if (objectGraph.client.isPad || objectGraph.client.isVision) {
+ return 15;
+ }
+ else {
+ return 6;
+ }
+}
+/**
+ * Inserts placeholder items into the given product page shelf
+ * @param objectGraph Current object graph
+ * @param shelf The input shelf
+ * @param token The current shelf token
+ * @param featuredContentId An optional FCID
+ */
+export function insertPlaceholdersIntoProductPageShelfIfRequired(objectGraph, shelf, token) {
+ if (objectGraph.client.isiOS &&
+ objectGraph.bag.isOnDemandShelfFetchingEnabled &&
+ placeholdersEnabled(objectGraph) &&
+ isSome(shelf) &&
+ serverData.isNullOrEmpty(shelf.items) &&
+ token.isFirstRender) {
+ token.showingPlaceholders = insertPlaceholdersIntoShelf(objectGraph, shelf, token.remainingItems.length);
+ }
+}
+/**
+ * Inserts placeholder items into the given shelf, if we pass some pre-flight checks
+ * @param objectGraph Current object graph
+ * @param shelf The input shelf
+ * @param shelfToken The current shelf token
+ * @param featuredContentId An optional FCID
+ */
+export function insertPlaceholdersIntoShelfIfRequired(objectGraph, shelf, shelfToken, displayLimit = undefined, featuredContentId) {
+ if (placeholdersEnabled(objectGraph) &&
+ isSome(shelf) &&
+ isSome(shelf.url) &&
+ serverData.isNullOrEmpty(shelf.items) &&
+ shelfToken.isFirstRender) {
+ shelfToken.showingPlaceholders = insertPlaceholdersIntoShelf(objectGraph, shelf, displayLimit !== null && displayLimit !== void 0 ? displayLimit : shelfToken.remainingItems.length, shelfToken.isSearchLandingPage, shelfToken.isArcadePage, featuredContentId);
+ }
+}
+/**
+ * Inserts placeholder items into the given generic page shelf
+ * @param objectGraph Current object graph
+ * @param shelf The input shelf
+ * @param token The current shelf token
+ * @param featuredContentId An optional FCID
+ */
+export function insertPlaceholdersIntoGenericPageShelf(objectGraph, shelf, token, featuredContentId) {
+ token.showingPlaceholders = insertPlaceholdersIntoShelf(objectGraph, shelf, token.remainingItems.length, token.isSearchLandingPage, token.isArcadePage, featuredContentId);
+}
+/**
+ * @param objectGraph The current object graph
+ * @param shelf The shelf to insert placeholders into
+ * @param remainaingItemCount The number of items remaining to load
+ * @param isSearchLandingPage Whether the current page is a search landing page
+ * @param isArcadePage Whether the current page is an arcade page
+ * @param featuredContentId The optional FCID
+ * @returns Whether placeholders were inserted
+ */
+function insertPlaceholdersIntoShelf(objectGraph, shelf, remainaingItemCount, isSearchLandingPage = false, isArcadePage = false, featuredContentId = null) {
+ // Return if the shelf already has items do not insert placeholders
+ if (serverData.isDefinedNonNullNonEmpty(shelf.items)) {
+ return false;
+ }
+ // Return if not a supported placeholder type or grouping is search
+ if (!contentTypeSupportsPlaceholders(objectGraph, shelf.contentType) || isSearchLandingPage) {
+ return false;
+ }
+ const placeholderCount = placeholderCountForItemCount(objectGraph, remainaingItemCount, featuredContentId);
+ // Generate placeholder items
+ const placeholderItems = new Array(placeholderCount);
+ for (let i = 0; i < placeholderCount; i += 1) {
+ placeholderItems[i] = new models.Placeholder();
+ }
+ if (serverData.isNullOrEmpty(placeholderItems)) {
+ // If we didn't generate any items then don't modify the shelf and just return
+ return false;
+ }
+ const rowsPerColumn = rowsPerColumnForShelf(objectGraph, shelf);
+ if (isSome(rowsPerColumn)) {
+ shelf.rowsPerColumn = rowsPerColumn;
+ }
+ // Swap the real content type to the placeholder type and assign the items
+ shelf.placeholderContentType = shelf.contentType;
+ shelf.contentType = "placeholder";
+ shelf.items = placeholderItems;
+ if (serverData.isDefinedNonNullNonEmpty(isArcadePage)) {
+ shelf.presentationHints = { ...shelf.presentationHints, isAppleArcadeContext: isArcadePage };
+ }
+ // If we're showing placeholders we never want the shelf to be hidden
+ shelf.isHidden = false;
+ return true;
+}
+export function isPlaceholderShelf(shelf) {
+ return shelf.contentType === "placeholder";
+}
+/**
+ * The preferred number of rows to use per column, for a given shelf
+ * @param objectGraph Current object graph
+ * @param shelf The shelf we're calculating for
+ * @returns The number of rows to use
+ */
+function rowsPerColumnForShelf(objectGraph, shelf) {
+ if (objectGraph.client.isVision) {
+ switch (shelf.contentType) {
+ case "smallLockup":
+ return 3;
+ case "mediumLockup":
+ return 2;
+ default:
+ return 1;
+ }
+ }
+ else if (isSome(shelf.rowsPerColumn)) {
+ return shelf.rowsPerColumn;
+ }
+ else {
+ switch (shelf.contentType) {
+ case "smallLockup":
+ return 3;
+ case "mediumLockup":
+ return 2;
+ case "largeLockup":
+ return 1;
+ case "categoryBrick":
+ // Do not default for Category Brick
+ return null;
+ default:
+ return 1;
+ }
+ }
+}
+//# sourceMappingURL=placeholders.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/preview-platform.js b/node_modules/@jet-app/app-store/tmp/src/common/preview-platform.js
new file mode 100644
index 0000000..bba4d5e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/preview-platform.js
@@ -0,0 +1,87 @@
+import { unwrapOptional as unwrap } from "@jet/environment/types/optional";
+import { attributeAsArrayOrEmpty } from "../foundation/media/attributes";
+import { dataFromDataContainer } from "../foundation/media/data-structure";
+import { deviceFamiliesFromData } from "./content/device-family";
+/**
+ * Retreive the current `PreviewPlatform`, in the format used to create a {@linkcode WithPlatform} `Intent`
+ *
+ * @example
+ * makeIntentWithPlatform({
+ * ...getPlatform(objectGraph)
+ * ...otherProps
+ * });
+ */
+export function getPlatform(objectGraph) {
+ var _a;
+ return {
+ platform: (_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.previewPlatform,
+ };
+}
+/**
+ * Determine the most relevant {@linkcode PreviewPlatform} from a Media API response that
+ * contains "device families", like an `app` or `app-bundle`
+ */
+export function inferPreviewPlatformFromDeviceFamilies(objectGraph, response) {
+ const data = unwrap(dataFromDataContainer(objectGraph, response));
+ const deviceFamilies = new Set(deviceFamiliesFromData(objectGraph, data, true));
+ if (deviceFamilies.has("iphone")) {
+ return "iphone";
+ }
+ if (deviceFamilies.has("ipad")) {
+ return "ipad";
+ }
+ if (deviceFamilies.has("mac")) {
+ return "mac";
+ }
+ if (deviceFamilies.has("realityDevice")) {
+ return "vision";
+ }
+ if (deviceFamilies.has("tvos")) {
+ return "tv";
+ }
+ if (deviceFamilies.has("watch")) {
+ return "watch";
+ }
+ throw new Error("Could not infer platform from device families");
+}
+/**
+ * Determine the most relevant {@linkcode PreviewPlatform} from a Media API response that
+ * contains "editorial platforms"", like `editorial-items`
+ */
+export function inferPreviewPlatformFromEditorialPlatforms(objectGraph, container) {
+ const data = unwrap(dataFromDataContainer(objectGraph, container));
+ const editorialPlatforms = new Set(attributeAsArrayOrEmpty(data, "editorialPlatforms"));
+ if (editorialPlatforms.has("iphone")) {
+ return "iphone";
+ }
+ if (editorialPlatforms.has("ipad")) {
+ return "ipad";
+ }
+ if (editorialPlatforms.has("desktop")) {
+ return "mac";
+ }
+ if (editorialPlatforms.has("realitydevice")) {
+ return "vision";
+ }
+ if (editorialPlatforms.has("watch")) {
+ return "watch";
+ }
+ if (editorialPlatforms.has("appletv")) {
+ return "tv";
+ }
+ throw new Error("Could not infer preview platform from editorial platforms");
+}
+/**
+ * Set the `previewPlatform` query param on the {@linkcode request}, if necessary
+ *
+ * This should only be used with requests being made for the following resources:
+ * - "Editorial" (Grouping Page, Editorial Page, Today, etc)
+ * - "Search"
+ */
+export function setPreviewPlatform(objectGraph, request) {
+ var _a;
+ return ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.previewPlatform)
+ ? request.addingQuery("previewPlatform", objectGraph.activeIntent.platform).addingQuery("platform", "web")
+ : request;
+}
+//# sourceMappingURL=preview-platform.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-footer-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-footer-shelf.js
new file mode 100644
index 0000000..809b042
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-footer-shelf.js
@@ -0,0 +1,87 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as privacyHeaderShelf from "./privacy-header-shelf";
+/**
+ * Builder for the privacy footer shelf.
+ */
+export function create(objectGraph, data, pageInformation, locationTracker) {
+ return validation.context("create", () => {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ if (objectGraph.client.isWatch) {
+ return null;
+ }
+ const privacyTypes = privacyHeaderShelf.privacyTypesFromData(objectGraph, data, false, "detailPage", pageInformation, locationTracker);
+ const privacyDataNotProvided = (privacyTypes.length === 1 && privacyTypes[0].identifier === "DATA_NOT_PROVIDED") ||
+ privacyTypes.length === 0;
+ if (privacyDataNotProvided && objectGraph.client.deviceType !== "tv") {
+ return null;
+ }
+ const shelf = new models.Shelf("privacyFooter");
+ const privacyFooter = privacyFooterFromData(objectGraph, data, pageInformation, locationTracker);
+ shelf.items = [privacyFooter];
+ return shelf;
+ });
+}
+/**
+ * Creates a privacy footer object.
+ * @param data The data blob
+ */
+export function privacyFooterFromData(objectGraph, data, pageInformation, locationTracker) {
+ return validation.context("privacyFooterFromData", () => {
+ const bodyText = bodyTextFromData(objectGraph, data, pageInformation, locationTracker);
+ const actions = actionsFromData(objectGraph, data, pageInformation, locationTracker);
+ let privacyTypes = [];
+ if (objectGraph.client.isTV) {
+ privacyTypes = privacyHeaderShelf.privacyTypesFromData(objectGraph, data, false, "productPage", pageInformation, locationTracker);
+ }
+ return new models.PrivacyFooter(bodyText, actions, privacyTypes.length);
+ });
+}
+/**
+ * Creates the main body text for the footer.
+ * @param data The data blob
+ */
+function bodyTextFromData(objectGraph, data, pageInformation, locationTracker) {
+ let text;
+ const learnMoreLinkText = objectGraph.loc.string("PRODUCT_PRIVACY_LEARN_MORE_LINK");
+ const linkedSubstrings = {};
+ const learnMoreAction = privacyHeaderShelf.createLearnMoreAction(objectGraph, pageInformation, locationTracker);
+ if (objectGraph.client.isTV || serverData.isNull(learnMoreAction)) {
+ text = objectGraph.loc.string("PRODUCT_PRIVACY_LEARN_MORE_NO_LINK");
+ }
+ else {
+ text = objectGraph.loc.string("PRODUCT_PRIVACY_LEARN_MORE_TEMPLATE");
+ text = text.replace("{learnMoreLink}", learnMoreLinkText);
+ if (serverData.isNull(learnMoreAction)) {
+ text = objectGraph.loc.string("PRODUCT_PRIVACY_LEARN_MORE_NO_LINK");
+ }
+ else {
+ linkedSubstrings[learnMoreLinkText] = learnMoreAction;
+ }
+ }
+ const textType = "text/plain";
+ const styledText = new models.StyledText(text, textType);
+ return new models.LinkableText(styledText, linkedSubstrings);
+}
+/**
+ * Creates the actions for displaying in the privacy footer.
+ */
+function actionsFromData(objectGraph, data, pageInformation, locationTracker) {
+ if (objectGraph.client.deviceType !== "tv") {
+ return [];
+ }
+ const actions = [];
+ const learnMoreAction = privacyHeaderShelf.createLearnMoreAction(objectGraph, pageInformation, locationTracker);
+ if (serverData.isDefinedNonNull(learnMoreAction)) {
+ actions.push(learnMoreAction);
+ }
+ const privacyDefinitionsAction = privacyHeaderShelf.createPrivacyDefinitionsAction(objectGraph, pageInformation, locationTracker);
+ if (serverData.isDefinedNonNull(privacyDefinitionsAction)) {
+ actions.push(privacyDefinitionsAction);
+ }
+ return actions;
+}
+//# sourceMappingURL=privacy-footer-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-header-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-header-shelf.js
new file mode 100644
index 0000000..b911d94
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-header-shelf.js
@@ -0,0 +1,427 @@
+import * as validation from "@jet/environment/json/validation";
+import { isNothing } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaRelationships from "../../foundation/media/relationships";
+import { Path, Protocol } from "../../foundation/network/url-constants";
+import * as urls from "../../foundation/network/urls";
+import * as constants from "../../foundation/util/constants";
+import * as contentAttributes from "../content/attributes";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as linksShelf from "../product-page/shelves/links-shelf";
+import * as privacyTypesShelf from "./privacy-types-shelf";
+import { makeRoutableArticlePageCanonicalUrl } from "../today/routable-article-page-url-utils";
+import { makeRoutableArticlePageIntent } from "../../api/intents/routable-article-page-intent";
+import { getPlatform } from "../preview-platform";
+import { getLocale } from "../locale";
+/**
+ * Builder for the privacy header shelf. This shelf is currently used
+ * on both the product page, and the privacy detail page.
+ */
+export function create(objectGraph, data, pageInformation, locationTracker) {
+ return validation.context("privacyShelf", () => {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ const shelf = new models.Shelf("privacyHeader");
+ shelf.title = objectGraph.loc.string("PRODUCT_PRIVACY_TITLE");
+ const privacyHeader = privacyHeaderFromData(objectGraph, data, false, false, pageInformation, locationTracker);
+ shelf.items = [privacyHeader];
+ if (objectGraph.client.deviceType !== "watch" && objectGraph.client.deviceType !== "tv") {
+ shelf.seeAllAction = privacyDetailActionFromData(objectGraph, data, "detailPage", pageInformation, locationTracker, null);
+ }
+ return shelf;
+ });
+}
+/**
+ * Creates a privacy header object.
+ * @param data The data blob
+ * @param isDetailHeader Whether this header is intended to be displayed on the detail page
+ * @param isDetailData Whether the included data is for the detail page
+ */
+export function privacyHeaderFromData(objectGraph, data, isDetailHeader, isDetailData, pageInformation, locationTracker) {
+ return validation.context("createPrivacyHeaderFromData", () => {
+ const bodyText = bodyTextFromData(objectGraph, data, isDetailHeader, isDetailData, pageInformation, locationTracker);
+ let seeDetailsAction;
+ let privacyPolicyAction;
+ const privacyDefinitionsText = privacyDefinitionsTextFromData(objectGraph, data, isDetailHeader, pageInformation, locationTracker);
+ let privacyDefinitionsAction;
+ let learnMoreText;
+ let learnMoreAction;
+ if (objectGraph.client.isTV || objectGraph.client.isWatch) {
+ if (!isDetailHeader) {
+ const destinationPrivacyTypeStyle = privacyTypesShelf.isIntermediateDetailPageEnabled(objectGraph)
+ ? "intermediateDetailPage"
+ : "detailPage";
+ seeDetailsAction = privacyDetailActionFromData(objectGraph, data, destinationPrivacyTypeStyle, pageInformation, locationTracker, null);
+ }
+ privacyPolicyAction = privacyPolicyActionFromData(objectGraph, data, pageInformation, locationTracker);
+ }
+ if (isDetailHeader) {
+ if (objectGraph.client.isWatch || objectGraph.client.isTV) {
+ privacyDefinitionsAction = createPrivacyDefinitionsAction(objectGraph, pageInformation, locationTracker);
+ }
+ learnMoreText = learnMoreTextFromData(objectGraph, data, isDetailHeader, pageInformation, locationTracker);
+ if (serverData.isDefinedNonNullNonEmpty(learnMoreText)) {
+ learnMoreAction = createLearnMoreAction(objectGraph, pageInformation, locationTracker);
+ }
+ }
+ const supplementaryItems = [];
+ if (serverData.isDefinedNonNull(privacyDefinitionsText)) {
+ const supplementaryItem = new models.PrivacyHeaderSupplementaryItem(privacyDefinitionsText, privacyDefinitionsAction);
+ supplementaryItems.push(supplementaryItem);
+ }
+ if (serverData.isDefinedNonNull(learnMoreText)) {
+ const supplementaryItem = new models.PrivacyHeaderSupplementaryItem(learnMoreText, learnMoreAction);
+ supplementaryItems.push(supplementaryItem);
+ }
+ let privacyTypes = [];
+ if ((objectGraph.client.isWatch || objectGraph.client.isTV) && !isDetailHeader) {
+ privacyTypes = privacyTypesFromData(objectGraph, data, isDetailData, "productPage", pageInformation, locationTracker);
+ }
+ const bodyActions = [];
+ if (objectGraph.client.isTV) {
+ if (serverData.isDefinedNonNull(seeDetailsAction)) {
+ bodyActions.push(seeDetailsAction);
+ }
+ if (serverData.isDefinedNonNull(privacyPolicyAction)) {
+ bodyActions.push(privacyPolicyAction);
+ }
+ }
+ if (objectGraph.client.isWatch) {
+ if (serverData.isDefinedNonNull(privacyPolicyAction)) {
+ bodyActions.push(privacyPolicyAction);
+ }
+ }
+ return new models.PrivacyHeader(bodyText, isDetailHeader, privacyTypes, bodyActions, supplementaryItems, seeDetailsAction);
+ });
+}
+/**
+ * Creates the main body text for the header.
+ * @param data The data blob
+ * @param isDetailHeader Whether this header is intended to be displayed on the detail page
+ * @param isDetailData Whether the included data is for the detail page
+ */
+function bodyTextFromData(objectGraph, data, isDetailHeader, isDetailData, pageInformation, locationTracker) {
+ let text;
+ let textType = "text/x-apple-as3-nqml";
+ const developer = mediaRelationships.relationshipData(objectGraph, data, "developer");
+ const isAppleOwnedDeveloperId = serverData.isDefinedNonNullNonEmpty(developer) && constants.appleOwnedDeveloperIds.indexOf(developer.id) > -1;
+ if (isDetailHeader && !isAppleOwnedDeveloperId) {
+ text = objectGraph.loc.string("PRODUCT_PRIVACY_DETAIL_HEADER_TEMPLATE");
+ }
+ else {
+ text = objectGraph.loc.string("PRODUCT_PRIVACY_HEADER_TEMPLATE");
+ }
+ const privacyTypes = privacyTypesFromData(objectGraph, data, isDetailData, "detailPage", pageInformation, locationTracker);
+ const privacyDataNotProvided = (privacyTypes.length === 1 && privacyTypes[0].identifier === "DATA_NOT_PROVIDED") || privacyTypes.length === 0;
+ if (privacyDataNotProvided) {
+ text = objectGraph.loc.string("PRODUCT_PRIVACY_HEADER_NO_DETAILS_TEMPLATE");
+ }
+ // Developer name
+ const developerName = mediaAttributes.attributeAsString(data, "artistName");
+ if (serverData.isDefinedNonNull(developerName)) {
+ text = text.replace("{developerName}", "<b>" + developerName + "</b>");
+ }
+ else {
+ // This shouldn't happen, but just in case, fallback to the text with no developer name placeholder
+ if (privacyDataNotProvided) {
+ text = objectGraph.loc.string("PRODUCT_PRIVACY_FALLBACK_HEADER_NO_DETAILS_TEMPLATE");
+ }
+ else {
+ if (isDetailHeader) {
+ text = objectGraph.loc.string("PRODUCT_PRIVACY_FALLBACK_DETAIL_HEADER_TEMPLATE");
+ }
+ else {
+ text = objectGraph.loc.string("PRODUCT_PRIVACY_FALLBACK_HEADER_TEMPLATE");
+ }
+ }
+ textType = "text/plain";
+ }
+ // Privacy policy link
+ const privacyPolicyLinkText = objectGraph.loc.string("PRODUCT_PRIVACY_SUMMARY_PRIVACY_POLICY_LINK");
+ text = text.replace("{privacyPolicyLink}", privacyPolicyLinkText);
+ const privacyPolicyAction = privacyPolicyActionFromData(objectGraph, data, pageInformation, locationTracker);
+ const linkedSubstrings = {};
+ if (serverData.isDefinedNonNull(privacyPolicyAction)) {
+ linkedSubstrings[privacyPolicyLinkText] = privacyPolicyAction;
+ }
+ // Manage choices
+ if (isDetailHeader) {
+ if (objectGraph.client.isiOS ||
+ objectGraph.client.isMac ||
+ objectGraph.client.isVision ||
+ objectGraph.client.isWeb) {
+ const managePrivacyChoicesAction = managePrivacyChoicesActionFromData(objectGraph, data, pageInformation, locationTracker);
+ if (serverData.isDefinedNonNull(managePrivacyChoicesAction)) {
+ const managePrivacyChoicesLink = objectGraph.loc.string("PRODUCT_PRIVACY_MANAGE_CHOICES_LINK");
+ text += "<br><br>";
+ text += objectGraph.loc
+ .string("PRODUCT_PRIVACY_MANAGE_CHOICES_TEMPLATE")
+ .replace("{manageChoicesLink}", managePrivacyChoicesLink);
+ managePrivacyChoicesAction.title = managePrivacyChoicesLink;
+ linkedSubstrings[managePrivacyChoicesLink] = managePrivacyChoicesAction;
+ }
+ }
+ else {
+ text += "<br><br>";
+ text += objectGraph.loc.string("PRODUCT_PRIVACY_MANAGE_CHOICES_NO_LINK");
+ }
+ }
+ const styledText = new models.StyledText(text, textType);
+ return new models.LinkableText(styledText, linkedSubstrings);
+}
+/**
+ * Creates the action for linking to the developer's privacy policy.
+ */
+function privacyPolicyActionFromData(objectGraph, data, pageInformation, locationTracker) {
+ let privacyPolicyLinkAction;
+ if (objectGraph.client.isTV) {
+ const hasPrivacyPolicy = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "hasPrivacyPolicyText");
+ if (!hasPrivacyPolicy) {
+ return null;
+ }
+ const url = linksShelf.privacyPolicyUrlFromData(objectGraph, data);
+ if (serverData.isNull(url)) {
+ return null;
+ }
+ const action = new models.FlowAction("unknown");
+ action.pageUrl = url;
+ privacyPolicyLinkAction = action;
+ }
+ else {
+ const privacyPolicyUrl = contentAttributes.contentAttributeAsString(objectGraph, data, "privacyPolicyUrl");
+ if (isNothing(privacyPolicyUrl) || serverData.isNullOrEmpty(privacyPolicyUrl)) {
+ return null;
+ }
+ privacyPolicyLinkAction = new models.ExternalUrlAction(privacyPolicyUrl, false);
+ }
+ privacyPolicyLinkAction.title = objectGraph.loc.string("PRODUCT_PRIVACY_SUMMARY_PRIVACY_POLICY_BUTTON_TITLE");
+ metricsHelpersClicks.addClickEventToAction(objectGraph, privacyPolicyLinkAction, {
+ targetType: "link",
+ id: "privacyPolicy",
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ });
+ return privacyPolicyLinkAction;
+}
+/**
+ * Creates the action for linking to the developer's manage privacy choices URL.
+ */
+function managePrivacyChoicesActionFromData(objectGraph, data, pageInformation, locationTracker) {
+ const privacyDetailsData = mediaAttributes.attributeAsDictionary(data, "privacyDetails");
+ const managePrivacyChoicesUrl = serverData.asString(privacyDetailsData, "managePrivacyChoicesUrl");
+ if (isNothing(managePrivacyChoicesUrl) || serverData.isNullOrEmpty(managePrivacyChoicesUrl)) {
+ return null;
+ }
+ const managePrivacyChoicesAction = new models.ExternalUrlAction(managePrivacyChoicesUrl, false);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, managePrivacyChoicesAction, {
+ targetType: "link",
+ id: "managePrivacyChoices",
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ });
+ return managePrivacyChoicesAction;
+}
+/**
+ * Creates the text for linking to the learn more page.
+ * @param data The data blob
+ * @param isDetailHeader Whether this header is intended to be displayed on the detail page
+ */
+function learnMoreTextFromData(objectGraph, data, isDetailHeader, pageInformation, locationTracker) {
+ if (!isDetailHeader || objectGraph.client.isTV) {
+ return null;
+ }
+ const learnMoreLink = objectGraph.loc.string("PRODUCT_PRIVACY_LEARN_MORE_LINK");
+ const learnMoreAction = createLearnMoreAction(objectGraph, pageInformation, locationTracker);
+ let text;
+ const linkedSubstrings = {};
+ if (serverData.isNull(learnMoreAction) || objectGraph.client.isWatch) {
+ text = objectGraph.loc.string("PRODUCT_PRIVACY_LEARN_MORE_NO_LINK");
+ }
+ else {
+ text = objectGraph.loc.string("PRODUCT_PRIVACY_LEARN_MORE_TEMPLATE").replace("{learnMoreLink}", learnMoreLink);
+ learnMoreAction.title = learnMoreLink;
+ linkedSubstrings[learnMoreLink] = learnMoreAction;
+ }
+ const styledText = new models.StyledText(text, "text/plain");
+ return new models.LinkableText(styledText, linkedSubstrings);
+}
+/**
+ * Creates the action for linking to the learn more URL.
+ */
+export function createLearnMoreAction(objectGraph, pageInformation, locationTracker) {
+ const editorialItemId = objectGraph.bag.appPrivacyLearnMoreEditorialItemId;
+ if (isNothing(editorialItemId) || editorialItemId.length === 0) {
+ return null;
+ }
+ const learnMoreAction = new models.FlowAction("article");
+ learnMoreAction.title = objectGraph.loc.string("PRODUCT_PRIVACY_LEARN_MORE_LINK");
+ learnMoreAction.pageUrl = `https://apps.apple.com/story/id${editorialItemId}`;
+ metricsHelpersClicks.addClickEventToAction(objectGraph, learnMoreAction, {
+ targetType: "button",
+ id: "privacyLearnMore",
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ });
+ if (objectGraph.client.isVision) {
+ learnMoreAction.presentation = "sheetPresent";
+ }
+ if (objectGraph.client.isWeb) {
+ const destination = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: editorialItemId,
+ });
+ const pageUrlString = makeRoutableArticlePageCanonicalUrl(objectGraph, destination);
+ learnMoreAction.pageUrl = pageUrlString;
+ learnMoreAction.destination = destination;
+ }
+ return learnMoreAction;
+}
+/**
+ * Creates the text for linking to the privacy definitions EI.
+ * @param data The data blob
+ * @param isDetailHeader Whether this header is intended to be displayed on the detail page
+ */
+function privacyDefinitionsTextFromData(objectGraph, data, isDetailHeader, pageInformation, locationTracker) {
+ if (!isDetailHeader) {
+ return null;
+ }
+ const privacyDefinitionsLink = objectGraph.loc.string("PRODUCT_PRIVACY_DEFINITIONS_LINK");
+ const text = objectGraph.loc
+ .string("PRODUCT_PRIVACY_DEFINITIONS_LINK_TEMPLATE")
+ .replace("{privacyDefinitionsLink}", privacyDefinitionsLink);
+ const privacyDefinitionsAction = createPrivacyDefinitionsAction(objectGraph, pageInformation, locationTracker);
+ if (serverData.isNull(privacyDefinitionsAction)) {
+ return null;
+ }
+ privacyDefinitionsAction.title = privacyDefinitionsLink;
+ const linkedSubstrings = {};
+ linkedSubstrings[privacyDefinitionsLink] = privacyDefinitionsAction;
+ const styledText = new models.StyledText(text, "text/plain");
+ return new models.LinkableText(styledText, linkedSubstrings);
+}
+/**
+ * Creates the action for linking to the privacy definitions page.
+ */
+export function createPrivacyDefinitionsAction(objectGraph, pageInformation, locationTracker) {
+ const editorialItemId = objectGraph.bag.appPrivacyDefinitionsEditorialItemId;
+ if (isNothing(editorialItemId) || editorialItemId.length === 0) {
+ return null;
+ }
+ const privacyDefinitionsAction = new models.FlowAction("article");
+ privacyDefinitionsAction.title = objectGraph.loc.string("PRODUCT_PRIVACY_DEFINITIONS_LINK");
+ privacyDefinitionsAction.pageUrl = `https://apps.apple.com/story/id${editorialItemId}`;
+ if (objectGraph.client.isWeb) {
+ const destination = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: editorialItemId,
+ });
+ const pageUrlString = makeRoutableArticlePageCanonicalUrl(objectGraph, destination);
+ privacyDefinitionsAction.pageUrl = pageUrlString;
+ privacyDefinitionsAction.destination = destination;
+ }
+ metricsHelpersClicks.addClickEventToAction(objectGraph, privacyDefinitionsAction, {
+ targetType: "button",
+ id: "privacyDefinitions",
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ });
+ return privacyDefinitionsAction;
+}
+/**
+ * Creates a list of privacy types, suitable for associating with the header.
+ * In practice, this means that the categories will only be included when necessary,
+ * to keep the data size as small as possible.
+ */
+export function privacyTypesFromData(objectGraph, data, isDetailData, destinationPrivacyTypeStyle, pageInformation, locationTracker) {
+ var _a;
+ let privacyTypes = [];
+ const privacyDataKey = isDetailData ? "privacyDetails" : "privacy";
+ const privacyData = mediaAttributes.attributeAsDictionary(data, privacyDataKey);
+ if (serverData.isDefinedNonNullNonEmpty(privacyData)) {
+ const includeCategories = objectGraph.client.deviceType !== "watch" || destinationPrivacyTypeStyle === "intermediateDetailPage";
+ privacyTypes =
+ (_a = privacyTypesShelf.privacyTypesFromData(objectGraph, privacyData, data, destinationPrivacyTypeStyle, includeCategories, pageInformation, locationTracker)) !== null && _a !== void 0 ? _a : [];
+ // If we only have a single privacy type, with no categories, force it to display in `productPage` style
+ // even on the intermediate detail page, as it has a nicer appearance for this case.
+ if (privacyTypes.length === 1 && privacyTypes[0].categories.length === 0) {
+ privacyTypes[0].style = "productPage";
+ }
+ }
+ return privacyTypes;
+}
+/**
+ * Creates an incomplete privacy detail page, sidepacking the privacy header shelf.
+ * On watchOS, when creating the `intermediateDetailPage`, we also sidepack the privacy types themselves,
+ * resulting in a complete (intermediate) detail page.
+ */
+export function privacyDetailSidepackPageFromData(objectGraph, data, destinationPrivacyTypeStyle, pageInformation, locationTracker) {
+ const shelves = [];
+ // Always sidepack the header, unless this will be the detail page for the watch,
+ // which does not display the header
+ if (objectGraph.client.deviceType !== "watch" || destinationPrivacyTypeStyle !== "detailPage") {
+ const headerShelf = new models.Shelf("privacyHeader");
+ const privacyHeader = privacyHeaderFromData(objectGraph, data, true, false, pageInformation, locationTracker);
+ headerShelf.items = [privacyHeader];
+ headerShelf.presentationHints = { isFirstShelf: true };
+ shelves.push(headerShelf);
+ }
+ if (
+ // On the watch, we want to sidepack the privacy types if coming from the product page
+ // as we already have all the information we need, and we can avoid having an incomplete page
+ (objectGraph.client.isWatch && destinationPrivacyTypeStyle === "intermediateDetailPage") ||
+ // The "detailPage" on the "web" client is presented as a modal, so we want to sidepack all
+ // of the data so it can display is immediately
+ (objectGraph.client.isWeb && destinationPrivacyTypeStyle === "detailPage")) {
+ const privacyTypes = privacyTypesFromData(objectGraph, data, objectGraph.client.isWeb, destinationPrivacyTypeStyle, pageInformation, locationTracker);
+ const shelf = new models.Shelf("privacyType");
+ if (privacyTypes.length > 0) {
+ shelf.items = privacyTypes;
+ shelves.push(shelf);
+ }
+ }
+ const page = new models.GenericPage(shelves);
+ if (objectGraph.client.deviceType !== "watch" || destinationPrivacyTypeStyle === "detailPage") {
+ page.isIncomplete = true;
+ }
+ page.title = objectGraph.loc.string("PRODUCT_PRIVACY_TITLE");
+ if (objectGraph.client.isMac) {
+ page.presentationOptions = ["prefersLargeTitle"];
+ }
+ return page;
+}
+/**
+ * Creates the action for linking to the privacy detail page.
+ */
+export function privacyDetailActionFromData(objectGraph, data, destinationPrivacyTypeStyle, pageInformation, locationTracker, scrollFocusPrivacyTypeId) {
+ if (serverData.isNull(data.id)) {
+ return null;
+ }
+ const seeDetailsAction = new models.FlowAction("privacyDetail");
+ seeDetailsAction.title = objectGraph.loc.string("ACTION_SEE_DETAILS");
+ seeDetailsAction.pageData = privacyDetailSidepackPageFromData(objectGraph, data, destinationPrivacyTypeStyle, pageInformation, locationTracker);
+ const productType = data.type === "app-bundles" ? Path.productBundle : Path.product;
+ let query;
+ if (serverData.isDefinedNonNullNonEmpty(scrollFocusPrivacyTypeId)) {
+ query = { privacyTypeId: scrollFocusPrivacyTypeId };
+ }
+ const pageUrl = urls.URL.fromComponents(Protocol.internal, null, `/${Path.privacyDetail}/${productType}/${data.id}`, query);
+ seeDetailsAction.pageUrl = pageUrl.build();
+ const seeDetailsClickOptions = {
+ targetType: "button",
+ id: "seeDetails",
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ };
+ if (serverData.isDefinedNonNull(scrollFocusPrivacyTypeId)) {
+ seeDetailsClickOptions.targetType = "privacyCard";
+ seeDetailsClickOptions.id = scrollFocusPrivacyTypeId;
+ }
+ metricsHelpersClicks.addClickEventToAction(objectGraph, seeDetailsAction, seeDetailsClickOptions);
+ return seeDetailsAction;
+}
+//# sourceMappingURL=privacy-header-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-suppression.js b/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-suppression.js
new file mode 100644
index 0000000..f43144f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-suppression.js
@@ -0,0 +1,34 @@
+import { isNothing } from "@jet/environment/types/optional";
+import * as serverData from "../../foundation/json-parsing/server-data";
+let suppressedPrivacyApps = null;
+function initialize(objectGraph) {
+ if (suppressedPrivacyApps !== null) {
+ return;
+ }
+ suppressedPrivacyApps = new Set();
+ for (const appId of objectGraph.bag.suppressedPrivacyAppIds) {
+ suppressedPrivacyApps.add(appId);
+ }
+}
+export function shouldSuppressPrivacyInformationForAdamId(objectGraph, adamId) {
+ initialize(objectGraph);
+ if (isNothing(suppressedPrivacyApps) || serverData.isNullOrEmpty(adamId)) {
+ return false;
+ }
+ return suppressedPrivacyApps.has(adamId);
+}
+export function shouldSuppressPrivacyInformationForBundleId(objectGraph, bundleId) {
+ initialize(objectGraph);
+ if (isNothing(suppressedPrivacyApps) || serverData.isNullOrEmpty(bundleId)) {
+ return false;
+ }
+ /// This handles a special case wildcard entry for macOS installers, to avoid the churn of adding new bundleIDs
+ /// for each macOS release. We specifically do not want to treat all entries in `suppressedPrivacyApps` as a regex,
+ /// as there is no expectation that any other entry will be a regex, and treating them as such may lead to false matches.
+ if (suppressedPrivacyApps.has("com.apple.InstallAssistant.*") &&
+ bundleId.startsWith("com.apple.InstallAssistant.")) {
+ return true;
+ }
+ return suppressedPrivacyApps.has(bundleId);
+}
+//# sourceMappingURL=privacy-suppression.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-types-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-types-shelf.js
new file mode 100644
index 0000000..11b58b6
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/privacy/privacy-types-shelf.js
@@ -0,0 +1,322 @@
+import * as validation from "@jet/environment/json/validation";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as contentArtwork from "../content/artwork/artwork";
+import * as content from "../content/content";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as privacyHeaderShelf from "./privacy-header-shelf";
+/**
+ * Builder for the privacy types shelf. This shelf is currently used
+ * on both the product page, and the privacy detail page.
+ */
+export function create(objectGraph, data, shelfMetrics) {
+ return validation.context("create", () => {
+ if (serverData.isNullOrEmpty(data) || objectGraph.client.isWatch) {
+ return null;
+ }
+ const privacyData = mediaAttributes.attributeAsDictionary(data, "privacy");
+ if (isNothing(privacyData) || serverData.isNullOrEmpty(privacyData)) {
+ return null;
+ }
+ const shelf = new models.Shelf("privacyType");
+ const title = objectGraph.loc.string("PRODUCT_PRIVACY_TITLE");
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "privacyCard",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ }, title);
+ const privacyTypes = privacyTypesFromData(objectGraph, privacyData, data, "productPage", true, shelfMetrics.metricsPageInformation, shelfMetrics.locationTracker);
+ if (privacyTypes === null || privacyTypes.length === 0) {
+ return null;
+ }
+ shelf.items = privacyTypes;
+ if (privacyTypes.length <= 2) {
+ shelf.presentationHints = { isLowDensity: true };
+ }
+ // We don't actually want a shelf title, but we populate it temporarily here
+ // so that impressions have the appropriate title without causing too much churn.
+ shelf.title = title;
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "appPrivacy");
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ shelf.title = null;
+ return shelf;
+ });
+}
+/**
+ * Creates a list of privacy types from the given data.
+ * @param privacyData The privacy data blob
+ * @param dataContainer The response data
+ * @param style The style that the privacy types should be created with
+ * @param includeCategories Whether the privacy categories should be included or not.
+ */
+export function privacyTypesFromData(objectGraph, privacyData, dataContainer, style, includeCategories, pageInformation, locationTracker) {
+ const types = serverData.asArrayOrEmpty(privacyData, "privacyTypes");
+ const privacyTypes = [];
+ for (const type of types) {
+ const typeData = serverData.asJSONData(type);
+ if (serverData.isDefinedNonNullNonEmpty(typeData)) {
+ const privacyType = privacyTypeFromData(objectGraph, typeData, dataContainer, style, includeCategories, pageInformation, locationTracker);
+ if (serverData.isDefinedNonNull(privacyType)) {
+ privacyTypes.push(privacyType);
+ }
+ }
+ }
+ if (privacyTypes.length === 0) {
+ const noDetailsPrivacyType = noDetailsProvidedPrivacyTypeFromDataContainer(objectGraph, dataContainer, style, pageInformation, locationTracker);
+ privacyTypes.push(noDetailsPrivacyType);
+ }
+ for (const privacyType of privacyTypes) {
+ const options = {
+ id: null,
+ kind: null,
+ softwareType: null,
+ title: privacyType.title,
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ targetType: "privacyCard",
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, privacyType, options);
+ metricsHelpersLocation.nextPosition(locationTracker);
+ }
+ return privacyTypes;
+}
+/**
+ * Creates a privacy type from the given data.
+ * @param data The privacy types data blob
+ * @param dataContainer The response data
+ * @param style The style that the privacy types should have.
+ * @param includeCategories Whether the privacy categories should be included or not.
+ */
+function privacyTypeFromData(objectGraph, data, dataContainer, style, includeCategories, pageInformation, locationTracker) {
+ const identifier = serverData.asString(data, "identifier");
+ const title = serverData.asString(data, "privacyType");
+ const detail = serverData.asString(data, "description");
+ if (isNothing(identifier) ||
+ serverData.isNullOrEmpty(identifier) ||
+ isNothing(title) ||
+ serverData.isNullOrEmpty(title) ||
+ isNothing(detail) ||
+ serverData.isNullOrEmpty(detail)) {
+ return null;
+ }
+ const artworkResourceUrl = artworkResourceUrlForPrivacyTypeIdentifier(objectGraph, identifier);
+ const artwork = artworkFromData(objectGraph, data, artworkResourceUrl);
+ const categories = includeCategories ? privacyCategoriesFromData(objectGraph, data, style) : [];
+ const purposes = privacyPurposesFromData(objectGraph, data, style);
+ const clickAction = privacyTypeClickActionFromDataContainer(objectGraph, dataContainer, identifier, style, pageInformation, locationTracker);
+ return new models.PrivacyType(identifier, title, detail, artwork, style, purposes, categories, clickAction);
+}
+/**
+ * Creates a suitable click action for a privacy type.
+ * @param dataContainer The response data
+ * @param identifier The identifier for the privacy type
+ * @param style The style that the privacy types should have.
+ */
+function privacyTypeClickActionFromDataContainer(objectGraph, dataContainer, identifier, style, pageInformation, locationTracker) {
+ let clickAction = null;
+ if (style === "productPage" || style === "intermediateDetailPage") {
+ const destinationStyle = isIntermediateDetailPageEnabled(objectGraph) && style === "productPage"
+ ? "intermediateDetailPage"
+ : "detailPage";
+ clickAction = privacyHeaderShelf.privacyDetailActionFromData(objectGraph, dataContainer, destinationStyle, pageInformation, locationTracker, identifier);
+ }
+ return clickAction;
+}
+/**
+ * Creates a privacy type object to represent the case where the developer has not yet provided any privacy details.
+ *
+ * @param dataContainer The response data
+ * @param style The style that the privacy types should have.
+ */
+function noDetailsProvidedPrivacyTypeFromDataContainer(objectGraph, dataContainer, style, pageInformation, locationTracker) {
+ const identifier = "DATA_NOT_PROVIDED";
+ const title = objectGraph.loc.string("PRODUCT_PRIVACY_NO_DETAILS_PROVIDED_TITLE");
+ const detail = objectGraph.loc.string("PRODUCT_PRIVACY_NO_DETAILS_PROVIDED_BODY");
+ const artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://exclamationmark.triangle");
+ const clickAction = privacyTypeClickActionFromDataContainer(objectGraph, dataContainer, identifier, style, pageInformation, locationTracker);
+ return new models.PrivacyType(identifier, title, detail, artwork, style, [], [], clickAction);
+}
+/**
+ * Creates a list of privacy categories from the given data.
+ * @param data The privacy data blob
+ * @param privacyTypeStyle The style that the parent privacy type style will have.
+ */
+function privacyCategoriesFromData(objectGraph, data, privacyTypeStyle) {
+ const categories = serverData.asArrayOrEmpty(data, "dataCategories");
+ const privacyCategories = [];
+ for (const category of categories) {
+ const categoryData = serverData.asJSONData(category);
+ if (serverData.isDefinedNonNullNonEmpty(categoryData)) {
+ const style = privacyTypeStyle === "productPage" ? "productPage" : "detailPage";
+ const privacyCategory = privacyCategoryFromData(objectGraph, categoryData, style);
+ if (serverData.isDefinedNonNull(privacyCategory)) {
+ privacyCategories.push(privacyCategory);
+ }
+ }
+ }
+ return privacyCategories;
+}
+/**
+ * Creates a privacy category from the given data.
+ * @param data The privacy data blob
+ * @param style The style that the privacy category should have.
+ */
+function privacyCategoryFromData(objectGraph, data, style) {
+ const identifier = serverData.asString(data, "identifier");
+ const title = serverData.asString(data, "dataCategory");
+ if (isNothing(identifier) ||
+ serverData.isNullOrEmpty(identifier) ||
+ isNothing(title) ||
+ serverData.isNullOrEmpty(title)) {
+ return null;
+ }
+ const artworkResourceUrl = artworkResourceUrlForPrivacyCategoryIdentifier(objectGraph, identifier);
+ const artwork = artworkFromData(objectGraph, data, artworkResourceUrl);
+ if (serverData.isNull(artwork)) {
+ return null;
+ }
+ let dataTypes = [];
+ if (style === "detailPage") {
+ dataTypes = serverData.asArrayOrEmpty(data, "dataTypes");
+ }
+ const privacyCategory = new models.PrivacyCategory(identifier, title, artwork, style, dataTypes);
+ if (identifier === "USAGE_DATA") {
+ privacyCategory.prefersSmallArtwork = true;
+ }
+ return privacyCategory;
+}
+/**
+ * Creates a list of privacy purposes from the given data.
+ * @param data The privacy data blob
+ * @param privacyTypeStyle The style that the parent privacy type style will have.
+ */
+function privacyPurposesFromData(objectGraph, data, privacyTypeStyle) {
+ const purposes = serverData.asArrayOrEmpty(data, "purposes");
+ const privacyPurposes = [];
+ for (const purpose of purposes) {
+ const purposeData = serverData.asJSONData(purpose);
+ if (serverData.isDefinedNonNullNonEmpty(purposeData)) {
+ const privacyPurpose = privacyPurposeFromData(objectGraph, purposeData, privacyTypeStyle);
+ if (serverData.isDefinedNonNull(privacyPurpose)) {
+ privacyPurposes.push(privacyPurpose);
+ }
+ }
+ }
+ return privacyPurposes;
+}
+/**
+ * Creates a privacy purpose from the given data.
+ * @param data The privacy data blob
+ * @param privacyTypeStyle The style that the parent privacy type style will have.
+ */
+function privacyPurposeFromData(objectGraph, data, privacyTypeStyle) {
+ const identifier = serverData.asString(data, "identifier");
+ const title = serverData.asString(data, "purpose");
+ const categories = privacyCategoriesFromData(objectGraph, data, privacyTypeStyle);
+ if (isNothing(identifier) ||
+ serverData.isNullOrEmpty(identifier) ||
+ isNothing(title) ||
+ serverData.isNullOrEmpty(title) ||
+ categories.length === 0) {
+ return null;
+ }
+ return new models.PrivacyPurpose(identifier, title, categories);
+}
+/**
+ * Creates a suitable artwork object for a privacy object, from the given data blob or artwork resource url
+ * @param data The privacy object data blob
+ * @param artworkResourceUrl An optional artwork resource url
+ */
+function artworkFromData(objectGraph, data, artworkResourceUrl) {
+ let artwork = null;
+ if (isSome(artworkResourceUrl) && (artworkResourceUrl === null || artworkResourceUrl === void 0 ? void 0 : artworkResourceUrl.length) > 0) {
+ artwork = contentArtwork.createArtworkForResource(objectGraph, artworkResourceUrl);
+ }
+ if (serverData.isNull(artwork)) {
+ const artworkData = serverData.asDictionary(data, "artwork");
+ if (serverData.isDefinedNonNull(artworkData)) {
+ artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 0 /* content.ArtworkUseCase.Default */,
+ allowingTransparency: true,
+ });
+ }
+ }
+ return artwork;
+}
+/**
+ * Maps a privacy type identifier to a known (SF symbol) artwork resource.
+ * @param privacyTypeIdentifier the given privacy type identifier.
+ */
+function artworkResourceUrlForPrivacyTypeIdentifier(objectGraph, privacyTypeIdentifier) {
+ switch (privacyTypeIdentifier) {
+ case "DATA_NOT_LINKED_TO_YOU":
+ return "resource://person.circle.slash";
+ case "DATA_USED_TO_TRACK_YOU":
+ return "systemimage://person.fill.viewfinder";
+ case "DATA_NOT_COLLECTED":
+ return "systemimage://checkmark.circle";
+ case "DATA_LINKED_TO_YOU":
+ return "systemimage://person.circle";
+ default:
+ return null;
+ }
+}
+/**
+ * Maps a privacy category identifier to a known (SF symbol) artwork resource.
+ * @param privacyCategoryIdentifier the given privacy category identifier.
+ */
+function artworkResourceUrlForPrivacyCategoryIdentifier(objectGraph, privacyCategoryIdentifier) {
+ switch (privacyCategoryIdentifier) {
+ case "FINANCIAL_INFO":
+ return "systemimage://creditcard.fill";
+ case "CONTACT_INFO":
+ return "systemimage://info.circle.fill";
+ case "OTHER":
+ return "systemimage://ellipsis.circle.fill";
+ case "SENSITIVE_INFO":
+ return "systemimage://eye.fill";
+ case "USAGE_DATA":
+ return "systemimage://chart.bar.fill";
+ case "CONTACTS":
+ return "systemimage://person.circle";
+ case "PURCHASES":
+ return "systemimage://bag.fill";
+ case "LOCATION":
+ return "systemimage://location.fill";
+ case "HEALTH_AND_FITNESS":
+ return "systemimage://heart.circle.fill";
+ case "IDENTIFIERS":
+ return "resource://person.crop.rectangle.line.fill";
+ case "USER_CONTENT":
+ return "systemimage://photo.fill.on.rectangle.fill";
+ case "BROWSING_HISTORY":
+ return "systemimage://clock.fill";
+ case "DIAGNOSTICS":
+ return "systemimage://gearshape.fill";
+ case "SEARCH_HISTORY":
+ return "systemimage://magnifyingglass.circle.fill";
+ case "BODY":
+ return "systemimage://figure";
+ case "SURROUNDING":
+ return "systemimage://arkit";
+ default:
+ return null;
+ }
+}
+/**
+ * Determines whether the intermediate detail privacy page is enabled.
+ */
+export function isIntermediateDetailPageEnabled(objectGraph) {
+ // Only watchOS has an intermediate detail page
+ if (objectGraph.client.deviceType !== "watch") {
+ return false;
+ }
+ return true;
+}
+//# sourceMappingURL=privacy-types-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/badges-common.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/badges-common.js
new file mode 100644
index 0000000..475110a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/badges-common.js
@@ -0,0 +1,123 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as content from "../../content/content";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as productPageUtil from "../product-page-util";
+export function badgeArtwork(objectGraph, artworkData, cropCode) {
+ // Filter out this badge if no artwork exists
+ if (!serverData.isDefinedNonNullNonEmpty(artworkData)) {
+ return null;
+ }
+ let cropCodeToUse = null;
+ if (objectGraph.client.isVision) {
+ cropCodeToUse = "bb";
+ }
+ else if (isNothing(cropCode)) {
+ cropCodeToUse = "sr";
+ }
+ else {
+ cropCodeToUse = cropCode;
+ }
+ // Filter out this badge if we can't create an artwork model
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ cropCode: cropCodeToUse,
+ useCase: 12 /* content.ArtworkUseCase.ProductPageBadge */,
+ allowingTransparency: true,
+ });
+ if (serverData.isNullOrEmpty(artwork)) {
+ return null;
+ }
+ return artwork;
+}
+/**
+ * Contains metadata for an action on a badge. This is useful when the configuration of a badge action is dependent on
+ * data in models that might be configured at a later time.
+ */
+export class BadgeActionMetadata {
+ setShelfId(shelfId, index) {
+ this.shelfId = shelfId;
+ this.index = index;
+ if (models.legacyProductPageKnownShelfIds.has(shelfId)) {
+ this.section = new models.ProductPageSection("shelf", shelfId);
+ }
+ }
+}
+/**
+ * Certain parameters on the actions for some badges depend on product page configuration done at a later point
+ * in the product page building. This function allows metadata configured at those various parts of the product page
+ * build process to be applied.
+ *
+ * Example: We configure the product page badges in the sidepack, but the action depends on there being an
+ * Information section, which is not part of the sidepack. We need the index within the Information section to inform
+ * scroll actions exactly which index to scroll to (iOS/macOS), or we need the Information item itself in order to
+ * display it (visionOS).
+ * @param badges The badges to apply the metadata to.
+ * @param badgeActionMetadataByKey The map of badge keys to their respective metadata, if any.
+ * @param shelfMetrics The metrics to
+ */
+export function applyActionMetadataToBadges(objectGraph, badges, badgeActionMetadataByKey, shelfMetrics) {
+ var _a, _b, _c, _d, _e;
+ let index = 0;
+ for (const badge of badges) {
+ const metadata = badgeActionMetadataByKey[badge.key];
+ if (metadata) {
+ let clickAction;
+ if (objectGraph.client.isVision) {
+ if (isSome(metadata.annotation)) {
+ // We only want to allow presenting an expanded annotation from a ribbon badge if there
+ // is useful extra information to show. This somewhat duplicates logic we have on the
+ // native side, but that logic is specific to annotations in the Information section,
+ // and has some additional checks.
+ const hasSummary = isSome(metadata.annotation.summary) || isSome((_a = metadata.annotation.items[0]) === null || _a === void 0 ? void 0 : _a.text);
+ const hasMultipleItems = metadata.annotation.items.length > 1;
+ const summaryIsDifferentToItem = isSome(metadata.annotation.summary) &&
+ ((_b = metadata.annotation.items[0]) === null || _b === void 0 ? void 0 : _b.text) !== metadata.annotation.summary;
+ const itemHasTextPairs = isSome(((_e = (_d = (_c = metadata.annotation.items[0]) === null || _c === void 0 ? void 0 : _c.textPairs) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0) > 0);
+ const hasLink = isSome(metadata.annotation.linkAction);
+ if (!hasSummary || hasMultipleItems || summaryIsDifferentToItem || itemHasTextPairs || hasLink) {
+ clickAction = metadata.annotation.expandAction;
+ }
+ }
+ }
+ else if (productPageUtil.isShelfBased(objectGraph)) {
+ clickAction = new models.ShelfBasedPageScrollAction(metadata.shelfId, null, null, null, metadata.index, true);
+ }
+ else if (isSome(metadata.section)) {
+ const action = new models.ProductPageScrollAction(metadata.section, true);
+ action.index = metadata.index;
+ clickAction = action;
+ }
+ badge.clickAction = clickAction;
+ }
+ let actionType;
+ switch (badge.key) {
+ case "contentRating":
+ case "editorsChoice":
+ case "rating":
+ case "languages":
+ case "size":
+ case "controller":
+ case "category":
+ actionType = "select";
+ break;
+ default:
+ actionType = "navigate";
+ }
+ if (badge.clickAction) {
+ metricsHelpersClicks.addClickEventToAction(objectGraph, badge.clickAction, {
+ targetType: "badge",
+ id: badge.key,
+ idType: "sequential",
+ actionType: actionType,
+ actionDetails: {
+ position: index,
+ },
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ });
+ }
+ index += 1;
+ }
+}
+//# sourceMappingURL=badges-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/badges.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/badges.js
new file mode 100644
index 0000000..2e57c73
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/badges.js
@@ -0,0 +1,153 @@
+import * as validation from "@jet/environment/json/validation";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as category from "./category-badge";
+import * as chart from "./chart-badge";
+import * as compatibility from "./compatibility-badge";
+import * as contentRating from "./content-rating-badge";
+import * as controller from "./controller-badge";
+import * as developer from "./developer-badge";
+import * as editorsChoice from "./editors-choice-badge";
+import * as friendsPlaying from "./friends-playing-badge";
+import * as highMotion from "./high-motion-badge";
+import * as language from "./language-badge";
+import * as multiplayer from "./multiplayer-badge";
+import * as preorder from "./preorder-badge";
+import * as rating from "./rating-badge";
+import * as size from "./size-badge";
+import * as spatialController from "./spatial-controller-badge";
+import * as storefrontContentRating from "./storefront-content-rating-badge";
+const preorderBadgeList = [
+ contentRating.createBadge,
+ storefrontContentRating.createBadge,
+ category.createBadge,
+ multiplayer.createBadge,
+ developer.createBadge,
+ controller.createBadge,
+ language.createBadge,
+];
+const standardBadgeList = [
+ rating.createBadge,
+ editorsChoice.createBadge,
+ chart.createBadge,
+ contentRating.createBadge,
+ storefrontContentRating.createBadge,
+];
+const ribbonBadgeList = [
+ rating.createBadge,
+ contentRating.createBadge,
+ storefrontContentRating.createBadge,
+ controller.createHighPriorityBadge,
+ friendsPlaying.createBadge,
+ chart.createBadge,
+ category.createBadge,
+ multiplayer.createBadge,
+ developer.createBadge,
+ controller.createLowPriorityBadge,
+ language.createBadge,
+ size.createBadge,
+];
+const gamesBadgeList = [
+ rating.createBadge,
+ contentRating.createBadge,
+ category.createBadge,
+ controller.createBadge,
+ developer.createBadge,
+];
+const visionRibbonBadgeList = [
+ preorder.createBadge,
+ compatibility.createBadge,
+ rating.createBadge,
+ spatialController.createRequiredBadge,
+ controller.createHighPriorityBadge,
+ spatialController.createHighPrioritySupportedBadge,
+ editorsChoice.createBadge,
+ contentRating.createBadge,
+ storefrontContentRating.createBadge,
+ highMotion.createBadge,
+ category.createBadge,
+ multiplayer.createBadge,
+ controller.createLowPriorityBadge,
+ spatialController.createLowPrioritySupportedBadge,
+ developer.createBadge,
+];
+const visionPreorderBadgeList = [
+ preorder.createBadge,
+ compatibility.createBadge,
+ editorsChoice.createBadge,
+ category.createBadge,
+ contentRating.createBadge,
+ storefrontContentRating.createBadge,
+ highMotion.createBadge,
+ multiplayer.createBadge,
+ controller.createBadge,
+ spatialController.createBadge,
+ developer.createBadge,
+];
+const tvBadgeList = [
+ rating.createBadge,
+ editorsChoice.createBadge,
+ controller.createBadge,
+ chart.createBadge,
+ contentRating.createBadge,
+ storefrontContentRating.createBadge,
+ category.createBadge,
+ multiplayer.createBadge,
+ developer.createBadge,
+];
+function createBadges(objectGraph, badgeList, data, embeddedInRibbon, excludeCategoryIfChartExists, metricsContext) {
+ const badges = [];
+ for (const f of badgeList) {
+ const badge = f(objectGraph, data, embeddedInRibbon, metricsContext);
+ if (badge) {
+ badges.push(badge);
+ }
+ }
+ if (excludeCategoryIfChartExists && badges.some((badge) => badge.key === "chartPosition")) {
+ // Category and chart badges are mutually exclusive, with chart taking priority
+ const categoryIndex = badges.findIndex((badge) => badge.key === "category");
+ if (categoryIndex >= 0) {
+ badges.splice(categoryIndex, 1);
+ }
+ }
+ return badges;
+}
+export function badgesFromResponse(objectGraph, data, embeddedInRibbon, metricsContext) {
+ if (!data) {
+ return [];
+ }
+ return validation.context("badgesFromLookupResponse", () => {
+ let badgeList;
+ let excludeCategoryIfChartExists = false;
+ const isPreorder = mediaAttributes.attributeAsBooleanOrFalse(data, "isPreorder");
+ if (preprocessor.GAMES_TARGET) {
+ badgeList = gamesBadgeList;
+ }
+ else if (isPreorder) {
+ if (objectGraph.client.isVision) {
+ badgeList = visionPreorderBadgeList;
+ }
+ else {
+ badgeList = preorderBadgeList;
+ }
+ }
+ else if (objectGraph.client.isTV) {
+ badgeList = tvBadgeList;
+ }
+ else if (objectGraph.client.isVision) {
+ badgeList = visionRibbonBadgeList;
+ }
+ else if (embeddedInRibbon) {
+ badgeList = ribbonBadgeList;
+ const ribbonSupportedOnPlatform = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.client.isWeb;
+ if (ribbonSupportedOnPlatform && badgeList.indexOf(editorsChoice.createBadge) === -1) {
+ badgeList.splice(1, 0, editorsChoice.createBadge);
+ }
+ excludeCategoryIfChartExists = true;
+ }
+ else {
+ badgeList = standardBadgeList;
+ }
+ return createBadges(objectGraph, badgeList, data, embeddedInRibbon, excludeCategoryIfChartExists, metricsContext);
+ });
+}
+//# sourceMappingURL=badges.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/category-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/category-badge.js
new file mode 100644
index 0000000..33d31c6
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/category-badge.js
@@ -0,0 +1,29 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import { categoryArtworkData } from "../../categories";
+import * as lockups from "../../lockups/lockups";
+import * as badgesCommon from "./badges-common";
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ const categoryName = lockups.categoryFromData(objectGraph, data);
+ if (serverData.isNullOrEmpty(categoryName)) {
+ return null;
+ }
+ const badgeContent = {};
+ const heading = objectGraph.loc.string("BADGE_CATEGORY_HEADING");
+ const style = objectGraph.client.isTV ? "annotation" : "standard";
+ const badgeType = objectGraph.client.isTV ? "custom" : "artwork";
+ const badge = new models.Badge(badgeType, "category", badgeContent, style, heading, categoryName, null, categoryName, heading, "infer");
+ const action = new models.FlowAction("page");
+ action.title = mediaAttributes.attributeAsString(data, "artistName");
+ if (embeddedInRibbon && objectGraph.client.deviceType !== "tv") {
+ const artworkData = categoryArtworkData(objectGraph, data, true);
+ // Artwork is required for artwork badges; if it's missing, hide the badge completely.
+ if (badgeType === "artwork" && serverData.isNull(artworkData)) {
+ return null;
+ }
+ badge.artwork = badgesCommon.badgeArtwork(objectGraph, artworkData, "bb");
+ }
+ return badge;
+}
+//# sourceMappingURL=category-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/chart-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/chart-badge.js
new file mode 100644
index 0000000..e70411d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/chart-badge.js
@@ -0,0 +1,37 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ const chartDataByStore = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "chartPositions");
+ if (chartDataByStore) {
+ const storeChartKey = content.badgeChartKeyForClientIdentifier(objectGraph, objectGraph.host.clientIdentifier);
+ if (storeChartKey) {
+ const chartData = serverData.asDictionary(chartDataByStore, storeChartKey);
+ if (chartData) {
+ const position = serverData.asNumber(chartData, "position");
+ const genreName = serverData.asString(chartData, "genreName");
+ const genre = serverData.asString(chartData, "genre");
+ const chart = serverData.asString(chartData, "chart");
+ const heading = objectGraph.loc.string("BADGE_CHART_POSITION_HEADING");
+ const accessibilityTitle = objectGraph.loc
+ .string("PRODUCT_ACCESSIBILITY_BADGE_CHART_POSITION_TITLE")
+ .replace("{chartPosition}", position.toString());
+ const badge = new models.Badge("chartPosition", "chartPosition", {
+ position: objectGraph.loc.decimal(position),
+ }, "standard", heading, genreName, null, accessibilityTitle, genreName, "view");
+ const chartUrl = content.chartUrlFromData(objectGraph, genre, chart);
+ // Charts are not currently supported for visionOS
+ if (isSome(chartUrl) && !objectGraph.client.isVision) {
+ const action = new models.FlowAction("topCharts");
+ action.pageUrl = chartUrl;
+ badge.clickAction = action;
+ }
+ return badge;
+ }
+ }
+ }
+ return null;
+}
+//# sourceMappingURL=chart-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/content-rating-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/content-rating-badge.js
new file mode 100644
index 0000000..83def78
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/content-rating-badge.js
@@ -0,0 +1,76 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../api/models";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as ageRatings from "../../content/age-ratings";
+import * as content from "../../content/content";
+import { artworkTemplateForBundleImage, createArtworkForResource } from "../../content/artwork/artwork";
+/**
+ * Builds a `Badge` model representing the Apple content rating. Additionally
+ * includes the Brazil age rating if user is in the BR storefront.
+ */
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ var _a;
+ return ((_a = createModernBadge(objectGraph, data, embeddedInRibbon, metricsContext)) !== null && _a !== void 0 ? _a : createLegacyBadge(objectGraph, data, embeddedInRibbon, metricsContext));
+}
+function createModernBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ const name = ageRatings.name(objectGraph, data);
+ if (isNothing(name)) {
+ return null;
+ }
+ let caption;
+ let longCaption;
+ if (preprocessor.GAMES_TARGET) {
+ caption = undefined;
+ }
+ else if (embeddedInRibbon && !objectGraph.client.isVision) {
+ if (ageRatings.hasInAppControls(objectGraph, data)) {
+ caption = objectGraph.loc.string("ProductPage.Badge.AgeRating.Caption.Ribbon.InAppControls");
+ }
+ else {
+ caption = objectGraph.loc.string("ProductPage.Badge.AgeRating.Caption.Ribbon");
+ }
+ }
+ else {
+ caption = objectGraph.loc.string("ProductPage.Badge.AgeRating.Caption");
+ }
+ const heading = preprocessor.GAMES_TARGET
+ ? undefined
+ : objectGraph.loc.string("ProductPage.Badge.AgeRating.Heading");
+ const accessibilityCaption = objectGraph.loc.string("ProductPage.Badge.AgeRating.AX.Caption");
+ const pictogramResource = ageRatings.pictogramResource(objectGraph, data);
+ const badgeType = isSome(pictogramResource) ? "artwork" : "contentRating";
+ const style = objectGraph.client.isTV ? "annotation" : "standard";
+ const badge = new models.Badge(badgeType, "contentRating", { contentRating: name }, style, heading, caption, longCaption, name, accessibilityCaption, "view");
+ if (isSome(pictogramResource)) {
+ badge.isMonochrome = false;
+ badge.artwork = createArtworkForResource(objectGraph, artworkTemplateForBundleImage(pictogramResource), 26, 26);
+ }
+ return badge;
+}
+function createLegacyBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ const contentRating = mediaAttributes.attributeAsString(data, "contentRatingsBySystem.appsApple.name");
+ if (isNothing(contentRating)) {
+ return null;
+ }
+ const badgeContent = { contentRating: contentRating };
+ const brazilRank = mediaAttributes.attributeAsNumber(data, "contentRatingsBySystem.appsBrazil.rank");
+ const contentRatingResource = content.storefrontContentRatingResourceForRank(objectGraph, brazilRank !== null && brazilRank !== void 0 ? brazilRank : undefined);
+ if (contentRatingResource) {
+ badgeContent.contentRatingResource = contentRatingResource;
+ }
+ let title;
+ if (preprocessor.GAMES_TARGET) {
+ title = undefined;
+ }
+ else if (embeddedInRibbon && !objectGraph.client.isVision) {
+ title = objectGraph.loc.string("BADGE_AGE_RATING_YEARS");
+ }
+ else {
+ title = objectGraph.loc.string("BADGE_AGE_RATING");
+ }
+ const heading = preprocessor.GAMES_TARGET ? undefined : objectGraph.loc.string("BADGE_AGE_RATING_HEADING");
+ const accessibilityCaption = objectGraph.loc.string("PRODUCT_ACCESSIBILITY_BADGE_AGE_RATING_TITLE");
+ const style = objectGraph.client.isTV ? "annotation" : "standard";
+ return new models.Badge("contentRating", "contentRating", badgeContent, style, heading, title, undefined, contentRating, accessibilityCaption, "view");
+}
+//# sourceMappingURL=content-rating-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/controller-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/controller-badge.js
new file mode 100644
index 0000000..439448e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/controller-badge.js
@@ -0,0 +1,178 @@
+import * as models from "../../../api/models";
+import * as artwork from "../../content/artwork/artwork";
+import * as productPageUtil from "../product-page-util";
+import * as gameController from "./../../content/game-controller";
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ var _a;
+ if (objectGraph.host.isTV) {
+ return createBadgeForTV(objectGraph, data, embeddedInRibbon, metricsContext);
+ }
+ else {
+ return ((_a = createHighPriorityBadge(objectGraph, data, embeddedInRibbon, metricsContext)) !== null && _a !== void 0 ? _a : createLowPriorityBadge(objectGraph, data, embeddedInRibbon, metricsContext));
+ }
+}
+/**
+ * Returns a badge that should displayed as higher priority in badge list
+ * - In iOS: returns value if badge is `CONTROLLER_RECOMMENDED`
+ * - In visionOS: returns value if badge is `CONTROLLER_REQUIRED`
+ *
+ * This is mutually exclusive with `createLowPriorityBadge`
+ */
+export function createHighPriorityBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ if (objectGraph.client.isiOS) {
+ return createRecommendedControllerBadgeForIOS(objectGraph, data, embeddedInRibbon, metricsContext);
+ }
+ else if (objectGraph.client.isVision) {
+ return createRequiredControllerBadgeForVisionOS(objectGraph, data, embeddedInRibbon, metricsContext);
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Returns a badge that should displayed as lower priority in badge list (eg: `CONTROLLER_OPTIONAL`)
+ *
+ * This is mutually exclusive with `createHighPriorityBadge`
+ */
+export function createLowPriorityBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ return createSupportedBadge(objectGraph, data, embeddedInRibbon, metricsContext);
+}
+// MARK: - Private methods
+function createBadgeForTV(objectGraph, data, embeddedInRibbon, metricsContext) {
+ if (objectGraph.host.isTV) {
+ let badge = null;
+ const controllerRequirement = gameController.controllerRequirementFromData(objectGraph, data);
+ let heading = null;
+ let caption = null;
+ switch (controllerRequirement) {
+ case "SIRI_REMOTE_OR_CONTROLLER_REQUIRED":
+ heading = objectGraph.loc.string("BADGE_SIRI_REMOTE_REQUIRED_OR_CONTROLLER_OPTIONAL_HEADING");
+ caption = objectGraph.loc.string("BADGE_MFI_SUPPORTED");
+ badge = new models.Badge("siriRemoteOrControllerRequired", "siriRemoteOrControllerRequired", {}, "standard", heading, caption, undefined, undefined, caption, "view");
+ break;
+ case "SIRI_REMOTE_REQUIRED":
+ heading = objectGraph.loc.string("BADGE_SIRI_REMOTE_REQUIRED_HEADING");
+ caption = objectGraph.loc.string("BADGE_MFI_SUPPORTED");
+ badge = new models.Badge("siriRemoteRequired", "siriRemoteRequired", {}, "standard", heading, caption, undefined, undefined, caption, "view");
+ break;
+ case "CONTROLLER_REQUIRED":
+ heading = objectGraph.loc.string("BADGE_MFI_REQUIRED_HEADING");
+ caption = objectGraph.loc.string("BADGE_MFI_SUPPORTED");
+ badge = new models.Badge("controllerRequired", "controllerRequired", {}, "standard", heading, caption, undefined, undefined, caption, "view");
+ break;
+ case "CONTROLLER_OPTIONAL":
+ heading = objectGraph.loc.string("BADGE_MFI_HEADING");
+ caption = objectGraph.loc.string("BADGE_MFI_SUPPORTED");
+ badge = new models.Badge("controllerOptional", "controllerOptional", {}, "standard", heading, caption, undefined, undefined, caption, "view");
+ break;
+ case "NO_BADGE":
+ break;
+ default:
+ break;
+ }
+ if (badge) {
+ badge.clickAction = productPageUtil.isShelfBased(objectGraph)
+ ? new models.ShelfBasedPageScrollAction("capabilities")
+ : new models.ProductPageScrollAction(new models.ProductPageSection("shelf", "capabilities"));
+ }
+ return badge;
+ }
+ else {
+ return null;
+ }
+}
+/// This creates a badge only if the controller type is "CONTROLLER_RECOMMENDED", otherwise no badge will be vended.
+/// Should use for iOS only as only iOS have `CONTROLLER_RECOMMENDED`
+function createRecommendedControllerBadgeForIOS(objectGraph, data, embeddedInRibbon, metricsContext) {
+ if (gameController.isGameControllerRecommended(objectGraph, data)) {
+ let heading;
+ let caption;
+ if (!preprocessor.GAMES_TARGET) {
+ heading = objectGraph.loc.string("BADGE_MFI_HEADING");
+ caption = objectGraph.loc.string("ProductPage.Badge.GameController.Recommended");
+ }
+ const badge = new models.Badge(createBadgeType(objectGraph), "controller", {}, "standard", heading, caption, undefined, undefined, caption, "view");
+ if (preprocessor.GAMES_TARGET) {
+ badge.artwork = artwork.createArtworkForResource(objectGraph, "systemimage://gamecontroller.fill");
+ }
+ else {
+ badge.artwork = createAppStoreControllerArtwork(objectGraph);
+ }
+ badge.clickAction = productPageUtil.isShelfBased(objectGraph)
+ ? new models.ShelfBasedPageScrollAction("capabilities")
+ : new models.ProductPageScrollAction(new models.ProductPageSection("shelf", "capabilities"));
+ return badge;
+ }
+ else {
+ return null;
+ }
+}
+/// This creates a badge only if the controller type is required, otherwise no badge will be vended.
+/// Should use for visionOS only.
+function createRequiredControllerBadgeForVisionOS(objectGraph, data, embeddedInRibbon, metricsContext) {
+ if (gameController.isGameControllerRequired(objectGraph, data)) {
+ if (gameController.isSpatialControllerRequired(objectGraph, data)) {
+ return null;
+ }
+ let heading;
+ let caption;
+ if (!preprocessor.GAMES_TARGET) {
+ heading = objectGraph.loc.string("BADGE_MFI_HEADING");
+ caption = objectGraph.loc.string("ProductPage.Badge.GameController.Required.Caption.v2");
+ }
+ const badge = new models.Badge(createBadgeType(objectGraph), "controller", {}, "standard", heading, caption, undefined, undefined, caption, "view");
+ badge.artwork = artwork.createArtworkForResource(objectGraph, "systemimage://gamecontroller.fill");
+ badge.clickAction = new models.ShelfBasedPageScrollAction("capabilities");
+ return badge;
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Creates a badge if a controller is supported (but does not consider the recommended or required case).
+ *
+ * In iOS, if the feature flag `gameControllerRecommendedRolloutRate` is off, `CONTROLLER_RECOMMENDED` will be consider as supported controller.
+ */
+function createSupportedBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ if (gameController.isGameControllerSupported(objectGraph, data) &&
+ !gameController.isGameControllerRequired(objectGraph, data) &&
+ !gameController.isGameControllerRecommended(objectGraph, data) &&
+ !gameController.isSpatialControllerRequired(objectGraph, data)) {
+ if (objectGraph.client.isVision && gameController.isSpatialControllerRequired(objectGraph, data)) {
+ return null;
+ }
+ let heading;
+ let caption;
+ if (!preprocessor.GAMES_TARGET) {
+ heading = objectGraph.loc.string("BADGE_MFI_HEADING");
+ caption = objectGraph.client.isVision
+ ? objectGraph.loc.string("ProductPage.Badge.GameController.Supported.Caption.v2")
+ : objectGraph.loc.string("BADGE_MFI_SUPPORTED");
+ }
+ const badge = new models.Badge(createBadgeType(objectGraph), "controller", {}, "standard", heading, caption, undefined, undefined, caption, "view");
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ badge.artwork = artwork.createArtworkForResource(objectGraph, "systemimage://gamecontroller.fill");
+ }
+ else {
+ badge.artwork = createAppStoreControllerArtwork(objectGraph);
+ }
+ badge.clickAction = productPageUtil.isShelfBased(objectGraph)
+ ? new models.ShelfBasedPageScrollAction("capabilities")
+ : new models.ProductPageScrollAction(new models.ProductPageSection("shelf", "capabilities"));
+ return badge;
+ }
+ else {
+ return null;
+ }
+}
+function createBadgeType(objectGraph) {
+ // Be warned, when `artworkControllerBadge` is enabled (as of LuckB), the client no longer supports "controller" as a badge type
+ return objectGraph.props.enabled("artworkControllerBadge") ? "artwork" : "controller";
+}
+function createAppStoreControllerArtwork(objectGraph) {
+ const controllerIcon = artwork.createArtworkForResource(objectGraph, "systemimage://gamecontroller.fill");
+ controllerIcon.imageScale = "small";
+ return controllerIcon;
+}
+//# sourceMappingURL=controller-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/developer-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/developer-badge.js
new file mode 100644
index 0000000..8f8992d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/developer-badge.js
@@ -0,0 +1,56 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { categoryArtworkData } from "../../categories";
+import { makeDeveloperPageIntent } from "../../../api/intents/developer-page-intent";
+import { getLocale } from "../../locale";
+import * as artwork from "../../content/artwork/artwork";
+import * as content from "../../content/content";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as badgesCommon from "./badges-common";
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ const developer = mediaRelationship.relationshipData(objectGraph, data, "developer");
+ const heading = objectGraph.loc.string("BADGE_DEVELOPER_HEADING");
+ const caption = mediaAttributes.attributeAsString(data, "artistName");
+ const style = objectGraph.client.isTV ? "annotation" : "standard";
+ const badgeType = objectGraph.client.isTV ? "custom" : "artwork";
+ const badge = new models.Badge(badgeType, "developer", {}, style, heading, caption, null, caption, heading, "infer");
+ if (embeddedInRibbon && objectGraph.client.deviceType !== "tv") {
+ const artworkData = categoryArtworkData(objectGraph, developer, true);
+ // Artwork is required for artwork badges; if it's missing, default to a generic image
+ if (badgeType === "artwork" && serverData.isNull(artworkData)) {
+ if (objectGraph.client.isVision) {
+ badge.artwork = artwork.createArtworkForResource(objectGraph, "systemimage://person.fill");
+ }
+ else {
+ badge.artwork = artwork.createArtworkForResource(objectGraph, "systemimage://person.crop.square", 26, 26);
+ }
+ }
+ else {
+ badge.artwork = badgesCommon.badgeArtwork(objectGraph, artworkData);
+ }
+ }
+ const url = content.developerUrlFromDeveloperData(objectGraph, developer);
+ if (serverData.isDefinedNonNull(url)) {
+ const action = new models.FlowAction("page");
+ action.title = mediaAttributes.attributeAsString(data, "artistName");
+ action.pageUrl = url;
+ if (developer) {
+ action.destination = makeDeveloperPageIntent({
+ ...getLocale(objectGraph),
+ id: developer.id,
+ });
+ }
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, {
+ targetType: "informationRibbon",
+ id: developer.id,
+ actionType: "navigate",
+ pageInformation: metricsContext.pageInformation,
+ locationTracker: metricsContext.locationTracker,
+ });
+ badge.clickAction = action;
+ }
+ return badge;
+}
+//# sourceMappingURL=developer-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/editors-choice-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/editors-choice-badge.js
new file mode 100644
index 0000000..eaf7445
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/editors-choice-badge.js
@@ -0,0 +1,55 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import { createArtworkForResource } from "../../content/artwork/artwork";
+import { supportsVisionOSCompatibleIOSBinaryFromData } from "../../content/content";
+import * as contentAttributes from "../../content/attributes";
+import * as productPageUtil from "../product-page-util";
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ const supportsVisionOSCompatibleIOSBinary = supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ if (supportsVisionOSCompatibleIOSBinary) {
+ return null;
+ }
+ const editorialBadgeInfo = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialBadgeInfo");
+ if (editorialBadgeInfo) {
+ const badgeType = serverData.asString(editorialBadgeInfo, "editorialBadgeType");
+ if (badgeType && badgeType === "editorialPriority") {
+ if (objectGraph.client.isVision) {
+ const caption = objectGraph.loc.string("EDITORS_CHOICE_SINGLE_LINE");
+ const badge = new models.Badge("artwork", "editorsChoice", {}, "standard", null, caption, null, null, caption);
+ badge.artwork = createArtworkForResource(objectGraph, "systemimage://laurel.leading");
+ badge.trailingArtwork = createArtworkForResource(objectGraph, "systemimage://laurel.trailing");
+ badge.clickAction = new models.ShelfBasedPageScrollAction("productRatings", "notPurchasedRatingsAndReviews", "purchasedRatingsAndReviews", data.id);
+ return badge;
+ }
+ else {
+ let caption;
+ const genreNames = mediaAttributes.attributeAsArrayOrEmpty(data, "genreNames");
+ if (genreNames[0] === "Games") {
+ caption = objectGraph.loc.string("EDITORIAL_BADGE_GAMES");
+ }
+ else {
+ caption = objectGraph.loc.string("EDITORIAL_BADGE_APPS");
+ }
+ let heading;
+ if (embeddedInRibbon) {
+ heading = objectGraph.loc.string("EDITORS_CHOICE_RIBBON_HEADING");
+ }
+ else {
+ heading = objectGraph.loc.string("EDITORS_CHOICE_SINGLE_LINE");
+ }
+ const accessibilityTitle = objectGraph.loc.string("PRODUCT_ACCESSIBILITY_BADGE_EDITORS_CHOICE");
+ const badge = new models.Badge("editorsChoice", "editorsChoice", {}, "standard", heading, caption, null, accessibilityTitle, caption);
+ if (productPageUtil.isShelfBased(objectGraph)) {
+ badge.clickAction = new models.ShelfBasedPageScrollAction(objectGraph.client.isiOS ? "editorsChoice" : "productRatings");
+ }
+ else {
+ badge.clickAction = new models.ProductPageScrollAction(new models.ProductPageSection("shelf", "reviews"));
+ }
+ return badge;
+ }
+ }
+ }
+ return null;
+}
+//# sourceMappingURL=editors-choice-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/friends-playing-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/friends-playing-badge.js
new file mode 100644
index 0000000..fbb660a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/friends-playing-badge.js
@@ -0,0 +1,23 @@
+import * as models from "../../../api/models";
+import * as contentAttributes from "../../content/attributes";
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ if (objectGraph.client.isTV || objectGraph.client.isVision) {
+ return null;
+ }
+ const isGameCenterEnabled = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isGameCenterEnabled");
+ if (!isGameCenterEnabled) {
+ return null;
+ }
+ const heading = objectGraph.loc.string("BADGE_FRIENDS_HEADING", "Friends");
+ const caption = "";
+ const style = "standard";
+ const badgeType = "friendsPlaying";
+ const badgeKey = "friendsPlaying";
+ const badgeContent = {
+ maxNumberOfAvatarsToDisplay: 3,
+ maxNumberOfFriendsBeforeTruncation: 99,
+ adamId: data.id,
+ };
+ return new models.Badge(badgeType, badgeKey, badgeContent, style, heading, caption, null, caption, heading, "infer");
+}
+//# sourceMappingURL=friends-playing-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/language-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/language-badge.js
new file mode 100644
index 0000000..a9e65ef
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/language-badge.js
@@ -0,0 +1,27 @@
+import * as models from "../../../api/models";
+import * as contentAttributes from "../../content/attributes";
+import { primaryLanguageLocaleFromLocales } from "../../content/content";
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ const supportedLocales = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, data, "supportedLocales");
+ const primaryLocale = primaryLanguageLocaleFromLocales(objectGraph, supportedLocales);
+ if (!primaryLocale) {
+ return null;
+ }
+ const badgeContent = {
+ paragraphText: primaryLocale.tag,
+ };
+ let caption;
+ const additionalLanguagesCount = supportedLocales.length - 1;
+ if (additionalLanguagesCount > 0) {
+ caption = objectGraph.loc
+ .stringWithCount("ProductPage.Badge.Language.Caption", additionalLanguagesCount)
+ .replace("{number_other_languages}", objectGraph.loc.formattedCount(additionalLanguagesCount));
+ }
+ else {
+ caption = primaryLocale.name;
+ }
+ const heading = objectGraph.loc.string("BADGE_LANGUAGE_HEADING");
+ const badge = new models.Badge("paragraph", "languages", badgeContent, "standard", heading, caption, null, null, caption, "view");
+ return badge;
+}
+//# sourceMappingURL=language-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/multiplayer-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/multiplayer-badge.js
new file mode 100644
index 0000000..cbf28f5
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/multiplayer-badge.js
@@ -0,0 +1,63 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as contentAttributes from "../../content/attributes";
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ if (objectGraph.host.isWatch) {
+ return null;
+ }
+ const minPlayers = contentAttributes.contentAttributeAsNumber(objectGraph, data, "minPlayers");
+ const maxPlayers = contentAttributes.contentAttributeAsNumber(objectGraph, data, "maxPlayers");
+ if (!serverData.isDefinedNonNull(minPlayers) || !serverData.isDefinedNonNull(maxPlayers)) {
+ return null;
+ }
+ // This can happen if the data is empty or corrupt.
+ if (minPlayers <= 0 || maxPlayers <= 0 || minPlayers > maxPlayers) {
+ return null;
+ }
+ const heading = objectGraph.loc.string("BADGE_PLAYERS_HEADING");
+ const caption = multiplayerBadgeCaptionText(objectGraph, minPlayers, maxPlayers, false);
+ const accessibilityHeading = multiplayerBadgeCaptionText(objectGraph, minPlayers, maxPlayers, true);
+ const accessibilityCaption = objectGraph.loc.string("BADGE_PLAYERS_HEADING");
+ const badgeContent = {
+ paragraphText: multiplayerBadgeParagraphText(objectGraph, minPlayers, maxPlayers),
+ };
+ const style = objectGraph.client.isTV ? "annotation" : "standard";
+ return new models.Badge("paragraph", "multiplayer", badgeContent, style, heading, caption, null, accessibilityHeading, accessibilityCaption, "view");
+}
+export function multiplayerBadgeParagraphText(objectGraph, minPlayers, maxPlayers) {
+ const maxPlayersToShow = 32;
+ if (minPlayers === maxPlayers) {
+ if (minPlayers <= maxPlayersToShow) {
+ return objectGraph.loc.formattedCount(minPlayers);
+ }
+ return objectGraph.loc
+ .string("BADGE_PLAYERS_CAPTION_MULTIPLAYER_INDEFINITE_MIN_AND_MAX_LIMIT")
+ .replace("{minPlayers}", objectGraph.loc.formattedCount(maxPlayersToShow));
+ }
+ if (maxPlayers <= maxPlayersToShow) {
+ // This game can be single player, but also have up to 32 players
+ return objectGraph.loc
+ .string("BADGE_PLAYERS_CAPTION_MULTIPLAYER_DEFINITE_LIMIT")
+ .replace("{minPlayers}", objectGraph.loc.formattedCount(minPlayers))
+ .replace("{maxPlayers}", objectGraph.loc.formattedCount(maxPlayers));
+ }
+ // This game can be single player, but also have up to 32 players
+ return objectGraph.loc
+ .string("BADGE_PLAYERS_CAPTION_MULTIPLAYER_INDEFINITE_MAX_LIMIT")
+ .replace("{minPlayers}", objectGraph.loc.formattedCount(minPlayers))
+ .replace("{maxPlayers}", objectGraph.loc.formattedCount(maxPlayersToShow));
+}
+export function multiplayerBadgeCaptionText(objectGraph, minPlayers, maxPlayers, isAccessibilityCaption) {
+ if (minPlayers === 1 && maxPlayers === 1) {
+ return objectGraph.client.isVision
+ ? objectGraph.loc.string("BADGE_PLAYERS_CAPTION_PLAYER")
+ : objectGraph.loc.string("BADGE_PLAYERS_CAPTION_SINGLE");
+ }
+ if (minPlayers === 1 || isAccessibilityCaption) {
+ return objectGraph.client.isVision
+ ? objectGraph.loc.string("BADGE_PLAYERS_CAPTION_PLAYERS")
+ : objectGraph.loc.string("BADGE_PLAYERS_CAPTION_MULTIPLAYER");
+ }
+ return objectGraph.loc.string("BADGE_PLAYERS_CAPTION_REQUIRED");
+}
+//# sourceMappingURL=multiplayer-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/rating-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/rating-badge.js
new file mode 100644
index 0000000..b598407
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/rating-badge.js
@@ -0,0 +1,72 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as productPageUtil from "../product-page-util";
+import * as reviews from "../reviews";
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ // Only show this badge for the ribbon if:
+ // - the property "ribbonReviewBadge" is enabled (YukonC+)
+ // - all TV clients (at the time of writing, `embeddedInRibbon` is always `false` for tv, but we have this check here incase that ever changes).
+ if (embeddedInRibbon && objectGraph.host.isWatch) {
+ return null;
+ }
+ const userRating = mediaAttributes.attributeAsDictionary(data, "userRating");
+ let badge = null;
+ const shouldSuppressReviews = reviews.shouldSuppressReviews(objectGraph, data);
+ if (!shouldSuppressReviews && userRating && userRating.value && userRating.ratingCount) {
+ const userRatingValue = serverData.asNumber(userRating, "value");
+ const roundedRatingValue = Math.round(userRatingValue * 10) / 10;
+ const ratingFormatted = objectGraph.loc.decimal(roundedRatingValue, 1);
+ const ratingCount = serverData.asNumber(userRating, "ratingCount");
+ const ratingCountFormatted = objectGraph.loc.formattedCount(ratingCount);
+ let caption;
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ caption = ratingCountFormatted;
+ }
+ else {
+ caption = objectGraph.loc
+ .stringWithCount("ProductPage.BadgeRating.Caption.ShortTemplate", ratingCount)
+ .replace("{count}", ratingCountFormatted);
+ }
+ const longCaption = objectGraph.loc
+ .stringWithCount("ProductPage.BadgeRating.Caption.LongTemplate", ratingCount)
+ .replace("{count}", ratingCountFormatted)
+ .replace("{rating}", ratingFormatted);
+ const accessibilityTitle = objectGraph.loc
+ .string("PRODUCT_ACCESSIBILITY_BADGE_STAR_RATING")
+ .replace("{starCount}", ratingFormatted);
+ badge = new models.Badge("rating", "rating", {
+ rating: roundedRatingValue,
+ ratingFormatted: ratingFormatted,
+ }, "standard", caption, caption, longCaption, accessibilityTitle, caption);
+ }
+ else {
+ // Prevent this item from appearing in the ribbon if there are not enough ratings.
+ if (embeddedInRibbon) {
+ return null;
+ }
+ const noRatingsCaption = objectGraph.loc.string("BADGE_NO_RATINGS_CAPTION");
+ if (shouldSuppressReviews && noRatingsCaption !== "BADGE_NO_RATINGS_CAPTION") {
+ badge = new models.Badge("rating", "rating", {}, "standard", null, noRatingsCaption, null, noRatingsCaption);
+ }
+ else if (!shouldSuppressReviews) {
+ const caption = objectGraph.loc.string("BADGE_NOT_ENOUGH_RATINGS_CAPTION");
+ badge = new models.Badge("rating", "rating", {}, "standard", null, caption, null, caption);
+ }
+ }
+ if (badge) {
+ let shelfBasedPageScrollAction;
+ if (objectGraph.client.isVision) {
+ shelfBasedPageScrollAction = new models.ShelfBasedPageScrollAction("productRatings", "notPurchasedRatingsAndReviews", "purchasedRatingsAndReviews", data.id);
+ }
+ else {
+ shelfBasedPageScrollAction = new models.ShelfBasedPageScrollAction("productRatings");
+ }
+ badge.clickAction = productPageUtil.isShelfBased(objectGraph)
+ ? shelfBasedPageScrollAction
+ : new models.ProductPageScrollAction(new models.ProductPageSection("shelf", "reviews"));
+ return badge;
+ }
+ return null;
+}
+//# sourceMappingURL=rating-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/size-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/size-badge.js
new file mode 100644
index 0000000..20a7d6b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/size-badge.js
@@ -0,0 +1,25 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as content from "../../content/content";
+import * as sad from "../../content/sad";
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ // rdar://63384336 (Size should not appear in SAD apps)
+ if (sad.systemApps(objectGraph).isSystemAppFromData(data)) {
+ return null;
+ }
+ const combinedFileSize = content.combinedFileSizeFromData(objectGraph, data);
+ if (serverData.isNull(combinedFileSize) || serverData.isNull(combinedFileSize.fileSizeByDevice)) {
+ return null;
+ }
+ const fileSizeAndUnit = content.fileSizeAndUnitFromCombinedFileSize(objectGraph, combinedFileSize);
+ if (!fileSizeAndUnit) {
+ return null;
+ }
+ const badgeContent = {
+ paragraphText: fileSizeAndUnit.size,
+ };
+ const caption = fileSizeAndUnit.unit;
+ const heading = objectGraph.loc.string("BADGE_SIZE_HEADING");
+ return new models.Badge("paragraph", "size", badgeContent, "standard", heading, caption, null, null, caption, "view");
+}
+//# sourceMappingURL=size-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/storefront-content-rating-badge.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/storefront-content-rating-badge.js
new file mode 100644
index 0000000..7567ac2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/badges/storefront-content-rating-badge.js
@@ -0,0 +1,75 @@
+import { isNothing, isSome } from "@jet/environment";
+import { Badge } from "../../../api/models";
+import { storefrontContentRatingNameForRank, storefrontContentRatingResourceForRank, } from "../../../common/content/content";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import { createArtworkForResource } from "../../content/artwork/artwork";
+/**
+ * Creates a storefront-specific content badge for storefronts requiring one.
+ */
+export function createBadge(objectGraph, data, embeddedInRibbon, metricsContext) {
+ if (isSome(mediaAttributes.attributeAsDictionary(data, "ageRating"))) {
+ // rdar://148625156 (Age Ratings & Assurance: iTMS11 shows double Age Rating badges in ribbon for 10791449607)
+ // This is a temporary crutch while iTMS11 is sometimes returning both
+ // the old and new age rating models. Sometime after Luck, this whole
+ // badge can be removed.
+ return null;
+ }
+ // Australia
+ const auContentRank = mediaAttributes.attributeAsNumber(data, "contentRatingsBySystem.appsAustralia.rank");
+ const auContentRating = mediaAttributes.attributeAsString(data, "contentRatingsBySystem.appsAustralia.name");
+ if (isSome(auContentRank) && isSome(auContentRating)) {
+ return createStorefrontContentRatingBadge(objectGraph, embeddedInRibbon, "AU", auContentRank, auContentRating, false);
+ }
+ // France
+ const frContentRank = mediaAttributes.attributeAsNumber(data, "contentRatingsBySystem.appsFrance.rank");
+ const frContentRating = mediaAttributes.attributeAsString(data, "contentRatingsBySystem.appsFrance.name");
+ if (isSome(frContentRank) && isSome(frContentRating)) {
+ return createStorefrontContentRatingBadge(objectGraph, embeddedInRibbon, "FR", frContentRank, frContentRating, false);
+ }
+ // Korea
+ const krContentRank = mediaAttributes.attributeAsNumber(data, "contentRatingsBySystem.appsKorea.rank");
+ const krContentRating = mediaAttributes.attributeAsString(data, "contentRatingsBySystem.appsKorea.name");
+ if (isSome(krContentRank) && isSome(krContentRating)) {
+ return createStorefrontContentRatingBadge(objectGraph, embeddedInRibbon, "KR", krContentRank, krContentRating, true);
+ }
+ return null;
+}
+function createStorefrontContentRatingBadge(objectGraph, embeddedInRibbon, storefrontCode, storefrontContentRank, storefrontContentRating, needsFullColor) {
+ const pictogramName = storefrontContentRatingNameForRank(objectGraph, storefrontContentRank);
+ const pictogramResource = storefrontContentRatingResourceForRank(objectGraph, storefrontContentRank);
+ if (isNothing(pictogramResource) || isNothing(pictogramName)) {
+ // Return early if there is no artwork to display for the badge. This
+ // is the case for the Korea 19+ ranking, or when `rank` is invalid.
+ return null;
+ }
+ const type = "artwork";
+ const key = "storefrontContentRating";
+ const content = { contentRating: pictogramName };
+ const style = objectGraph.client.isTV ? "annotation" : "standard";
+ const heading = objectGraph.loc.string(`ProductPage.Badge.AgeRating.${storefrontCode}.Heading`);
+ let caption;
+ if (embeddedInRibbon && !objectGraph.client.isVision) {
+ caption = objectGraph.loc.string("BADGE_AGE_RATING_YEARS");
+ }
+ else if (objectGraph.client.isVision) {
+ caption = objectGraph.loc.string(`ProductPage.Badge.AgeRating.${storefrontCode}.Heading`);
+ }
+ else {
+ caption = objectGraph.loc.string("BADGE_AGE_RATING");
+ }
+ let axTitle;
+ if (objectGraph.client.isTV) {
+ // We just want to show the Korea pictogram on TV with nothing after it.
+ axTitle = undefined;
+ }
+ else {
+ axTitle = storefrontContentRating;
+ }
+ const axCaption = objectGraph.loc.string(`ProductPage.Badge.AgeRating.${storefrontCode}.AX.Caption`);
+ const badge = new Badge(type, key, content, style, heading, caption, undefined, axTitle, axCaption, "view");
+ // Unlike most badges, the Korea pictogram artwork needs to render in full color.
+ badge.isMonochrome = !needsFullColor;
+ badge.artwork = createArtworkForResource(objectGraph, `resource://${pictogramResource}`, 26, 26);
+ return badge;
+}
+//# sourceMappingURL=storefront-content-rating-badge.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/banner.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/banner.js
new file mode 100644
index 0000000..3fd9547
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/banner.js
@@ -0,0 +1,460 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaPlatformAttributes from "../../foundation/media/platform-attributes";
+import * as color from "../../foundation/util/color-util";
+import * as contentDeviceFamily from "../content/device-family";
+import * as client from "../../foundation/wrappers/client";
+import * as contentArtwork from "../content/artwork/artwork";
+import * as contentAttributes from "../content/attributes";
+import * as content from "../content/content";
+import * as filtering from "../filtering";
+import * as lockups from "../lockups/lockups";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import { externalPurchasesLearnMoreAction, externalPurchasesPlacementIsEnabled, hasExternalPurchasesForData, } from "../offers/external-purchases";
+import * as productPageUtil from "../product-page/product-page-util";
+import { createExpandActionForAnnotation } from "./shelves/annotations/annotations";
+import * as compatibilityAnnotation from "./shelves/annotations/compatibility-annotation";
+import { appPlatformTitle } from "../../gameservicesui/src/common/media-preview-platform";
+import { hasRemoteDownloadIdentifiers, supportsVisionPlatformForVisionCompanion } from "../offers/offers";
+/**
+ * Create a product page banner.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @param bannerContext A collection of any other variables used when creating this banner.
+ * @returns A banner.
+ */
+export function create(objectGraph, data, bannerContext) {
+ let message = null;
+ let focusedMessage = null;
+ let action = null;
+ let fullProductAction = null;
+ let leadingArtwork = null;
+ let leadingArtworkTintColor = null;
+ let includeBackgroundBorder = false;
+ const bannerKind = null;
+ // 32-bit only app (disables buy button)
+ // `requires32bit` is used only for macOS apps, which is why we have two similar flags here
+ const is32bitOnly = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "is32bitOnly");
+ const requires32bit = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "requires32bit");
+ if (is32bitOnly || requires32bit) {
+ if (objectGraph.client.isMac) {
+ message = objectGraph.loc.string("ProductPage.Banner.UpdateRequired.macOS");
+ }
+ else if (objectGraph.client.isVision) {
+ message = objectGraph.loc.string("ProductPage.Banner.UpdateRequired.Vision");
+ }
+ else {
+ message = objectGraph.loc.string("ProductPage.Banner.UpdateRequired.iOS");
+ }
+ bannerContext.offerButtonShouldBeDisabled = true;
+ }
+ // Is SAD watch-only app
+ if (message === null && filtering.shouldFilter(objectGraph, data, 1024 /* filtering.Filter.SADWatchApps */)) {
+ message = objectGraph.loc.string("OFFER_WATCH_ONLY_BANNER");
+ bannerContext.offerButtonShouldBeDisabled = true;
+ }
+ // Required capabilities mismatch
+ if (message === null && !lockups.deviceHasCapabilitiesFromData(objectGraph, data)) {
+ message = objectGraph.loc.string("NOT_COMPATIBLE_BANNER");
+ if (preprocessor.GAMES_TARGET) {
+ message = objectGraph.loc.string("GameDetails.Page.Banner.NotCompatible.Message");
+ }
+ else if (objectGraph.client.isTV) {
+ // We don't support product page scrolling for this particular banner on TV
+ message = objectGraph.loc.string("NOT_COMPATIBLE_BANNER_NO_LINK");
+ }
+ else if (objectGraph.client.isVision) {
+ // For visionOS, we show a sheet rather than scrolling
+ const annotation = compatibilityAnnotation.createAnnotation(objectGraph, data, false, false, false, null);
+ if (isSome(annotation)) {
+ action = createExpandActionForAnnotation(objectGraph, annotation);
+ if (isSome(action)) {
+ action.title = objectGraph.loc.string("NOT_COMPATIBLE_BANNER_LINK_TITLE");
+ message = objectGraph.loc
+ .string("NOT_COMPATIBLE_BANNER_TEMPLATE")
+ .replace("{linkTitle}", action.title);
+ }
+ }
+ bannerContext.offerButtonShouldBeDisabled = true;
+ }
+ else if (objectGraph.client.isiOS || objectGraph.client.isMac) {
+ // For iOS / macOS, add an action that allows users to scroll to the Compatibility item
+ if (productPageUtil.isShelfBased(objectGraph)) {
+ const scrollAction = new models.ShelfBasedPageScrollAction("information");
+ scrollAction.title = objectGraph.loc.string("NOT_COMPATIBLE_BANNER_LINK_TITLE");
+ scrollAction.indexId = "compatibilityAnnotation";
+ // Here we use the `fullProductAction` action rather than just `action`,
+ // as our `ProductPageScrollAction` will only be valid once the full product page has loaded
+ fullProductAction = scrollAction;
+ }
+ else {
+ const informationSection = new models.ProductPageSection("shelf", "information");
+ const scrollAction = new models.ProductPageScrollAction(informationSection);
+ scrollAction.title = objectGraph.loc.string("NOT_COMPATIBLE_BANNER_LINK_TITLE");
+ scrollAction.indexId = "compatibilityAnnotation";
+ // Here we use the `fullProductAction` action rather than just `action`,
+ // as our `ProductPageScrollAction` will only be valid once the full product page has loaded
+ fullProductAction = scrollAction;
+ }
+ message = objectGraph.loc
+ .string("NOT_COMPATIBLE_BANNER_TEMPLATE")
+ .replace("{linkTitle}", fullProductAction.title);
+ bannerContext.offerButtonShouldBeDisabled = true;
+ }
+ }
+ // Filtered system & legacy apps
+ if (message === null &&
+ filtering.shouldFilter(objectGraph, data, 4 /* filtering.Filter.UnsupportedSystemDeletableApps */ | 32 /* filtering.Filter.LegacyApps */)) {
+ message = objectGraph.loc.string("UNSUPPORTED_CAPABILITIES");
+ bannerContext.offerButtonShouldBeDisabled = true;
+ }
+ // App is supported on this OS version
+ if (filtering.shouldFilter(objectGraph, data, 512 /* filtering.Filter.MinimumOSRequirement */)) {
+ const minOSVersion = content.minimumOSVersionFromData(objectGraph, data, objectGraph.appleSilicon.isSupportEnabled);
+ switch (objectGraph.client.deviceType) {
+ case "mac":
+ message = objectGraph.loc.string("UNSUPPORTED_MACOS_VERSION").replace("{osVersion}", minOSVersion);
+ break;
+ case "phone":
+ case "pad":
+ message = objectGraph.loc.string("UNSUPPORTED_IOS_VERSION").replace("{osVersion}", minOSVersion);
+ break;
+ case "tv":
+ message = objectGraph.loc.string("UNSUPPORTED_TVOS_VERSION").replace("{osVersion}", minOSVersion);
+ break;
+ case "watch":
+ const minWatchOSVersion = contentAttributes.contentAttributeAsString(objectGraph, data, "minimumWatchOSVersion");
+ message = objectGraph.loc
+ .string("UNSUPPORTED_WATCHOS_VERSION")
+ .replace("{osVersion}", minWatchOSVersion);
+ break;
+ case "vision":
+ message = objectGraph.loc.string("UNSUPPORTED_VISIONOS_VERSION").replace("{osVersion}", minOSVersion);
+ break;
+ default:
+ message = objectGraph.loc.string("UNSUPPORTED_CAPABILITIES");
+ break;
+ }
+ // If the device is a phone but the app is watch-only, use the unsupported watch message.
+ if (objectGraph.client.isPhone &&
+ contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS")) {
+ const minWatchOSVersion = contentAttributes.contentAttributeAsString(objectGraph, data, "minimumWatchOSVersion");
+ message = objectGraph.loc.string("UNSUPPORTED_WATCHOS_VERSION").replace("{osVersion}", minWatchOSVersion);
+ }
+ }
+ // App is supported on companion's OS version
+ if (objectGraph.client.isWatch &&
+ filtering.shouldFilter(objectGraph, data, 2048 /* filtering.Filter.MinimumCompanionOSRequirement */)) {
+ const minOSVersion = mediaPlatformAttributes.platformAttributeAsString(data, contentAttributes.bestAttributePlatformFromData(objectGraph, data), "minimumOSVersion");
+ message = objectGraph.loc.string("UNSUPPORTED_IOS_VERSION").replace("{osVersion}", minOSVersion);
+ }
+ // If the current device is not supported
+ if (filtering.shouldFilter(objectGraph, data, 128 /* filtering.Filter.UnsupportedPlatform */)) {
+ bannerContext.offerButtonShouldBeDisabled = true;
+ message = objectGraph.loc.string("NOT_COMPATIBLE_BANNER");
+ if (preprocessor.GAMES_TARGET) {
+ const supportedPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ if (supportedPlatforms.length === 1) {
+ const supportedPlatform = supportedPlatforms[0];
+ const platformTitle = appPlatformTitle(objectGraph, supportedPlatform);
+ message = objectGraph.localizer.string("GameDetails.Page.Banner.OnlyAvailable.Message", {
+ platform: platformTitle,
+ });
+ const systemImageName = content.systemImageNameForAppPlatform(supportedPlatform);
+ leadingArtwork = contentArtwork.createArtworkForResource(objectGraph, `systemimage://${systemImageName}`);
+ }
+ else {
+ message = objectGraph.loc.string("GameDetails.Page.Banner.NotCompatible.Message");
+ }
+ }
+ else if (objectGraph.client.isTV) {
+ // We don't support product page scrolling for this particular banner on TV
+ message = objectGraph.loc.string("NOT_COMPATIBLE_BANNER_NO_LINK");
+ }
+ else if (objectGraph.client.isVision) {
+ // For visionOS, we show a sheet rather than scrolling
+ const annotation = compatibilityAnnotation.createAnnotation(objectGraph, data, false, false, false, null);
+ if (isSome(annotation)) {
+ action = createExpandActionForAnnotation(objectGraph, annotation);
+ if (isSome(action)) {
+ action.title = objectGraph.loc.string("NOT_COMPATIBLE_BANNER_LINK_TITLE");
+ message = objectGraph.loc
+ .string("NOT_COMPATIBLE_BANNER_TEMPLATE")
+ .replace("{linkTitle}", action.title);
+ }
+ }
+ bannerContext.offerButtonShouldBeDisabled = true;
+ }
+ else if (objectGraph.client.isiOS || objectGraph.client.isMac) {
+ if (productPageUtil.isShelfBased(objectGraph)) {
+ const scrollAction = new models.ShelfBasedPageScrollAction("information");
+ scrollAction.title = objectGraph.loc.string("NOT_COMPATIBLE_BANNER_LINK_TITLE");
+ scrollAction.indexId = "compatibilityAnnotation";
+ // Here we use the `fullProductAction` action rather than just `action`,
+ // as our `ProductPageScrollAction` will only be valid once the full product page has loaded
+ fullProductAction = scrollAction;
+ message = objectGraph.loc
+ .string("NOT_COMPATIBLE_BANNER_TEMPLATE")
+ .replace("{linkTitle}", scrollAction.title);
+ }
+ else {
+ // For iOS / macOS, add an action that allows users to scroll to the Compatibility item
+ const informationSection = new models.ProductPageSection("shelf", "information");
+ const scrollAction = new models.ProductPageScrollAction(informationSection);
+ scrollAction.title = objectGraph.loc.string("NOT_COMPATIBLE_BANNER_LINK_TITLE");
+ scrollAction.indexId = "compatibilityAnnotation";
+ // Here we use the `fullProductAction` action rather than just `action`,
+ // as our `ProductPageScrollAction` will only be valid once the full product page has loaded
+ fullProductAction = scrollAction;
+ message = objectGraph.loc
+ .string("NOT_COMPATIBLE_BANNER_TEMPLATE")
+ .replace("{linkTitle}", scrollAction.title);
+ }
+ }
+ }
+ // Perform Rosetta filtering for macOS.
+ if (filtering.shouldFilter(objectGraph, data, 8192 /* filtering.Filter.MacOSRosetta */)) {
+ if (productPageUtil.isShelfBased(objectGraph)) {
+ const scrollAction = new models.ShelfBasedPageScrollAction("information");
+ scrollAction.title = objectGraph.loc.string("NOT_COMPATIBLE_BANNER_LINK_TITLE");
+ scrollAction.indexId = "compatibilityAnnotation";
+ // Here we use the `fullProductAction` action rather than just `action`,
+ // as our `ProductPageScrollAction` will only be valid once the full product page has loaded
+ fullProductAction = scrollAction;
+ }
+ else {
+ const informationSection = new models.ProductPageSection("shelf", "information");
+ const scrollAction = new models.ProductPageScrollAction(informationSection);
+ scrollAction.title = objectGraph.loc.string("NOT_COMPATIBLE_BANNER_LINK_TITLE");
+ scrollAction.indexId = "compatibilityAnnotation";
+ // Here we use the `fullProductAction` action rather than just `action`,
+ // as our `ProductPageScrollAction` will only be valid once the full product page has loaded
+ fullProductAction = scrollAction;
+ }
+ message = objectGraph.loc
+ .string("NOT_COMPATIBLE_BANNER_TEMPLATE")
+ .replace("{linkTitle}", fullProductAction.title);
+ }
+ // App is not supported by the current companion, eg. dynamic duo app on a Tinker watch
+ const isPreorder = mediaAttributes.attributeAsBooleanOrFalse(data, "isPreorder");
+ if (content.isUnsupportedByCurrentCompanion(objectGraph, data)) {
+ if (isPreorder) {
+ if (objectGraph.host.isWatch) {
+ message = objectGraph.loc.string("UNSUPPORTED_COMPANION_CONFIGURATION_PREORDER");
+ }
+ else {
+ message = objectGraph.loc.string("ProductPage.WatchOS.PreOrderRequiresiPhone");
+ }
+ }
+ else {
+ message = objectGraph.loc.string("UNSUPPORTED_COMPANION_CONFIGURATION", "Requires iPhone");
+ }
+ bannerContext.offerButtonShouldBeDisabled = true;
+ }
+ // App is not supported on paired watch OS version
+ const minimumWatchOSVersionString = contentAttributes.contentAttributeAsString(objectGraph, data, "minimumWatchOSVersion");
+ if (message === null &&
+ bannerContext.clientIdentifierOverride === client.watchIdentifier &&
+ content.isActivePairedWatchOSBelowVersion(objectGraph, minimumWatchOSVersionString)) {
+ message = objectGraph.loc
+ .string("ProductPage.Banner.PairedWatchOSVersionBelowMinimum")
+ .replace("{osVersion}", minimumWatchOSVersionString);
+ }
+ /// App requires a visionOS device
+ const isVisionOnlyApp = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "realityDevice");
+ if (isVisionOnlyApp &&
+ !objectGraph.client.isVision &&
+ !objectGraph.client.isWeb &&
+ !preprocessor.GAMES_TARGET &&
+ !objectGraph.client.isCompanionVisionApp) {
+ const learnMoreTitle = objectGraph.loc.string("BANNER_VISION_ONLY_APP_LEARN_MORE_LINK");
+ action = visionOnlyAppLearnMoreAction(objectGraph, learnMoreTitle, bannerContext.metricsPageInformation, bannerContext.metricsLocationTracker);
+ message = visionOnlyAppMessage(objectGraph, learnMoreTitle, action);
+ fullProductAction = null;
+ leadingArtwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://visionpro");
+ leadingArtworkTintColor = color.named("secondaryText");
+ includeBackgroundBorder = true;
+ }
+ if (supportsVisionPlatformForVisionCompanion(objectGraph, data) && !hasRemoteDownloadIdentifiers(objectGraph)) {
+ // This case is where we support the download to a visionOS device, but we don't have anything to download to.
+ bannerContext.offerButtonShouldBeDisabled = true;
+ message = objectGraph.loc.string("ProductPage.Banner.Companion.VisionDeviceRequired");
+ }
+ else if (objectGraph.client.isCompanionVisionApp &&
+ !supportsVisionPlatformForVisionCompanion(objectGraph, data)) {
+ // This case is the app doesn't support remote downloads, show this banner.
+ bannerContext.offerButtonShouldBeDisabled = true;
+ message = objectGraph.loc.string("ProductPage.Banner.Companion.RemoteDownloadUnavailable");
+ }
+ // External purchases
+ // If we have no banner, or the offer button is not disabled, give priority to the external purchases banner
+ const hasExternalPurchases = hasExternalPurchasesForData(objectGraph, data);
+ const externalPurchasesEnabled = externalPurchasesPlacementIsEnabled(objectGraph, "product-page-banner");
+ if ((message === null || !bannerContext.offerButtonShouldBeDisabled) &&
+ hasExternalPurchases &&
+ externalPurchasesEnabled) {
+ const learnMoreTitle = objectGraph.loc.string("ProductPage.ExternalPurchasesBanner.LearnMore");
+ action = externalPurchasesLearnMoreAction(objectGraph, learnMoreTitle, bannerContext.metricsPageInformation, bannerContext.metricsLocationTracker);
+ message = externalPurchasesMessage(objectGraph, learnMoreTitle, action);
+ fullProductAction = null;
+ if (objectGraph.bag.externalPurchasesIncludeProductPageBannerIcon) {
+ if (objectGraph.bag.externalPurchasesProductPageBannerIconVariant === "secondaryInfoCircle") {
+ leadingArtwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://info.circle");
+ leadingArtworkTintColor = color.named("secondaryText");
+ }
+ else {
+ leadingArtwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://exclamationmark.triangle");
+ if (objectGraph.client.isVision) {
+ leadingArtworkTintColor = color.named("secondaryText");
+ }
+ else {
+ leadingArtworkTintColor = color.named("systemRed");
+ }
+ }
+ }
+ else {
+ leadingArtwork = null;
+ leadingArtworkTintColor = null;
+ }
+ if (objectGraph.client.isTV) {
+ if (isSome(objectGraph.bag.externalPurchasesProductPageBannerTextVariant)) {
+ const key = `ProductPage.ExternalPurchasesBanner.Focused.Variant${objectGraph.bag.externalPurchasesProductPageBannerTextVariant}`;
+ focusedMessage = objectGraph.loc.string(key);
+ }
+ else {
+ focusedMessage = objectGraph.loc.string("ProductPage.ExternalPurchasesBanner.Focused");
+ }
+ }
+ includeBackgroundBorder = true;
+ }
+ if ((message === null || message === void 0 ? void 0 : message.length) > 0) {
+ return new models.Banner(message, focusedMessage, action, fullProductAction, leadingArtwork, leadingArtworkTintColor, includeBackgroundBorder, bannerKind);
+ }
+ else if (!bannerContext.offerButtonShouldBeDisabled) {
+ // If no banner or disabled button for above reasons, show an AppState based Banner if needed
+ const appCapabilities = content.appBinaryTraitsFromData(objectGraph, data);
+ if (serverData.isDefinedNonNullNonEmpty(appCapabilities)) {
+ return createExternalBrowserCompatibilityBanner(objectGraph, appCapabilities);
+ }
+ }
+ return null;
+}
+function createExternalBrowserBanner(appAction, appCapabilities, message) {
+ return new models.Banner(message, undefined, undefined, undefined, undefined, undefined, undefined, undefined, {
+ type: "canPerformAppAction",
+ appAction: appAction,
+ appCapabilities: appCapabilities,
+ });
+}
+function createExternalBrowserCompatibilityBanner(objectGraph, appCapabilities) {
+ const message = objectGraph.loc.string("ProductPage.Banner.ExternalBrowser.Message");
+ let unknownMessage = message;
+ let buyMessage = message;
+ let downloadMessage = message;
+ let updateMessage = message;
+ let openMessage = message;
+ // Debug builds append current app state to banner text for testing.
+ if (["debug"].includes(objectGraph.client.buildType)) {
+ unknownMessage += "\n(Internal: unknown)";
+ buyMessage += "\n(Internal: buyOrGet)";
+ downloadMessage += "\n(Internal: redownload)";
+ updateMessage += "\n(Internal: update)";
+ openMessage += "\n(Internal: open)";
+ }
+ return new models.AppStateBanner(
+ /* unknownBanner */ createExternalBrowserBanner("install", appCapabilities, unknownMessage),
+ /* buyBanner */ createExternalBrowserBanner("install", appCapabilities, buyMessage),
+ /* downloadBanner */ createExternalBrowserBanner("install", appCapabilities, downloadMessage),
+ /* updateBanner */ createExternalBrowserBanner("update", appCapabilities, updateMessage),
+ /* openBanner */ createExternalBrowserBanner("launch", appCapabilities, openMessage));
+}
+/**
+ * Creates the banner message to display for external purchases.
+ *
+ * @param objectGraph Current object graph
+ * @param learnMoreTitle The title for a "Learn More" link
+ * @param learnMoreAction The action associated with the "Learn More" link, if any
+ * @returns The built message, which may or ma
+ */
+function externalPurchasesMessage(objectGraph, learnMoreTitle, learnMoreAction) {
+ if (learnMoreTitle &&
+ learnMoreAction &&
+ !objectGraph.client.isTV &&
+ !objectGraph.client.isWatch &&
+ !objectGraph.client.isWeb) {
+ if (objectGraph.client.isMac || objectGraph.client.isVision) {
+ if (isSome(objectGraph.bag.externalPurchasesProductPageBannerTextVariant)) {
+ const key = `ProductPage.ExternalPurchasesBanner.WithLink_WithoutNewline.Variant${objectGraph.bag.externalPurchasesProductPageBannerTextVariant}`;
+ return objectGraph.loc.string(key).replace("{learnMoreLink}", learnMoreTitle);
+ }
+ else {
+ return objectGraph.loc
+ .string("ProductPage.ExternalPurchasesBanner.WithLink_WithoutNewline")
+ .replace("{learnMoreLink}", learnMoreTitle);
+ }
+ }
+ else {
+ if (isSome(objectGraph.bag.externalPurchasesProductPageBannerTextVariant)) {
+ const key = `ProductPage.ExternalPurchasesBanner.WithLink.Variant${objectGraph.bag.externalPurchasesProductPageBannerTextVariant}`;
+ return objectGraph.loc.string(key).replace("{learnMoreLink}", learnMoreTitle);
+ }
+ else {
+ return objectGraph.loc
+ .string("ProductPage.ExternalPurchasesBanner.WithLink")
+ .replace("{learnMoreLink}", learnMoreTitle);
+ }
+ }
+ }
+ else {
+ if (isSome(objectGraph.bag.externalPurchasesProductPageBannerTextVariant)) {
+ const key = `ProductPage.ExternalPurchasesBanner.NoLink.Variant${objectGraph.bag.externalPurchasesProductPageBannerTextVariant}`;
+ return objectGraph.loc.string(key);
+ }
+ else {
+ return objectGraph.loc.string("ProductPage.ExternalPurchasesBanner.NoLink");
+ }
+ }
+}
+/**
+ * The message to use when viewing a visionOS only app on other platforms.
+ * @param objectGraph Current object graph
+ * @param learnMoreTitle Title for the learn more action
+ * @param learnMoreAction An optional learn more action
+ * @returns A message string
+ */
+function visionOnlyAppMessage(objectGraph, learnMoreTitle, learnMoreAction) {
+ if (learnMoreTitle && learnMoreAction) {
+ return objectGraph.loc.string("BANNER_VISION_ONLY_APP_WITH_LINK").replace("{learnMoreLink}", learnMoreTitle);
+ }
+ else {
+ return objectGraph.loc.string("BANNER_VISION_ONLY_APP_NO_LINK");
+ }
+}
+/**
+ * Creates a learn more action, for when viewing a visionOS only app on other platforms.
+ * @param objectGraph Current object graph
+ * @param title The title to assign to the action
+ * @param metricsPageInformation Metrics page info
+ * @param metricsLocationTracker Metrics location tracker
+ * @returns A built action
+ */
+export function visionOnlyAppLearnMoreAction(objectGraph, title, metricsPageInformation, metricsLocationTracker) {
+ const editorialItemId = objectGraph.bag.visionOnlyAppLearnMoreEditorialItemId;
+ if (serverData.isNullOrEmpty(editorialItemId)) {
+ return null;
+ }
+ const action = new models.FlowAction("article");
+ action.title = title;
+ action.pageUrl = `https://apps.apple.com/story/id${editorialItemId}`;
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, {
+ id: "LearnMore",
+ targetType: "link",
+ actionType: "navigate",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ });
+ return action;
+}
+//# sourceMappingURL=banner.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/bundle-page-common.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/bundle-page-common.js
new file mode 100644
index 0000000..a0852c6
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/bundle-page-common.js
@@ -0,0 +1,19 @@
+import { defaultAdditionalPlatformsForClient, Request } from "../../foundation/media/data-fetching";
+/**
+ * Generates a {@linkcode Request} to the `app-bundles` Media API endpoint
+ */
+export function makeBundlePageRequest(objectGraph, intent) {
+ const mediaApiRequest = new Request(objectGraph)
+ .withIdOfType(intent.id, "app-bundles")
+ .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph))
+ .includingRelationships(["genres", "developer", "apps", "developer-other-apps", "reviews"]);
+ if (objectGraph.client.isWeb) {
+ // Ensure that the "web" client fully hydrates all PDP shelves
+ mediaApiRequest.addingQuery("sparseLimit[developer-other-apps]", "15");
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ mediaApiRequest.includingAttributes(["ageRating"]);
+ }
+ }
+ return mediaApiRequest;
+}
+//# sourceMappingURL=bundle-page-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/intent-controller-routing.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/intent-controller-routing.js
new file mode 100644
index 0000000..83c76a1
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/intent-controller-routing.js
@@ -0,0 +1,81 @@
+import { makeProductPageIntent as baseMakeProductPageIntent, } from "../../api/intents/product-page-intent";
+import { makeAppEventPageIntent } from "../../api/intents/app-event-page-intent";
+import { generateRoutes } from "../util/generate-routes";
+import { makeSeeAllPageIntent, SEE_ALL_TYPES, } from "../../api/intents/see-all-page-intent";
+import { Path } from "../../foundation/network/url-constants";
+import { normalizePreviewPlaform } from "../../api/models/preview-platform";
+/// MARK: Common Routing Definitions
+const PRODUCT_PAGE_URL_PATTERN_WITH_APPNAME = "/app/{appName}/{id}";
+const PRODUCT_PAGE_URL_PATTERN_WITHOUT_APPNAME = "/app/{id}";
+const PRODUCT_PAGE_OPTIONAL_QUERY_PARAMS = {
+ optionalQuery: ["platform", "ppid", "lic"],
+};
+function makeProductPageIntent(opts) {
+ const { ...clone } = opts;
+ // The `{appName}` dynamic segment is part of the URL for SEO purposes, but is not actually part
+ // of the `Intent` since it is not necessary for data-fetching. This data must not end up as
+ // part of the `Intent` as the value cannot be consistently normalized, which can break important
+ // caching behavior within the `web` client
+ delete clone["appName"];
+ return baseMakeProductPageIntent(clone);
+}
+const { routes: productPageRoutesWithAppNameSlug } = generateRoutes(
+// @ts-expect-error `generateRoutes` does not handle a typed `Platform` value correctly
+makeProductPageIntent, PRODUCT_PAGE_URL_PATTERN_WITH_APPNAME, [], PRODUCT_PAGE_OPTIONAL_QUERY_PARAMS);
+const { routes: productPageRoutesWithoutAppNameSlug, makeCanonicalUrl: makeProductPageURLWithoutAppSlug } = generateRoutes(baseMakeProductPageIntent, PRODUCT_PAGE_URL_PATTERN_WITHOUT_APPNAME, [],
+// @ts-expect-error `generateRoutes` does not handle a typed `Platform` value correctly
+PRODUCT_PAGE_OPTIONAL_QUERY_PARAMS);
+// If an "App Clip Demo" URL is hit, we redirect to the product page via the setting of the `canonicalUrl` on `page`
+const { routes: appClipDemoPageRoutes } = generateRoutes(
+// @ts-expect-error `generateRoutes` does not handle a typed `Platform` value correctly
+makeProductPageIntent, "/demo/{id}", [], PRODUCT_PAGE_OPTIONAL_QUERY_PARAMS);
+export const productPageRoutes = (objectGraph) => [
+ ...productPageRoutesWithAppNameSlug(objectGraph),
+ ...productPageRoutesWithoutAppNameSlug(objectGraph),
+ ...appClipDemoPageRoutes(objectGraph),
+];
+export { makeProductPageURLWithoutAppSlug };
+function makeAppEventPageIntentFromURLParams(options) {
+ const { eventid: id, platform, storefront, language } = options;
+ return makeAppEventPageIntent({
+ storefront,
+ language,
+ platform,
+ id,
+ });
+}
+const { routes: appEventPageRoutesWithAppNameSlug } = generateRoutes(
+// @ts-expect-error working around type-safety issues in `generateRoutes`
+makeAppEventPageIntentFromURLParams, PRODUCT_PAGE_URL_PATTERN_WITH_APPNAME, ["eventid"], PRODUCT_PAGE_OPTIONAL_QUERY_PARAMS);
+const { routes: appEventPageRoutesWithoutAppNameSlug } = generateRoutes(
+// @ts-expect-error working around type-safety issues in `generateRoutes`
+makeAppEventPageIntentFromURLParams, PRODUCT_PAGE_URL_PATTERN_WITHOUT_APPNAME, ["eventid"], PRODUCT_PAGE_OPTIONAL_QUERY_PARAMS);
+export const appEventPageRoutes = (objectGraph) => [
+ ...appEventPageRoutesWithAppNameSlug(objectGraph),
+ ...appEventPageRoutesWithoutAppNameSlug(objectGraph),
+];
+function makeSeeAllPageIntentFromURL(params) {
+ const { id, storefront, language, platform } = params;
+ return makeSeeAllPageIntent({
+ id,
+ storefront,
+ language,
+ "platform": normalizePreviewPlaform(platform),
+ "see-all": params["see-all"],
+ });
+}
+const optionalQueryRuleParams = PRODUCT_PAGE_OPTIONAL_QUERY_PARAMS.optionalQuery.map((p) => `${p}?`);
+const allSeeAllRules = [];
+SEE_ALL_TYPES.forEach((seeAllType) => {
+ const queryWithSeeAll = [`see-all=${seeAllType}`, ...optionalQueryRuleParams];
+ [PRODUCT_PAGE_URL_PATTERN_WITH_APPNAME, PRODUCT_PAGE_URL_PATTERN_WITHOUT_APPNAME].forEach((pattern) => {
+ allSeeAllRules.push({ path: pattern, query: queryWithSeeAll });
+ allSeeAllRules.push({ path: `/{${Path.storeFront}}${pattern}`, query: queryWithSeeAll });
+ });
+});
+const { routes: seeAllPageRoutes, makeCanonicalUrl: makeSeeAllPageURL } = generateRoutes(makeSeeAllPageIntentFromURL, PRODUCT_PAGE_URL_PATTERN_WITHOUT_APPNAME, ["see-all"], {
+ extraRules: allSeeAllRules,
+ optionalQuery: ["platform"],
+});
+export { seeAllPageRoutes, makeSeeAllPageURL };
+//# sourceMappingURL=intent-controller-routing.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-common.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-common.js
new file mode 100644
index 0000000..d3bc1f2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-common.js
@@ -0,0 +1,604 @@
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { Request, defaultAdditionalPlatformsForClient } from "../../foundation/media/data-fetching";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import { Host, Parameters, Path, Protocol, ShelfRefreshType } from "../../foundation/network/url-constants";
+import * as urls from "../../foundation/network/urls";
+import * as routingComponents from "../../foundation/routing/routing-components";
+import * as color from "../../foundation/util/color-util";
+import * as validation from "@jet/environment/json/validation";
+import * as todayHorizontalCardUtil from "../../common/today/today-horizontal-card-util";
+import * as client from "../../foundation/wrappers/client";
+import * as content from "../content/content";
+import * as lockups from "../lockups/lockups";
+import { addClickEventToSeeAllAction } from "../metrics/helpers/clicks";
+import * as metricsHelpersModels from "../metrics/helpers/models";
+import { addMetricsEventsToPageWithInformation, addPageChangeFieldsToShelfWithInformation, } from "../metrics/helpers/page";
+import { ProductPageShelfMetrics } from "./product-page-shelf-metrics";
+import * as moreByDeveloperShelf from "./shelves/more-by-developer-shelf";
+import * as similarItemsShelf from "./shelves/similar-items-shelf";
+import * as smallStoryCardShelf from "./shelves/small-story-card-shelf";
+// MARK: - Constants
+/// Write review action query parameter value
+export const writeReviewAction = "write-review";
+/// Reviews action query parameter value
+export const reviewsAction = "reviews";
+/// Key for request / response for App Promotion (App Event or Winback offer) data.
+export const appPromotionRequirementKey = "appPromotionRequirementKey";
+/// The color to use for shelves that have gray backgrounds.
+export const grayShelfBackgroundColor = color.dynamicWith(color.fromHex("F0F0F8"), color.fromHex("303031"));
+/**
+ * The `ProductPageOnDemandShelfType` enum is used to specify the type of shelf that is being rendered via
+ * secondary lookup
+ */
+export var ProductPageOnDemandShelfType;
+(function (ProductPageOnDemandShelfType) {
+ ProductPageOnDemandShelfType["MoreByDeveloper"] = "moreByDeveloper";
+ ProductPageOnDemandShelfType["SimilarItems"] = "similarItems";
+ ProductPageOnDemandShelfType["SmallStory"] = "smallStory";
+})(ProductPageOnDemandShelfType || (ProductPageOnDemandShelfType = {}));
+/**
+ * The `ProductPageShelfToken` type is responsible for plumbing through all
+ * the data needed to render an incomplete shelf (a shelf that requires a
+ * lookup) on the product page.
+ */
+export class ProductPageShelfToken {
+ // endregion
+ constructor(productId, remainingItems, title, shouldInferSeeAllFromFetchedItems, capabilities, contentType, offerStyle, spotlightInAppProductIdentifier, refreshUrl, recoMetricsData, supportsArcade, onDemandShelfType) {
+ //* *************************
+ //* Secondary Fetch Properties
+ //* *************************
+ // Whether this is the first render of the shelf.
+ this.isFirstRender = true;
+ //* *************************
+ //* Bundle Properties
+ //* *************************
+ this.isBundleShelf = false;
+ //* *************************
+ //* Placeholder Properties
+ //* *************************
+ // Whether this shelf is currently showing placeholder cells
+ // if this is the case we generally use this flag to tell the shelf not to
+ // merge after the additional fetch
+ this.showingPlaceholders = false;
+ this.productId = productId;
+ this.onDemandShelfType = onDemandShelfType;
+ this.remainingItems = remainingItems;
+ this.title = title;
+ this.shouldInferSeeAllFromFetchesItems = shouldInferSeeAllFromFetchedItems;
+ this.capabilities = capabilities;
+ this.contentType = contentType;
+ this.offerStyle = offerStyle;
+ this.spotlightInAppProductIdentifier = spotlightInAppProductIdentifier;
+ this.refreshUrl = refreshUrl;
+ this.recoMetricsData = recoMetricsData;
+ this.supportsArcade = supportsArcade;
+ }
+}
+/**
+ * Returns the shelf content URL for a given array of adamIds.
+ * @param token
+ * @param sourcePageInformation
+ * @returns A string containing a URL.
+ */
+export function shelfContentUrl(objectGraph, token, shelfMetrics, destinationPageInformation) {
+ if (serverData.isNullOrEmpty(token.remainingItems)) {
+ return null;
+ }
+ return (`${Protocol.internal}:/${Path.product}/${Path.shelf}/` +
+ encodedShelfToken(token, shelfMetrics, destinationPageInformation));
+}
+export function encodedShelfToken(token, shelfMetrics, destinationPageInformation) {
+ token.sourceSequenceId = shelfMetrics.getSequenceId();
+ token.sourcePageInformation = shelfMetrics.metricsPageInformation;
+ token.sourceLocationTracker = shelfMetrics.locationTracker;
+ token.destinationPageInformation = destinationPageInformation;
+ return encodeURIComponent(JSON.stringify(token));
+}
+export function lockupsFromDataContainer(objectGraph, dataContainer, shelfMetrics, artworkUseCase, shelfContentType, offerStyle, filter, recoMetricsData, shouldShowOnUnsupportedPlatform) {
+ return lockupsFromData(objectGraph, dataContainer.data, shelfMetrics, artworkUseCase, shelfContentType, offerStyle, filter, recoMetricsData, shouldShowOnUnsupportedPlatform);
+}
+export function lockupsFromData(objectGraph, data, shelfMetrics, artworkUseCase, shelfContentType, offerStyle, filter, recoMetricsData, shouldShowOnUnsupportedPlatform) {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ const remainingItems = [];
+ const baseLockupOptions = {
+ metricsOptions: {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ recoMetricsData: recoMetricsData,
+ },
+ artworkUseCase: artworkUseCase,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfContentType),
+ };
+ const bundleLockupOptions = { shouldShowSupportedPlatformLabel: shouldShowOnUnsupportedPlatform !== null && shouldShowOnUnsupportedPlatform !== void 0 ? shouldShowOnUnsupportedPlatform : false };
+ const styleLockupOptions = serverData.isNull(offerStyle) ? {} : { offerStyle: offerStyle };
+ const lockupListOptions = {
+ shouldShowOnUnsupportedPlatform: shouldShowOnUnsupportedPlatform !== null && shouldShowOnUnsupportedPlatform !== void 0 ? shouldShowOnUnsupportedPlatform : false,
+ lockupOptions: { ...baseLockupOptions, ...styleLockupOptions, ...bundleLockupOptions },
+ shouldConsiderDataPastLastAvailable: true,
+ contentUnavailable: (_index, dataItem) => {
+ remainingItems.push(dataItem);
+ return false;
+ },
+ filter: filter,
+ };
+ /// Maximum number of items allowed in shelves on watchOS.
+ const watchItemLimit = 3;
+ const items = lockups.lockupsFromData(objectGraph, data, lockupListOptions);
+ if (objectGraph.client.isWatch) {
+ const trimmedItems = items.slice(0, watchItemLimit);
+ const trimmedRemainingItems = trimmedItems.length < watchItemLimit ? remainingItems.slice(0, watchItemLimit - trimmedItems.length) : [];
+ return {
+ items: trimmedItems,
+ remainingItems: trimmedRemainingItems,
+ };
+ }
+ else {
+ return {
+ items,
+ remainingItems,
+ };
+ }
+}
+/**
+ * Moves the item with the specified id to the front of the lockup items.
+ * @param id The id (adamId or iAP product identifier) for the lockup to move to the front.
+ * @param items The list of items in which this lockup may appear.
+ */
+export function moveLockupToFront(objectGraph, id, items) {
+ if (!items) {
+ return;
+ }
+ let oldIndex = -1;
+ let spotlightLockup = null;
+ items.forEach((item, index) => {
+ const lockup = item;
+ const iapLockup = lockup;
+ const lockupMatchesSpotlightAdamId = lockup && lockup.adamId === id;
+ const inAppPurchaseMatchesSpotlightIdentifier = iapLockup && iapLockup.productIdentifier === id;
+ if (lockupMatchesSpotlightAdamId || inAppPurchaseMatchesSpotlightIdentifier) {
+ oldIndex = index;
+ spotlightLockup = lockup;
+ // Set the theme if an iAP
+ if (iapLockup) {
+ iapLockup.theme = "spotlight";
+ iapLockup.offerDisplayProperties =
+ iapLockup.offerDisplayProperties.newOfferDisplayPropertiesChangingAppearance(false, "colored", "ad", { type: "blue" });
+ }
+ }
+ });
+ if (oldIndex !== -1) {
+ items.splice(oldIndex, 1);
+ items.splice(0, 0, spotlightLockup);
+ }
+}
+const supportedCardMediaKinds = ["brandedSingleApp", "grid", "artwork", "video"];
+/**
+ * Determines whether or not to include the today card on a shelf on the product page.
+ *
+ * @param card The today card in question.
+ * @return `true` if-and-only-if the card should be included on the product page's shelf.
+ */
+export function includeTodayCardOnProductPage(card) {
+ if (supportedCardMediaKinds.indexOf(card.media.kind) === -1) {
+ return false;
+ }
+ const flowAction = card.clickAction;
+ if (!flowAction) {
+ return true;
+ }
+ const urlString = flowAction.pageUrl;
+ if (!urlString) {
+ return true;
+ }
+ // We drop cards that link back to the same product page.
+ const url = new urls.URL(urlString);
+ const productPageRoute = allPageRoutes();
+ for (const ruleDefinition of productPageRoute) {
+ const productPageRule = new routingComponents.UrlRule(ruleDefinition);
+ if (productPageRule.matches(url)) {
+ return false;
+ }
+ }
+ return true;
+}
+export function allPageRoutes() {
+ return [
+ {
+ protocol: Protocol.https,
+ path: `/{countryCode}/${Path.product}/{appName}/{id}`,
+ query: [
+ `${Parameters.action}?`,
+ `${Parameters.offerName}?`,
+ `${Parameters.isPPT}?`,
+ `${Parameters.appEventId}?-caseInsensitive`,
+ `${Parameters.offerItemId}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ `${Parameters.webBrowser}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ path: `/{countryCode}/${Path.productBundle}/{appName}/{id}`,
+ query: [
+ `${Parameters.action}?`,
+ `${Parameters.offerName}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ path: `/{countryCode}/${Path.product}/{id}`,
+ query: [
+ `${Parameters.action}?`,
+ `${Parameters.offerName}?`,
+ `${Parameters.appEventId}?-caseInsensitive`,
+ `${Parameters.offerItemId}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ `${Parameters.webBrowser}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ path: `/{countryCode}/${Path.productBundle}/{id}`,
+ query: [
+ `${Parameters.action}?`,
+ `${Parameters.offerName}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ path: `/${Path.product}/{appName}/{id}`,
+ query: [
+ `${Parameters.action}?`,
+ `${Parameters.offerName}?`,
+ `${Parameters.appEventId}?-caseInsensitive`,
+ `${Parameters.offerItemId}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ `${Parameters.webBrowser}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ path: `/${Path.productBundle}/{appName}/{id}`,
+ query: [
+ `${Parameters.action}?`,
+ `${Parameters.offerName}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ path: `/${Path.product}/{id}`,
+ query: [
+ `${Parameters.action}?`,
+ `${Parameters.offerName}?`,
+ `${Parameters.appEventId}?-caseInsensitive`,
+ `${Parameters.offerItemId}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ `${Parameters.webBrowser}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ path: `/${Path.product}/{id}`,
+ query: [
+ `${Parameters.v0}?`,
+ `${Parameters.metrics}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ `${Parameters.webBrowser}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ path: `/${Path.productBundle}/{id}`,
+ query: [
+ `${Parameters.action}?`,
+ `${Parameters.offerName}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ query: [
+ Parameters.bundleIdentifier,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ `${Parameters.webBrowser}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ query: [
+ Parameters.action,
+ Parameters.ids,
+ `${Parameters.isPurchasesApp}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ path: `WebObjects/MZStorePlatform.woa/ra/{apiVersion}/{realm}/catalog/{countryCode}/apps/{id}`,
+ query: [`${Parameters.isPreloading}?`, `${Parameters.isViewOnly}?`, `${Parameters.includeUnlistedApps}?`],
+ },
+ {
+ protocol: Protocol.https,
+ path: `WebObjects/MZStorePlatform.woa/ra/{apiVersion}/{realm}/catalog/{countryCode}/app-bundles/{id}`,
+ query: [],
+ },
+ {
+ protocol: Protocol.https,
+ path: `{apiVersion}/catalog/{countryCode}/apps/{id}`,
+ query: [
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ `${Parameters.offerItemId}?`,
+ ],
+ },
+ {
+ protocol: Protocol.https,
+ path: `{apiVersion}/catalog/{countryCode}/app-bundles/{id}`,
+ query: [`${Parameters.isPreloading}?`, `${Parameters.isViewOnly}?`, `${Parameters.includeUnlistedApps}?`],
+ },
+ {
+ protocol: Protocol.https,
+ hostName: `${Host.product}`,
+ path: `/${Path.siri}/{id}`,
+ query: [`${Parameters.isPreloading}?`, `${Parameters.isViewOnly}?`, `${Parameters.includeUnlistedApps}?`],
+ },
+ {
+ protocol: Protocol.https,
+ path: `/${Path.store}/${Path.viewSoftware}`,
+ query: [
+ Parameters.id,
+ `${Parameters.v0}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ ],
+ },
+ {
+ protocol: Protocol.internal,
+ path: `/${Path.product}/{id}`,
+ query: [
+ Parameters.invalidateWidgetsOnFailure,
+ `${Parameters.metrics}?`,
+ `${Parameters.offerItemId}?`,
+ `${Parameters.appEventId}?`,
+ `${Parameters.isPreloading}?`,
+ `${Parameters.isViewOnly}?`,
+ `${Parameters.includeUnlistedApps}?`,
+ `${Parameters.webBrowser}?`,
+ ],
+ },
+ ];
+}
+export function generateShelfRequest(objectGraph, url, parameters) {
+ const tokenJson = parameters["token"];
+ const token = JSON.parse(tokenJson);
+ // Determine the resource type appropriate for our shelf.
+ let resourceType;
+ if (token.isBundleShelf) {
+ resourceType = "app-bundles";
+ }
+ else {
+ switch (token.contentType) {
+ case "smallStoryCard":
+ case "todayBrick":
+ case "miniTodayCard":
+ resourceType = "editorial-items";
+ break;
+ case "inAppPurchaseLockup":
+ resourceType = "in-apps";
+ break;
+ default:
+ resourceType = "apps";
+ }
+ }
+ const attributes = ["editorialArtwork", "editorialVideo", "minimumOSVersion"];
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.client.isVision) {
+ attributes.push("compatibilityControllerRequirement");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (content.shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ return new Request(objectGraph)
+ .withIdsOfType(token.remainingItems.map((item) => item.id), resourceType)
+ .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph))
+ .includingAttributes(resourceType === "in-apps" ? [] : attributes);
+}
+/**
+ * Render a shelf for the product page.
+ * @param objectGraph The object graph.
+ * @param data The Data received for the shelf.
+ * @param parameters The Parameters for the shelf.
+ * @param adResponse A response for the provided ad fetch, if any.
+ * @param addPageChangeMetrics Whether to attach pageChange metrics to the created shelf.
+ * @returns
+ */
+export async function renderShelf(objectGraph, data, parameters, adResponse, addPageChangeMetrics = false) {
+ const tokenJson = parameters["token"];
+ // Parse the shelf token.
+ // The `iAdInfo` object is constructed manually so that it's a proper `IAdSearchInformation` with callable functions.
+ const token = JSON.parse(tokenJson, (key, value) => {
+ if (typeof value === "object" && key === "iAdInfo" && serverData.isDefinedNonNullNonEmpty(value)) {
+ const iAdInfo = metricsHelpersModels.IAdSearchInformation.from(objectGraph, value);
+ return iAdInfo;
+ }
+ return value;
+ });
+ token.isFirstRender = false;
+ // Check if the data has reco metrics data. Prefer this over what's in the token, as it's more
+ // relevant to what we're building.
+ const recoMetricsData = mediaDataStructure.metricsFromMediaApiObject(data);
+ token.recoMetricsData = recoMetricsData !== null && recoMetricsData !== void 0 ? recoMetricsData : token.recoMetricsData;
+ if (parameters[Parameters.shelfRefreshType] === ShelfRefreshType.productPageSimilarItems) {
+ ProductPageShelfMetrics.resetLocationTrackerForSimilarItemsDuringDownloadShelf(objectGraph, token);
+ }
+ const shelf = shelfFromDataContainer(objectGraph, data, token, adResponse);
+ // TODO: <rdar://problem/39439657> Media API: Product Page Incomplete Shelf Metrics
+ // shelf.networkTimingMetrics = response[metricsHelpers.timingValues];
+ if (addPageChangeMetrics) {
+ addPageChangeFieldsToShelfWithInformation(objectGraph, shelf, token.sourcePageInformation);
+ }
+ // Ensure the refreshUrl is maintained on the completed shelf.
+ shelf.refreshUrl = token.refreshUrl;
+ if (parameters[Parameters.shelfRefreshType] === ShelfRefreshType.productPageSimilarItems) {
+ ProductPageShelfMetrics.addImpressionsFieldsToSimilarItemsDuringDownloadShelf(objectGraph, shelf, token);
+ }
+ // Configure 'See All' using fetched items
+ if (token.shouldInferSeeAllFromFetchesItems) {
+ const seeAllAction = new models.FlowAction("page");
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ const seeAllShelf = new models.Shelf(shelf.contentType);
+ seeAllShelf.items = lockups.shallowCopyLockupsOverridingProperties(objectGraph, shelf.items, "infer", true);
+ const seeAllPage = new models.GenericPage([seeAllShelf]);
+ addMetricsEventsToPageWithInformation(objectGraph, seeAllPage, token.destinationPageInformation);
+ seeAllPage.title = token.title;
+ seeAllAction.pageData = seeAllPage;
+ addClickEventToSeeAllAction(objectGraph, seeAllAction, null, {
+ pageInformation: token.sourcePageInformation,
+ locationTracker: token.sourceLocationTracker,
+ });
+ shelf.seeAllAction = seeAllAction;
+ }
+ if (serverData.isNullOrEmpty(shelf.items) && !token.hasExistingContent) {
+ shelf.isHidden = true;
+ }
+ return shelf;
+}
+/**
+ * Configures a shelf from a lookup response.
+ *
+ * This function is to be used by 'incomplete' shelves, i.e. shelves that have no
+ * items but which require a fetch using their URL.
+ *
+ * @param dataContainer Data container array with app data.
+ * @param token Token containing page context.
+ * @param adResponse A response for the provided ad fetch, if any.
+ * @return The fully-configured shelf.
+ */
+function shelfFromDataContainer(objectGraph, dataContainer, token, adResponse) {
+ return validation.context("shelfFromLookupResponse", () => {
+ switch (token.onDemandShelfType) {
+ case ProductPageOnDemandShelfType.SimilarItems:
+ return similarItemsShelf.createSecondaryShelf(objectGraph, dataContainer.data, token, adResponse);
+ case ProductPageOnDemandShelfType.MoreByDeveloper:
+ return moreByDeveloperShelf.createSecondaryShelf(objectGraph, dataContainer.data, token);
+ case ProductPageOnDemandShelfType.SmallStory:
+ return smallStoryCardShelf.createSecondaryShelf(objectGraph, dataContainer.data, token);
+ default:
+ break;
+ }
+ const shelf = new models.Shelf(token.contentType);
+ // Check if the dataContainer has reco metrics data. Prefer this over what's in the token, as it's more
+ // relevant to what we're building.
+ const recoMetricsData = mediaDataStructure.metricsFromMediaApiObject(dataContainer);
+ const metricsOptions = {
+ pageInformation: token.sourcePageInformation,
+ locationTracker: token.sourceLocationTracker,
+ excludeAttribution: true,
+ recoMetricsData: recoMetricsData !== null && recoMetricsData !== void 0 ? recoMetricsData : token.recoMetricsData,
+ };
+ let shelfModels;
+ switch (token.contentType) {
+ case "todayBrick":
+ // TODO: <rdar://problem/37505151> Media Api: TODO: Product page inline editorial item shelf
+ const inlineCards = todayHorizontalCardUtil.featuredInTodayCardsFromData(objectGraph, dataContainer.data, metricsOptions.pageInformation, metricsOptions.locationTracker, includeTodayCardOnProductPage);
+ if (serverData.isDefinedNonNull(inlineCards)) {
+ shelfModels = [inlineCards];
+ }
+ break;
+ default:
+ const lockupOptions = {
+ metricsOptions: metricsOptions,
+ offerStyle: token.offerStyle,
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, token.contentType),
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, token.contentType),
+ };
+ if (token.contentType === "inAppPurchaseLockup") {
+ lockupOptions.skipDefaultClickAction = true;
+ }
+ const options = { lockupOptions: lockupOptions };
+ if (token.isBundleShelf) {
+ options.filter = 0 /* filtering.Filter.None */;
+ }
+ const lockupModels = lockups.lockupsFromDataContainer(objectGraph, dataContainer, options);
+ const sortableClients = { [client.watchIdentifier]: "round", [client.messagesIdentifier]: "pill" };
+ const sortableStyle = sortableClients[objectGraph.host.clientIdentifier];
+ if (sortableStyle) {
+ lockupModels.sort((a, b) => {
+ const aIsSortableStyle = a.icon.style === sortableStyle;
+ const bIsSortableStyle = b.icon.style === sortableStyle;
+ if (aIsSortableStyle && bIsSortableStyle) {
+ return 0;
+ }
+ else if (aIsSortableStyle && !bIsSortableStyle) {
+ return -1;
+ }
+ else {
+ return 1;
+ }
+ });
+ }
+ const spotlightId = token.spotlightInAppProductIdentifier;
+ if (spotlightId) {
+ moveLockupToFront(objectGraph, spotlightId, lockupModels);
+ }
+ shelfModels = lockupModels;
+ break;
+ }
+ shelf.items = shelfModels;
+ shelf.mergeWhenFetched = true;
+ return shelf;
+ });
+}
+/**
+ * Returns the *unsupported* set of media kinds for small story card shelf on given platform.
+ * @param platform Platform to find unsupported media kinds for.
+ */
+export function filteredMediaCardKindsForSmallStoryCardOnPlatform(platform) {
+ const filteredMediaKinds = new Set(["appIcon"]); // Policy: No app icon.
+ if (platform === "macOS") {
+ filteredMediaKinds.add("brandedSingleApp");
+ filteredMediaKinds.add("list");
+ filteredMediaKinds.add("inAppPurchase");
+ filteredMediaKinds.add("river");
+ }
+ return filteredMediaKinds;
+}
+//# sourceMappingURL=product-page-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-shelf-metrics.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-shelf-metrics.js
new file mode 100644
index 0000000..7e97587
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-shelf-metrics.js
@@ -0,0 +1,82 @@
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+export class ProductPageShelfMetrics {
+ constructor(metricsPageInformation, locationTracker, sequenceId = 0) {
+ this.sequenceId = 0;
+ this.metricsPageInformation = metricsPageInformation;
+ this.locationTracker = locationTracker;
+ this.sequenceId = sequenceId;
+ }
+ addImpressionsToShelf(objectGraph, shelf, targetType, overrideId, overrideIdType, recoMetricsData, title) {
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, {
+ id: overrideId !== null && overrideId !== void 0 ? overrideId : `${this.sequenceId}`,
+ idType: overrideIdType !== null && overrideIdType !== void 0 ? overrideIdType : "sequential",
+ kind: null,
+ softwareType: null,
+ targetType: targetType,
+ title: title !== null && title !== void 0 ? title : shelf.title,
+ pageInformation: this.metricsPageInformation,
+ locationTracker: this.locationTracker,
+ recoMetricsData: recoMetricsData,
+ });
+ this.sequenceId++;
+ }
+ getSequenceId() {
+ return this.sequenceId;
+ }
+ // rdar://55462137 (Metrics: Swoosh "You May Also Like" instrumentation)
+ addImpressionsFieldsToSimilarItemsShelf(objectGraph, shelf, targetType, idType) {
+ if (!shelf) {
+ return;
+ }
+ const options = {
+ id: ProductPageShelfMetrics.similarItemsShelfId,
+ kind: null,
+ softwareType: null,
+ targetType: targetType,
+ title: shelf.title,
+ pageInformation: this.metricsPageInformation,
+ locationTracker: this.locationTracker,
+ idType: idType,
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, options);
+ this.sequenceId++;
+ }
+ /**
+ * Reset the location tracker before creating the YMAL During Download shelf.
+ * @param objectGraph The object graph.
+ * @param shelfToken The shelf token being used to build the shelf.
+ */
+ static resetLocationTrackerForSimilarItemsDuringDownloadShelf(objectGraph, shelfToken) {
+ // Reset the position of the location tracker, as it's based on the old position
+ const locationTracker = shelfToken.sourceLocationTracker;
+ metricsHelpersLocation.setCurrentPosition(locationTracker, 0);
+ metricsHelpersLocation.currentLocation(locationTracker).id =
+ ProductPageShelfMetrics.similarItemsDuringDownloadShelfId;
+ }
+ /**
+ * Add impressions fields to the YMAL During Download shelf.
+ * @param objectGraph The object graph.
+ * @param shelf The created shelf to add impressions fields to.
+ * @param shelfToken The shelf token used to build the shelf.
+ */
+ static addImpressionsFieldsToSimilarItemsDuringDownloadShelf(objectGraph, shelf, shelfToken) {
+ if (!shelf) {
+ return;
+ }
+ const options = {
+ id: ProductPageShelfMetrics.similarItemsDuringDownloadShelfId,
+ kind: null,
+ softwareType: null,
+ targetType: "similarItems",
+ title: shelfToken.title,
+ pageInformation: shelfToken.sourcePageInformation,
+ locationTracker: shelfToken.sourceLocationTracker,
+ idType: "relationship",
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, options);
+ }
+}
+ProductPageShelfMetrics.similarItemsShelfId = "customers-also-bought-apps";
+ProductPageShelfMetrics.similarItemsDuringDownloadShelfId = "customers-also-bought-apps-download";
+//# sourceMappingURL=product-page-shelf-metrics.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-util.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-util.js
new file mode 100644
index 0000000..3199e81
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-util.js
@@ -0,0 +1,65 @@
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as content from "../content/content";
+import * as productPage from "./product-page";
+import * as shelfBasedProductPage from "./shelf-based/shelf-based-product-page";
+import { Parameters } from "../../foundation/network/url-constants";
+export function isShelfBased(objectGraph) {
+ return (objectGraph.featureFlags.isEnabled("shelves_2_0_product") ||
+ objectGraph.client.isiOS ||
+ objectGraph.client.isTV ||
+ objectGraph.client.isVision ||
+ objectGraph.client.isWeb ||
+ preprocessor.GAMES_TARGET);
+}
+export async function createProductPageFromResponse(objectGraph, response, options = {}, additionalPageRequirements = {}, referrerData = null, isForPreloading = false, isViewOnly = false) {
+ const productData = mediaDataStructure.dataFromDataContainer(objectGraph, response);
+ const supportsArcade = content.isArcadeSupported(objectGraph, productData);
+ const shelfBasedPage = await shelfBasedProductPage.createProductPageFromResponse(objectGraph, response, options, additionalPageRequirements, referrerData, isViewOnly);
+ // In SkyE for PPE we are calling directly into fetchPage and not fetchShelfBasedPage which expects a non-shelf-based
+ // product page so in the preloading case we should return the non-shelf-based version
+ // rdar://90971491 (Error: [object Object] is not expected type XD - JS fix)
+ return isShelfBased(objectGraph) && !isForPreloading
+ ? shelfBasedPage
+ : productPage.createProductPageFromShelfBasedProductPage(objectGraph, shelfBasedPage, supportsArcade, options);
+}
+export function createProductPageSidePackFromResponse(objectGraph, response, options = {}) {
+ if (!objectGraph.client.isSidepackingEnabled) {
+ return null;
+ }
+ const supportsArcade = content.isArcadeSupported(objectGraph, response);
+ const shelfBasedPage = shelfBasedProductPage.createProductPageSidePackFromResponse(objectGraph, response, options);
+ return isShelfBased(objectGraph)
+ ? shelfBasedPage
+ : productPage.createProductPageFromShelfBasedProductPage(objectGraph, shelfBasedPage, supportsArcade, options);
+}
+export function badgesFromInfoRibbonShelf(objectGraph, shelf) {
+ if (serverData.isNullOrEmpty(shelf === null || shelf === void 0 ? void 0 : shelf.items)) {
+ return [];
+ }
+ if (isShelfBased(objectGraph)) {
+ return shelf.items;
+ }
+ else {
+ const informationRibbonItems = shelf.items;
+ if (informationRibbonItems.length > 0) {
+ return informationRibbonItems[0].badges;
+ }
+ else {
+ return [];
+ }
+ }
+}
+/**
+ * @param parameters The query params from a page request
+ * @returns The app event id if it exists
+ */
+export function appEventIdFromParameters(parameters) {
+ for (const [paramKey, paramValue] of Object.entries(parameters)) {
+ if (paramKey.toLocaleLowerCase() === Parameters.appEventId.toLocaleLowerCase()) {
+ return paramValue;
+ }
+ }
+ return null;
+}
+//# sourceMappingURL=product-page-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-variants.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-variants.js
new file mode 100644
index 0000000..71b4935
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page-variants.js
@@ -0,0 +1,499 @@
+/**
+ * Builds objects for Product Page Variants (PPV)
+ *
+ * PPV has two components:
+ * - Custom Product Pages: Special versions of product pages with different metadata (e.g. artwork).
+ * - Product Page Treatments: A/B testing treatments for icon, artwork, etc.
+ *
+ * At a high level:
+ * - Multiple versions of product pages now exist (Default and Custom Pages)
+ * - Each version of product page **may** have a special A/B testing treatment determined by xp_ab testing cookie.
+ *
+ * Example page configurations are:
+ * - Default page with no treatment.
+ * - Default page with treatment B
+ * - Custom page A with treatment C.
+ *
+ * There are two signals that determine which variant to show:
+ * - `meta.cppData`: Describes which CPP to show / link to. May be explicitly requested via `ppid` param, or automagically programmed.
+ * - `meta.experimentData`: Describes which experiments are running, so client can select the correct treatment.
+ *
+ * Based on contents of above, we choose which asset to use for given model. As a rule, we use assets in the following order:
+ * 1. Assets for CPP (if any)
+ * 2. Assets for Treatment (if any)
+ * 3. Default Assets
+ * i.e. CPP overrides Treatments overrides Default
+ *
+ * For 2021, scope is limited to:
+ * - iOS only
+ * - CPPs don't have treatments (though we don't care at our layer)
+ */
+import { asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, isNullOrEmpty, traverse, } from "../../foundation/json-parsing/server-data";
+import { unreachable } from "../../foundation/util/errors";
+import { contentAttributeAsDictionary } from "../content/attributes";
+import { productVariantTreatmentId } from "../../foundation/experimentation/product-page-experiments";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+// region API
+/**
+ * Whether or not custom attributes data should be fetched. This is effectively a feature gate.
+ * Downstream builder code should parameterize based presence of custom attributes within response, not this flag.
+ */
+export function shouldFetchCustomAttributes(objectGraph) {
+ // Custom attributes are used on web or for PPV (iOS Only)
+ return (objectGraph.client.isWeb || objectGraph.host.isiOS) && objectGraph.bag.enableProductPageVariants;
+}
+/**
+ * Returns the app variant data for given `data` and `treatmentGroupId`
+ * @param data The data for app resource
+ * @param treatmentGroupId The treatment group to retrieve variant for. (For testing)
+ */
+export function productVariantDataForData(objectGraph, data, treatmentGroupId) {
+ if (treatmentGroupId === undefined) {
+ treatmentGroupId = getClientTreatmentGroupId(objectGraph);
+ }
+ if (isNullOrEmpty(data.id)) {
+ return undefined;
+ }
+ const variantData = {
+ adamID: data.id,
+ productPageId: defaultIdentifier,
+ treatmentPageIdMap: { [defaultIdentifier]: defaultIdentifier },
+ experimentIdMap: {},
+ experimentLocaleMap: {},
+ };
+ if (isNullOrEmpty(traverse(data, "meta", null))) {
+ return variantData; // skip if `meta` is empty.
+ }
+ /**
+ * Determine productPageId (CPP or default)
+ */
+ copyCustomProductPageData(objectGraph, variantData, data);
+ /**
+ * Determine treatmentPageId or `default` pageId
+ *
+ * # Why default, and not `variantData.productPageId` (which can be a CPP id?)
+ * - At product-level, CPPs DON'T support AB Testing Treatments today.
+ */
+ copyExperimentPageData(objectGraph, variantData, defaultIdentifier, treatmentGroupId, data);
+ if (!preprocessor.PRODUCTION_BUILD && objectGraph.client.isiOS) {
+ if (objectGraph.bag.enableAdditionalLoggingForPPV) {
+ objectGraph.console.log(`[PPV] productVariantDataForData: id: ${data.id} productPageId: ${variantData.productPageId} treatmentPageId[default]: ${variantData.treatmentPageIdMap[defaultIdentifier]}`);
+ }
+ }
+ return variantData;
+}
+/**
+ * Copy custom product page data into `ProductVariantData` for given MAPI apps resource.
+ * @param objectGraph Object graph
+ * @param variantData Variant data to copy CPP data to
+ * @param data Apps resource data to source CPP data from
+ */
+function copyCustomProductPageData(objectGraph, variantData, data) {
+ const cppData = traverse(data, "meta.cppData", null);
+ if (isNullOrEmpty(cppData)) {
+ return;
+ }
+ // MAPI checks the validity of the ppid provided, so if it exists, always use it.
+ const customProductPageId = asString(cppData, "ppid");
+ if (isDefinedNonNullNonEmpty(customProductPageId)) {
+ variantData.productPageId = customProductPageId;
+ }
+}
+/**
+ * Copy treatment page data into `ProductVariantData` for a specified `pageId`.
+ * @param objectGraph Object graph
+ * @param variantData Variant data to copy experiment data to
+ * @param pageId The page id to determine treatment for. (Treatments are unique to each page id)
+ * @param data Apps resource data to source experiment data from
+ */
+function copyExperimentPageData(objectGraph, variantData, pageId, treatmentGroupId, data) {
+ const experimentData = traverse(data, "meta.experimentData", null);
+ if (isNullOrEmpty(experimentData)) {
+ return;
+ }
+ // Evaluate for specified `pageId`, which may **not** match `variantData.productPageId`
+ const pageExperimentData = traverse(experimentData, pageId, null);
+ if (isNullOrEmpty(pageExperimentData)) {
+ return;
+ }
+ const experimentId = asString(pageExperimentData, "experimentId");
+ const experimentLocale = asString(pageExperimentData, "experimentLocale");
+ let treatmentPageId = null;
+ const trafficAllocation = traverse(pageExperimentData, "trafficAllocation", null);
+ if (isDefinedNonNullNonEmpty(trafficAllocation)) {
+ // Nonpersonalized endpoint. Resolve treatment from traffic allocation
+ treatmentPageId = matchingTreatmentPageIdFromTrafficAllocation(objectGraph, trafficAllocation, treatmentGroupId);
+ }
+ else {
+ // Personalized endpoint. Infer treatment from thinned variation.
+ treatmentPageId = matchingTreatmentPageIdFromThinnedCustomAttributes(objectGraph, data, pageId);
+ }
+ if (isDefinedNonNullNonEmpty(treatmentPageId) && isDefinedNonNullNonEmpty(experimentId)) {
+ variantData.treatmentPageIdMap[pageId] = treatmentPageId;
+ variantData.experimentIdMap[pageId] = experimentId;
+ if (isDefinedNonNullNonEmpty(experimentLocale)) {
+ variantData.experimentLocaleMap[pageId] = experimentLocale;
+ }
+ }
+}
+/**
+ * Selects the variant custom attribute for given key in custom attributes json.
+ *
+ * For a given attribute key, e.g. `customArtwork`, and a variant data specifying which product page id and treatment id,
+ * it looks in the following locations in priority order:
+ * - customAttributes.{productPageId}.{treatmentPageId}
+ * - customAttributes.{productPageId}.default
+ * - customAttributes.default.{treatmentPageId}
+ * - customAttributes.default.default.
+ *
+ * This effectively means:
+ * - Custom Product Page w/ AB Treatment.
+ * - Custom Product Page w/ default treament
+ * - Default Product Page w/ AB Treatment.
+ * - Default Product Page w/ default treament
+ *
+ * @param objectGraph Dependency cocktail
+ * @param customAttributes A `customAttributes` JSON attribute dict. This is a field in a specific platform attribute, e.g. `platformAttributes.ios.customAttributes`
+ * @param productVariantData Specifies product page id and treatment id to use.
+ * @param attributeKey The attribute to find
+ * @param allowNondefaultTreatmentInNondefaultPage Whether or not to use nondefault treatment can be fetched for nondefault page. Used to limit AB Testing effects on CPPs
+ * @returns `JSONValue` containing attribute for given key, or `null`
+ */
+export function variantAttributeForKey(objectGraph, customAttributes, productVariantData, attributeKey, allowNondefaultTreatmentInNondefaultPage) {
+ if (isNullOrEmpty(customAttributes)) {
+ return null;
+ }
+ // Contains a set of search paths for custom attributes by priority
+ let searchPaths;
+ if (productVariantData.productPageId !== defaultIdentifier) {
+ /**
+ * `productPageId` is nondefault (CPP). Priorities are:
+ * - CPP Page Data
+ * - AB Treatment on Default Page (may be default treatment) IF attribute allows AB testing on CPP (allowNondefaultTreatmentInNondefaultPage).
+ * - Default Treatment on Default Page
+ *
+ * Note that treatment for `productPageId` is skipped today.
+ */
+ const treatmentForDefault = productVariantData.treatmentPageIdMap[defaultIdentifier];
+ if (allowNondefaultTreatmentInNondefaultPage) {
+ // Allow default.treatment after cpp search path.
+ searchPaths = [
+ `${productVariantData.productPageId}.${defaultIdentifier}.${attributeKey}`,
+ `${defaultIdentifier}.${treatmentForDefault}.${attributeKey}`,
+ `${defaultIdentifier}.${defaultIdentifier}.${attributeKey}`,
+ ];
+ }
+ else {
+ // Skip default.treatment after cpp search path.
+ searchPaths = [
+ `${productVariantData.productPageId}.${defaultIdentifier}.${attributeKey}`,
+ `${defaultIdentifier}.${defaultIdentifier}.${attributeKey}`,
+ ];
+ }
+ }
+ else {
+ /**
+ * `productPageId` is default. Priorities are:
+ * - AB Treatment on Default Page (may be default)
+ * - Default Treatment on Default Page
+ */
+ const treatmentForDefault = productVariantData.treatmentPageIdMap[defaultIdentifier];
+ searchPaths = [
+ `${defaultIdentifier}.${treatmentForDefault}.${attributeKey}`,
+ `${defaultIdentifier}.${defaultIdentifier}.${attributeKey}`,
+ ];
+ }
+ for (const path of searchPaths) {
+ const variantAttribute = traverse(customAttributes, path);
+ if (isDefinedNonNull(variantAttribute)) {
+ // `variantAttribute` can be an "empty" override, e.g. no screenshots or no video.
+ return variantAttribute;
+ }
+ }
+ return null;
+}
+/**
+ * Extract the product variant ID (a.k.a. `ppid`, a.k.a. Custom Product Page Identiier) from `ProductVariantData`
+ *
+ * # Product Variant ID v.s. `productPageId`
+ * Product Variant ID is the canonical value for `ppid` query param, used to **request** custom variants for apps resource.
+ * `productPageId` may be equal to product page id, except for when `productPageId` is default which indicates a lack of product variant id.
+ *
+ * @param productVariantData The data to get data for.
+ * @returns `string` or `null` for the product variant id
+ * @seealso customProductPageIdForData
+ */
+export function productVariantIDForVariantData(productVariantData) {
+ // Default is not a valid variant ID.
+ if (isNothing(productVariantData) || productVariantData.productPageId === defaultIdentifier) {
+ return null;
+ }
+ return productVariantData.productPageId;
+}
+/**
+ * Convenience function to retrieve the custom product page id for given `Data` directly
+ * @param objectGraph Object graph
+ * @param data The data to get custom product page id for.
+ * @seealso productVariantIDForVariantData
+ */
+export function customProductPageIdForData(objectGraph, data) {
+ const variantData = productVariantDataForData(objectGraph, data);
+ return productVariantIDForVariantData(variantData);
+}
+/**
+ * Extract all available product variant IDs (a.k.a. `ppId` or `cppId`) from the given app data.
+ *
+ * @param objectGraph Dependencies all the way down.
+ * @param data The app data from which to get the available product variant IDs.
+ * @returns `string[]` for the available product variant ids, or null if there are none.
+ */
+export function allProductVariantIdsForData(objectGraph, data) {
+ const customAttributes = contentAttributeAsDictionary(objectGraph, data, "customAttributes");
+ if (customAttributes === null || isNullOrEmpty(customAttributes)) {
+ return null;
+ }
+ const keys = Object.keys(customAttributes);
+ const allProductVariantIds = keys.filter((key) => key !== defaultIdentifier);
+ return allProductVariantIds;
+}
+/**
+ * Determines the treatment of product page to use for given data from a trafficAllocation json.
+ * This is used on unpersonalized endpoints, where client must resolve traffic allocation manually.
+ * @param trafficAllocation The traffic allocation for a specific page.
+ * @param treatmentGroupId The treatment group to retrieve treatment page id for, e.g. "5". This is usually provided by a cookie.
+ */
+function matchingTreatmentPageIdFromTrafficAllocation(objectGraph, trafficAllocation, treatmentGroupId) {
+ if (treatmentGroupId === undefined || isNullOrEmpty(treatmentGroupId)) {
+ return defaultIdentifier; // Default if no AB bucket.
+ }
+ /**
+ * Iterate over traffic allocation map that looks like:
+ * "85b6a82c-43e6-11eb-b378-0242ac130002": ["1", "19", "51", ...]
+ * "43de034f-43e6-11eb-b378-0242ac130002": ["2", "34", "55", ...]
+ */
+ for (const [treatmentPageId, includedTreatmentGroups] of Object.entries(trafficAllocation)) {
+ if (isDefinedNonNullNonEmpty(includedTreatmentGroups) &&
+ includedTreatmentGroups.indexOf(treatmentGroupId) !== -1) {
+ return treatmentPageId;
+ }
+ }
+ return defaultIdentifier;
+}
+/**
+ * Determines the treatment of product page to use for given data based on thinned custom attributes.
+ * This is used on personalized endpoints, where server can thin the response.
+ * @param pageExperimentData The experiment data specific a page variant
+ * @param treatmentGroupId The treatment group to retrieve treatment page id for, e.g. "5". This is usually provided by a cookie.
+ */
+function matchingTreatmentPageIdFromThinnedCustomAttributes(objectGraph, data, pageId) {
+ // Traverse keys on `customAttributes` to find treatment page id.
+ const customAttributesForPage = contentAttributeAsDictionary(objectGraph, data, `customAttributes.${pageId}`);
+ if (isNullOrEmpty(customAttributesForPage)) {
+ return defaultIdentifier;
+ }
+ const treatmentPageId = Object.keys(customAttributesForPage)[0];
+ if (isNullOrEmpty(treatmentPageId)) {
+ return defaultIdentifier;
+ }
+ return treatmentPageId;
+}
+// endregion
+// region Treatment Group ID
+/**
+ * Retrieve the treatment group id, which is a identifier for a traffic bucket the client belongs in.
+ */
+function getClientTreatmentGroupId(objectGraph) {
+ /**
+ * Use xp_ab cookie value
+ */
+ const treatmentId = productVariantTreatmentId(objectGraph);
+ if (!preprocessor.PRODUCTION_BUILD) {
+ if (lastSeenClientTreatmentId !== treatmentId) {
+ objectGraph.console.log("[PPV] Treatment Group ID", treatmentId);
+ lastSeenClientTreatmentId = treatmentId;
+ }
+ }
+ return treatmentId;
+}
+// endregion
+// region API - Metrics
+/**
+ * Builds the metrics dictionary to add to page fields for a software page w/ product variant features (CPP, AB Testing)
+ * @param productVariantData The variant data of **page** to build page fields for.
+ */
+export function pageFieldsForPageInfoProductVariantData(productVariantData) {
+ const fields = {};
+ /**
+ * Custom Product Page Fields
+ */
+ if (productVariantDataHasVariant(productVariantData, "customProductPage")) {
+ fields["pageCustomId"] = productVariantData.productPageId;
+ }
+ /**
+ * AB Testing Page Fields.
+ * Always from "default" (instead of productVariantData.productPageId).
+ * CPPs don't support AB testing.
+ */
+ const treatmentPageId = productVariantData.treatmentPageIdMap[defaultIdentifier];
+ const experimentId = productVariantData.experimentIdMap[defaultIdentifier];
+ const experimentLocale = productVariantData.experimentLocaleMap[defaultIdentifier];
+ if (productVariantDataHasVariant(productVariantData, "abExperiment")) {
+ fields["pageVariantId"] = treatmentPageId;
+ fields["pageExperimentId"] = experimentId;
+ fields["pageExperimentLocale"] = experimentLocale;
+ }
+ return fields;
+}
+/**
+ * Builds content field included in impressions and locations metrics for a specific product variant.
+ * @param productVariantData The variant data of impressionable data to build impression fields for.
+ */
+export function contentFieldsForProductVariantData(productVariantData) {
+ const fields = {};
+ /**
+ * Custom Product Page Content Fields.
+ * Used to describe the Custom Product Page data being presented in a lockup,
+ * and the Custom Product Page the lockup points to.
+ */
+ if (productVariantDataHasVariant(productVariantData, "customProductPage")) {
+ fields["pageCustomId"] = productVariantData.productPageId;
+ }
+ /**
+ * AB Testing Content Fields.
+ * Always from "default" attributes (instead of productVariantData.productPageId).
+ * CPPs don't support AB testing.
+ */
+ const treatmentPageId = productVariantData.treatmentPageIdMap[defaultIdentifier];
+ const experimentId = productVariantData.experimentIdMap[defaultIdentifier];
+ const experimentLocale = productVariantData.experimentLocaleMap[defaultIdentifier];
+ if (productVariantDataHasVariant(productVariantData, "abExperiment")) {
+ fields["variantId"] = treatmentPageId;
+ fields["experimentId"] = experimentId;
+ fields["experimentLocale"] = experimentLocale;
+ }
+ return fields;
+}
+/**
+ * Update `buyParams` with PPV metrics fields from the page and content's product variant data
+ *
+ * # Why are there two product variant data?
+ * Consider the following scenario:
+ * - On Page for App A that has AB tests
+ * - The page features App B, which also has AB Tests.
+ * On the given page, there are two AB tests occuring in the page. In the `PurchaseConfiguration` for App A and App B, there are two product variant captured:
+ * - Page Product Variant Data for App A (Since we're on App A's Product Page)
+ * - Item Product Variant Data for App A or B, for App A and B respectively.
+ *
+ * @param buyParams The buyParam to add product page variant metrics to
+ * @param adamID The adam id of item being purchased.
+ * @param pageProductVariantData The product variant data for **PAGE** the purchase is occuring on. May be the same as `itemProductVariantData`. May be undefined.
+ * @param itemProductVariantData The product variant data for **ITEM** being purchased. May be undefined.
+ */
+export function addProductPageVariantMetricsToBuyParams(buyParams, adamID, pageProductVariantData, itemProductVariantData) {
+ /**
+ * Only add certain page / fields of fields if data is present, adam id matches, and information isn't redundant.
+ */
+ const addPageFields = isDefinedNonNull(pageProductVariantData) && pageProductVariantData.adamID === adamID;
+ const addContentFields = isDefinedNonNull(itemProductVariantData) && itemProductVariantData.adamID === adamID && !addPageFields; // if we're adding page fields, don't add item variant fields
+ // Product variant data of **PAGE** to buy params.
+ if (addPageFields && isDefinedNonNull(pageProductVariantData)) {
+ const productVariantPageFields = pageFieldsForPageInfoProductVariantData(pageProductVariantData);
+ for (const key of Object.keys(productVariantPageFields)) {
+ const value = asString(productVariantPageFields, key);
+ if (isSome(value)) {
+ buyParams.set(key, value);
+ }
+ }
+ }
+ // Product variant data of **ITEM** within a page to buy params.
+ if (addContentFields && isDefinedNonNull(itemProductVariantData)) {
+ const productVariantFields = contentFieldsForProductVariantData(itemProductVariantData);
+ for (const key of Object.keys(productVariantFields)) {
+ const value = asString(productVariantFields, key);
+ if (isSome(value)) {
+ buyParams.set(key, value);
+ }
+ }
+ }
+}
+/**
+ * Whether or not product variant data has an custom product page / ab testing variants for it.
+ * While we always create a `ProductVariantData` to parse the attributes, this is used to determine if the data actually has
+ * an developer-supplied variant for custom product pages or ab testing experiment from a content perspective.
+ *
+ * @param productVariantData The product variant data to check.
+ * @param variantType The type of variation to query if product variant actually exist for.
+ * @returns Whether or not specified variant exists for content.
+ */
+export function productVariantDataHasVariant(productVariantData, variantType) {
+ if (isNull(productVariantData)) {
+ return false;
+ }
+ switch (variantType) {
+ case "customProductPage":
+ return (isDefinedNonNullNonEmpty(productVariantData.productPageId) &&
+ productVariantData.productPageId !== defaultIdentifier);
+ case "abExperiment":
+ return isDefinedNonNullNonEmpty(productVariantData.experimentIdMap);
+ default:
+ unreachable(variantType);
+ }
+}
+/**
+ * Convenience API for `productVariantDataHasVariant`
+ * @param objectGraph Dependency soup
+ * @param data The apps resource to determine whether a variant is present for
+ * @param variantType The type of variation to query if product variant actually exist for.
+ * @returns Whether or not specified variant exists for content.
+
+ */
+export function appDataHasVariant(objectGraph, data, variantType) {
+ const variantData = productVariantDataForData(objectGraph, data);
+ if (isNothing(variantData)) {
+ return null;
+ }
+ return productVariantDataHasVariant(variantData, variantType);
+}
+// endregion
+// region Requests
+/**
+ * Add PPV specific query parameters necessary to fetch the correct variant in subsequent requests for unhydrated items.
+ * Specifically this adds:
+ * &ppid[apps:<appid>]=<cppid>
+ * for every `Data` resource that had a non-default variant identifier.
+ *
+ * This should be called for every `Request` initializer that passes a `Data[]`, found via "new .*Request\(.*s\)" regex.
+ *
+ * ## Why does this not live in `Request` initializer?
+ * It would be ideal to handle this in the initializer for `Request` that accepts `Data[]` (and extracts `ids`), but
+ * `Request` lives in `foundation` and (tries) to not have feature specific logic, just URL abstractions.
+ * Adding CPP ID determining logic in `Request` constructor violates dependency rules.
+ * This can be solved by another layer of abstraction (see `BaseRequest`), but most requests use `Requests` directly today.
+ *
+ * @param request The request to modify.
+ * @param items Items to add PPV query params for.
+ */
+export function addVariantParametersToRequestForItems(objectGraph, request, items) {
+ /**
+ * For catalog requets for `Data[]`, we also need to specify custom variant if it was initially vended with one:
+ * so the same custom variant is fetched
+ */
+ for (const item of items) {
+ const cppId = customProductPageIdForData(objectGraph, item);
+ if (isDefinedNonNull(cppId)) {
+ request.addingQuery(`ppid[apps:${item.id}]`, `${cppId}`);
+ }
+ }
+}
+// endregion
+// region Constants
+/**
+ * A constant to designate a `default` productPageId or treatmentPageId identifier.
+ */
+const defaultIdentifier = "default";
+/**
+ * Last seen client treatment group id for logging.
+ */
+let lastSeenClientTreatmentId;
+// endregion Constants
+//# sourceMappingURL=product-page-variants.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page.js
new file mode 100644
index 0000000..06e2851
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/product-page.js
@@ -0,0 +1,225 @@
+//
+// product-page.ts
+// AppStoreKit
+//
+// Created by Kevin MacWhinnie on 8/17/16.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as productPageUtil from "./product-page-util";
+import { sectionTitleForPlatform } from "./reviews";
+import { legacyProductPageSectionsFromMappings } from "./shelf-based/product-page-section-mapping";
+import * as productOrdering from "./shelf-based/product-page-shelf-ordering/product-page-shelf-ordering";
+import * as productMediaShelf from "./shelves/product-media-shelf";
+/**
+ * Creates a non-ShelfBasedProductPage from a ShelfBased on
+ * @param objectGraph
+ * @param shelfBasedProductPageModel
+ * @param isArcadeApp
+ * @param options
+ */
+export function createProductPageFromShelfBasedProductPage(objectGraph, shelfBasedProductPageModel, isArcadeApp, options) {
+ return validation.context("createProductPageFromShelfBasedProductPage", () => {
+ var _a, _b, _c, _d;
+ const productPage = new models.ProductPage();
+ // Lockup Level Properties
+ productPage.adamId = shelfBasedProductPageModel.lockup.adamId;
+ productPage.bundleId = shelfBasedProductPageModel.lockup.bundleId;
+ productPage.icon = shelfBasedProductPageModel.lockup.icon;
+ productPage.isEditorsChoice = shelfBasedProductPageModel.lockup.isEditorsChoice;
+ productPage.ordinal = shelfBasedProductPageModel.lockup.ordinal;
+ productPage.title = shelfBasedProductPageModel.lockup.title;
+ productPage.subtitle = shelfBasedProductPageModel.lockup.subtitle;
+ productPage.developerTagline = shelfBasedProductPageModel.lockup.developerTagline;
+ productPage.editorialTagline = shelfBasedProductPageModel.lockup.editorialTagline;
+ productPage.editorialDescription = shelfBasedProductPageModel.lockup.editorialDescription;
+ productPage.shortEditorialDescription = shelfBasedProductPageModel.lockup.shortEditorialDescription;
+ productPage.rating = shelfBasedProductPageModel.lockup.rating;
+ productPage.ratingCount = shelfBasedProductPageModel.lockup.ratingCount;
+ productPage.ageRating = shelfBasedProductPageModel.lockup.ageRating;
+ productPage.buttonAction = shelfBasedProductPageModel.lockup.buttonAction;
+ productPage.offerDisplayProperties = shelfBasedProductPageModel.lockup.offerDisplayProperties;
+ productPage.titleOfferDisplayProperties = shelfBasedProductPageModel.titleOfferDisplayProperties;
+ productPage.clickAction = shelfBasedProductPageModel.lockup.clickAction;
+ productPage.children = shelfBasedProductPageModel.lockup.children;
+ productPage.contextMenuData = shelfBasedProductPageModel.lockup.contextMenuData;
+ productPage.shelfBackground = shelfBasedProductPageModel.lockup.shelfBackground;
+ productPage.searchAd = shelfBasedProductPageModel.lockup.searchAd;
+ productPage.searchAdOpportunity = shelfBasedProductPageModel.lockup.searchAdOpportunity;
+ productPage.crossLinkTitle = shelfBasedProductPageModel.lockup.crossLinkTitle;
+ productPage.crossLinkSubtitle = shelfBasedProductPageModel.lockup.crossLinkSubtitle;
+ productPage.tertiaryTitle = shelfBasedProductPageModel.lockup.tertiaryTitle;
+ productPage.tertiaryTitleAction = shelfBasedProductPageModel.lockup.tertiaryTitleAction;
+ productPage.tertiaryTitleArtwork = shelfBasedProductPageModel.lockup.tertiaryTitleArtwork;
+ productPage.flowPreviewActionsConfiguration = shelfBasedProductPageModel.lockup.flowPreviewActionsConfiguration;
+ productPage.productDescription = shelfBasedProductPageModel.lockup.productDescription;
+ productPage.itemBackground = shelfBasedProductPageModel.lockup.itemBackground;
+ // Page Level Properties
+ productPage.developerAction = shelfBasedProductPageModel.developerAction;
+ productPage.ageRatingAction = shelfBasedProductPageModel.ageRatingAction;
+ productPage.logoArtwork = shelfBasedProductPageModel.logoArtwork;
+ productPage.navigationBarIconArtwork = shelfBasedProductPageModel.navigationBarIconArtwork;
+ productPage.uberArtwork = (_a = shelfBasedProductPageModel.uber) === null || _a === void 0 ? void 0 : _a.artwork;
+ productPage.uberArtworkForCompactDisplay = (_b = shelfBasedProductPageModel.uber) === null || _b === void 0 ? void 0 : _b.compactArtwork;
+ productPage.uberVideo = (_c = shelfBasedProductPageModel.uber) === null || _c === void 0 ? void 0 : _c.video;
+ productPage.uberStyle = (_d = shelfBasedProductPageModel.uber) === null || _d === void 0 ? void 0 : _d.style;
+ productPage.media = allProductMediaFromShelfBasedProductPage(shelfBasedProductPageModel);
+ if (serverData.isDefinedNonNull(shelfBasedProductPageModel.shelfMapping["informationRibbon"])) {
+ productPage.badges = productPageUtil.badgesFromInfoRibbonShelf(objectGraph, shelfBasedProductPageModel.shelfMapping["informationRibbon"]);
+ }
+ else if (objectGraph.host.isWatch || objectGraph.host.isTV) {
+ // copy sidepack badge for watch and tv.
+ productPage.badges = shelfBasedProductPageModel.badges;
+ }
+ productPage.shareAction = shelfBasedProductPageModel.shareAction;
+ productPage.pageMetrics = shelfBasedProductPageModel.pageMetrics;
+ productPage.pageRenderMetrics = shelfBasedProductPageModel.pageRenderMetrics;
+ productPage.isComplete = !shelfBasedProductPageModel.isIncomplete;
+ productPage.hasDarkUserInterfaceStyle = shelfBasedProductPageModel.hasDarkUserInterfaceStyle;
+ productPage.mediaSectionTitle = objectGraph.client.isTV
+ ? null
+ : objectGraph.loc.string("ProductPage.Section.ScreenshotsPreview.Title");
+ productPage.expandedOfferDetails = shelfBasedProductPageModel.expandedOfferDetails;
+ productPage.regularPriceFormatted = shelfBasedProductPageModel.regularPriceFormatted;
+ productPage.theme = shelfBasedProductPageModel.theme;
+ productPage.externalVersionIdentifier = shelfBasedProductPageModel.externalVersionIdentifier;
+ productPage.updateBuyParams = shelfBasedProductPageModel.updateBuyParams;
+ productPage.appPlatforms = shelfBasedProductPageModel.appPlatforms;
+ productPage.descriptionHeader = shelfBasedProductPageModel.descriptionHeader;
+ productPage.description = shelfBasedProductPageModel.description;
+ productPage.banner = shelfBasedProductPageModel.banner;
+ productPage.secondaryBanner = shelfBasedProductPageModel.secondaryBanner;
+ productPage.fullProductFetchedAction = shelfBasedProductPageModel.fullProductFetchedAction;
+ productPage.appPromotionDetailPageFlowAction = shelfBasedProductPageModel.appPromotionDetailPageFlowAction;
+ productPage.pageRefreshPolicy = shelfBasedProductPageModel.pageRefreshPolicy;
+ productPage.purchasedOrdering = legacyProductPageSectionsFromMappings(objectGraph, productOrdering.sectionOrdering(objectGraph, true, objectGraph.host.platform, isArcadeApp, false, false, options));
+ productPage.notPurchasedOrdering = legacyProductPageSectionsFromMappings(objectGraph, productOrdering.sectionOrdering(objectGraph, false, objectGraph.host.platform, isArcadeApp, false, false, options));
+ productPage.shelfMapping = productShelfMappingFromShelfBasedProductPage(objectGraph, shelfBasedProductPageModel);
+ productPage.alwaysAllowReviews = shelfBasedProductPageModel.alwaysAllowReviews;
+ return productPage;
+ });
+}
+function productShelfMappingFromShelfBasedProductPage(objectGraph, shelfBasedPage) {
+ const shelfMapping = {};
+ for (const knownShelfId of models.legacyProductPageKnownShelfIds) {
+ let shelf;
+ if (knownShelfId === "reviews") {
+ shelf = productReviewsShelfFromShelfBasedProductPage(objectGraph, shelfBasedPage);
+ }
+ else {
+ shelf = shelfBasedPage.shelfMapping[knownShelfId];
+ }
+ if (serverData.isDefinedNonNullNonEmpty(shelf)) {
+ shelfMapping[knownShelfId] = shelf;
+ }
+ }
+ return shelfMapping;
+}
+export function allProductMediaFromShelfBasedProductPage(shelfBasedPage) {
+ const productMedia = [];
+ for (const shelf of Object.values(shelfBasedPage.shelfMapping)) {
+ if (productMediaShelf.isProductMediaShelf(shelf)) {
+ const productMediaMetaData = shelf.contentsMetadata;
+ const productMediaItems = shelf.items;
+ productMedia.push(new models.ProductMedia(productMediaItems, productMediaMetaData.platform, productMediaMetaData.allPlatforms, productMediaMetaData.platformDescription, productMediaMetaData.allPlatformsDescription, productMediaMetaData.allPlatformsDescriptionPlacement));
+ }
+ }
+ return productMedia;
+}
+export function mediaPreviewContentFromShelfBasedProductPage(shelfBasedPage) {
+ const content = [];
+ for (const shelf of Object.values(shelfBasedPage.shelfMapping)) {
+ if (productMediaShelf.isProductMediaShelf(shelf)) {
+ const productMediaMetaData = shelf.contentsMetadata;
+ const productMediaItems = shelf.items;
+ const media = new models.ProductMedia(productMediaItems, productMediaMetaData.platform, productMediaMetaData.allPlatforms, productMediaMetaData.platformDescription, productMediaMetaData.allPlatformsDescription, productMediaMetaData.allPlatformsDescriptionPlacement);
+ content.push({
+ metaData: productMediaMetaData,
+ media: media,
+ });
+ }
+ }
+ return content;
+}
+function productReviewsShelfFromShelfBasedProductPage(objectGraph, shelfBasedPage) {
+ const productRatingsShelf = shelfBasedPage.shelfMapping["productRatings"];
+ const allProductReviewsShelf = shelfBasedPage.shelfMapping["allProductReviews"];
+ const editorsChoiceProductReviewsShelf = objectGraph.client.isiOS
+ ? shelfBasedPage.shelfMapping["editorsChoice"]
+ : shelfBasedPage.shelfMapping["editorsChoiceProductReviews"];
+ const tapToRateProductReviewActionShelf = shelfBasedPage.shelfMapping["tapToRateProductReviewAction"];
+ const writeAReviewProductReviewActionShelf = shelfBasedPage.shelfMapping["writeAReviewProductReviewAction"];
+ if (serverData.isNullOrEmpty(productRatingsShelf) &&
+ serverData.isNullOrEmpty(allProductReviewsShelf) &&
+ serverData.isNullOrEmpty(editorsChoiceProductReviewsShelf) &&
+ serverData.isNullOrEmpty(tapToRateProductReviewActionShelf) &&
+ serverData.isNullOrEmpty(writeAReviewProductReviewActionShelf)) {
+ return null;
+ }
+ let editorsChoiceReview = null;
+ let userReviews = [];
+ let ratings = null;
+ let tapToRate = null;
+ let writeReviewAction = null;
+ let supportAction = null;
+ let seeAllAction = null;
+ if (serverData.isDefinedNonNullNonEmpty(allProductReviewsShelf === null || allProductReviewsShelf === void 0 ? void 0 : allProductReviewsShelf.items)) {
+ for (const item of allProductReviewsShelf.items) {
+ const productReview = item;
+ switch (productReview.sourceType) {
+ case "editorsChoice":
+ editorsChoiceReview = productReview.review;
+ break;
+ case "user":
+ userReviews.push(productReview.review);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ else {
+ userReviews = null;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(editorsChoiceProductReviewsShelf === null || editorsChoiceProductReviewsShelf === void 0 ? void 0 : editorsChoiceProductReviewsShelf.items)) {
+ if (objectGraph.client.isiOS) {
+ // On iOS, Editors' Choice is a separate shelf to reviews. On all other platforms, editors choice is embedded in the reviews list.
+ editorsChoiceReview = editorsChoiceProductReviewsShelf.items[0];
+ }
+ else {
+ const productReview = editorsChoiceProductReviewsShelf.items[0];
+ editorsChoiceReview = productReview.review;
+ }
+ }
+ if (serverData.isDefinedNonNullNonEmpty(tapToRateProductReviewActionShelf === null || tapToRateProductReviewActionShelf === void 0 ? void 0 : tapToRateProductReviewActionShelf.items)) {
+ const productReviewAction = tapToRateProductReviewActionShelf.items[0];
+ tapToRate = productReviewAction.action;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(writeAReviewProductReviewActionShelf === null || writeAReviewProductReviewActionShelf === void 0 ? void 0 : writeAReviewProductReviewActionShelf.items)) {
+ const productReviewAction = writeAReviewProductReviewActionShelf.items[0];
+ const writeAReview = productReviewAction.action;
+ writeReviewAction = writeAReview.writeReviewAction;
+ supportAction = writeAReview.supportAction;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(productRatingsShelf === null || productRatingsShelf === void 0 ? void 0 : productRatingsShelf.items)) {
+ ratings = productRatingsShelf.items[0];
+ seeAllAction = productRatingsShelf === null || productRatingsShelf === void 0 ? void 0 : productRatingsShelf.seeAllAction;
+ }
+ const reviewsContainer = new models.ReviewsContainer();
+ reviewsContainer.adamId = shelfBasedPage.lockup.adamId;
+ reviewsContainer.ratings = ratings;
+ reviewsContainer.tapToRate = tapToRate;
+ reviewsContainer.editorsChoice = editorsChoiceReview;
+ reviewsContainer.reviews = userReviews;
+ reviewsContainer.supportAction = supportAction;
+ reviewsContainer.writeReviewAction = writeReviewAction;
+ reviewsContainer.alwaysAllowReviews = shelfBasedPage.alwaysAllowReviews;
+ const reviewsShelf = new models.Shelf("reviewsContainer");
+ reviewsShelf.title = sectionTitleForPlatform(objectGraph);
+ reviewsShelf.items = [reviewsContainer];
+ reviewsShelf.seeAllAction = seeAllAction;
+ return reviewsShelf;
+}
+//# sourceMappingURL=product-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/reviews.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/reviews.js
new file mode 100644
index 0000000..26ac212
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/reviews.js
@@ -0,0 +1,1077 @@
+/**
+ * Created by km on 10/14/16.
+ */
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import { Path, Protocol, Parameters } from "../../foundation/network/url-constants";
+import * as urls from "../../foundation/network/urls";
+import * as objects from "../../foundation/util/objects";
+import * as contentAttributes from "../content/attributes";
+import * as sad from "../content/sad";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as pagination from "../builders/pagination";
+import * as artworkBuilder from "../content/artwork/artwork";
+import { supportedAppPlatformsFromData } from "../content/content";
+import * as flowPreview from "../content/flow-preview";
+import * as productPageUtil from "./product-page-util";
+import { createProductReviewActions } from "./shelves/shelf-based-reviews-shelves";
+import { flowPreviewActionsConfigurationForReviewSummaryFromData } from "../content/flow-preview";
+import { isNothing, isSome, unwrapOptional } from "@jet/environment";
+import { createEditorsChoiceModel } from "./shelves/editors-choice-shelf";
+import { subtitleFromData } from "../lockups/lockups";
+import { makeProductPageIntent } from "../../api/intents/product-page-intent";
+import { getPlatform } from "../preview-platform";
+import { getLocale } from "../locale";
+import { actionFor } from "../../foundation/runtime/action-provider";
+import { makeSeeAllPageIntent } from "../../api/intents/see-all-page-intent";
+import { makeSeeAllPageURL } from "./intent-controller-routing";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+const displayableKindMobileSoftware = "11";
+const displayableKindMobileSoftwareBundle = "43";
+const displayableKindMacSoftware = "30";
+const displayableKindMacSoftwareBundle = "44";
+// Mapping of displayable-kind from software to bundle
+const softwareToBundleKindMap = {
+ [displayableKindMobileSoftware]: displayableKindMobileSoftwareBundle,
+ [displayableKindMacSoftware]: displayableKindMacSoftwareBundle,
+};
+/// The default sort id to use, `Most Helpful`.
+const sortIdDefault = "helpful";
+/**
+ * Token used to construct a personalized reviews shelf.
+ */
+export class PersonalizedReviewsShelfToken {
+}
+/**
+ * Creates a pagination url for reviews pagination.
+ */
+export function reviewsPaginationUrl(objectGraph, adamId, sort) {
+ return `${Protocol.internal}:/${Path.reviews}/${Path.shelf}/${adamId}/${sort}`;
+}
+/**
+ * Creates a page url for changing sorting order.
+ */
+export function sortedReviewsPageUrl(objectGraph, adamId, sort, sortedReviewsPageToken = null) {
+ let url = `${Protocol.internal}:/${Path.reviews}/${adamId}/${sort}`;
+ if (serverData.isDefinedNonNullNonEmpty(sortedReviewsPageToken)) {
+ url = `${url}/?${Parameters.token}=${encodeURIComponent(JSON.stringify(sortedReviewsPageToken))}`;
+ }
+ return url;
+}
+/**
+ * Creates a pagination token.
+ */
+export function createReviewsPageToken(objectGraph, adamId, nextHref, sort, remainingContent) {
+ return {
+ url: reviewsPaginationUrl(objectGraph, adamId, sort),
+ remainingContent: remainingContent,
+ nextHref: nextHref,
+ profile: "lockup",
+ maxPerPage: pagination.suggestedMaxPerPage,
+ highestOrdinal: 0,
+ metricsPageInformation: null,
+ metricsLocationTracker: null,
+ };
+}
+/**
+ * Whether reviews should be suppressed for the given app.
+ *
+ * @param {Data} data MAPI data blob describing the app.
+ * @returns {boolean} Returns `true` if reviews are restricted.
+ */
+export function shouldSuppressReviews(objectGraph, data) {
+ if (!serverData.isDefinedNonNull(data)) {
+ return false;
+ }
+ // If we have a system deletable and force-1st-party-app-review bag key
+ const allowsReviewsForSADApp = shouldAllowReviewsForSADApp(objectGraph, data);
+ if (allowsReviewsForSADApp) {
+ return false;
+ }
+ // Suppress reviews for first party apps
+ const isFirstPartyHideableApp = mediaAttributes.attributeAsBooleanOrFalse(data, "isFirstPartyHideableApp");
+ if (isFirstPartyHideableApp) {
+ return true;
+ }
+ // Suppress reviews for restricted apps
+ const areReviewsRestricted = mediaAttributes.attributeAsBooleanOrFalse(data, "reviewsRestricted");
+ if (areReviewsRestricted) {
+ return true;
+ }
+ return false;
+}
+/**
+ * Whether reviews should be allowed for the given app if it is a 1st party/SAD app.
+ *
+ * @param {Data} data MAPI data blob describing the app.
+ * @returns {boolean} Returns `true` if the app is a 1st party/SAD app and current bag has reviews enabled for 1st party/SAD apps.
+ */
+export function shouldAllowReviewsForSADApp(objectGraph, data) {
+ if (serverData.isNullOrEmpty(data) || !objectGraph.bag.enableSystemAppReviews) {
+ return false;
+ }
+ // Since the bag for watch and iOS have different apps included as SAD apps, it is possible that we are not showing reviews for the following scenario:
+ // The watch is direct linked to an app that is iOS only. This means that is should be SAD app from the iOS side, but is not a SAD app on the watch bag,
+ // so it doesn't qualify for reviews. However, these apps have the metadata tag of "isFirstPartyHideableApp" that we will use to circumvent this limitation.
+ // For watch, we will see if an app is a SAD app for the watch, or if an app is technically a SAD app for the iPhone we are viewing on the watch.
+ if (objectGraph.client.isWatch) {
+ return (sad.systemApps(objectGraph).isSystemAppFromData(data) ||
+ mediaAttributes.attributeAsBooleanOrFalse(data, "isFirstPartyHideableApp"));
+ }
+ else {
+ return sad.systemApps(objectGraph).isSystemAppFromData(data);
+ }
+}
+/**
+ * Whether reviews should be allowed for a given app, this returns true for all standard apps, for system apps
+ * this will return true only if system app reviews are enabled. For non-shelf-based product pages we return `true` because
+ * this logic is handled natively.
+ *
+ * @param objectGraph
+ * @param {Data} data MAPI data blob describing the app.
+ * @returns {boolean} Returns `true` if the app is not a system app or if system app reviews are allowed.
+ */
+export function isAppReviewable(objectGraph, data) {
+ if (productPageUtil.isShelfBased(objectGraph)) {
+ return true;
+ }
+ if (serverData.isNullOrEmpty(data)) {
+ return false;
+ }
+ let isSystemApp;
+ // Since the bag for watch and iOS have different apps included as SAD apps, it is possible that we are not showing reviews for the following scenario:
+ // The watch is direct linked to an app that is iOS only. This means that is should be SAD app from the iOS side, but is not a SAD app on the watch bag,
+ // so it doesn't qualify for reviews. However, these apps have the metadata tag of "isFirstPartyHideableApp" that we will use to circumvent this limitation.
+ // For watch, we will see if an app is a SAD app for the watch, or if an app is technically a SAD app for the iPhone we are viewing on the watch.
+ if (objectGraph.client.isWatch) {
+ isSystemApp =
+ sad.systemApps(objectGraph).isSystemAppFromData(data) ||
+ mediaAttributes.attributeAsBooleanOrFalse(data, "isFirstPartyHideableApp");
+ }
+ else {
+ isSystemApp = sad.systemApps(objectGraph).isSystemAppFromData(data);
+ }
+ return !isSystemApp || objectGraph.bag.enableSystemAppReviews;
+}
+/**
+ * Generates a tap-to-rate endpoint URL for the given parameter.
+ * @param adamId The adam id of the product to get review data for.
+ * @param isBundle Whether this is a bundle adamId
+ * @returns A URL for posting tap-to-rate data.
+ */
+function assembleUserRateURL(objectGraph, adamId, isBundle) {
+ return createBaseURL(objectGraph.bag.userRateURL, isBundle).param("id", adamId).build();
+}
+/**
+ * Generates a writeUserReview endpoint URL for the given parameter.
+ * @param adamId The adam id of the product to be reviewed.
+ * @param isBundle Whether this is a bundle adamId
+ * @returns A URL for posting write review data.
+ */
+function assembleWriteReviewURL(objectGraph, adamId, isBundle) {
+ return createBaseURL(objectGraph.bag.writeReviewURL, isBundle).param("id", adamId).build();
+}
+function createBaseURL(baseURL, isBundle) {
+ const url = urls.URL.from(baseURL);
+ const displayableKind = serverData.asString(url.query, "displayable-kind");
+ // Convert displayable-kind for bundles
+ if (isBundle && (displayableKind === null || displayableKind === void 0 ? void 0 : displayableKind.length) > 0) {
+ url.query["displayable-kind"] = softwareToBundleKindMap[displayableKind] || displayableKind;
+ }
+ return url;
+}
+/**
+ * Create and return a rate action for a given adamId.
+ * @param adamId The adamId of the app to rate
+ * @param isBundle Whether this is a bundle adamId
+ * @param appName the name of the app
+ * @returns A new rate action.
+ */
+export function userRateAction(objectGraph, adamId, isBundle, appName = null) {
+ const successToast = new models.AlertAction("toast");
+ successToast.title = objectGraph.loc.string("TOAST_TAP_TO_RATE_TITLE");
+ successToast.message = objectGraph.loc.string("TOAST_TAP_TO_RATE_DESCRIPTION");
+ successToast.artwork = artworkBuilder.createArtworkForResource(objectGraph, "resource://ToastStar.png", 95.0, 90.0);
+ const action = new models.RateAction(assembleUserRateURL(objectGraph, adamId, isBundle));
+ action.adamId = adamId;
+ action.method = "POST";
+ action.isStoreRequest = true;
+ action.disableCache = true;
+ action.successAction = successToast;
+ const ratingParameter = new models.HttpTemplateParameter("rating", "urlQuery", "decimalPad");
+ const appVersionParameter = new models.HttpTemplateParameter("version-to-review", "urlQuery", "decimalPad");
+ action.parameters = [ratingParameter, appVersionParameter];
+ if (objectGraph.client.isTV && (appName === null || appName === void 0 ? void 0 : appName.length) > 0) {
+ action.title = objectGraph.loc.string("TV_RATE_PRODUCT").replace("{title}", appName);
+ }
+ return action;
+}
+export function createProductReviewsList(objectGraph, ratingsData, reviewItems, includeMoreAction = false, editorsChoiceReview, reviewSummary, shelfMetrics) {
+ return validation.context("createProductReviewsList", () => {
+ const productReviews = [];
+ if (serverData.isDefinedNonNullNonEmpty(reviewSummary) && objectGraph.client.isiOS) {
+ if (isSome(shelfMetrics)) {
+ const shelfMetricsOptions = {
+ id: `${shelfMetrics.getSequenceId()}`,
+ kind: null,
+ softwareType: null,
+ targetType: "reviewSummary",
+ title: objectGraph.loc.string("ProductPage.ReviewSummary.Body.Title"),
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ idType: "sequential",
+ };
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "reviewSummary",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ }, objectGraph.loc.string("ProductPage.ReviewSummary.Body.Title"));
+ metricsHelpersImpressions.addImpressionFields(objectGraph, reviewSummary, shelfMetricsOptions);
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ }
+ productReviews.push(reviewSummary);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(editorsChoiceReview) && !objectGraph.client.isiOS) {
+ productReviews.push(editorsChoiceReview);
+ }
+ if (serverData.isDefinedNonNull(reviewItems) && reviewItems.length > 0) {
+ const isWatchStore = objectGraph.client.isWatch;
+ // We omit the actions on watchOS, they aren't used.
+ const includeActions = !isWatchStore;
+ // We limit the number of reviews to one on watchOS. That's all
+ // we can display inline, and we want to reduce memory usage.
+ const fixedUpReviewItems = isWatchStore ? reviewItems.slice(0, 1) : reviewItems;
+ const includeFeedbackActions = objectGraph.client.isVision;
+ const userReviews = createReviewItems(objectGraph, objectGraph.client.guid, ratingsData, fixedUpReviewItems, includeMoreAction && includeActions, includeFeedbackActions && includeActions, includeActions, shelfMetrics);
+ if (isSome(shelfMetrics)) {
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "mostHelpfulReviews",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ }, null);
+ }
+ userReviews.forEach((userReview, index) => {
+ const productReview = new models.ProductReview();
+ productReview.sourceType = "user";
+ productReview.review = userReview;
+ productReviews.push(productReview);
+ if (isSome(shelfMetrics)) {
+ const options = {
+ id: productReview.id,
+ idType: "its_id",
+ kind: null,
+ softwareType: null,
+ title: null,
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "helpfulReview",
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, productReview, options);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ }
+ });
+ if (isSome(shelfMetrics)) {
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ }
+ }
+ return productReviews;
+ });
+}
+export function createEditorsChoiceReview(objectGraph, data) {
+ return validation.context("editorsChoiceItem", () => {
+ if (objectGraph.client.isiOS) {
+ // On iOS, Editors' Choice is a separate shelf to reviews. On all other platforms, editors choice is embedded in the reviews list.
+ return null;
+ }
+ const editorsChoice = createEditorsChoiceModel(objectGraph, data);
+ if (isSome(editorsChoice)) {
+ const productReview = new models.ProductReview();
+ productReview.sourceType = "editorsChoice";
+ productReview.review = editorsChoice;
+ if (objectGraph.client.isVision) {
+ productReview.review.clickAction = createEditorsChoiceDetailActionForEditorsChoice(objectGraph, productReview.review);
+ }
+ return productReview;
+ }
+ return null;
+ });
+}
+/**
+ * Creates a flow action for navigating to the expanded view of an editors choice item.
+ *
+ * @param objectGraph The current object graph
+ * @param editorsChoice The editors choice item
+ * @returns A flow action
+ */
+function createEditorsChoiceDetailActionForEditorsChoice(objectGraph, editorsChoice) {
+ const flowAction = new models.FlowAction("editorsChoiceDetail");
+ flowAction.pageData = objects.shallowCopyOf(editorsChoice);
+ return flowAction;
+}
+/**
+ * Converts an array of user review rows into an array of review objects.
+ * @param deviceId The UUID for the user's device.
+ * @param rows User review rows from an API response.
+ * @returns An array of zero or more review objects.
+ */
+export function createReviewItems(objectGraph, deviceId, ratingsData, reviewItems, includeMoreAction = false, includeFeedbackActions = false, includeFlowPreviewActionsConfiguration = true, shelfMetrics) {
+ return validation.context("createReviewItems", () => {
+ const reviewDateString = function (date, isEdited) {
+ if (isEdited) {
+ const now = new Date();
+ const withinAnHour = (now.getTime() - date.getTime()) * 1000 < 60 * 60;
+ if (withinAnHour) {
+ return objectGraph.loc.string("TimeAgo.Edited.JustNow");
+ }
+ else {
+ return objectGraph.loc
+ .string("TimeAgo.Edited.Time")
+ .replace("{time}", objectGraph.loc.timeAgoWithContext(date, "standalone"));
+ }
+ }
+ else {
+ return objectGraph.loc.timeAgoWithContext(date, "standalone");
+ }
+ };
+ return reviewItems.map((item) => {
+ var _a;
+ const review = new models.Review();
+ review.id = serverData.asString(item, "id", "coercible");
+ review.title = mediaAttributes.attributeAsString(item, "title");
+ const rawDate = mediaAttributes.attributeAsString(item, "date");
+ if (rawDate) {
+ review.date = new Date(rawDate);
+ review.dateText = reviewDateString(review.date, mediaAttributes.attributeAsBooleanOrFalse(item, "isEdited"));
+ }
+ review.contents = mediaAttributes.attributeAsString(item, "review");
+ review.rating = mediaAttributes.attributeAsNumber(item, "rating");
+ review.reviewerName = mediaAttributes.attributeAsString(item, "userName");
+ review.dateAuthorText = objectGraph.loc
+ .string("ProductPage.Section.Reviews.DateAuthor")
+ .replace("{date}", review.dateText)
+ .replace("{author}", review.reviewerName);
+ let responseToReviewText = null;
+ const responseId = mediaAttributes.attributeAsString(item, "developerResponse.id");
+ if ((responseId === null || responseId === void 0 ? void 0 : responseId.length) > 0) {
+ const response = new models.Response();
+ response.id = responseId;
+ response.contents = mediaAttributes.attributeAsString(item, "developerResponse.body");
+ const rawResponseDate = mediaAttributes.attributeAsString(item, "developerResponse.modified");
+ if (rawResponseDate) {
+ response.date = new Date(rawResponseDate);
+ response.dateText = reviewDateString(response.date, false);
+ }
+ review.response = response;
+ responseToReviewText = review.response.contents;
+ }
+ if (includeFlowPreviewActionsConfiguration) {
+ const adamId = serverData.asString(ratingsData, "adamId");
+ review.flowPreviewActionsConfiguration = flowPreview.flowPreviewActionsConfigurationForReviewFromData(objectGraph, item, deviceId, adamId, responseToReviewText);
+ }
+ if (includeFeedbackActions) {
+ const voteActions = [
+ flowPreview.voteActionFromData(objectGraph, item, deviceId, true),
+ flowPreview.voteActionFromData(objectGraph, item, deviceId, false),
+ ];
+ if (((_a = objectGraph.bag.reportConcernUrl) === null || _a === void 0 ? void 0 : _a.length) > 0) {
+ voteActions.push(flowPreview.reportConcernActionFromData(objectGraph, item, deviceId));
+ }
+ review.voteActions = voteActions;
+ }
+ if (includeMoreAction) {
+ if (objectGraph.client.isVision) {
+ review.moreAction = createReviewDetailActionForReview(objectGraph, review);
+ // Clear out the voteActions, as we only need them in the copy of the review
+ // attached to the `moreAction`.
+ review.voteActions = null;
+ }
+ else {
+ review.moreAction = singleReviewPageActionFromReviewItem(objectGraph, deviceId, ratingsData, item, shelfMetrics);
+ }
+ }
+ return review;
+ });
+ });
+}
+/**
+ * Create a ratings object from the responses of the requests to get product reviews.
+ * @param deviceId The UUID for the user's device.
+ * @param ratingsData The basic review data response.
+ * @returns A customer reviews object.
+ */
+export function ratingsFromApiResponses(objectGraph, deviceId, context, ratingsData) {
+ if (!ratingsData) {
+ return null;
+ }
+ return validation.context("ratingsFromApiResponses", () => {
+ const ratings = new models.Ratings();
+ ratings.productId = serverData.asString(ratingsData, "adamId", "coercible");
+ ratings.ratingAverage = serverData.asNumber(ratingsData, "ratingAverage");
+ ratings.totalNumberOfRatings = serverData.asNumber(ratingsData, "ratingCount");
+ ratings.totalNumberOfReviews = serverData.asNumber(ratingsData, "totalNumberOfReviews");
+ ratings.context = context;
+ // Translate rating counts for each star level to represent the number of ratings out of `totalNumberOfRatings`.
+ const absoluteRatingsCounts = serverData
+ .asArrayOrEmpty(ratingsData, "ratingCountList")
+ .slice()
+ .reverse();
+ const totalAbsoluteRatingsCounts = absoluteRatingsCounts.reduce((a, b) => a + b, 0);
+ if (totalAbsoluteRatingsCounts > 0) {
+ ratings.ratingCounts = absoluteRatingsCounts.map((absoluteCount) => (absoluteCount / totalAbsoluteRatingsCounts) * ratings.totalNumberOfRatings);
+ }
+ else {
+ ratings.ratingCounts = absoluteRatingsCounts;
+ }
+ const hasRatings = ratings.ratingAverage > 0 && ratings.ratingCounts;
+ if (!hasRatings) {
+ const wasReset = serverData.asBooleanOrFalse(ratingsData, "wasReset");
+ ratings.status = wasReset
+ ? objectGraph.loc.string("RATINGS_STATUS_DEVELOPER_RESET")
+ : objectGraph.loc.string("RATINGS_STATUS_NOT_ENOUGH_RATINGS");
+ }
+ return ratings;
+ });
+}
+export function createReviewSummaryProductReview(objectGraph, data, shelfMetrics) {
+ return validation.context("createReviewSummaryProductReview", () => {
+ const reviewSummary = reviewSummaryFromData(objectGraph, data, shelfMetrics);
+ if (isNothing(reviewSummary)) {
+ return null;
+ }
+ const reviewSummaryProductReview = new models.ProductReview();
+ reviewSummaryProductReview.review = reviewSummary;
+ reviewSummaryProductReview.sourceType = "reviewSummary";
+ return reviewSummaryProductReview;
+ });
+}
+/**
+ * Create a review summary object from the product page data
+ * @param data Product page data
+ * @param objectGraph Current object graph
+ * @returns The review summary object
+ */
+export function reviewSummaryFromData(objectGraph, data, shelfMetrics) {
+ if (!isReviewSummaryEnabled(objectGraph)) {
+ return null;
+ }
+ const reviewSummaryRelationship = mediaRelationship.relationship(data, "review-summary");
+ if (isNothing(reviewSummaryRelationship)) {
+ return null;
+ }
+ const reviewSummaryDataArray = reviewSummaryRelationship.data;
+ if (serverData.isNullOrEmpty(reviewSummaryDataArray)) {
+ return null;
+ }
+ const reviewSummaryData = reviewSummaryDataArray[0];
+ const reviewSummaryId = serverData.asString(reviewSummaryData, "id", "coercible");
+ const reviewSummaryBodyWithTitle = createReviewSummaryBody(objectGraph, reviewSummaryData, true);
+ const reviewSummaryBodyNoTitle = createReviewSummaryBody(objectGraph, reviewSummaryData, false);
+ const subtitle = objectGraph.loc.string("ProductPage.ReviewSummary.Subtitle");
+ const reviewSummaryReportConcernData = objectGraph.bag.reviewSummaryReportConcernData;
+ if (isNothing(reviewSummaryBodyNoTitle) || isNothing(reviewSummaryBodyWithTitle)) {
+ return null;
+ }
+ const appName = mediaAttributes.attributeAsString(data, "name");
+ const reviewSummaryText = contentAttributes.contentAttributeAsString(objectGraph, reviewSummaryData, "text");
+ const reviewSummaryArtwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://text.line.2.summary");
+ const reviewSummary = new models.ReviewSummary(reviewSummaryBodyWithTitle, reviewSummaryBodyNoTitle, subtitle, reviewSummaryArtwork, "leading", "text/markdown", flowPreviewActionsConfigurationForReviewSummaryFromData(objectGraph, reviewSummaryReportConcernData, data.id, appName, reviewSummaryId, reviewSummaryText, objectGraph.client.guid));
+ // Add metrics before serializing token for url
+ const shelfMetricsOptions = {
+ id: `${shelfMetrics.getSequenceId()}`,
+ kind: null,
+ softwareType: null,
+ targetType: "reviewSummary",
+ title: objectGraph.loc.string("ProductPage.ReviewSummary.Body.Title"),
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ idType: "sequential",
+ };
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "reviewSummary",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ }, reviewSummary.title);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, reviewSummary, shelfMetricsOptions);
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ return reviewSummary;
+}
+/**
+ * Checks if the review summary module should be enabled
+ * @param objectGraph
+ * @returns true if the feature flag and bag key are enabled and the platform is iOS
+ */
+export function isReviewSummaryEnabled(objectGraph) {
+ return objectGraph.client.isiOS && objectGraph.bag.enableReviewSummarization;
+}
+/**
+ * Builds the review summary text using the review summary text
+ * @param objectGraph The current object graph
+ * @param reviewSummaryData The review summary content attributes
+ * @returns The built review summary body text
+ */
+function createReviewSummaryBody(objectGraph, reviewSummaryData, withTitle) {
+ const reviewSummaryText = contentAttributes.contentAttributeAsString(objectGraph, reviewSummaryData, "text");
+ if (isNothing(reviewSummaryText)) {
+ return null;
+ }
+ const reviewSummaryTitle = objectGraph.loc.string("ProductPage.ReviewSummary.Body.Title");
+ const reviewSummaryTitleStyled = `^[${reviewSummaryTitle}](jetFont: 'reviewSummaryTitle')`;
+ const reviewSummaryTextStyled = `^[${reviewSummaryText}](jetFont: 'reviewSummaryText')`;
+ if (!withTitle) {
+ return reviewSummaryTextStyled;
+ }
+ const reviewSummaryBody = objectGraph.loc
+ .string("ProductPage.ReviewSummary.Body")
+ .replace("{styledTitle}", reviewSummaryTitleStyled)
+ .replace("{reviewSummary}", reviewSummaryTextStyled);
+ return reviewSummaryBody;
+}
+export function starRatingsFromRatings(ratings) {
+ if (!ratings) {
+ return null;
+ }
+ const starRatings = new models.ProductStarRatings();
+ copyRatingsInfoIntoOther(ratings, starRatings);
+ return starRatings;
+}
+export function starRatingsHistogramFromRatings(ratings) {
+ if (!ratings) {
+ return null;
+ }
+ const starRatings = new models.ProductStarRatingsHistogram();
+ copyRatingsInfoIntoOther(ratings, starRatings);
+ return starRatings;
+}
+export function noRatingsFromRatings(ratings) {
+ if (!ratings) {
+ return null;
+ }
+ const noRatings = new models.ProductNoRatings();
+ copyRatingsInfoIntoOther(ratings, noRatings);
+ return noRatings;
+}
+function copyRatingsInfoIntoOther(original, other) {
+ other.ratingAverage = original.ratingAverage;
+ other.ratingCounts = original.ratingCounts;
+ other.totalNumberOfRatings = original.totalNumberOfRatings;
+ other.totalNumberOfReviews = original.totalNumberOfReviews;
+ other.status = original.status;
+ other.reviews = original.reviews;
+ other.actions = original.actions;
+ other.nextPage = original.nextPage;
+}
+/**
+ * Create a tap to rate model from the lookup response
+ * @param adamId The adamId of the app.
+ * @param appName The name of the app.
+ * @param isBundle Whether this is a bundle adamId
+ * @param rating The current rating
+ * @returns A single tap to rate model object
+ */
+export function tapToRateWithAdamId(objectGraph, adamId, isBundle, appName = null, rating = null) {
+ const tapToRate = new models.TapToRate();
+ switch (objectGraph.client.deviceType) {
+ case "tv":
+ tapToRate.title = objectGraph.loc.string("TV_SELECT_TO_RATE");
+ break;
+ case "mac":
+ tapToRate.title = objectGraph.loc.string("CLICK_TO_RATE");
+ break;
+ default:
+ tapToRate.title = objectGraph.client.isiOS
+ ? objectGraph.loc.string("TAP_TO_RATE")
+ : objectGraph.loc.string("TAP_TO_RATE_LEGACY");
+ break;
+ }
+ tapToRate.rating = rating;
+ tapToRate.rateAction = userRateAction(objectGraph, adamId, isBundle, appName);
+ return tapToRate;
+}
+/**
+ * Creates a platform-specific action to write a review.
+ * @param adamId The app's identifier.
+ * @param isBundle Whether this is a bundle adamId
+ * @param appIcon The app's icon.
+ * @param isRated Whether the app has an existing rating
+ * @returns Action to show the write a review sheet.
+ */
+export function createWriteReviewAction(objectGraph, adamId, isBundle, appIcon, isRated = false, lockupSubtitle, lockupTitle) {
+ return validation.context("createWriteReviewAction", () => {
+ const title = isRated ? objectGraph.loc.string("EDIT_REVIEW") : objectGraph.loc.string("WRITE_A_REVIEW");
+ const writeReviewUrl = assembleWriteReviewURL(objectGraph, adamId, isBundle);
+ let appIconWithCrop;
+ if (isSome(appIcon)) {
+ // The artwork template is ultimately passed to AMS, and doesn't go through our pipeline,
+ // so we need to ensure the crop code and file type is already populated.
+ const variant = artworkBuilder.createArtworkVariantForClient(objectGraph, true, false, 1 /* ArtworkUseCase.LockupIconSmall */);
+ const template = appIcon.template
+ .replace("{c}", `${appIcon.crop}-${variant.quality}`)
+ .replace("{f}", variant.format);
+ appIconWithCrop = new models.Artwork(template, appIcon.width, appIcon.height, appIcon.variants);
+ appIconWithCrop.backgroundColor = appIcon.backgroundColor;
+ appIconWithCrop.textColor = appIcon.textColor;
+ appIconWithCrop.checksum = appIcon.checksum;
+ appIconWithCrop.style = appIcon.style;
+ appIconWithCrop.crop = appIcon.crop;
+ appIconWithCrop.contentMode = appIcon.contentMode;
+ appIconWithCrop.imageScale = appIcon.imageScale;
+ }
+ let writeReviewAction;
+ switch (objectGraph.client.deviceType) {
+ case "mac": {
+ const action = new models.WriteReviewAction(adamId, writeReviewUrl);
+ action.title = title;
+ action.appIcon = appIconWithCrop;
+ action.itemDescription = lockupSubtitle;
+ action.appName = lockupTitle;
+ writeReviewAction = action;
+ break;
+ }
+ default: {
+ if (objectGraph.featureFlags.isEnabled("review_composer_redesign")) {
+ const action = new models.WriteReviewAction(adamId, writeReviewUrl);
+ action.title = title;
+ action.appName = lockupTitle;
+ action.itemDescription = lockupSubtitle;
+ action.appIcon = appIconWithCrop;
+ action.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://square.and.pencil");
+ writeReviewAction = action;
+ break;
+ }
+ else {
+ const action = new models.FlowAction("writeReview");
+ action.title = title;
+ action.pageUrl = writeReviewUrl;
+ action.pageData = adamId;
+ action.presentationContext = "presentModal";
+ action.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://square.and.pencil");
+ writeReviewAction = action;
+ break;
+ }
+ }
+ }
+ return writeReviewAction;
+ });
+}
+/**
+ * Creates and returns an action for writing a product review
+ * @param data The response data to read from.
+ * @returns An action or null
+ */
+export function writeReviewActionFromData(objectGraph, data, lockupSubtitle, lockupTitle, appIcon) {
+ return validation.context(`writeReviewActionFromData`, () => {
+ const adamId = data.id;
+ if ((adamId === null || adamId === void 0 ? void 0 : adamId.length) > 0) {
+ const isBundle = data.type === "app-bundles";
+ const rating = mediaAttributes.attributeAsNumber(data, "rating");
+ const isRated = serverData.isDefinedNonNull(rating) && rating > 0;
+ return createWriteReviewAction(objectGraph, adamId, isBundle, appIcon, isRated, lockupSubtitle, lockupTitle);
+ }
+ return null;
+ });
+}
+/**
+ * Creates and returns an action for navigating to a product's ratings & reviews
+ * @param clickAction The flow action for viewing the product
+ * @returns An action or null
+ */
+export function seeRatingsAndReviewsActionFromClickAction(objectGraph, adamId, clickAction) {
+ return validation.context(`seeRatingsAndReviewsActionFromData`, () => {
+ const flowAction = objects.shallowCopyOf(clickAction);
+ if (clickAction.pageData instanceof models.ProductPage ||
+ clickAction.pageData instanceof models.ShelfBasedProductPage) {
+ let shelfBasedPageScrollAction;
+ if (objectGraph.client.isVision) {
+ shelfBasedPageScrollAction = new models.ShelfBasedPageScrollAction("productRatings", "notPurchasedRatingsAndReviews", "purchasedRatingsAndReviews", adamId);
+ }
+ else {
+ shelfBasedPageScrollAction = new models.ShelfBasedPageScrollAction("productRatings");
+ }
+ const scrollAction = productPageUtil.isShelfBased(objectGraph)
+ ? shelfBasedPageScrollAction
+ : new models.ProductPageScrollAction(new models.ProductPageSection("shelf", "reviews"));
+ const productPageData = objects.shallowCopyOf(clickAction.pageData);
+ productPageData.fullProductFetchedAction = scrollAction;
+ flowAction.pageData = productPageData;
+ }
+ return flowAction;
+ });
+}
+/**
+ * Create a custom shelf for the reviews module
+ * @param deviceId The UUID for the user's device.
+ * @param reviewData The basic review data response.
+ * @param rows The review row data response.
+ * @returns A custom shelf with one ratings module
+ */
+export function reviewsShelfForReviewsData(objectGraph, deviceId, ratingsData, reviewItems, includeMoreAction = false, includeFeedbackActions = false, includeFlowPreviewActionsConfiguration = true, shelfMetrics) {
+ return validation.context("reviewsShelfForReviewsData", () => {
+ if (objectGraph.client.isiOS) {
+ const shelf = new models.Shelf("productReview");
+ shelfMetrics === null || shelfMetrics === void 0 ? void 0 : shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "ratingsDetails");
+ shelf.items = createProductReviewsList(objectGraph, ratingsData, reviewItems, includeMoreAction, null, null, shelfMetrics);
+ return shelf;
+ }
+ else {
+ const shelf = new models.Shelf("reviews");
+ shelfMetrics === null || shelfMetrics === void 0 ? void 0 : shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "ratingsDetails");
+ shelf.items = createReviewItems(objectGraph, deviceId, ratingsData, reviewItems, includeMoreAction, includeFeedbackActions, includeFlowPreviewActionsConfiguration, shelfMetrics);
+ return shelf;
+ }
+ });
+}
+/**
+ * Create a reviews container shelf. Optionally pass in reviews to horizontally scroll them.
+ */
+export function reviewsContainerShelfForReviewsData(objectGraph, deviceId, ratingsData, reviewItems, appName, productData, appIcon, editorsChoice, shelfMetrics, nextPageHref, includeSeeAllAction = false, shouldIncludePersonalizationUrl = true, rating = null, tvOnlyApp = false) {
+ return validation.context("reviewsContainerShelfForReviewsData", () => {
+ const reviewsContainer = reviewsContainerForReviewsData(objectGraph, deviceId, ratingsData, reviewItems, productData, appName, appIcon, editorsChoice, shelfMetrics, rating, tvOnlyApp);
+ const shelf = new models.Shelf("reviewsContainer");
+ shelf.title = sectionTitleForPlatform(objectGraph);
+ shelf.items = [reviewsContainer];
+ shelfMetrics === null || shelfMetrics === void 0 ? void 0 : shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "ratingsDetails");
+ if (includeSeeAllAction &&
+ serverData.isDefinedNonNull(reviewsContainer.reviews) &&
+ reviewsContainer.reviews.length > 0) {
+ shelf.seeAllAction = reviewsPageActionFromReviewsData(objectGraph, deviceId, ratingsData, reviewItems, nextPageHref, appName, appIcon, false, false);
+ }
+ return shelf;
+ });
+}
+/**
+ * Create a personalized reviews container shelf
+ */
+export function personalizedReviewsContainerShelf(objectGraph, deviceId, token, dataContainer) {
+ return validation.context("personalizedReviewsContainerShelf", () => {
+ const data = mediaDataStructure.dataFromDataContainer(objectGraph, dataContainer);
+ const rating = mediaAttributes.attributeAsNumber(data, "rating");
+ return reviewsContainerShelfForReviewsData(objectGraph, deviceId, token.ratingsData, token.reviewItems, token.appName, data, token.appIcon, token.editorsChoice, token.shelfMetrics, token.nextPageHref, token.includeSeeAllAction, false, rating);
+ });
+}
+/**
+ * Create a reviews container
+ */
+export function reviewsContainerForReviewsData(objectGraph, deviceId, ratingsData, reviewItems, productData, appName, appIcon, editorsChoice, shelfMetrics, rating = null, tvOnlyApp = false) {
+ return validation.context("reviewsContainerForReviewsData", () => {
+ const reviewsContainer = new models.ReviewsContainer();
+ const adamId = serverData.asString(ratingsData, "adamId");
+ const isBundle = serverData.asBooleanOrFalse(ratingsData, "isBundle");
+ reviewsContainer.adamId = adamId;
+ // Ratings
+ reviewsContainer.ratings = ratingsFromApiResponses(objectGraph, deviceId, "details", ratingsData);
+ if (objectGraph.client.isiOS) {
+ const reviewSummary = reviewSummaryFromData(objectGraph, productData, shelfMetrics);
+ if (serverData.isDefinedNonNull(reviewSummary)) {
+ reviewsContainer.reviewSummary = reviewSummary;
+ }
+ }
+ // Tap to Rate
+ if (!tvOnlyApp || objectGraph.client.isTV) {
+ reviewsContainer.tapToRate = tapToRateWithAdamId(objectGraph, adamId, isBundle, appName, rating);
+ }
+ if (objectGraph.client.isWeb) {
+ const productPageIntent = makeProductPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: unwrapOptional(adamId),
+ });
+ const productAction = actionFor(productPageIntent, objectGraph);
+ productAction.title = appName;
+ reviewsContainer.productAction = productAction;
+ }
+ // Reviews
+ if (serverData.isDefinedNonNull(reviewItems) && reviewItems.length > 0) {
+ const isWatchStore = objectGraph.client.isWatch;
+ // We omit the actions on watchOS, they aren't used.
+ const includeActions = !isWatchStore;
+ // We limit the number of reviews to one on watchOS. That's all
+ // we can display inline, and we want to reduce memory usage.
+ const fixedUpReviewItems = isWatchStore ? reviewItems.slice(0, 1) : reviewItems;
+ reviewsContainer.reviews = createReviewItems(objectGraph, deviceId, ratingsData, fixedUpReviewItems, includeActions, false, includeActions, shelfMetrics);
+ }
+ // No reviews affects ratings message
+ if (serverData.isNull(reviewsContainer.reviews) || reviewsContainer.reviews.length === 0) {
+ const hasRatings = reviewsContainer.ratings.ratingAverage > 0 && reviewsContainer.ratings.ratingCounts;
+ const wasReset = serverData.asBooleanOrFalse(ratingsData, "wasReset");
+ if (!hasRatings && !wasReset) {
+ reviewsContainer.ratings.status = objectGraph.loc.string("RATINGS_STATUS_NOT_ENOUGH_RATINGS_OR_REVIEWS");
+ }
+ }
+ // Editors choice
+ if (!serverData.isNull(editorsChoice)) {
+ reviewsContainer.editorsChoice = editorsChoice;
+ }
+ // Write a review action
+ if (!tvOnlyApp || objectGraph.client.isTV) {
+ const isRated = serverData.isDefinedNonNull(rating) && rating > 0;
+ reviewsContainer.writeReviewAction = createWriteReviewAction(objectGraph, adamId, isBundle, appIcon, isRated, subtitleFromData(objectGraph, productData), appName);
+ }
+ // Support action
+ const supportUrl = serverData.asString(ratingsData, "supportUrl");
+ if (supportUrl) {
+ const supportAction = new models.ExternalUrlAction(supportUrl, false);
+ supportAction.title = objectGraph.loc.string("APP_SUPPORT");
+ reviewsContainer.supportAction = supportAction;
+ }
+ let alwaysAllowReviews = false;
+ for (const item of reviewItems) {
+ if (shouldAllowReviewsForSADApp(objectGraph, item)) {
+ alwaysAllowReviews = true;
+ break;
+ }
+ }
+ reviewsContainer.alwaysAllowReviews = alwaysAllowReviews;
+ return reviewsContainer;
+ });
+}
+function createSortOption(objectGraph, adamId, sortId, title, sortActionTitle, sortedReviewsPageToken = null) {
+ const url = sortedReviewsPageUrl(objectGraph, adamId, sortId, sortedReviewsPageToken);
+ return new models.ReviewsPageSortOption(sortId, title, sortActionTitle, url);
+}
+/**
+ * Create and return the learn more action on the Review Summary context menu
+ * @param objectGraph
+ * @returns a flow action for the learn more article page for review summary generation
+ */
+export function reviewSummaryLearnMoreAction(objectGraph) {
+ const editorialItemId = objectGraph.bag.reviewSummarizationLearnMoreEditorialItemId;
+ if (serverData.isNullOrEmpty(editorialItemId)) {
+ return null;
+ }
+ const title = objectGraph.loc.string("Action.LearnMore");
+ const flowAction = new models.FlowAction("article");
+ flowAction.title = title;
+ flowAction.pageUrl = `https://apps.apple.com/story/id${editorialItemId}`;
+ flowAction.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://questionmark.circle");
+ return flowAction;
+}
+/**
+ * Create a reviews page
+ * @param deviceId The UUID for the user's device.
+ * @param ratingsData The basic ratings response.
+ * @param reviewItems Reviews row data.
+ * @param isFirstPage Whether this is the first page.
+ * @param nextPageHref the Media API href for next page.
+ * @param appName The name of the app.
+ * @param appAdamId The adamId of the app.
+ * @param appIcon The app icon to use.
+ * @param sortId The sort identifier for this page.
+ * @param productData Product data for the app
+ * @returns A page object to be presented using a FlowAction
+ */
+function reviewsPageForReviewsData(objectGraph, deviceId, ratingsData, reviewItems, isFirstPage, nextPageHref = null, appAdamId = null, appName, appIcon = null, productData, shelfMetrics, includeMoreAction = false, includeFeedbackActions = false, sortId = sortIdDefault, sortedReviewsPageToken = null) {
+ const page = new models.ReviewsPage();
+ page.shelves = [];
+ return validation.context("reviewsPageForReviewsData", () => {
+ const adamId = serverData.isNull(appAdamId) ? serverData.asString(ratingsData, "adamId") : appAdamId;
+ if (isFirstPage &&
+ !serverData.isNull(ratingsData) &&
+ !objectGraph.client.isiOS &&
+ !objectGraph.client.isVision) {
+ // Reviews container shelf
+ const containerShelf = reviewsContainerShelfForReviewsData(objectGraph, deviceId, ratingsData, [], appName, productData, appIcon, null, shelfMetrics);
+ page.trailingNavBarAction = reviewSummaryLearnMoreAction(objectGraph);
+ page.shelves.push(containerShelf);
+ }
+ // Ratings & review actions
+ if (isFirstPage && objectGraph.client.isVision && serverData.isDefinedNonNull(productData)) {
+ const appPlatforms = supportedAppPlatformsFromData(objectGraph, productData);
+ const tvOnlyApp = appPlatforms.length === 1 && appPlatforms[0] === "tv";
+ const isBundle = productData.type === "app-bundles";
+ page.ratings = ratingsFromApiResponses(objectGraph, deviceId, "details", ratingsData);
+ page.productReviewActions = createProductReviewActions(objectGraph, productData, appName, ratingsData, isBundle, tvOnlyApp, appIcon);
+ }
+ const pageComponents = createReviewsPageComponents(objectGraph, deviceId, adamId, ratingsData, reviewItems, nextPageHref, includeMoreAction, includeFeedbackActions, sortId, sortedReviewsPageToken, shelfMetrics);
+ pageComponents.reviewsShelf.presentationHints = {
+ ...pageComponents.reviewsShelf.presentationHints,
+ isSortable: isFirstPage && pageComponents.reviewsShelf.presentationHints.isSortable,
+ };
+ page.adamId = adamId;
+ page.shelves.push(pageComponents.reviewsShelf);
+ page.nextPage = pageComponents.paginationToken;
+ page.initialSortOptionIdentifier = pageComponents.initialSortId;
+ page.sortActionSheetTitle = pageComponents.sortActionSheetTitle;
+ page.sortOptions = pageComponents.sorts;
+ page.alwaysAllowReviews = shouldAllowReviewsForSADApp(objectGraph, productData);
+ return page;
+ });
+}
+/// THe id used to concat the sort optin if there is one
+const baseReviewsShelfId = "ReviewsPage.ShelfId";
+/// Create the known shelfId for the shelf that includes the reviews
+export function reviewsShelfIdForSortId(sortOptionId) {
+ if (serverData.isNullOrEmpty(sortOptionId)) {
+ return baseReviewsShelfId;
+ }
+ return `${baseReviewsShelfId}.${sortOptionId}`;
+}
+/**
+ * @param objectGraph The dependency graph for the app.
+ * @param deviceId The id used when submitting reviews.
+ * @param ratingsData The ratings data for this app.
+ * @param reviewItems The actual reivews data
+ * @param nextPageHref The url to fetch more reviews.
+ * @param includeMoreAction Whether to include the more action on the reviews shelf.
+ * @param includeFeedbackActions Whether to include the feedback actions on the reviews shelf.
+ * @param sortId The sort identifier for this page.
+ * @returns The components needed to create a reviews page.
+ */
+export function createReviewsPageComponents(objectGraph, deviceId, adamId, ratingsData, reviewItems, nextPageHref = null, includeMoreAction = false, includeFeedbackActions = false, sortId = sortIdDefault, sortedReviewsPageToken = null, shelfMetrics) {
+ // Ensure an even number of items are on the page for two column layout paging
+ const shouldTruncateShelfItems = (nextPageHref === null || nextPageHref === void 0 ? void 0 : nextPageHref.length) > 0 && serverData.isDefinedNonNullNonEmpty(reviewItems) && reviewItems.length % 2 > 0;
+ const itemsForReviewsShelf = shouldTruncateShelfItems ? reviewItems.slice(0, reviewItems.length - 1) : reviewItems;
+ const remainingItems = shouldTruncateShelfItems
+ ? reviewItems.slice(reviewItems.length - 1)
+ : [];
+ const reviewsShelf = reviewsShelfForReviewsData(objectGraph, deviceId, ratingsData, itemsForReviewsShelf, includeMoreAction, includeFeedbackActions, true, shelfMetrics);
+ reviewsShelf.id = reviewsShelfIdForSortId(sortId);
+ const pageComponents = {
+ reviewsShelf,
+ };
+ reviewsShelf.presentationHints = {
+ isSortable: reviewItems.length > 0 || (nextPageHref === null || nextPageHref === void 0 ? void 0 : nextPageHref.length) > 0,
+ isSeeAllContext: true,
+ };
+ pageComponents.initialSortId = sortId;
+ pageComponents.sortActionSheetTitle = objectGraph.loc.string("REVIEWS_SORT_BY");
+ pageComponents.sorts = [
+ createSortOption(objectGraph, adamId, "helpful", objectGraph.loc.string("REVIEWS_MOST_HELPFUL"), objectGraph.loc.string(objectGraph.client.isiOS ? "REVIEWS_MOST_HELPFUL" : "REVIEWS_SORT_BY_MOST_HELPFUL"), sortedReviewsPageToken),
+ createSortOption(objectGraph, adamId, "favorable", objectGraph.loc.string("REVIEWS_MOST_FAVORABLE"), objectGraph.loc.string(objectGraph.client.isiOS ? "REVIEWS_MOST_FAVORABLE" : "REVIEWS_SORT_BY_MOST_FAVORABLE"), sortedReviewsPageToken),
+ createSortOption(objectGraph, adamId, "critical", objectGraph.loc.string("REVIEWS_MOST_CRITICAL"), objectGraph.loc.string(objectGraph.client.isiOS ? "REVIEWS_MOST_CRITICAL" : "REVIEWS_SORT_BY_MOST_CRITICAL"), sortedReviewsPageToken),
+ createSortOption(objectGraph, adamId, "recent", objectGraph.loc.string("REVIEWS_MOST_RECENT"), objectGraph.loc.string(objectGraph.client.isiOS ? "REVIEWS_MOST_RECENT" : "REVIEWS_SORT_BY_MOST_RECENT"), sortedReviewsPageToken),
+ ];
+ if ((nextPageHref === null || nextPageHref === void 0 ? void 0 : nextPageHref.length) > 0) {
+ pageComponents.paginationToken = createReviewsPageToken(objectGraph, adamId, nextPageHref, sortId, remainingItems);
+ }
+ return pageComponents;
+}
+/**
+ * Create a partial reviews page from a reviews media api container
+ */
+export function partialReviewsPageForReviewsMediaContainer(objectGraph, deviceId, adamId, prependingItems, data, isFirstPage, sortId, sortedReviewsPageToken, shelfMetrics) {
+ return validation.context("reviewsPageForReviewsMediaContainer", () => {
+ let reviewItems;
+ if (serverData.isDefinedNonNullNonEmpty(data.data)) {
+ reviewItems = prependingItems.concat(data.data);
+ }
+ else {
+ reviewItems = prependingItems;
+ }
+ const includeMoreAction = objectGraph.client.isVision;
+ const includeFeedbackActions = objectGraph.client.isVision;
+ return reviewsPageForReviewsData(objectGraph, deviceId, null, reviewItems, isFirstPage, data.next, adamId, null, null, null, shelfMetrics, includeMoreAction, includeFeedbackActions, sortId, sortedReviewsPageToken);
+ });
+}
+/**
+ * Creates a flow action to display a reviews page.
+ * @param deviceId The UUID for the user's device.
+ * @param ratingsData The ratings data response
+ * @param reviewsData The review row data response.
+ * @param nextPageHref The Media API href for the next page.
+ * @param appName The name of the app
+ * @param appIcon The app's icon.
+ * @param productData The data for the product
+
+ * @returns A `FlowAction` object pointing to a reviews page.
+ */
+export function reviewsPageActionFromReviewsData(objectGraph, deviceId, ratingsData, reviewsData, nextPageHref = null, appName = null, appIcon = null, includeMoreAction, includeFeedbackActions, productData, shelfMetrics) {
+ if (!ratingsData) {
+ return null;
+ }
+ return validation.context("reviewsPageActionFromReviewsData", () => {
+ const pageData = reviewsPageForReviewsData(objectGraph, deviceId, ratingsData, reviewsData, true, nextPageHref, null, appName, appIcon, productData, shelfMetrics, includeMoreAction, includeFeedbackActions);
+ pageData.title = pageTitleForPlatform(objectGraph);
+ const action = new models.FlowAction("reviews");
+ action.pageData = pageData;
+ action.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ if (objectGraph.client.isWeb) {
+ const destination = makeSeeAllPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ "id": pageData.adamId,
+ "see-all": "reviews",
+ });
+ action.destination = destination;
+ action.pageUrl = makeSeeAllPageURL(objectGraph, destination);
+ const { metricsPageInformation, locationTracker } = shelfMetrics !== null && shelfMetrics !== void 0 ? shelfMetrics : {};
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, {
+ id: "SeeAllReviews",
+ actionType: "navigate",
+ locationTracker: locationTracker,
+ pageInformation: metricsPageInformation,
+ }, false, "button");
+ }
+ return action;
+ });
+}
+/**
+ * Creates a flow action to display a single reviewreviewsPageUrl
+ * @param deviceId The UUID for the user's device.
+ * @param ratingsData The ratings data response
+ * @param item The review data from a single row data response.
+ * @returns A `FlowAction` object pointing to a reviews page.
+ */
+function singleReviewPageActionFromReviewItem(objectGraph, deviceId, ratingsData, item, shelfMetrics) {
+ if (!item) {
+ return null;
+ }
+ return validation.context("singleReviewActionFromReviewData", () => {
+ // Reviews shelf
+ const reviewsShelf = reviewsShelfForReviewsData(objectGraph, deviceId, ratingsData, [item], null, null, null, shelfMetrics);
+ const page = new models.ReviewsPage();
+ page.adamId = serverData.asString(ratingsData, "adamId");
+ page.targetReviewId = serverData.asString(item, "id", "coercible");
+ page.shelves = [reviewsShelf];
+ const action = new models.FlowAction("reviews");
+ action.pageData = page;
+ action.title = sectionTitleForPlatform(objectGraph);
+ return action;
+ });
+}
+export function pageTitleForPlatform(objectGraph) {
+ if (objectGraph.client.isWatch) {
+ return objectGraph.loc.string("ProductPage.Section.Reviews.Title");
+ }
+ else {
+ return null;
+ }
+}
+export function sectionTitleForPlatform(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "tv":
+ return objectGraph.loc.string("TV_PRODUCT_SECTION_RATINGS");
+ case "watch":
+ return null;
+ default:
+ return objectGraph.loc.string("PRODUCT_SECTION_REVIEWS");
+ }
+}
+/**
+ * Creates a flow action for navigating to the expanded view of a review.
+ *
+ * @param objectGraph The current object graph
+ * @param review The source review
+ * @returns A flow action
+ */
+function createReviewDetailActionForReview(objectGraph, review) {
+ const flowAction = new models.FlowAction("reviewDetail");
+ const detailReview = objects.shallowCopyOf(review);
+ detailReview.moreAction = null;
+ flowAction.pageData = detailReview;
+ return flowAction;
+}
+//# sourceMappingURL=reviews.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-section-mapping.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-section-mapping.js
new file mode 100644
index 0000000..0605b10
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-section-mapping.js
@@ -0,0 +1,40 @@
+import * as models from "../../../api/models/index";
+/**
+ * ProductPageShelfMapping is a type that allows us to consolidate the logic around shelf
+ * ordering between ShelfBasedProductPage and ProductPage for now until we move over
+ * to ShelfBased completely.
+ */
+export class ProductPageSectionMapping {
+ constructor(sectionType, shelfId, shelfBasedMappedIds) {
+ this.sectionType = sectionType;
+ this.shelfId = shelfId;
+ this.shelfBasedMappedIds = shelfBasedMappedIds;
+ }
+ /// Create the ProductPageSection for this mapping
+ createProductPageSection(objectGraph) {
+ return models.legacyProductPageNonShelfSections.has(this.sectionType)
+ ? new models.ProductPageSection(this.sectionType, null)
+ : new models.ProductPageSection(this.sectionType, this.shelfId);
+ }
+ /// Create the list of shelfIds to use for this section when in a ShelfBasedProductPage
+ createShelfIdList() {
+ return this.shelfBasedMappedIds || [this.shelfId];
+ }
+}
+/// Create the list of ProductPageSections for use with the non-ShelfBasedProduct pages
+export function legacyProductPageSectionsFromMappings(objectGraph, sectionMappings) {
+ return sectionMappings.map((mapping) => {
+ return mapping.createProductPageSection(objectGraph);
+ });
+}
+/// Create the list of ProductPageShelfIds for use with the ShelfBasedProduct pages
+export function productPageShelfIdsFromMappings(sectionMappings) {
+ return sectionMappings
+ .map((mapping) => {
+ return mapping.createShelfIdList();
+ })
+ .reduce((accumulator, currentValue) => {
+ return accumulator.concat(currentValue);
+ });
+}
+//# sourceMappingURL=product-page-section-mapping.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-ios-arcade.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-ios-arcade.js
new file mode 100644
index 0000000..e0e0451
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-ios-arcade.js
@@ -0,0 +1,215 @@
+import { ProductPageSectionMapping } from "../product-page-section-mapping";
+/// The at which to place a section that is in the spotlight.
+export const purchasedSpotlightIndex = 5;
+export const notPurchasedSpotlightIndex = 5;
+// MARK: - Installed
+export const purchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "arcadeProductPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", [
+ "productRatings",
+ "allProductReviews",
+ "allProductReviewActions",
+ "productReviewsFooter",
+ ]),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+export const purchasedOrderingCompact = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "arcadeProductPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", [
+ "productRatings",
+ "reviewSummary",
+ "userProductReviews",
+ "editorsChoiceProductReviews",
+ "productReviewsFooter",
+ "tapToRateProductReviewAction",
+ "writeAReviewProductReviewAction",
+ ]),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+// MARK: - Not Installed
+/// The standard ordering to use for an app that is not installed.
+export const notPurchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "arcadeProductPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", ["productRatings", "allProductReviews", "productReviewsFooter"]),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "notPurchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+export const notPurchasedOrderingCompact = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "arcadeProductPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", [
+ "productRatings",
+ "reviewSummary",
+ "userProductReviews",
+ "editorsChoiceProductReviews",
+ "productReviewsFooter",
+ ]),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "notPurchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+// MARK: - During Download
+export const downloadingOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "arcadeProductPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", [
+ "productRatings",
+ "allProductReviewActions",
+ "allProductReviews",
+ "productReviewsFooter",
+ ]),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+export const downloadingOrderingCompact = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "arcadeProductPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", [
+ "productRatings",
+ "tapToRateProductReviewAction",
+ "reviewSummary",
+ "userProductReviews",
+ "editorsChoiceProductReviews",
+ "productReviewsFooter",
+ "writeAReviewProductReviewAction",
+ ]),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+//# sourceMappingURL=product-page-shelf-ordering-ios-arcade.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-ios.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-ios.js
new file mode 100644
index 0000000..4ebf248
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-ios.js
@@ -0,0 +1,247 @@
+import { ProductPageSectionMapping } from "../product-page-section-mapping";
+/// The at which to place a section that is in the spotlight.
+export const purchasedSpotlightIndex = 5;
+export const notPurchasedSpotlightIndex = 5;
+// MARK: - Installed
+export const purchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "productPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "bundleChildren"),
+ new ProductPageSectionMapping("shelf", "bundleParents"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "subscriptions"),
+ new ProductPageSectionMapping("shelf", "inAppPurchases"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", [
+ "productRatings",
+ "allProductReviews",
+ "allProductReviewActions",
+ "productReviewsFooter",
+ ]),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+];
+export const purchasedOrderingCompact = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "productPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "bundleChildren"),
+ new ProductPageSectionMapping("shelf", "bundleParents"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "subscriptions"),
+ new ProductPageSectionMapping("shelf", "inAppPurchases"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", [
+ "productRatings",
+ "reviewSummary",
+ "userProductReviews",
+ "editorsChoiceProductReviews",
+ "productReviewsFooter",
+ "tapToRateProductReviewAction",
+ "writeAReviewProductReviewAction",
+ ]),
+ new ProductPageSectionMapping("shelf", "tapToRateProductReviewAction"),
+ new ProductPageSectionMapping("shelf", "userProductReviews"),
+ new ProductPageSectionMapping("shelf", "editorsChoiceProductReviews"),
+ new ProductPageSectionMapping("shelf", "writeAReviewProductReviewAction"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+];
+// MARK: - Not Installed
+/// The standard ordering to use for an app that is not installed.
+export const notPurchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "productPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "bundleChildren"),
+ new ProductPageSectionMapping("shelf", "bundleParents"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", ["productRatings", "allProductReviews", "productReviewsFooter"]),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "subscriptions"),
+ new ProductPageSectionMapping("shelf", "inAppPurchases"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "notPurchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+];
+export const notPurchasedOrderingCompact = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "productPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "bundleChildren"),
+ new ProductPageSectionMapping("shelf", "bundleParents"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", [
+ "productRatings",
+ "reviewSummary",
+ "userProductReviews",
+ "editorsChoiceProductReviews",
+ "productReviewsFooter",
+ ]),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "subscriptions"),
+ new ProductPageSectionMapping("shelf", "inAppPurchases"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "notPurchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+];
+// MARK: - During Download
+export const downloadingOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "productPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "bundleChildren"),
+ new ProductPageSectionMapping("shelf", "bundleParents"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "subscriptions"),
+ new ProductPageSectionMapping("shelf", "inAppPurchases"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", [
+ "productRatings",
+ "allProductReviews",
+ "allProductReviewActions",
+ "productReviewsFooter",
+ ]),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+export const downloadingOrderingCompact = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "productPageInlineMessage"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "bundleChildren"),
+ new ProductPageSectionMapping("shelf", "bundleParents"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "subscriptions"),
+ new ProductPageSectionMapping("shelf", "inAppPurchases"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "editorsChoice"),
+ new ProductPageSectionMapping("shelf", "reviews", [
+ "productRatings",
+ "reviewSummary",
+ "userProductReviews",
+ "editorsChoiceProductReviews",
+ "productReviewsFooter",
+ "tapToRateProductReviewAction",
+ "writeAReviewProductReviewAction",
+ ]),
+ new ProductPageSectionMapping("shelf", "tapToRateProductReviewAction"),
+ new ProductPageSectionMapping("shelf", "userProductReviews"),
+ new ProductPageSectionMapping("shelf", "editorsChoiceProductReviews"),
+ new ProductPageSectionMapping("shelf", "writeAReviewProductReviewAction"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+//# sourceMappingURL=product-page-shelf-ordering-ios.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-mac-arcade.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-mac-arcade.js
new file mode 100644
index 0000000..39d0345
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-mac-arcade.js
@@ -0,0 +1,62 @@
+import { ProductPageSectionMapping } from "../product-page-section-mapping";
+/// The at which to place a section that is in the spotlight.
+export const purchasedSpotlightIndex = 5;
+export const notPurchasedSpotlightIndex = 5;
+// MARK: - Installed
+export const purchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "reviews"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+// MARK: - Not Installed
+/// The standard ordering to use for an app that is not installed.
+export const notPurchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("shelf", "reviews"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "notPurchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+//# sourceMappingURL=product-page-shelf-ordering-mac-arcade.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-mac.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-mac.js
new file mode 100644
index 0000000..032cb9a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-mac.js
@@ -0,0 +1,70 @@
+import { ProductPageSectionMapping } from "../product-page-section-mapping";
+/// The at which to place a section that is in the spotlight.
+export const purchasedSpotlightIndex = 5;
+export const notPurchasedSpotlightIndex = 5;
+// MARK: - Installed
+export const purchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "bundleChildren"),
+ new ProductPageSectionMapping("shelf", "bundleParents"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "subscriptions"),
+ new ProductPageSectionMapping("shelf", "inAppPurchases"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "reviews"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+];
+// MARK: - Not Installed
+/// The standard ordering to use for an app that is not installed.
+export const notPurchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "informationRibbon"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "bundleChildren"),
+ new ProductPageSectionMapping("shelf", "bundleParents"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "appEvents"),
+ new ProductPageSectionMapping("shelf", "editorialQuote"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("shelf", "reviews"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "subscriptions"),
+ new ProductPageSectionMapping("shelf", "inAppPurchases"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "notPurchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "featuredIn"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+];
+//# sourceMappingURL=product-page-shelf-ordering-mac.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-tvos-arcade.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-tvos-arcade.js
new file mode 100644
index 0000000..61536f4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-tvos-arcade.js
@@ -0,0 +1,50 @@
+import { ProductPageSectionMapping } from "../product-page-section-mapping";
+/// The at which to place a section that is in the spotlight.
+export const purchasedSpotlightIndex = 5;
+export const notPurchasedSpotlightIndex = 5;
+// MARK: - Installed
+export const purchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "textCards"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "purchasedRatingsAndReviews"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "textLinksShelf"),
+];
+// MARK: - Not Installed
+/// The standard ordering to use for an app that is not installed.
+export const notPurchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "textCards"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("shelf", "notPurchasedRatingsAndReviews"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "notPurchasedLinks"),
+ new ProductPageSectionMapping("shelf", "textLinksShelf"),
+];
+//# sourceMappingURL=product-page-shelf-ordering-tvos-arcade.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-tvos.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-tvos.js
new file mode 100644
index 0000000..f0067ef
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-tvos.js
@@ -0,0 +1,50 @@
+import { ProductPageSectionMapping } from "../product-page-section-mapping";
+/// The at which to place a section that is in the spotlight.
+export const purchasedSpotlightIndex = 5;
+export const notPurchasedSpotlightIndex = 5;
+// MARK: - Installed
+export const purchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "textCards"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "purchasedRatingsAndReviews"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "textLinksShelf"),
+];
+// MARK: - Not Installed
+/// The standard ordering to use for an app that is not installed.
+export const notPurchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "textCards"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("shelf", "notPurchasedRatingsAndReviews"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "notPurchasedLinks"),
+ new ProductPageSectionMapping("shelf", "textLinksShelf"),
+];
+//# sourceMappingURL=product-page-shelf-ordering-tvos.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-visionos-arcade.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-visionos-arcade.js
new file mode 100644
index 0000000..38f85f2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-visionos-arcade.js
@@ -0,0 +1,54 @@
+import { ProductPageSectionMapping } from "../product-page-section-mapping";
+/// The index at which to place a section that is in the spotlight.
+export const purchasedSpotlightIndex = 5;
+export const notPurchasedSpotlightIndex = 5;
+// MARK: - Installed
+export const purchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "textCards"),
+ new ProductPageSectionMapping("shelf", "productReviewsHeader"),
+ new ProductPageSectionMapping("shelf", "purchasedRatingsAndReviews"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+// MARK: - Not Installed
+/// The standard ordering to use for an app that is not installed.
+export const notPurchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "textCards"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("shelf", "productReviewsHeader"),
+ new ProductPageSectionMapping("shelf", "notPurchasedRatingsAndReviews"),
+ new ProductPageSectionMapping("shelf", "achievements"),
+ new ProductPageSectionMapping("shelf", "friendsPlaying"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "notPurchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+];
+//# sourceMappingURL=product-page-shelf-ordering-visionos-arcade.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-visionos.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-visionos.js
new file mode 100644
index 0000000..91ffa9d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-visionos.js
@@ -0,0 +1,50 @@
+import { ProductPageSectionMapping } from "../product-page-section-mapping";
+/// The index at which to place a section that is in the spotlight.
+export const purchasedSpotlightIndex = 5;
+export const notPurchasedSpotlightIndex = 5;
+// MARK: - Installed
+export const purchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "textCards"),
+ new ProductPageSectionMapping("shelf", "productReviewsHeader"),
+ new ProductPageSectionMapping("shelf", "purchasedRatingsAndReviews"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "purchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+];
+// MARK: - Not Installed
+/// The standard ordering to use for an app that is not installed.
+export const notPurchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "preorderDisclaimer"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "textCards"),
+ new ProductPageSectionMapping("shelf", "videos"),
+ new ProductPageSectionMapping("shelf", "productReviewsHeader"),
+ new ProductPageSectionMapping("shelf", "notPurchasedRatingsAndReviews"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "privacyFooter"),
+ new ProductPageSectionMapping("shelf", "accessibilityHeader"),
+ new ProductPageSectionMapping("shelf", "accessibilityFeatures"),
+ new ProductPageSectionMapping("shelf", "accessibilityDeveloperLink"),
+ new ProductPageSectionMapping("shelf", "information"),
+ new ProductPageSectionMapping("shelf", "notPurchasedLinks"),
+ new ProductPageSectionMapping("shelf", "capabilities"),
+ new ProductPageSectionMapping("shelf", "moreByDeveloper"),
+ new ProductPageSectionMapping("shelf", "similarItems"),
+];
+//# sourceMappingURL=product-page-shelf-ordering-visionos.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-watch.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-watch.js
new file mode 100644
index 0000000..aeec356
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-watch.js
@@ -0,0 +1,28 @@
+import { ProductPageSectionMapping } from "../product-page-section-mapping";
+/// The at which to place a section that is in the spotlight.
+export const purchasedSpotlightIndex = 3;
+export const notPurchasedSpotlightIndex = 3;
+// MARK: - Installed
+export const purchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "actionLinks"),
+];
+// MARK: - Not Installed
+/// The standard ordering to use for an app that is not installed.
+export const notPurchasedOrdering = [
+ new ProductPageSectionMapping("header", "header"),
+ new ProductPageSectionMapping("topLockup", "topLockup"),
+ new ProductPageSectionMapping("screenshots", "screenshots"),
+ new ProductPageSectionMapping("shelf", "description"),
+ new ProductPageSectionMapping("shelf", "mostRecentVersion"),
+ new ProductPageSectionMapping("shelf", "privacyHeader"),
+ new ProductPageSectionMapping("shelf", "privacyTypes"),
+ new ProductPageSectionMapping("shelf", "actionLinks"),
+];
+//# sourceMappingURL=product-page-shelf-ordering-watch.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-web-arcade.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-web-arcade.js
new file mode 100644
index 0000000..205553a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-web-arcade.js
@@ -0,0 +1,15 @@
+import * as iosOrdering from "./product-page-shelf-ordering-ios-arcade";
+/// The index at which to place a section that is in the spotlight.
+export const purchasedSpotlightIndex = iosOrdering.purchasedSpotlightIndex;
+export const notPurchasedSpotlightIndex = iosOrdering.notPurchasedSpotlightIndex;
+// MARK: - Installed
+export const purchasedOrdering = [...iosOrdering.purchasedOrdering];
+export const purchasedOrderingCompact = [...iosOrdering.purchasedOrderingCompact];
+// MARK: - Not Installed
+/// The standard ordering to use for an app that is not installed.
+export const notPurchasedOrdering = [...iosOrdering.notPurchasedOrdering];
+export const notPurchasedOrderingCompact = [...iosOrdering.notPurchasedOrderingCompact];
+// MARK: - During Download
+export const downloadingOrdering = [...iosOrdering.downloadingOrdering];
+export const downloadingOrderingCompact = [...iosOrdering.downloadingOrderingCompact];
+//# sourceMappingURL=product-page-shelf-ordering-web-arcade.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-web.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-web.js
new file mode 100644
index 0000000..ee1a8fd
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering-web.js
@@ -0,0 +1,15 @@
+import * as iosOrdering from "./product-page-shelf-ordering-ios";
+/// The index at which to place a section that is in the spotlight.
+export const purchasedSpotlightIndex = iosOrdering.purchasedSpotlightIndex;
+export const notPurchasedSpotlightIndex = iosOrdering.notPurchasedSpotlightIndex;
+// MARK: - Installed
+export const purchasedOrdering = [...iosOrdering.purchasedOrdering];
+export const purchasedOrderingCompact = [...iosOrdering.purchasedOrderingCompact];
+// MARK: - Not Installed
+/// The standard ordering to use for an app that is not installed.
+export const notPurchasedOrdering = [...iosOrdering.notPurchasedOrdering];
+export const notPurchasedOrderingCompact = [...iosOrdering.notPurchasedOrderingCompact];
+// MARK: - During Download
+export const downloadingOrdering = [...iosOrdering.downloadingOrdering];
+export const downloadingOrderingCompact = [...iosOrdering.downloadingOrderingCompact];
+//# sourceMappingURL=product-page-shelf-ordering-web.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering.js
new file mode 100644
index 0000000..ec5f39b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/product-page-shelf-ordering/product-page-shelf-ordering.js
@@ -0,0 +1,117 @@
+//
+// product-page-shelf-ordering.ts
+// AppStoreKit
+//
+// Created by Dersu Abolfathi on 2/15/17.
+// Copyright (c) 2017 Apple Inc. All rights reserved.
+//
+import { ProductPageSectionMapping } from "../product-page-section-mapping";
+import * as iOSOrdering from "./product-page-shelf-ordering-ios";
+import * as iOSArcadeOrdering from "./product-page-shelf-ordering-ios-arcade";
+import * as macOrdering from "./product-page-shelf-ordering-mac";
+import * as macArcadeOrdering from "./product-page-shelf-ordering-mac-arcade";
+import * as tvOSOrdering from "./product-page-shelf-ordering-tvos";
+import * as tvOSArcadeOrdering from "./product-page-shelf-ordering-tvos-arcade";
+import * as watchOSOrdering from "./product-page-shelf-ordering-watch";
+import * as visionOSOrdering from "./product-page-shelf-ordering-visionos";
+import * as visionOSArcadeOrdering from "./product-page-shelf-ordering-visionos-arcade";
+import * as webOrdering from "./product-page-shelf-ordering-web";
+import * as webArcadeOrdering from "./product-page-shelf-ordering-web-arcade";
+export function orderingForPlatform(objectGraph, hostPlatform, supportsArcade) {
+ switch (hostPlatform) {
+ case "tvOS":
+ return supportsArcade ? tvOSArcadeOrdering : tvOSOrdering;
+ case "watchOS":
+ return watchOSOrdering;
+ case "iOS":
+ return supportsArcade ? iOSArcadeOrdering : iOSOrdering;
+ case "macOS":
+ return supportsArcade ? macArcadeOrdering : macOrdering;
+ case "xrOS":
+ return supportsArcade ? visionOSArcadeOrdering : visionOSOrdering;
+ case "web":
+ return supportsArcade ? webArcadeOrdering : webOrdering;
+ default:
+ return iOSOrdering;
+ }
+}
+/**
+ * Determines the overall section ordering for a product page.
+ * @param objectGraph
+ * @param isPurchased = Whether the requested ordering is for the purchased state.
+ * @param hostPlatform
+ * @param supportsArcade
+ * @param isCompact - ordering for compact display
+ * @param isDownloading Whether the section ordering is for the downloading order of a product page.
+ * @param options - The options to use for the product page configuration.
+ * note: - This function is backed by a small, straightforward set of human-readable
+ * arrays. Customization with regards to 'spotlighting' (placing a section at the
+ * top of the page, depending on how the product page was reached) is applied through
+ * the 'options' parameter.
+ * @return The ordering for the overall product page sections.
+ */
+export function sectionOrdering(objectGraph, isPurchased, hostPlatform, supportsArcade, isCompact, isDownloading, options) {
+ const ordering = orderingForPlatform(objectGraph, hostPlatform, supportsArcade);
+ // Create a shallow copy of the appropriate ordering
+ let sections = [];
+ if (isPurchased) {
+ sections =
+ isCompact && ordering.purchasedOrderingCompact
+ ? ordering.purchasedOrderingCompact.slice()
+ : ordering.purchasedOrdering.slice();
+ }
+ else if (isDownloading && ordering.downloadingOrdering) {
+ sections =
+ isCompact && ordering.downloadingOrderingCompact
+ ? ordering.downloadingOrderingCompact.slice()
+ : ordering.downloadingOrdering.slice();
+ }
+ else {
+ sections =
+ isCompact && ordering.notPurchasedOrderingCompact
+ ? ordering.notPurchasedOrderingCompact.slice()
+ : ordering.notPurchasedOrdering.slice();
+ }
+ const spotlightIndex = isPurchased ? ordering.purchasedSpotlightIndex : ordering.notPurchasedSpotlightIndex;
+ // Case: Our options dictate spotlighting of a section
+ if (options && options.spotlightSection) {
+ // Re-Insert the section at the spotlight index
+ const index = indexOfSectionInOrdering(options.spotlightSection, sections);
+ if (index !== -1) {
+ sections.splice(index, 1);
+ sections.splice(spotlightIndex, 0, options.spotlightSection);
+ }
+ }
+ return sections;
+}
+/**
+ * Determines the index of a particular section within an ordering of sections.
+ * @param section The section in question.
+ * @param ordering The ordering in which the section's index is desired.
+ * @returns The index of the section, or -1 if it does not appear in the ordering.
+ */
+function indexOfSectionInOrdering(section, ordering) {
+ for (let index = 0; index < ordering.length; index++) {
+ const aSection = ordering[index];
+ if (section.sectionType === aSection.sectionType &&
+ section.shelfId === aSection.shelfId &&
+ JSON.stringify(section.shelfBasedMappedIds) === JSON.stringify(aSection.shelfBasedMappedIds)) {
+ return index;
+ }
+ }
+ return -1;
+}
+/**
+ * Determines the appropriate product page section for the iAP type.
+ * @param isSubscription The type for the iAP product.
+ * @returns {ProductPageSection} The section appropriate for this iAP type.
+ */
+export function inAppPurchaseSection(isSubscription) {
+ if (isSubscription) {
+ return new ProductPageSectionMapping("shelf", "subscriptions");
+ }
+ else {
+ return new ProductPageSectionMapping("shelf", "inAppPurchases");
+ }
+}
+//# sourceMappingURL=product-page-shelf-ordering.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/shelf-based-product-page.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/shelf-based-product-page.js
new file mode 100644
index 0000000..572e8b9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelf-based/shelf-based-product-page.js
@@ -0,0 +1,1059 @@
+//
+// product-page.ts
+// AppStoreKit
+//
+// Created by Kevin MacWhinnie on 8/17/16.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import { isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import { AdInteractionAction, SelectAppAction } from "../../../api/models";
+import { AdvertActionMetrics, } from "../../../api/models/metrics/advert-action-metrics";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { Parameters } from "../../../foundation/network/url-constants";
+import * as urls from "../../../foundation/network/urls";
+import * as color from "../../../foundation/util/color-util";
+import { isAdPlacementEnabled } from "../../ads/ad-common";
+import { isNothing } from "@jet/environment/types/optional";
+import { makeDeveloperPageIntent } from "../../../api/intents/developer-page-intent";
+import { makeGamesResponse } from "../../../gameservicesui/src/api/data-models/game-mapi";
+import { deviceFamilyForAccessibilityLabels } from "../../accessibility/accessibility-common";
+import * as appPromotion from "../../app-promotions/app-promotion";
+import * as appPromotionsShelf from "../../app-promotions/app-promotions-shelf";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as filtering from "../../filtering";
+import { shelfForUnifiedMessage } from "../../grouping/grouping-common";
+import * as externalDeepLink from "../../linking/external-deep-link";
+import { getLocale } from "../../locale";
+import { attachAdActionMetricsToAction } from "../../lockups/ad-lockups";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as metricsHelpersModels from "../../metrics/helpers/models";
+import * as metricsHelpersPage from "../../metrics/helpers/page";
+import * as metricsUtil from "../../metrics/helpers/util";
+import { MetricsReferralContext } from "../../metrics/metrics-referral-context";
+import * as offers from "../../offers/offers";
+import * as privacyFooterShelf from "../../privacy/privacy-footer-shelf";
+import * as privacyHeaderShelf from "../../privacy/privacy-header-shelf";
+import * as privacySuppression from "../../privacy/privacy-suppression";
+import * as privacyTypesShelf from "../../privacy/privacy-types-shelf";
+import * as refresh from "../../refresh/page-refresh-controller";
+import * as sharing from "../../sharing";
+import * as productBadges from "../badges/badges";
+import * as productBadgesCommon from "../badges/badges-common";
+import * as banner from "../banner";
+import * as productPageCommon from "../product-page-common";
+import { ProductPageShelfMetrics } from "../product-page-shelf-metrics";
+import * as productPageUtil from "../product-page-util";
+import * as productPageVariants from "../product-page-variants";
+import * as reviews from "../reviews";
+import * as accessibilityShelves from "../shelves/accessibility-shelves";
+import * as achievementsShelf from "../shelves/achievements-shelf";
+import * as actionLinksShelf from "../shelves/action-links-shelf";
+import * as annotationsShelf from "../shelves/annotations/annotations";
+import * as categoryAnnotation from "../shelves/annotations/category-annotation";
+import * as compatibilityAnnotation from "../shelves/annotations/compatibility-annotation";
+import * as contentRatingAnnotation from "../shelves/annotations/content-rating-annotation";
+import * as highMotionAnnotation from "../shelves/annotations/high-motion-annotation";
+import * as languageAnnotation from "../shelves/annotations/languages-annotation";
+import * as sizeAnnotation from "../shelves/annotations/size-annotation";
+import * as bundleChildrenShelf from "../shelves/bundle-children-shelf";
+import * as bundleParentsShelf from "../shelves/bundle-parents-shelf";
+import * as capabilitiesShelf from "../shelves/capabilities-shelf";
+import * as descriptionShelf from "../shelves/description-shelf";
+import * as editorialQuoteShelf from "../shelves/editorial-quote-shelf";
+import { createEditorsChoiceShelf } from "../shelves/editors-choice-shelf";
+import * as featuredInShelf from "../shelves/featured-in-shelf";
+import * as friendsPlayingShelf from "../shelves/friends-playing-shelf";
+import * as inAppPurchasesShelf from "../shelves/in-app-purchases-shelf";
+import * as linksShelf from "../shelves/links-shelf";
+import * as moreByDeveloperShelf from "../shelves/more-by-developer-shelf";
+import * as productMediaShelf from "../shelves/product-media-shelf";
+import * as ribbonShelf from "../shelves/ribbon-shelf";
+import * as reviewsShelf from "../shelves/shelf-based-reviews-shelves";
+import * as similarItemsShelf from "../shelves/similar-items-shelf";
+import * as smallStoryCardShelf from "../shelves/small-story-card-shelf";
+import * as textLinksShelf from "../shelves/text-links-shelf";
+import * as textCardsShelf from "../shelves/textcard-shelf";
+import * as versionHistoryShelves from "../shelves/version-history-shelves";
+import * as lockups from "./../../lockups/lockups";
+import { productPageShelfIdsFromMappings } from "./product-page-section-mapping";
+import * as shelfBasedProductOrdering from "./product-page-shelf-ordering/product-page-shelf-ordering";
+import { tryAwait } from "../../../foundation/util/promise-util";
+import { withAsyncValidationContext } from "../../../foundation/util/validation-util";
+/**
+ * Create a side pack using product data.
+ *
+ * @param response The raw response for a product page.
+ * @param options The options to use for the product page configuration.
+ * @returns A product page side pack model object.
+ */
+export function createProductPageSidePackFromResponse(objectGraph, response, options) {
+ return validation.context("createProductPageSidePackFromResponse", () => {
+ if (!objectGraph.client.isSidepackingEnabled) {
+ return null;
+ }
+ const shelfMetrics = createSidePackShelfMetrics(objectGraph, response, options);
+ const sidePack = createProductPageSidePack(objectGraph, response, shelfMetrics, options);
+ // Apply back action for the side pack.
+ metricsHelpersPage.copyMetricsEventsIntoSidepackedPagewithInformation(objectGraph, sidePack, shelfMetrics.metricsPageInformation);
+ return sidePack;
+ });
+}
+/**
+ * Creates a full product page using product data (i.e. a product side pack + all other details required to render a product page).
+ *
+ * @param objectGraph
+ * @param response The raw response for a product page.
+ * @param options The options to use for the product page configuration.
+ * @param additionalPageRequirements Optional additional page requirements for the product page response.
+ * @returns A product page model object.
+ */
+export async function createProductPageFromResponse(objectGraph, response, options, additionalPageRequirements, referrerData, isViewOnly = false) {
+ return await withAsyncValidationContext("createProductPageFromResponse", async () => {
+ var _a;
+ const productData = mediaDataStructure.dataFromDataContainer(objectGraph, response);
+ if (serverData.isNullOrEmpty(productData)) {
+ return null;
+ }
+ const isBundle = productData.type === "app-bundles";
+ if (objectGraph.client.isWatch && isBundle) {
+ throw new models.ItemNotAvailableError("Bundles are not supported on watchOS");
+ }
+ const appEventDataContainer = additionalPageRequirements === null || additionalPageRequirements === void 0 ? void 0 : additionalPageRequirements[productPageCommon.appPromotionRequirementKey];
+ const appEventData = mediaDataStructure.dataFromDataContainer(objectGraph, appEventDataContainer);
+ // When we are rendering a product page with an AppEventDetails page on we need to make sure the referral goes
+ // to the app event not the product page.
+ const appEventReferrerData = serverData.isDefinedNonNullNonEmpty(appEventData)
+ ? referrerData
+ : null;
+ const productReferrerData = serverData.isDefinedNonNullNonEmpty(appEventData)
+ ? null
+ : referrerData;
+ const shelfMetrics = createProductPageShelfMetrics(objectGraph, response, options);
+ const productPage = createProductPageSidePack(objectGraph, productData, shelfMetrics, options, appEventData, productReferrerData, appEventReferrerData, isViewOnly);
+ shelfMetrics.metricsPageInformation.baseFields["platformDisplayStyle"] =
+ metricsUtil.metricsPlatformDisplayStyleFromData(objectGraph, productData, productPage.lockup.icon, options === null || options === void 0 ? void 0 : options.clientIdentifierOverride);
+ if (isAdPlacementEnabled(objectGraph, "productPageYMAL") ||
+ isAdPlacementEnabled(objectGraph, "productPageYMALDuringDownload")) {
+ // Create a new empty IAdSearchInfo, as we won't yet have any ad data.
+ shelfMetrics.metricsPageInformation.iAdInfo = new metricsHelpersModels.IAdSearchInformation(objectGraph, "productPageYMAL", metricsHelpersModels.IAdSearchInformation.createInitialSlotInfos(objectGraph, "productPageYMAL", null, null));
+ }
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, productData);
+ const isWebBrowserContext = (_a = options === null || options === void 0 ? void 0 : options.webBrowser) !== null && _a !== void 0 ? _a : false;
+ if (!isWebBrowserContext) {
+ // Add metrics events (including the back button event) for this full product page response.
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, productPage, shelfMetrics.metricsPageInformation, (fields) => {
+ // <rdar://problem/43026165> Metrics: Add a deviceCompatibility field to the metrics events
+ const supportsMacOSCompatibleIOSBinary = content.supportsMacOSCompatibleIOSBinaryFromData(objectGraph, productData, objectGraph.appleSilicon.isSupportEnabled);
+ const supportsVisionOSCompatibleIOSBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, productData);
+ const isMacOSAppBuyable = content.isMacOSAppBuyableAndRunnableFromData(objectGraph, productData, objectGraph.appleSilicon.isSupportEnabled, objectGraph.appleSilicon.isRosettaAvailable);
+ const isBuyable = content.buyableOnDevice(objectGraph, productData, appPlatforms, objectGraph.client.deviceType, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary, isMacOSAppBuyable);
+ const isPreorderable = content.preorderableOnDevice(objectGraph, appPlatforms, objectGraph.client.deviceType, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
+ const isPurchasable = appPlatforms && (isBuyable || isPreorderable);
+ const isRunnable = content.runnableOnDeviceWithData(objectGraph, productData, objectGraph.client.deviceType, objectGraph.appleSilicon.isSupportEnabled);
+ if (isPurchasable && isRunnable) {
+ fields["deviceCompatibility"] = "runnable";
+ }
+ else if (isPurchasable) {
+ fields["deviceCompatibility"] = "purchasable";
+ }
+ else {
+ fields["deviceCompatibility"] = "none";
+ }
+ if (serverData.isDefinedNonNullNonEmpty(productReferrerData)) {
+ MetricsReferralContext.shared.addReferralContextToMetricsFieldsIfNecessary(fields);
+ }
+ }, isWebBrowserContext);
+ }
+ // Create refresh controller to gather information on when we may need to refresh.
+ const refreshController = refresh.newPageRefreshControllerFromResponse(response, true);
+ await addFullProductShelfMappingToProductPage(objectGraph, productPage, productData, shelfMetrics, options, refreshController, isViewOnly);
+ // Apply refresh policy to page.
+ productPage.pageRefreshPolicy = refresh.pageRefreshPolicyForController(objectGraph, refreshController);
+ productPage.isIncomplete = false;
+ productPage.alwaysAllowReviews = reviews.shouldAllowReviewsForSADApp(objectGraph, productData);
+ productPage.canonicalURL = mediaAttributes.attributeAsString(productData, "url");
+ // Remove interactive elements from view-only product pages.
+ if (isViewOnly) {
+ productPage.shareAction = null;
+ productPage.titleOfferDisplayProperties = null;
+ productPage.expandedOfferDetails = null;
+ productPage.lockup.offerDisplayProperties = null;
+ productPage.banner = null;
+ }
+ return productPage;
+ });
+}
+/**
+ * Creates a side pack model object using JSON data.
+ * @param objectGraph
+ * @param data The data representing the product page side pack.
+ * @param shelfMetrics The metrics that describe the containing shelf.
+ * @param options Any options to consider whilst creating this model object.
+ * @param appPromotionData The data representing a deep linked app promotion (App Event or Winback Offer).
+ * @param productReferrerData The referrerData for the product page
+ * @param appEventReferrerData The referrer data for the app event details page
+ */
+function createProductPageSidePack(objectGraph, data, shelfMetrics, options, appPromotionData, productReferrerData, appEventReferrerData, isViewOnly = false) {
+ return validation.context("createProductPageSidePack", () => {
+ var _a;
+ let clientIdentifierOverride;
+ if (serverData.isDefinedNonNull(options)) {
+ clientIdentifierOverride = options.clientIdentifierOverride;
+ }
+ const id = data.id;
+ const name = mediaAttributes.attributeAsString(data, "name");
+ const isFirstPartyHideableApp = mediaAttributes.attributeAsBooleanOrFalse(data, "isFirstPartyHideableApp");
+ const shouldSuppressReviews = reviews.shouldSuppressReviews(objectGraph, data);
+ const supportsArcade = content.isArcadeSupported(objectGraph, data);
+ const metricsOptions = {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ id: id,
+ isAdvert: shelfMetrics.metricsPageInformation.iAdInfo
+ ? shelfMetrics.metricsPageInformation.iAdInfo.iAdIsPresent
+ : false,
+ };
+ const productPage = new models.ShelfBasedProductPage();
+ const productLockup = new models.Lockup();
+ // Add metrics information
+ const isPreorder = mediaAttributes.attributeAsBoolean(data, "isPreorder");
+ if (isPreorder &&
+ serverData.isDefinedNonNull(shelfMetrics) &&
+ serverData.isDefinedNonNull(shelfMetrics.metricsPageInformation)) {
+ shelfMetrics.metricsPageInformation.offerType = "preorder";
+ shelfMetrics.metricsPageInformation.offerReleaseDate = offers.expectedReleaseDateFromData(objectGraph, data);
+ }
+ productLockup.adamId = id;
+ productLockup.bundleId = contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId");
+ productPage.uber = createUber(objectGraph, data, clientIdentifierOverride);
+ const useInlineUberStyle = productPage.uber && productPage.uber.style === "inline";
+ productLockup.icon = content.iconFromData(objectGraph, data, {
+ useCase: 10 /* content.ArtworkUseCase.ProductPageIcon */,
+ withJoeColorPlaceholder: preprocessor.GAMES_TARGET || undefined,
+ }, clientIdentifierOverride);
+ productPage.title = name;
+ productPage.isIncomplete = true;
+ productLockup.title = name;
+ productLockup.subtitle = lockups.subtitleFromData(objectGraph, data);
+ productLockup.developerTagline = contentAttributes.contentAttributeAsString(objectGraph, data, "subtitle");
+ productPage.regularPriceFormatted = mediaAttributes.attributeAsString(data, "regularPriceFormatted");
+ productPage.logoArtwork = content.productLogoArtworkFromData(objectGraph, data);
+ productPage.navigationBarIconArtwork = createNavigationBarIconArtwork(objectGraph, data, clientIdentifierOverride);
+ if (!shouldSuppressReviews) {
+ productLockup.rating = mediaAttributes.attributeAsNumber(data, "userRating.value");
+ }
+ // For the web client, we show an "Open in Mac App Store" button, which is shown on by basis of whether
+ // the app's binary is compatible with macOS, so we need to pas this attribute.
+ if (objectGraph.client.isWeb) {
+ productLockup.isIOSBinaryMacOSCompatible = mediaAttributes.attributeAsBooleanOrFalse(data, "isIOSBinaryMacOSCompatible");
+ productLockup.clickAction = lockups.actionFromData(objectGraph, data, metricsOptions, null);
+ }
+ // Add primary text color for tvOS
+ if (objectGraph.client.isTV && serverData.isDefinedNonNullNonEmpty(productPage.uber)) {
+ let backgroundColor;
+ if (serverData.isDefinedNonNull(productPage.uber.video) &&
+ serverData.isDefinedNonNull(productPage.uber.video.preview)) {
+ backgroundColor = productPage.uber.video.preview.backgroundColor;
+ }
+ else if (serverData.isDefinedNonNull(productPage.uber.artwork)) {
+ backgroundColor = productPage.uber.artwork.backgroundColor;
+ }
+ else if (serverData.isDefinedNonNull(productPage.uber.iconArtwork)) {
+ backgroundColor = productPage.uber.iconArtwork.backgroundColor;
+ }
+ productPage.hasDarkUserInterfaceStyle = serverData.isDefinedNonNull(backgroundColor)
+ ? color.isDarkColor(backgroundColor, 50)
+ : false;
+ }
+ productLockup.children = lockups.childrenFromLockupData(objectGraph, data, {
+ metricsOptions: metricsOptions,
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ });
+ const productButtonAction = createProductLockupButtonAction(objectGraph, data, productLockup.icon, shelfMetrics, metricsOptions, options, productReferrerData);
+ productLockup.buttonAction = productButtonAction.buttonAction;
+ // Release date and pre-order treatment
+ if (isPreorder) {
+ // Apply theme
+ productPage.theme = "blue";
+ // Create expanded offer
+ let expandedOfferTitle;
+ let expandedOfferSubtitle = null;
+ if (supportsArcade) {
+ expandedOfferTitle = "";
+ expandedOfferSubtitle = objectGraph.loc.uppercased(content.dynamicPreorderDateFromData(objectGraph, data, objectGraph.loc.string("Offer.Expanded.Title.ComingSoon")));
+ }
+ else {
+ expandedOfferTitle = objectGraph.loc.string("Offer.Expanded.Title.ComingSoon");
+ if (serverData.isDefinedNonNull(productButtonAction.offerAction.expectedReleaseDate)) {
+ const dateFormat = "MMM d, yyyy";
+ const formattedReleaseDate = objectGraph.loc.formatDate(dateFormat, productButtonAction.offerAction.expectedReleaseDate);
+ const fallbackLabel = objectGraph.loc
+ .string("PREORDER_EXPANDED_OFFER_SUBTITLE")
+ .replace("{releaseDate}", formattedReleaseDate);
+ expandedOfferSubtitle = content.dynamicPreorderDateFromData(objectGraph, data, fallbackLabel);
+ }
+ else if (objectGraph.client.isTV) {
+ // The top lockup for tvOS does not display the expandedOfferTitle on the product page,
+ // so populate the subtitle with `Coming Soon` instead if there is no expected release date.
+ expandedOfferSubtitle = objectGraph.loc.string("Offer.Expanded.Title.ComingSoon");
+ }
+ }
+ productPage.expandedOfferDetails = new models.ProductPageExpandedOfferDetails(expandedOfferTitle, expandedOfferSubtitle);
+ }
+ if (useInlineUberStyle && supportsArcade) {
+ productPage.theme = "white";
+ }
+ // Buy Offer
+ let offerStyle;
+ let offerEnvironment;
+ if (useInlineUberStyle && supportsArcade) {
+ offerStyle = "white";
+ offerEnvironment = "arcadeProductPage";
+ }
+ else {
+ offerStyle = "colored";
+ offerEnvironment = "productPage";
+ }
+ const titleOfferEnvironment = objectGraph.client.isVision || objectGraph.client.isiOS ? "navigationBar" : "productPage";
+ const titleOfferStyle = "colored";
+ const offerType = supportsArcade ? "arcadeApp" : "app";
+ const offerContext = ((_a = options === null || options === void 0 ? void 0 : options.webBrowser) !== null && _a !== void 0 ? _a : false) ? "productPageBrowserChoice" : "productPage";
+ productLockup.offerDisplayProperties = offers.displayPropertiesFromOfferAction(objectGraph, productButtonAction.offerAction, offerType, data, isPreorder, false, offerStyle, offerEnvironment, null, null, offerContext, false, false, clientIdentifierOverride);
+ productPage.titleOfferDisplayProperties = offers.displayPropertiesFromOfferAction(objectGraph, productButtonAction.offerAction, offerType, data, isPreorder, false, titleOfferStyle, titleOfferEnvironment, null, null, offerContext);
+ // Developer
+ let developerAction;
+ const developer = mediaRelationship.relationshipData(objectGraph, data, "developer");
+ const developerUrl = content.developerUrlFromDeveloperData(objectGraph, developer);
+ if (serverData.isDefinedNonNull(developerUrl)) {
+ developerAction = new models.FlowAction("page");
+ developerAction.title = mediaAttributes.attributeAsString(data, "artistName");
+ developerAction.pageUrl = developerUrl;
+ metricsHelpersClicks.addClickEventToAction(objectGraph, developerAction, {
+ targetType: "button",
+ id: developer.id,
+ actionType: "navigate",
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ });
+ if (objectGraph.client.isWeb) {
+ developerAction.destination = makeDeveloperPageIntent({
+ ...getLocale(objectGraph),
+ id: developer.id,
+ });
+ }
+ }
+ productPage.developerAction = developerAction;
+ // Add share action
+ productPage.shareAction = sharing.shareProductActionFromData(objectGraph, data, shelfMetrics.metricsPageInformation, shelfMetrics.locationTracker, clientIdentifierOverride);
+ // Tertiary badge
+ productLockup.tertiaryTitle = lockups.badgeFromData(objectGraph, data);
+ productLockup.tertiaryTitleAction = lockups.badgeActionFromData(objectGraph, data);
+ productLockup.tertiaryTitleArtwork = lockups.badgeArtworkFromData(objectGraph, data);
+ // Banner Context
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ const bannerContext = {
+ appPlatforms: appPlatforms,
+ clientIdentifierOverride: clientIdentifierOverride,
+ offerButtonShouldBeDisabled: false,
+ metricsPageInformation: shelfMetrics.metricsPageInformation,
+ metricsLocationTracker: shelfMetrics.locationTracker,
+ webBrowser: options === null || options === void 0 ? void 0 : options.webBrowser,
+ };
+ // Show Banner if present, otherwise show AppStateBanner.
+ const bannerToDisplay = banner.create(objectGraph, data, bannerContext);
+ if (bannerToDisplay instanceof models.Banner) {
+ productPage.banner = bannerToDisplay;
+ }
+ else if (bannerToDisplay instanceof models.AppStateBanner) {
+ productPage.appStateBanner = bannerToDisplay;
+ }
+ const offerDisplayProperties = productLockup.offerDisplayProperties;
+ if (bannerContext.offerButtonShouldBeDisabled && serverData.isDefinedNonNullNonEmpty(offerDisplayProperties)) {
+ productLockup.offerDisplayProperties = offerDisplayProperties.newOfferDisplayPropertiesChangingAppearance(false, "disabled");
+ }
+ // Configure in-line updates for product page
+ const isMininimumOSRequirementMet = !filtering.shouldFilter(objectGraph, data, 512 /* filtering.Filter.MinimumOSRequirement */);
+ if (!isFirstPartyHideableApp && !bannerContext.offerButtonShouldBeDisabled && isMininimumOSRequirementMet) {
+ const updateOfferData = offers.updateOfferDataFromData(objectGraph, data);
+ productPage.updateBuyParams = serverData.asString(updateOfferData, "buyParams");
+ productPage.externalVersionIdentifier = contentAttributes.contentAttributeAsNumber(objectGraph, data, "externalVersionId");
+ }
+ // Description header
+ if (objectGraph.client.isTV) {
+ if (productLockup.developerTagline) {
+ productPage.descriptionHeader = objectGraph.loc
+ .string("PRODUCT_DESCRIPTION_HEADER")
+ .replace("{title}", productPage.title)
+ .replace("{subtitle}", productLockup.developerTagline);
+ }
+ else {
+ productPage.descriptionHeader = productPage.title;
+ }
+ productPage.description = descriptionShelf.paragraphFromData(objectGraph, data);
+ }
+ if (supportsArcade) {
+ productLockup.editorialTagline = objectGraph.loc.string("APPLE_ARCADE");
+ }
+ // Badges
+ if (objectGraph.client.isTV || objectGraph.client.isWatch || objectGraph.client.isVision) {
+ const embeddedInRibbon = objectGraph.client.isVision;
+ productPage.badges = productBadges.badgesFromResponse(objectGraph, data, embeddedInRibbon, {
+ locationTracker: shelfMetrics.locationTracker,
+ pageInformation: shelfMetrics.metricsPageInformation,
+ });
+ }
+ productPage.lockup = productLockup;
+ productPage.appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ // App Promotion
+ if (serverData.isDefinedNonNull(appPromotionData)) {
+ const appPromotionOrDate = appPromotion.appPromotionOrDateFromData(objectGraph, appPromotionData, data, false, false, "dark", "white", true, metricsOptions, true, false, null, false, false);
+ if (serverData.isDefinedNonNull(appPromotionData) && appPromotionOrDate instanceof models.AppPromotion) {
+ const appPromotionItem = appPromotionOrDate;
+ productPage.appPromotionDetailPageFlowAction = appPromotion.detailPageFlowActionFromData(objectGraph, appPromotionData, data, appPromotionItem, metricsOptions, "never", false, appEventReferrerData);
+ }
+ }
+ addSidePackShelfMappingToProductPage(objectGraph, productPage, data, shelfMetrics, options, isViewOnly);
+ if (productPage.uber && !objectGraph.client.isVision) {
+ productPage.presentationOptions.push("prefersOverlayedPageHeader");
+ productPage.presentationOptions.push("prefersNonStandardBackButton");
+ }
+ productPage.alwaysAllowReviews = reviews.shouldAllowReviewsForSADApp(objectGraph, data);
+ /**
+ * Adverts: Apply metrics to the lockup action and the back button
+ */
+ const iAdDismissAdActionMetrics = options === null || options === void 0 ? void 0 : options.iAdDismissAdActionMetrics;
+ if (isSome(iAdDismissAdActionMetrics)) {
+ const dismissActionMetrics = new AdvertActionMetrics(serverData.asString(iAdDismissAdActionMetrics.instanceId), serverData.asString(iAdDismissAdActionMetrics.adamId), serverData.asString(iAdDismissAdActionMetrics.bundleId), serverData.asString(iAdDismissAdActionMetrics.advertType), serverData.asString(iAdDismissAdActionMetrics.invocation), serverData.asString(iAdDismissAdActionMetrics.purchaseType), serverData.asString(iAdDismissAdActionMetrics.reportingDestination));
+ const adInteractionAction = new AdInteractionAction(dismissActionMetrics);
+ productPage.pageDisappearedAction = adInteractionAction;
+ const buyActionMetrics = new AdvertActionMetrics(serverData.asString(iAdDismissAdActionMetrics.instanceId), serverData.asString(iAdDismissAdActionMetrics.adamId), serverData.asString(iAdDismissAdActionMetrics.bundleId), serverData.asString(iAdDismissAdActionMetrics.advertType), "offerButtonPress", serverData.asString(iAdDismissAdActionMetrics.purchaseType), serverData.asString(iAdDismissAdActionMetrics.reportingDestination));
+ productPage.lockup.buttonAction = attachAdActionMetricsToAction(objectGraph, productPage.lockup.buttonAction, buyActionMetrics);
+ }
+ return productPage;
+ });
+}
+/**
+ * Creates the shelf metrics for a side pack.
+ * @param data The data describing the side pack.
+ * @param options The side pack options.
+ */
+function createSidePackShelfMetrics(objectGraph, data, options) {
+ const productUrl = mediaAttributes.attributeAsString(data, "url");
+ const name = mediaAttributes.attributeAsString(data, "name");
+ const artistName = mediaAttributes.attributeAsString(data, "artistName");
+ let iAdClickInfo = null;
+ if (options) {
+ iAdClickInfo = options.iAdClickFields;
+ }
+ const pageInformation = metricsHelpersPage.fakeMetricsPageInformation(objectGraph, "Software", data.id, `${artistName}_${name}`, iAdClickInfo);
+ pageInformation.pageUrl = productUrl;
+ const locationTracker = metricsHelpersLocation.newLocationTracker();
+ return new ProductPageShelfMetrics(pageInformation, locationTracker);
+}
+/**
+ * Creates the shelf metrics for a product page response.
+ * @param response The product page response.
+ * @param options The product page options.
+ */
+function createProductPageShelfMetrics(objectGraph, response, options) {
+ var _a;
+ const productData = mediaDataStructure.dataFromDataContainer(objectGraph, response);
+ const isBundle = productData.type === "app-bundles";
+ // Add the toro data for the click to the page data
+ if (serverData.isDefinedNonNull(options)) {
+ if (options.iAdClickFields) {
+ response[metricsHelpersModels.iAdURLParameterStringToken] = JSON.stringify(options.iAdClickFields);
+ }
+ if (options.iAdLineItem) {
+ response[metricsHelpersModels.iAdURLLineItemParameterStringToken] = options.iAdLineItem;
+ }
+ }
+ // <rdar://problem/33764430> Toro: iAd is missing AD_OPEN figaro event when tapping on ad and pressing OPEN through the product page
+ const pageType = isBundle ? "SoftwareBundle" : "Software";
+ const pageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, pageType, productData.id, response);
+ const pageUrl = new urls.URL(pageInformation.pageUrl);
+ const isWebBrowserContext = (_a = options === null || options === void 0 ? void 0 : options.webBrowser) !== null && _a !== void 0 ? _a : false;
+ if (isWebBrowserContext) {
+ // If this is the web browser context, swap the `webBrowser=1` param with `context=browserChoice`.
+ pageUrl.param("context", "browserChoice");
+ pageUrl.removeParam("webBrowser");
+ pageInformation.pageUrl = pageUrl.build();
+ }
+ const locationTracker = metricsHelpersLocation.newLocationTracker();
+ return new ProductPageShelfMetrics(pageInformation, locationTracker);
+}
+/**
+ * When configuring a product page, it might be the case that we are doing so with
+ * the intention of 'spotlighting' a particular section. This essentially means
+ * that the context of the product page presentation dictates a particular section
+ * appear at the top (just underneath the top lockup). This function applies these
+ * spotlighting options to the product page.
+ * @param options The options for the product page.
+ * @param productPage
+ */
+function applySpotlightOptions(objectGraph, options, productPage) {
+ if (options && options.spotlightSection) {
+ const spotlightId = options.spotlightInAppProductIdentifier;
+ if (spotlightId && options.spotlightSection) {
+ const shelf = productPage.shelfMapping[options.spotlightSection.shelfId];
+ if (!shelf) {
+ return;
+ }
+ // Apply Presentation Hints
+ shelf.presentationHints = { isInProductPageSpotlight: true };
+ // Shelf Items
+ productPageCommon.moveLockupToFront(objectGraph, spotlightId, shelf.items);
+ // Shelf URL
+ if (shelf.url) {
+ const url = new urls.URL(shelf.url);
+ if (url) {
+ shelf.url = url.param(Parameters.offerName, spotlightId).build();
+ }
+ }
+ }
+ }
+}
+function addSidePackShelfMappingToProductPage(objectGraph, productPage, data, shelfMetrics, options, isViewOnly) {
+ productPage.shelfMapping["topLockup"] = new models.Shelf("marker", "productTopLockup");
+ if (objectGraph.client.deviceType !== "tv" && !objectGraph.client.isVision) {
+ const descriptionShelfContext = {
+ developerAction: productPage.developerAction,
+ };
+ productPage.shelfMapping["description"] = descriptionShelf.create(objectGraph, data, shelfMetrics, descriptionShelfContext);
+ }
+ // Badges
+ if (objectGraph.client.isWeb || objectGraph.client.isiOS || objectGraph.client.isMac) {
+ const ribbonShelfContext = {
+ useInlineUberStyle: productPage.uber && productPage.uber.style === "inline",
+ };
+ productPage.shelfMapping["informationRibbon"] = ribbonShelf.create(objectGraph, data, shelfMetrics, ribbonShelfContext);
+ }
+ addShelfOrderingToProductPage(objectGraph, productPage, [], content.isArcadeSupported(objectGraph, data), options);
+}
+async function addFullProductShelfMappingToProductPage(objectGraph, productPage, data, shelfMetrics, options, refreshController, isViewOnly) {
+ var _a, _b, _c;
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ const isBundle = data.type === "app-bundles";
+ const isPreorder = mediaAttributes.attributeAsBoolean(data, "isPreorder");
+ const macOnlyApp = appPlatforms.length === 1 && appPlatforms[0] === "mac";
+ const shouldShowMacOnlyReviews = !macOnlyApp || objectGraph.client.isMac;
+ const shouldSuppressReviews = reviews.shouldSuppressReviews(objectGraph, data);
+ const shouldShowRatingsAndReviews = (shouldShowMacOnlyReviews || objectGraph.client.isWeb) && !isPreorder && !shouldSuppressReviews;
+ const clientIdentifierOverride = serverData.isDefinedNonNullNonEmpty(options)
+ ? options.clientIdentifierOverride
+ : null;
+ const isFirstPartyHideableApp = mediaAttributes.attributeAsBooleanOrFalse(data, "isFirstPartyHideableApp");
+ const offerDisplayProperties = productPage.lockup.offerDisplayProperties;
+ const isFreeProduct = serverData.isDefinedNonNull(offerDisplayProperties) ? offerDisplayProperties.isFree : true;
+ // Bundle Children
+ let bundleChildren;
+ if (isBundle) {
+ const childrenRelationship = mediaRelationship.relationship(data, "apps");
+ bundleChildren = bundleChildrenShelf.create(objectGraph, data, childrenRelationship, shelfMetrics);
+ }
+ if (bundleChildren) {
+ productPage.shelfMapping["bundleChildren"] = bundleChildren;
+ }
+ // Bundles This App Is Part Of
+ const bundleParentRelationship = mediaRelationship.relationship(data, "app-bundles");
+ const bundleParents = bundleParentsShelf.create(objectGraph, data, bundleParentRelationship, shelfMetrics);
+ if (bundleParents) {
+ productPage.shelfMapping["bundleParents"] = bundleParents;
+ }
+ if (preprocessor.GAMES_TARGET) {
+ productPage.gamesResponse = makeGamesResponse(objectGraph, data);
+ }
+ const ratingsAndReviewsShelfContext = {
+ productTitle: productPage.title,
+ shouldShowRatingsAndReviews: shouldShowRatingsAndReviews,
+ isFirstPartyHideableApp: isFirstPartyHideableApp,
+ isBundle: isBundle,
+ isPreorder: isPreorder,
+ };
+ const ratingsAndReviewsShelves = reviewsShelf.createReviewsShelves(objectGraph, data, shelfMetrics, ratingsAndReviewsShelfContext, productPage.lockup.subtitle);
+ if (serverData.isDefinedNonNullNonEmpty(ratingsAndReviewsShelves)) {
+ productPage.shelfMapping["purchasedRatingsAndReviews"] =
+ ratingsAndReviewsShelves.purchasedRatingsAndReviewsComponentShelf;
+ productPage.shelfMapping["notPurchasedRatingsAndReviews"] =
+ ratingsAndReviewsShelves.notPurchasedRatingsAndReviewsComponentShelf;
+ productPage.shelfMapping["productRatings"] = ratingsAndReviewsShelves.ratingsShelf;
+ productPage.shelfMapping["allProductReviewActions"] = ratingsAndReviewsShelves.allReviewActionsShelf;
+ productPage.shelfMapping["allProductReviews"] = ratingsAndReviewsShelves.allProductReviewsShelf;
+ productPage.shelfMapping["userProductReviews"] = ratingsAndReviewsShelves.userProductReviewsShelf;
+ if (objectGraph.client.isiOS) {
+ productPage.shelfMapping["editorsChoice"] = createEditorsChoiceShelf(objectGraph, data);
+ }
+ else {
+ productPage.shelfMapping["editorsChoiceProductReviews"] =
+ ratingsAndReviewsShelves.editorsChoiceProductReviewsShelf;
+ }
+ productPage.shelfMapping["tapToRateProductReviewAction"] = ratingsAndReviewsShelves.tapToRateActionsShelf;
+ productPage.shelfMapping["writeAReviewProductReviewAction"] = ratingsAndReviewsShelves.writeAReviewActionsShelf;
+ productPage.shelfMapping["productReviewsHeader"] = ratingsAndReviewsShelves.productReviewsHeader;
+ }
+ if (objectGraph.client.isiOS) {
+ const productReviewsFooter = reviewsShelf.createProductReviewsLearnMoreShelf(objectGraph);
+ if (isSome(productReviewsFooter)) {
+ productPage.shelfMapping["productReviewsFooter"] = productReviewsFooter;
+ }
+ }
+ const reviewSummary = reviewsShelf.createReviewSummary(objectGraph, data, shelfMetrics);
+ if (isSome(reviewSummary) && objectGraph.client.isiOS) {
+ productPage.shelfMapping["reviewSummary"] = reviewSummary;
+ }
+ // Most Recent Version
+ const versionHistoryShelfContext = {
+ isFirstPartyHideableApp: isFirstPartyHideableApp,
+ isBundle: isBundle,
+ isPreorder: isPreorder,
+ };
+ const mostRecentVersion = versionHistoryShelves.create(objectGraph, data, shelfMetrics, versionHistoryShelfContext);
+ if (mostRecentVersion) {
+ productPage.shelfMapping["mostRecentVersion"] = mostRecentVersion;
+ }
+ productPage.shelfMapping["textCards"] = textCardsShelf.create(objectGraph, data, shelfMetrics);
+ // Privacy
+ const bundleId = contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId");
+ if (objectGraph.bag.enablePrivacyNutritionLabels &&
+ !privacySuppression.shouldSuppressPrivacyInformationForAdamId(objectGraph, data.id) &&
+ !privacySuppression.shouldSuppressPrivacyInformationForBundleId(objectGraph, bundleId)) {
+ const privacyHeader = privacyHeaderShelf.create(objectGraph, data, shelfMetrics.metricsPageInformation, shelfMetrics.locationTracker);
+ const privacyTypes = privacyTypesShelf.create(objectGraph, data, shelfMetrics);
+ if (serverData.isDefinedNonNull(privacyHeader) &&
+ (serverData.isDefinedNonNull(privacyTypes) || objectGraph.client.isWatch)) {
+ productPage.shelfMapping["privacyHeader"] = privacyHeader;
+ if (serverData.isDefinedNonNull(privacyTypes)) {
+ productPage.shelfMapping["privacyTypes"] = privacyTypes;
+ }
+ const privacyFooter = privacyFooterShelf.create(objectGraph, data, shelfMetrics.metricsPageInformation, shelfMetrics.locationTracker);
+ if (serverData.isDefinedNonNull(privacyFooter)) {
+ productPage.shelfMapping["privacyFooter"] = privacyFooter;
+ }
+ }
+ }
+ // Information
+ const ageRatingBadgeActionMetadata = new productBadgesCommon.BadgeActionMetadata();
+ const languageBadgeActionMetadata = new productBadgesCommon.BadgeActionMetadata();
+ const sizeBadgeActionMetadata = new productBadgesCommon.BadgeActionMetadata();
+ const categoryBadgeActionMetadata = new productBadgesCommon.BadgeActionMetadata();
+ const highMotionBadgeActionMetadata = new productBadgesCommon.BadgeActionMetadata();
+ const metadataHandler = (factory, annotation, currentIndex) => {
+ var _a, _b;
+ if (factory === contentRatingAnnotation.createAnnotation) {
+ ageRatingBadgeActionMetadata.setShelfId("information", currentIndex);
+ ageRatingBadgeActionMetadata.annotation = annotation;
+ productPage.ageRatingAction = new models.ShelfBasedPageScrollAction("information", null, null, null, currentIndex, true);
+ productPage.ageRatingAction.title = objectGraph.loc.string("ProductPage.Banner.AskForException.Review.ViewAgeRatingLink");
+ }
+ // If we have a product page banner which needs to scroll to a particular information section index, populate the index here
+ if (factory === compatibilityAnnotation.createAnnotation && serverData.isDefinedNonNull(productPage.banner)) {
+ // This banner has a variant that mentions the Support (Information) section. Since we now have a Support section,
+ // overwrite the original `action` with the `fullProductAction`
+ if (serverData.isDefinedNonNull(productPage.banner.fullProductAction)) {
+ productPage.banner.action = productPage.banner.fullProductAction;
+ }
+ const action = productPage.banner.action;
+ if (serverData.isDefinedNonNull(action)) {
+ if (productPageUtil.isShelfBased(objectGraph)) {
+ if (action instanceof models.ShelfBasedPageScrollAction &&
+ ((_a = action.shelfId) === null || _a === void 0 ? void 0 : _a.length) > 0 &&
+ action.shelfId === "information" &&
+ action.indexId === "compatibilityAnnotation") {
+ action.index = currentIndex;
+ }
+ }
+ else {
+ if (action instanceof models.ProductPageScrollAction &&
+ ((_b = action.section.shelfId) === null || _b === void 0 ? void 0 : _b.length) > 0 &&
+ action.section.shelfId === "information" &&
+ action.indexId === "compatibilityAnnotation") {
+ action.index = currentIndex;
+ }
+ }
+ }
+ }
+ if (factory === languageAnnotation.createAnnotation) {
+ languageBadgeActionMetadata.setShelfId("information", currentIndex);
+ languageBadgeActionMetadata.annotation = annotation;
+ }
+ if (factory === sizeAnnotation.createAnnotation) {
+ sizeBadgeActionMetadata.setShelfId("information", currentIndex);
+ sizeBadgeActionMetadata.annotation = annotation;
+ }
+ if (factory === categoryAnnotation.createAnnotation) {
+ categoryBadgeActionMetadata.setShelfId("information", currentIndex);
+ categoryBadgeActionMetadata.annotation = annotation;
+ }
+ if (factory === highMotionAnnotation.createAnnotation) {
+ highMotionBadgeActionMetadata.setShelfId("information", currentIndex);
+ highMotionBadgeActionMetadata.annotation = annotation;
+ }
+ };
+ const supportsArcade = content.isArcadeSupported(objectGraph, data);
+ const information = annotationsShelf.create(objectGraph, data, isFirstPartyHideableApp, shelfMetrics, metadataHandler, supportsArcade, isFreeProduct, false);
+ if (information) {
+ productPage.shelfMapping["information"] = information;
+ }
+ // App Events
+ const appEvents = appPromotionsShelf.appPromotionsShelfForProductPage(objectGraph, data, shelfMetrics, refreshController);
+ if (serverData.isDefinedNonNull(appEvents)) {
+ productPage.shelfMapping["appEvents"] = appEvents;
+ }
+ // Editorial Quote
+ if (supportsArcade) {
+ const editorialQuote = editorialQuoteShelf.create(objectGraph, data, shelfMetrics);
+ if (editorialQuote) {
+ productPage.shelfMapping["editorialQuote"] = editorialQuote;
+ }
+ }
+ // Links
+ // If the app is purchased, we will show the link for report a problem
+ productPage.shelfMapping["notPurchasedLinks"] = linksShelf.create(objectGraph, data, shelfMetrics, false, isFreeProduct, supportsArcade);
+ productPage.shelfMapping["purchasedLinks"] = linksShelf.create(objectGraph, data, shelfMetrics, true, isFreeProduct, supportsArcade);
+ productPage.shelfMapping["textLinksShelf"] = textLinksShelf.create(objectGraph, data, shelfMetrics);
+ // Capabilities
+ const capabilities = capabilitiesShelf.create(objectGraph, data, isFreeProduct, shelfMetrics);
+ if (capabilities) {
+ productPage.shelfMapping["capabilities"] = capabilities;
+ }
+ const isWebBrowserContext = (_a = options === null || options === void 0 ? void 0 : options.webBrowser) !== null && _a !== void 0 ? _a : false;
+ // Featured In
+ if (!objectGraph.client.isVision && !isWebBrowserContext) {
+ const featuredInContainer = mediaRelationship.relationship(data, "related-editorial-items");
+ if (serverData.isDefinedNonNullNonEmpty(featuredInContainer)) {
+ const isSmallStoryCardsSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isWeb;
+ const featuredIn = isSmallStoryCardsSupported
+ ? smallStoryCardShelf.createInitialShelf(objectGraph, data, featuredInContainer.data, shelfMetrics)
+ : featuredInShelf.create(objectGraph, data, featuredInContainer, shelfMetrics);
+ if (featuredIn) {
+ productPage.shelfMapping["featuredIn"] = featuredIn;
+ }
+ }
+ }
+ // Friends Playing
+ const friendsPlayingShelfContext = {
+ isPreorder: isPreorder,
+ };
+ const friendsPlayingBadgeActionMetadata = new productBadgesCommon.BadgeActionMetadata();
+ const friendsPlaying = friendsPlayingShelf.create(objectGraph, data, shelfMetrics, friendsPlayingShelfContext);
+ if (friendsPlaying) {
+ productPage.shelfMapping["friendsPlaying"] = friendsPlaying;
+ friendsPlayingBadgeActionMetadata.setShelfId("friendsPlaying");
+ }
+ // Achievements
+ const achievementsShelfContext = {
+ isPreorder: isPreorder,
+ };
+ const achievements = achievementsShelf.create(objectGraph, data, shelfMetrics, achievementsShelfContext);
+ if (achievements) {
+ productPage.shelfMapping["achievements"] = achievements;
+ }
+ // More by Developer
+ if (!isViewOnly && !isWebBrowserContext) {
+ const moreByDeveloper = moreByDeveloperShelf.createInitialShelf(objectGraph, data, shelfMetrics);
+ if (isSome(moreByDeveloper)) {
+ productPage.shelfMapping["moreByDeveloper"] = moreByDeveloper;
+ }
+ }
+ // Similar Items
+ if (!isViewOnly && !isWebBrowserContext) {
+ const similarItems = similarItemsShelf.createInitialShelf(objectGraph, data, shelfMetrics);
+ if (isSome(similarItems)) {
+ productPage.shelfMapping["similarItems"] = similarItems;
+ }
+ }
+ // Subscriptions
+ const subscriptionsShelfContext = {
+ isForSubscriptions: true,
+ options: options,
+ };
+ const subscriptionsResult = await tryAwait(inAppPurchasesShelf.create(objectGraph, data, shelfMetrics, subscriptionsShelfContext));
+ if (subscriptionsResult.success && isSome(subscriptionsResult.value)) {
+ productPage.shelfMapping["subscriptions"] = subscriptionsResult.value;
+ }
+ // In-App Purchases
+ const inAppPurchaseShelfContext = {
+ isForSubscriptions: false,
+ options: options,
+ };
+ const inAppPurchasesResult = await tryAwait(inAppPurchasesShelf.create(objectGraph, data, shelfMetrics, inAppPurchaseShelfContext));
+ if (inAppPurchasesResult.success && isSome(inAppPurchasesResult.value)) {
+ productPage.shelfMapping["inAppPurchases"] = inAppPurchasesResult.value;
+ }
+ const productMediaShelves = productMediaShelf.create(objectGraph, data, clientIdentifierOverride, shelfMetrics);
+ productPage.shelfMapping = {
+ ...productPage.shelfMapping,
+ ...productMediaShelves.shelfMapping,
+ };
+ // We need to extract the device family that matches the screenshots we display, for the accesssibility shelves
+ const firstMediaAppPlatform = (_b = productMediaShelves === null || productMediaShelves === void 0 ? void 0 : productMediaShelves.allProductMedia[0]) === null || _b === void 0 ? void 0 : _b.mediaPlatform.appPlatform;
+ const deviceFamily = deviceFamilyForAccessibilityLabels(firstMediaAppPlatform);
+ const actionLinksShelfContext = {
+ productTitle: productPage.title,
+ shouldShowRatingsAndReviews: shouldShowRatingsAndReviews,
+ isFirstPartyHideableApp: isFirstPartyHideableApp,
+ isBundle: isBundle,
+ isPreorder: isPreorder,
+ isArcadeApp: supportsArcade,
+ isFreeProduct: isFreeProduct,
+ deviceFamily: deviceFamily,
+ };
+ const actionLinks = actionLinksShelf.create(objectGraph, data, shelfMetrics, actionLinksShelfContext);
+ if (actionLinks) {
+ productPage.shelfMapping["actionLinks"] = actionLinks;
+ }
+ // Accessibility Support
+ const isWebViewingWatch = objectGraph.client.isWeb && ((_c = objectGraph.activeIntent) === null || _c === void 0 ? void 0 : _c.platform) === "watch";
+ if (!objectGraph.client.isWatch &&
+ !isWebViewingWatch &&
+ isSome(deviceFamily) &&
+ accessibilityShelves.shouldShowAccessibilitySection(objectGraph, data, deviceFamily)) {
+ const accessibilityHeader = accessibilityShelves.createHeaderShelf(objectGraph, data, deviceFamily, false, shelfMetrics, shelfMetrics.metricsPageInformation, shelfMetrics.locationTracker);
+ productPage.shelfMapping["accessibilityHeader"] = accessibilityHeader;
+ const accessibilityFeatures = accessibilityShelves.createFeaturesShelf(objectGraph, data, deviceFamily, false, shelfMetrics, shelfMetrics.metricsPageInformation, shelfMetrics.locationTracker);
+ if (isSome(accessibilityFeatures)) {
+ productPage.shelfMapping["accessibilityFeatures"] = accessibilityFeatures;
+ }
+ const accessibilityDeveloperLink = accessibilityShelves.createDeveloperLinkShelf(objectGraph, data, false, shelfMetrics.metricsPageInformation, shelfMetrics.locationTracker);
+ if (isSome(accessibilityDeveloperLink)) {
+ productPage.shelfMapping["accessibilityDeveloperLink"] = accessibilityDeveloperLink;
+ }
+ }
+ if (objectGraph.client.isiOS) {
+ const placementId = supportsArcade
+ ? "arcadeProductPageInlineMessage"
+ : "productPageInlineMessage";
+ const unifiedMessageShelf = shelfForUnifiedMessage(objectGraph, placementId);
+ productPage.shelfMapping[placementId] = unifiedMessageShelf;
+ }
+ applySpotlightOptions(objectGraph, options, productPage);
+ const badgeActionMetadataByKey = {};
+ const contentRatingKey = "contentRating";
+ const storefrontContentRatingKey = "storefrontContentRating";
+ const languageKey = "languages";
+ const sizeKey = "size";
+ const categoryKey = "category";
+ const friendsPlayingType = "friendsPlaying";
+ const highMotionType = "highMotion";
+ badgeActionMetadataByKey[contentRatingKey] = ageRatingBadgeActionMetadata;
+ badgeActionMetadataByKey[storefrontContentRatingKey] = ageRatingBadgeActionMetadata;
+ badgeActionMetadataByKey[languageKey] = languageBadgeActionMetadata;
+ badgeActionMetadataByKey[sizeKey] = sizeBadgeActionMetadata;
+ badgeActionMetadataByKey[categoryKey] = categoryBadgeActionMetadata;
+ badgeActionMetadataByKey[friendsPlayingType] = friendsPlayingBadgeActionMetadata;
+ badgeActionMetadataByKey[highMotionType] = highMotionBadgeActionMetadata;
+ if (productPage.badges) {
+ productBadgesCommon.applyActionMetadataToBadges(objectGraph, productPage.badges, badgeActionMetadataByKey, shelfMetrics);
+ }
+ // If we have an info ribbon, we need to apply actions to badges there too.
+ if (productPage.shelfMapping["informationRibbon"] != null) {
+ productBadgesCommon.applyActionMetadataToBadges(objectGraph, productPageUtil.badgesFromInfoRibbonShelf(objectGraph, productPage.shelfMapping["informationRibbon"]), badgeActionMetadataByKey, shelfMetrics);
+ }
+ addShelfOrderingToProductPage(objectGraph, productPage, productMediaShelves.sectionMappings, supportsArcade, options);
+}
+function addShelfOrderingToProductPage(objectGraph, productPage, productMediaSectionMappings, supportsArcade, options) {
+ const mapShelfOrderings = function (originalShelfOrdering, mediaSection, isMediaExpanded) {
+ const updatedSectionMappings = originalShelfOrdering
+ .map((sectionMapping) => {
+ if (sectionMapping.shelfId === "screenshots" && isMediaExpanded) {
+ return productMediaSectionMappings;
+ }
+ else if (sectionMapping.shelfId === "screenshots") {
+ return productMediaSectionMappings[0] ? [productMediaSectionMappings[0]] : [];
+ }
+ else {
+ return [sectionMapping];
+ }
+ })
+ .reduce((previousValue, currentValue) => {
+ return previousValue.concat(currentValue);
+ });
+ return productPageShelfIdsFromMappings(updatedSectionMappings);
+ };
+ const purchasedOrdering = shelfBasedProductOrdering.sectionOrdering(objectGraph, true, objectGraph.host.platform, supportsArcade, false, false, options);
+ const notPurchasedOrdering = shelfBasedProductOrdering.sectionOrdering(objectGraph, false, objectGraph.host.platform, supportsArcade, false, false, options);
+ const purchasedOrderingCompact = shelfBasedProductOrdering.sectionOrdering(objectGraph, true, objectGraph.host.platform, supportsArcade, true, false, options);
+ const notPurchasedOrderingCompact = shelfBasedProductOrdering.sectionOrdering(objectGraph, false, objectGraph.host.platform, supportsArcade, true, false, options);
+ const downloadingOrdering = shelfBasedProductOrdering.sectionOrdering(objectGraph, false, objectGraph.host.platform, supportsArcade, false, true, options);
+ const downloadingOrderingCompact = shelfBasedProductOrdering.sectionOrdering(objectGraph, false, objectGraph.host.platform, supportsArcade, true, true, options);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdPurcahsed] = mapShelfOrderings(purchasedOrdering, productMediaSectionMappings, false);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdPurcahsedExpandedMedia] = mapShelfOrderings(purchasedOrdering, productMediaSectionMappings, true);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdNotPurcahsed] = mapShelfOrderings(notPurchasedOrdering, productMediaSectionMappings, false);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdNotPurcahsedExpandedMedia] = mapShelfOrderings(notPurchasedOrdering, productMediaSectionMappings, true);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdPurcahsedCompact] = mapShelfOrderings(purchasedOrderingCompact, productMediaSectionMappings, false);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdPurcahsedExpandedMediaCompact] = mapShelfOrderings(purchasedOrderingCompact, productMediaSectionMappings, true);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdNotPurcahsedCompact] = mapShelfOrderings(notPurchasedOrderingCompact, productMediaSectionMappings, false);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdNotPurcahsedExpandedMediaCompact] =
+ mapShelfOrderings(notPurchasedOrderingCompact, productMediaSectionMappings, true);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdDownloading] = mapShelfOrderings(downloadingOrdering, productMediaSectionMappings, false);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdDownloadingExpandedMedia] = mapShelfOrderings(downloadingOrdering, productMediaSectionMappings, true);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdDownloadingCompact] = mapShelfOrderings(downloadingOrderingCompact, productMediaSectionMappings, false);
+ productPage.shelfOrderings[models.shelfBasedProductPageOrderingIdDownloadingExpandedMediaCompact] =
+ mapShelfOrderings(downloadingOrderingCompact, productMediaSectionMappings, true);
+ productPage.defaultShelfOrdering = models.shelfBasedProductPageOrderingIdNotPurcahsed;
+ for (const shelfId of Object.keys(productPage.shelfMapping)) {
+ if (serverData.isDefinedNonNullNonEmpty(productPage.shelfMapping[shelfId])) {
+ productPage.shelfMapping[shelfId].id = shelfId;
+ }
+ }
+}
+function productUberVideoPlaybackIdForProduct(data) {
+ return `ProductPage.${data.id}.productUberVideoPlaybackId`;
+}
+export function createUber(objectGraph, data, clientIdentifierOverride) {
+ const attributePlatform = content.iconAttributePlatform(objectGraph, data, clientIdentifierOverride);
+ if (objectGraph.client.isVision && attributePlatform !== "xros") {
+ // Ubers are reserved for native apps only
+ return null;
+ }
+ const supportsArcade = content.isArcadeSupported(objectGraph, data);
+ const uberStyle = shouldUseInlineUberStyle(objectGraph, supportsArcade) ? "inline" : "above";
+ const uber = new models.Uber(uberStyle);
+ uber.artwork = content.productUberFromData(objectGraph, data, {
+ supportsArcade: supportsArcade,
+ });
+ if (objectGraph.client.isiOS) {
+ uber.compactArtwork = content.productUberFromData(objectGraph, data, {
+ supportsArcade: supportsArcade,
+ prefersCompactVariant: true,
+ });
+ }
+ uber.video = content.productEditorialVideoFromData(objectGraph, data, 21 /* content.ArtworkUseCase.Uber */);
+ if (serverData.isDefinedNonNull(uber.video)) {
+ uber.video.playbackId = productUberVideoPlaybackIdForProduct(data);
+ }
+ if (objectGraph.client.isTV || (objectGraph.client.isVision && attributePlatform === "xros")) {
+ uber.iconArtwork = content.iconFromData(objectGraph, data, {
+ useCase: 10 /* content.ArtworkUseCase.ProductPageIcon */,
+ withJoeColorPlaceholder: objectGraph.client.isTV,
+ allowingTransparency: !objectGraph.client.isTV,
+ }, clientIdentifierOverride);
+ if (objectGraph.client.isVision && isNothing(uber.artwork) && isNothing(uber.video)) {
+ uber.style = "background";
+ }
+ }
+ // If this is an Arcade app, but we don't have an Arcade Uber to show, use the first video/screenshot instead.
+ if (supportsArcade &&
+ serverData.isNullOrEmpty(uber.video) &&
+ serverData.isNullOrEmpty(uber.artwork) &&
+ !objectGraph.client.isVision) {
+ const includedAppPlatformsForProductMedia = includedAppPlatformsForProductMediaOnDevice(objectGraph, objectGraph.client.deviceType);
+ const productVariantData = productPageVariants.productVariantDataForData(objectGraph, data);
+ const productMedia = content.productMediaFromData(objectGraph, data, 11 /* content.ArtworkUseCase.ProductPageScreenshots */, includedAppPlatformsForProductMedia, productVariantData, clientIdentifierOverride);
+ if (productMedia.length > 0 && productMedia[0].items.length > 0) {
+ const topAsset = productMedia[0].items[0];
+ uber.video = topAsset.video;
+ uber.artwork = topAsset.screenshot;
+ }
+ }
+ // Defer to uberArtwork if no uberArtworkForCompactDisplay exists.
+ if (serverData.isNullOrEmpty(uber.compactArtwork) && objectGraph.client.isiOS) {
+ uber.compactArtwork = uber.artwork;
+ }
+ return uber.isValidUber(objectGraph.client.isTV, objectGraph.client.isVision) ? uber : null;
+}
+/**
+ * Creates the icon artwork to display in the navigation bar of the product page. Currently only used on visionOS.
+ * @param objectGraph Current object graph.
+ * @param data The product data.
+ * @param clientIdentifierOverride Any client identifier override.
+ * @returns An artwork representing the icon, or null.
+ */
+function createNavigationBarIconArtwork(objectGraph, data, clientIdentifierOverride) {
+ if (!objectGraph.client.isVision) {
+ return null;
+ }
+ const iconArtwork = content.iconFromData(objectGraph, data, {
+ useCase: 10 /* content.ArtworkUseCase.ProductPageIcon */,
+ }, clientIdentifierOverride);
+ return iconArtwork;
+}
+function createProductLockupButtonAction(objectGraph, data, productIcon, shelfMetrics, metricsOptions, options, referrerData) {
+ var _a;
+ const offerData = offers.offerDataFromData(objectGraph, data);
+ const bundleId = contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId");
+ const isPreorder = mediaAttributes.attributeAsBoolean(data, "isPreorder");
+ const metricsPlatformDisplayStyle = metricsUtil.metricsPlatformDisplayStyleFromData(objectGraph, data, productIcon, options === null || options === void 0 ? void 0 : options.clientIdentifierOverride);
+ const offerAction = offers.offerActionFromOfferData(objectGraph, offerData, data, isPreorder, false, metricsPlatformDisplayStyle, metricsOptions, "productPage", referrerData, options === null || options === void 0 ? void 0 : options.webBrowser);
+ if ((_a = options === null || options === void 0 ? void 0 : options.webBrowser) !== null && _a !== void 0 ? _a : false) {
+ return {
+ buttonAction: new SelectAppAction(data.id),
+ offerAction: offerAction,
+ };
+ }
+ let buttonAction;
+ if (serverData.isDefinedNonNullNonEmpty(offerAction)) {
+ if (serverData.isDefinedNonNull(options)) {
+ offerAction.lineItem = options.iAdLineItem;
+ }
+ const clientIdentifierOverride = serverData.isDefinedNonNullNonEmpty(options)
+ ? options.clientIdentifierOverride
+ : null;
+ buttonAction = offers.wrapOfferActionIfNeeded(objectGraph, offerAction, data, isPreorder, metricsOptions, "productPage", clientIdentifierOverride);
+ if (serverData.isDefinedNonNull(options) && serverData.isDefinedNonNullNonEmpty(buttonAction)) {
+ const attributePlatform = contentAttributes.defaultAttributePlatform(objectGraph);
+ const productVariantData = productPageVariants.productVariantDataForData(objectGraph, data);
+ const cppDeepLinkUrl = contentAttributes.customAttributeAsString(objectGraph, data, productVariantData, "customDeepLink", attributePlatform);
+ const hasCppDeepLink = serverData.isDefinedNonNullNonEmpty(cppDeepLinkUrl) &&
+ !serverData.asBooleanOrFalse(options.isCppDeepLinkDisabled);
+ const hasExternalDeepLink = serverData.isDefinedNonNullNonEmpty(options.externalDeepLinkUrl);
+ let deepLinkUrl;
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ const hasAlignedRegionDeepLink = isSome(options.alignedRegionDeepLinkUrl);
+ if (hasCppDeepLink || hasExternalDeepLink || hasAlignedRegionDeepLink) {
+ if (hasAlignedRegionDeepLink) {
+ deepLinkUrl = options.alignedRegionDeepLinkUrl;
+ }
+ else if (hasCppDeepLink) {
+ deepLinkUrl = cppDeepLinkUrl;
+ }
+ else {
+ deepLinkUrl = options.externalDeepLinkUrl;
+ }
+ buttonAction = externalDeepLink.deepLinkActionWrappingAction(objectGraph, buttonAction, offerAction.adamId, bundleId, deepLinkUrl, false, metricsOptions);
+ }
+ }
+ else {
+ if (hasCppDeepLink || hasExternalDeepLink) {
+ deepLinkUrl = hasCppDeepLink ? cppDeepLinkUrl : options.externalDeepLinkUrl;
+ buttonAction = externalDeepLink.deepLinkActionWrappingAction(objectGraph, buttonAction, offerAction.adamId, bundleId, deepLinkUrl, false, metricsOptions);
+ }
+ }
+ }
+ }
+ return {
+ buttonAction: buttonAction,
+ offerAction: offerAction,
+ };
+}
+/**
+ * Returns the list of included app platforms for the product media shelves.
+ * This is used to constrain some platforms to media type of just that platform, e.g. tvOS can only show tvOS screenshots and video.
+ *
+ * @param deviceType Device type to find the included app platforms for product media for.
+ * @returns models.AppPlatform[] | null An array of supported app platforms for product media, or `null` if there are no constraints and should fallback to sorting logic in content.ts
+ */
+function includedAppPlatformsForProductMediaOnDevice(objectGraph, deviceType) {
+ // We want to return all product media platforms, so that we can generate
+ // the appropriate platform selector description text.
+ return null;
+}
+/**
+ * Source of truth for the decision on whether or not the product page should be displayed in the inline uber style
+ *
+ * @param supportsArcade Does the product say it supports Arcade
+ */
+function shouldUseInlineUberStyle(objectGraph, supportsArcade) {
+ if (objectGraph.client.isVision) {
+ return true;
+ }
+ return supportsArcade && (objectGraph.client.isiOS || objectGraph.client.isMac);
+}
+//# sourceMappingURL=shelf-based-product-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/accessibility-shelves.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/accessibility-shelves.js
new file mode 100644
index 0000000..18e2c25
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/accessibility-shelves.js
@@ -0,0 +1,544 @@
+import * as models from "../../../api/models";
+import * as validation from "@jet/environment/json/validation";
+import * as contentArtwork from "../../content/artwork/artwork";
+import { isNothing, isSome } from "@jet/environment";
+import { attributeAsDictionary, attributeAsString, URL } from "@apple-media-services/media-api";
+import { asString } from "../../../foundation/json-parsing/server-data";
+import { isEmpty, isNotEmpty } from "../../../foundation/util/array-util";
+import { Path, Protocol } from "../../../foundation/network/url-constants";
+import { isProductAccessibilityLabelsEnabled, shouldSuppressAccessibilityLabelsForAdamId, shouldSuppressAccessibilityLabelsForBundleId, } from "../../accessibility/accessibility-common";
+import { attributeAsArray } from "../../../foundation/media/attributes";
+import { contentAttributeAsString } from "../../content/attributes";
+import { newLocationTracker, nextPosition } from "../../metrics/helpers/location";
+import { addClickEventToAction } from "../../metrics/helpers/clicks";
+import { addMetricsEventsToPageWithInformation, metricsPageInformationFromMediaApiResponse, } from "../../metrics/helpers/page";
+import { makeRoutableArticlePageIntent } from "../../../api/intents/routable-article-page-intent";
+import { getLocale } from "../../locale";
+import { getPlatform } from "../../preview-platform";
+// region Validation
+/**
+ * Returns whether the accessibility section should be displayed on the product page, based on whether accessibility
+ * data is available for the device family we want to use, and the feature is enabled.
+ */
+export function shouldShowAccessibilitySection(objectGraph, data, deviceFamily) {
+ var _a;
+ const accessibilityData = attributeAsDictionary(data, `accessibility.${deviceFamily}`);
+ const bundleId = (_a = contentAttributeAsString(objectGraph, data, "bundleId")) !== null && _a !== void 0 ? _a : "";
+ return (isProductAccessibilityLabelsEnabled(objectGraph) &&
+ !shouldSuppressAccessibilityLabelsForAdamId(objectGraph, data.id) &&
+ !shouldSuppressAccessibilityLabelsForBundleId(objectGraph, bundleId) &&
+ isSome(accessibilityData));
+}
+// region Shelves
+/**
+ * Builds the header shelf for the accessibility product page section or details page.
+ */
+export function createHeaderShelf(objectGraph, data, deviceFamily, isSeeAllContext, shelfMetrics, pageInformation, locationTracker) {
+ return validation.context("createHeaderShelf", () => {
+ const shelf = new models.Shelf("accessibilityParagraph");
+ shelf.presentationHints = { isSeeAllContext: isSeeAllContext };
+ // Only applicable for tvOS. Since tvOS doesn't support inline linkable text, we have to show any actions as
+ // distinct buttons instead. This should remain empty for all other platforms.
+ const actions = [];
+ const title = objectGraph.loc.string("ProductPage.Accessibility.Shelf.Title");
+ // Add shelf header / see all action on product page of all platforms, except watchOS
+ if (!isSeeAllContext && !objectGraph.client.isWatch) {
+ shelf.title = title;
+ // Only add a details action if there are supported features
+ const supportedFeatures = supportedFeaturesForDeviceFamily(data, deviceFamily, isSeeAllContext);
+ const hasSupportedFeatures = isSome(supportedFeatures) && isNotEmpty(supportedFeatures);
+ if (hasSupportedFeatures) {
+ const seeDetailsAction = createSeeDetailsAction(objectGraph, data, deviceFamily, "button", pageInformation, locationTracker);
+ if (objectGraph.client.isTV) {
+ actions.push(seeDetailsAction);
+ }
+ else {
+ shelf.seeAllAction = seeDetailsAction;
+ }
+ }
+ }
+ if (objectGraph.client.isTV && !isSeeAllContext) {
+ // Add learn more link action, if applicable
+ const learnMoreAction = createLearnMoreAction(objectGraph, pageInformation, locationTracker);
+ if (isSome(learnMoreAction)) {
+ actions.push(learnMoreAction);
+ }
+ }
+ // Create item model
+ const text = headerTextFromData(objectGraph, data, deviceFamily, isSeeAllContext, pageInformation, locationTracker);
+ const item = new models.AccessibilityParagraph(text, actions);
+ // Decorate item with impression information
+ if (isSome(shelfMetrics) && !isSeeAllContext && !objectGraph.client.isWatch) {
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "accessibilityOverview");
+ nextPosition(shelfMetrics.locationTracker);
+ }
+ // Add item to shelf
+ shelf.items = [item];
+ return shelf;
+ });
+}
+/**
+ * Builds a shelf for `accessibilityFeatures`, which will display the supported accessibility features card on the
+ * product page, or a list on the details page, if there are 1 or more reported features.
+ */
+export function createFeaturesShelf(objectGraph, data, deviceFamily, isSeeAllContext, shelfMetrics = null, pageInformation, locationTracker) {
+ return validation.context("createFeaturesShelf", () => {
+ const features = featuresFromData(objectGraph, data, deviceFamily, isSeeAllContext);
+ // We only want to show this shelf if there is at least 1 feature
+ if (isEmpty(features)) {
+ return null;
+ }
+ // Create shelf
+ const shelf = new models.Shelf("accessibilityFeatures");
+ shelf.presentationHints = { isSeeAllContext: isSeeAllContext };
+ // Create item
+ const title = objectGraph.loc.string("ProductPage.Accessibility.Card.SupportedFeaturesTitle");
+ const item = new models.AccessibilityFeatures(title, contentArtwork.createArtworkForResource(objectGraph, "systemimage://accessibility"), features);
+ // Decorate with metrics and add see all action for the product page
+ if (!isSeeAllContext) {
+ if (isSome(shelfMetrics)) {
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "supportedFeaturesCard", undefined, undefined, null, title);
+ nextPosition(shelfMetrics.locationTracker);
+ }
+ // Add the see details click action for the card.
+ item.clickAction = createSeeDetailsAction(objectGraph, data, deviceFamily, "card", pageInformation, locationTracker);
+ }
+ // Add the item to the shelf
+ shelf.items = [item];
+ return shelf;
+ });
+}
+/**
+ * Builds a shelf with the developer's accessibility site link, if provided.
+ *
+ * Not applicable on tvOS; we don't show the developer link at all on tvOS, since opening external urls is not supported.
+ */
+export function createDeveloperLinkShelf(objectGraph, data, isSeeAllContext, pageInformation, locationTracker) {
+ return validation.context("createDeveloperLinkShelf", () => {
+ if (objectGraph.client.isTV) {
+ // We don't show the developer link at all on tvOS, since opening external urls is not supported.
+ return null;
+ }
+ const text = developerLinkTextFromData(objectGraph, data, isSeeAllContext, pageInformation, locationTracker);
+ if (isNothing(text)) {
+ return null;
+ }
+ const shelf = new models.Shelf("accessibilityParagraph");
+ shelf.items = [new models.AccessibilityParagraph(text, [])];
+ shelf.presentationHints = { isSeeAllContext: isSeeAllContext };
+ return shelf;
+ });
+}
+/**
+ * Builds a shelf with the learn more link & developer link actions as buttons, if present, to be displayed on the
+ * intermediary details page.
+ *
+ * Only applicable on watchOS.
+ */
+function createWatchActionsShelf(objectGraph, data, pageInformation, locationTracker) {
+ const shelf = new models.Shelf("action");
+ const items = [];
+ const developerLinkAction = createDeveloperLinkAction(objectGraph, data, false, pageInformation, locationTracker);
+ if (isSome(developerLinkAction)) {
+ items.push(developerLinkAction);
+ }
+ const learnMoreAction = createLearnMoreAction(objectGraph, pageInformation, locationTracker);
+ if (isSome(learnMoreAction)) {
+ items.push(learnMoreAction);
+ }
+ if (isEmpty(items)) {
+ return null;
+ }
+ else {
+ shelf.items = items;
+ return shelf;
+ }
+}
+// region Data to Model Mapping
+/**
+ * Returns an array of `AccessibilityFeature` models mapped from the data.
+ */
+function featuresFromData(objectGraph, data, deviceFamily, isSeeAllContext) {
+ const supportedFeatures = supportedFeaturesForDeviceFamily(data, deviceFamily, isSeeAllContext);
+ if (isNothing(supportedFeatures)) {
+ return [];
+ }
+ return supportedFeatures.reduce((features, feature) => {
+ // Extract properties from data
+ const identifier = asString(feature, "identifier");
+ const title = asString(feature, "title");
+ const description = asString(feature, "description");
+ // Validate required data
+ if (isNothing(identifier) || identifier.length === 0 || isNothing(title) || title.length === 0) {
+ return features;
+ }
+ // Map the identifier to a system image
+ const systemImage = systemImageForFeature(identifier);
+ if (isNothing(systemImage)) {
+ return features;
+ }
+ // Create the feature model
+ const artwork = contentArtwork.createArtworkForResource(objectGraph, `systemimage://${systemImage}`);
+ artwork.imageScale = imageScaleForFeature(identifier);
+ features.push(new models.AccessibilityFeature(title, description !== null && description !== void 0 ? description : null, artwork));
+ return features;
+ }, []);
+}
+/**
+ * Creates the header text based on the data available.
+ */
+function headerTextFromData(objectGraph, data, deviceFamily, isSeeAllContext, pageInformation, locationTracker) {
+ // Define input for the header text
+ const supportedFeatures = supportedFeaturesForDeviceFamily(data, deviceFamily, isSeeAllContext);
+ const learnMoreLinkAction = createLearnMoreAction(objectGraph, pageInformation, locationTracker);
+ const developerName = attributeAsString(data, "artistName");
+ const hasDeveloperName = isSome(developerName) && isNotEmpty(developerName);
+ // Localize the template for the text
+ let templateKey;
+ if (objectGraph.client.isTV) {
+ // Since the learn more link is a standalone button on tvOS, we want to ensure that we don't use the inline link version.
+ templateKey = headerTemplateLocKey(isSeeAllContext, supportedFeatures, null, hasDeveloperName);
+ }
+ else if (objectGraph.client.isWatch) {
+ templateKey = watchHeaderTemplateLocKey(supportedFeatures, hasDeveloperName);
+ }
+ else {
+ templateKey = headerTemplateLocKey(isSeeAllContext, supportedFeatures, learnMoreLinkAction, hasDeveloperName);
+ }
+ let text = objectGraph.loc.string(templateKey);
+ let textType = "text/plain";
+ const linkedSubstrings = {};
+ // Replace the learn more link with localized text, and add the relevant action
+ if (isSome(learnMoreLinkAction)) {
+ const learnMoreLinkText = objectGraph.loc.string("Action.LearnMore");
+ text = text.replace("{learnMoreLink}", learnMoreLinkText);
+ linkedSubstrings[learnMoreLinkText] = learnMoreLinkAction;
+ }
+ // Add the developer name
+ if (hasDeveloperName) {
+ text = text.replace("{developerName}", `<b>${developerName}</b>`);
+ textType = "text/x-apple-as3-nqml";
+ }
+ const styledText = new models.StyledText(text, textType);
+ return new models.LinkableText(styledText, linkedSubstrings);
+}
+/**
+ * Creates the text for linking to the developer accessibility site, if provided.
+ *
+ * This is not used on tvOS.
+ */
+function developerLinkTextFromData(objectGraph, data, isSeeAllContext, pageInformation, locationTracker) {
+ const action = createDeveloperLinkAction(objectGraph, data, isSeeAllContext, pageInformation, locationTracker);
+ if (isNothing(action)) {
+ return null;
+ }
+ const developerLinkText = objectGraph.loc.string("Accessibility.DeveloperUrlLinkTitle");
+ const text = objectGraph.loc
+ .string("Accessibility.DeveloperUrlText")
+ .replace("{accessibilitySite}", developerLinkText);
+ const linkedSubstrings = {};
+ linkedSubstrings[developerLinkText] = action;
+ const styledText = new models.StyledText(text, "text/plain");
+ return new models.LinkableText(styledText, linkedSubstrings);
+}
+// region Data Parsing
+/**
+ * Returns the list of supported features for the device family that we want to display.
+ */
+function developerLinkUrl(data, isSeeAllContext) {
+ const accessibilityKey = isSeeAllContext ? "accessibilityDetails" : "accessibility";
+ return attributeAsString(data, `${accessibilityKey}.developerUrl`);
+}
+/**
+ * Returns the list of supported features for the device family that we want to display.
+ */
+function supportedFeaturesForDeviceFamily(data, deviceFamily, isSeeAllContext) {
+ const accessibilityKey = isSeeAllContext ? "accessibilityDetails" : "accessibility";
+ return attributeAsArray(data, `${accessibilityKey}.${deviceFamily}.supportedCategories`);
+}
+// region Actions
+/**
+ * Creates the action for displaying the intermediary details page on watchOS.
+ *
+ * This intermediary page is unique to watchOS, and is really a mix between what we show on the product page vs details
+ * page on all the other platforms. It is a complete page, sidepacked with all the data we have on hand from the product
+ * page request (which is basically all the accessibility data, except for the feature descriptions).
+ *
+ * The "end" details page can be actioned by tapping on the features card platter on this page, and will load the
+ * feature descriptions.
+ */
+export function createSeeIntermediaryDetailsAction(objectGraph, data, deviceFamily) {
+ const action = new models.FlowAction("page");
+ action.title = objectGraph.loc.string("ProductPage.Accessibility.Shelf.Title");
+ action.pageData = accessibilityIntermediaryDetailsSidePackedPageFromData(objectGraph, data, deviceFamily);
+ // A click event for this is added in `action-link-shelf.ts`.
+ return action;
+}
+/**
+ * Creates the action for displaying the accessibility details page.
+ */
+function createSeeDetailsAction(objectGraph, data, deviceFamily, targetType, pageInformation, locationTracker) {
+ const seeDetailsAction = new models.FlowAction("accessibilityDetails");
+ if (objectGraph.client.isWatch) {
+ // On watchOS, we only display the supported feature descriptions on this "end" details page, which we need to
+ // load. This initial page is empty.
+ seeDetailsAction.pageData = watchAccessibilityDetailsPage(objectGraph);
+ }
+ else {
+ seeDetailsAction.title = objectGraph.loc.string("ACTION_SEE_DETAILS");
+ seeDetailsAction.pageData = accessibilityDetailsSidePackedPageFromData(objectGraph, data, deviceFamily, pageInformation, locationTracker);
+ }
+ const productType = data.type === "app-bundles" ? Path.productBundle : Path.product;
+ const pageUrl = URL.fromComponents(Protocol.internal, null, `/${Path.accessibilityDetails}/${productType}/${data.id}`, { deviceFamily: deviceFamily });
+ seeDetailsAction.pageUrl = pageUrl.build();
+ if (objectGraph.props.enabled("supportsClickEventsOnSwiftUIHostingCell")) {
+ addClickEventToAction(objectGraph, seeDetailsAction, {
+ id: "AccessibilityDetails",
+ targetType: targetType,
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ });
+ }
+ return seeDetailsAction;
+}
+/**
+ * Creates the action for linking to the learn more URL.
+ */
+function createLearnMoreAction(objectGraph, pageInformation, locationTracker) {
+ const editorialItemId = objectGraph.bag.accessibilityLearnMoreEditorialItemId;
+ if (isNothing(editorialItemId)) {
+ return null;
+ }
+ const learnMoreAction = new models.FlowAction("article");
+ learnMoreAction.title = objectGraph.loc.string("Action.LearnMore");
+ learnMoreAction.pageUrl = `https://apps.apple.com/story/id${editorialItemId}`;
+ if (objectGraph.client.isWeb) {
+ learnMoreAction.destination = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: editorialItemId,
+ });
+ }
+ if (objectGraph.client.isVision) {
+ learnMoreAction.presentation = "sheetPresent";
+ }
+ if (objectGraph.props.enabled("supportsClickEventsOnSwiftUIHostingCell")) {
+ addClickEventToAction(objectGraph, learnMoreAction, {
+ id: "LearnMore",
+ targetType: "button",
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ });
+ }
+ return learnMoreAction;
+}
+/**
+ * Creates the action for linking to the developer provided accessibility site.
+ */
+function createDeveloperLinkAction(objectGraph, data, isSeeAllContext, pageInformation, locationTracker) {
+ const url = developerLinkUrl(data, isSeeAllContext);
+ if (isNothing(url) || isEmpty(url)) {
+ return null;
+ }
+ const action = new models.ExternalUrlAction(url, false);
+ action.title = objectGraph.loc.string("Accessibility.DeveloperUrlLinkActionTitle");
+ if (objectGraph.props.enabled("supportsClickEventsOnSwiftUIHostingCell")) {
+ addClickEventToAction(objectGraph, action, {
+ id: "AccessibilitySite",
+ targetType: "link",
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ });
+ }
+ return action;
+}
+// region Details
+/**
+ * Creates a complete, intermediary accessibility details page, sidepacking all data loaded from the product page.
+ *
+ * This intermediary page is unique to watchOS, and is really a mix between what we show on the product page vs details
+ * page on all the other platforms. The "end" details page can be actioned by tapping on the features card platter on
+ * this page, and will load the feature descriptions.
+ *
+ * NOTE: These shelves are created with `isSeeAllContext = false`, which is important for the `accessibilityFeatures`
+ * shelf, so that the corresponding view is displayed as a card platter.
+ */
+function accessibilityIntermediaryDetailsSidePackedPageFromData(objectGraph, data, deviceFamily) {
+ const shelves = [];
+ // Create metrics objects
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "AppAccessibilityDetails", data.id, data);
+ const locationTracker = newLocationTracker();
+ // Add header shelf
+ const headerShelf = createHeaderShelf(objectGraph, data, deviceFamily, false, null, pageInformation, locationTracker);
+ shelves.push(headerShelf);
+ // Add features shelf, if applicable
+ const featuresShelf = createFeaturesShelf(objectGraph, data, deviceFamily, false, null, pageInformation, locationTracker);
+ if (isSome(featuresShelf)) {
+ shelves.push(featuresShelf);
+ }
+ // Add actions shelf, if applicable
+ const actionsShelf = createWatchActionsShelf(objectGraph, data, pageInformation, locationTracker);
+ if (isSome(actionsShelf)) {
+ shelves.push(actionsShelf);
+ }
+ // Create and return page
+ const page = new models.GenericPage(shelves);
+ page.title = objectGraph.loc.string("AccessibilityDetails.Title");
+ addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation);
+ return page;
+}
+/**
+ * Creates an incomplete accessibility details page, sidepacking the header shelf & optional developer link shelf.
+ */
+function accessibilityDetailsSidePackedPageFromData(objectGraph, data, deviceFamily, pageInformation, locationTracker) {
+ const shelves = [];
+ // Add header shelf
+ const headerShelf = createHeaderShelf(objectGraph, data, deviceFamily, true, null, pageInformation, locationTracker);
+ shelves.push(headerShelf);
+ // Add developer link shelf, if applicable
+ const developerLinkShelf = createDeveloperLinkShelf(objectGraph, data, true, pageInformation, locationTracker);
+ if (isSome(developerLinkShelf)) {
+ shelves.push(developerLinkShelf);
+ }
+ // The web client displays accessibilityDetailPage as a modal,
+ // so we include accessibility feature data for immediate display.
+ if (objectGraph.client.isWeb) {
+ const featuresShelf = createFeaturesShelf(objectGraph, data, deviceFamily, true, null, pageInformation, locationTracker);
+ if (isSome(featuresShelf)) {
+ shelves.push(featuresShelf);
+ }
+ }
+ // Create and return page
+ const page = new models.GenericPage(shelves);
+ page.title = objectGraph.loc.string("AccessibilityDetails.Title");
+ page.isIncomplete = true;
+ page.presentationOptions = ["prefersLargeTitle", "prefersIndirectTouch", "prefersReadableContentAlignedTitle"];
+ return page;
+}
+/**
+ * Creates an empty accessibility details page for watchOS.
+ *
+ * This page is displayed when the customer taps on the features card platter on the intermediary details page, and
+ * is intended to display the descriptions of the supported features, which needs to be loaded.
+ */
+function watchAccessibilityDetailsPage(objectGraph) {
+ const page = new models.GenericPage([]);
+ page.title = objectGraph.loc.string("ProductPage.Accessibility.Card.SupportedFeaturesTitle");
+ page.isIncomplete = true;
+ return page;
+}
+// region Helpers
+/**
+ * Returns a system image name based on the feature identifier.
+ */
+function systemImageForFeature(identifier) {
+ switch (identifier) {
+ case "VOICEOVER":
+ return "voiceover";
+ case "VOICE_CONTROL":
+ return "voice.control";
+ case "LARGER_TEXT":
+ return "textformat.size";
+ case "SUFFICIENT_CONTRAST":
+ return "circle.lefthalf.filled.inverse";
+ case "DARK_INTERFACE":
+ return "appearance.darkmode";
+ case "DIFFERENTIATE_WITHOUT_COLOR_ALONE":
+ return "xmark.triangle.circle.square.fill";
+ case "REDUCED_MOTION":
+ return "circle.dotted.and.circle";
+ case "CAPTIONS":
+ return "captions.bubble.fill";
+ case "AUDIO_DESCRIPTIONS":
+ return "quote.bubble.fill";
+ default:
+ return null;
+ }
+}
+/**
+ * Returns an image scale we want to override based on the feature identifier.
+ */
+function imageScaleForFeature(identifier) {
+ switch (identifier) {
+ case "VOICE_CONTROL":
+ return "small";
+ default:
+ return null;
+ }
+}
+/**
+ * Returns the localization key for the header template text, based on the information available.
+ *
+ * There are a few different variations of this template. There are variations for the following conditions:
+ * 1. Whether we are displaying the text on the product page vs details page
+ * 2. What has been reported by the developer:
+ * a. Reported with at least 1 supported feature
+ * b. Reported with NO supported features
+ * c. Not reported yet
+ * 3. Whether the learn more link is available
+ * 4. For the details page, whether the developer name is available (as these strings attempt to use the developer name)
+ */
+function headerTemplateLocKey(isSeeAllContext, supportedFeatures, learnMoreLinkAction, hasDeveloperName) {
+ if (isSeeAllContext) {
+ // We can only get to the details page if there is at least 1 supported feature, so we don't need to worry
+ // about the reported state (2b) or (2c) from comment above.
+ if (isSome(learnMoreLinkAction) && hasDeveloperName) {
+ return "AccessibilityDetails.Header";
+ }
+ else if (hasDeveloperName) {
+ return "AccessibilityDetails.Header.NoLearnMoreLink";
+ }
+ else if (isSome(learnMoreLinkAction)) {
+ return "AccessibilityDetails.Header.NoDeveloperName";
+ }
+ else {
+ return "AccessibilityDetails.Header.NoLearnMoreLink.NoDeveloperName";
+ }
+ }
+ else {
+ const isReported = isSome(supportedFeatures);
+ const hasSupportedFeatures = isReported && isNotEmpty(supportedFeatures);
+ if (hasSupportedFeatures) {
+ return isSome(learnMoreLinkAction)
+ ? "ProductPage.Accessibility.Header.SupportedFeatures"
+ : "ProductPage.Accessibility.Header.SupportedFeatures.NoLearnMoreLink";
+ }
+ else if (isReported) {
+ return isSome(learnMoreLinkAction)
+ ? "ProductPage.Accessibility.Header.NoSupportedFeatures"
+ : "ProductPage.Accessibility.Header.NoSupportedFeatures.NoLearnMoreLink";
+ }
+ else {
+ return isSome(learnMoreLinkAction)
+ ? "ProductPage.Accessibility.Header.NoReportedFeatures"
+ : "ProductPage.Accessibility.Header.NoReportedFeatures.NoLearnMoreLink";
+ }
+ }
+}
+/**
+ * Returns the localization key for the header template text for watchOS, based on the information available.
+ *
+ * This is similar to the logic for the other platforms (refer to comment of `headerTemplateLocKey` function above),
+ * however only returns a subset of those template keys because:
+ * - Inline linkable text is not supported on watchOS
+ * - We always display text in the see all context on watchOS
+ */
+function watchHeaderTemplateLocKey(supportedFeatures, hasDeveloperName) {
+ const isReported = isSome(supportedFeatures);
+ const hasSupportedFeatures = isReported && isNotEmpty(supportedFeatures);
+ if (hasSupportedFeatures) {
+ if (hasDeveloperName) {
+ return "AccessibilityDetails.Header.NoLearnMoreLink";
+ }
+ else {
+ return "AccessibilityDetails.Header.NoLearnMoreLink.NoDeveloperName";
+ }
+ }
+ else if (isReported) {
+ return "ProductPage.Accessibility.Header.NoSupportedFeatures.NoLearnMoreLink";
+ }
+ else {
+ return "ProductPage.Accessibility.Header.NoReportedFeatures.NoLearnMoreLink";
+ }
+}
+//# sourceMappingURL=accessibility-shelves.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/achievements-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/achievements-shelf.js
new file mode 100644
index 0000000..3e8488c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/achievements-shelf.js
@@ -0,0 +1,82 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaPlatformAttributes from "../../../foundation/media/platform-attributes";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as contentAttributes from "../../content/attributes";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as productPageCommon from "../product-page-common";
+import { makeGameCenterHeader, openGamesUIAction } from "../../arcade/arcade-common";
+export function create(objectGraph, data, shelfMetrics, shelfContext) {
+ return validation.context("achievementsShelf", () => {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ if (objectGraph.client.isWeb) {
+ return null;
+ }
+ // rdar://66581575 (D421/18A342b: Achievements shouldn\u2019t be shown for pre-order games)
+ if (shelfContext.isPreorder) {
+ return null;
+ }
+ const isGameCenterEnabled = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isGameCenterEnabled");
+ if (!isGameCenterEnabled) {
+ return null;
+ }
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
+ const bundleId = mediaPlatformAttributes.platformAttributeAsString(data, attributePlatform, "bundleId");
+ if (serverData.isNullOrEmpty(bundleId)) {
+ return null;
+ }
+ const title = objectGraph.loc.string("ProductPage.Section.Achievements.Title", "Achievements");
+ const shelf = new models.Shelf("gameCenterPlayerGameAchievementSummary");
+ shelf.header = makeGameCenterHeader(objectGraph, title);
+ shelf.isHorizontal = true;
+ shelf.items = [];
+ shelf.mergeWhenFetched = true;
+ const token = new productPageCommon.ProductPageShelfToken(data.id, [], shelf.title, false, undefined, null, null, null);
+ token.sourceLocationTracker = shelfMetrics.locationTracker;
+ token.destinationPageInformation = shelfMetrics.metricsPageInformation;
+ shelf.url = `${Protocol.internal}:/${Path.product}/${Path.shelf}/?${Parameters.isGameCenterAchievementsShelf}=true&${Parameters.bundleId}=${bundleId}&${Parameters.token}=${productPageCommon.encodedShelfToken(token, shelfMetrics, shelfMetrics.metricsPageInformation)}`;
+ shelf.batchGroup = "gameCenter";
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ shelf.footerTitle = objectGraph.loc.string("Lockup.Footer.GamesApp");
+ shelf.footerAction = openGamesUIAction(objectGraph, { pageAchievements: {} });
+ shelf.footerStyle = {
+ $kind: "games",
+ bundleID: "com.apple.games",
+ width: 16,
+ height: 16,
+ };
+ return shelf;
+ });
+}
+export function createShelfWithAchievementSummary(objectGraph, achievementSummary, token) {
+ const shelf = new models.Shelf("gameCenterPlayerGameAchievementSummary");
+ shelf.isHorizontal = true;
+ if (achievementSummary) {
+ const impressionMetricsOptions = {
+ pageInformation: token.destinationPageInformation,
+ locationTracker: token.sourceLocationTracker,
+ kind: "achievements",
+ title: "Achievements",
+ id: "achievements_summary",
+ idType: "its_id",
+ softwareType: null,
+ targetType: "achievements",
+ badges: {
+ gameCenter: true,
+ },
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, impressionMetricsOptions);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, achievementSummary.action, impressionMetricsOptions);
+ shelf.items = [achievementSummary];
+ }
+ shelf.mergeWhenFetched = true;
+ shelf.batchGroup = "gameCenter";
+ shelf.isHidden = shelf.items.length === 0;
+ return shelf;
+}
+//# sourceMappingURL=achievements-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/action-links-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/action-links-shelf.js
new file mode 100644
index 0000000..155f616
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/action-links-shelf.js
@@ -0,0 +1,136 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as productCapabilities from "../../content/product-capabilities";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersPage from "../../metrics/helpers/page";
+import * as reviews from "../reviews";
+import * as informationShelf from "./annotations/annotations";
+import * as versionHistoryShelves from "./version-history-shelves";
+import * as accessibilityShelves from "../shelves/accessibility-shelves";
+import { isSome } from "@jet/environment";
+/**
+ * Create a shelf of action links for the product page.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @param shelfContext A collection of any other variables used when creating this shelf.
+ * @returns A shelf of action links.
+ */
+export function create(objectGraph, data, shelfMetrics, shelfContext) {
+ return validation.context("createActionLinks", () => {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ if (objectGraph.client.isWatch) {
+ return standardActionLinks(objectGraph, data, shelfContext.productTitle, shelfMetrics, shelfContext.shouldShowRatingsAndReviews, shelfContext.isFirstPartyHideableApp, shelfContext.isBundle, shelfContext.isPreorder, shelfContext.isArcadeApp, shelfContext.isFreeProduct, shelfContext.deviceFamily);
+ }
+ return null;
+ });
+}
+function clickOptionsForActionLinkAction(objectGraph, productId, shelfMetrics) {
+ return {
+ targetType: "button",
+ actionType: "navigate",
+ id: productId,
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ };
+}
+function standardActionLinks(objectGraph, productData, productTitle, shelfMetrics, shouldShowRatingsAndReviews, isFirstPartyHideableApp, isBundle, isPreorder, isArcadeApp, isFreeProduct, deviceFamily) {
+ return validation.context("actionLinkShelf", () => {
+ const shelf = new models.Shelf("action");
+ const items = [];
+ // Ratings & Reviews
+ if (shouldShowRatingsAndReviews) {
+ const reviewsData = mediaRelationship.relationship(productData, "reviews");
+ const ratingsData = mediaAttributes.attributeAsDictionary(productData, "userRating");
+ ratingsData.ratingAverage = serverData.asNumber(ratingsData, "value");
+ ratingsData.adamId = productData.id;
+ ratingsData.isBundle = isBundle;
+ ratingsData.supportUrl = contentAttributes.contentAttributeAsString(objectGraph, productData, "supportURLForLanguage");
+ const reviewItems = mediaDataStructure.dataCollectionFromDataContainer(reviewsData);
+ const appIcon = content.iconFromData(objectGraph, productData, {
+ useCase: 2 /* content.ArtworkUseCase.LockupIconMedium */,
+ });
+ const ratingsAndReviewsLink = reviews.reviewsPageActionFromReviewsData(objectGraph, objectGraph.client.guid, ratingsData, reviewItems, reviewsData.next, productTitle, appIcon, false, false);
+ ratingsAndReviewsLink.title = objectGraph.loc.string("ProductPage.Section.Reviews.Title");
+ items.push(ratingsAndReviewsLink);
+ }
+ // Version History
+ const versions = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, productData, "versionHistory");
+ if (versions.length > 0 && !isFirstPartyHideableApp && !isBundle && !isPreorder) {
+ const versionHistoryLinkAction = versionHistoryShelves.versionHistoryPageAction(objectGraph, versions, productData.id);
+ // Metrics (page information configured in version history builder)
+ metricsHelpersClicks.addClickEventToAction(objectGraph, versionHistoryLinkAction, clickOptionsForActionLinkAction(objectGraph, productData.id, shelfMetrics));
+ items.push(versionHistoryLinkAction);
+ }
+ // Accessibility
+ if (isSome(deviceFamily) &&
+ accessibilityShelves.shouldShowAccessibilitySection(objectGraph, productData, deviceFamily)) {
+ const action = accessibilityShelves.createSeeIntermediaryDetailsAction(objectGraph, productData, deviceFamily);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, clickOptionsForActionLinkAction(objectGraph, productData.id, shelfMetrics));
+ items.push(action);
+ }
+ // Info List
+ const infoShelf = informationShelf.create(objectGraph, productData, isFirstPartyHideableApp, shelfMetrics, null, isArcadeApp, isFreeProduct, false);
+ const infoPage = new models.GenericPage([infoShelf]);
+ const infoLinkAction = new models.FlowAction("page");
+ infoLinkAction.title = objectGraph.loc.string("ProductPage.Section.Information.Title");
+ infoPage.title = infoLinkAction.title;
+ // Metrics
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "information");
+ const infoPageInformation = metricsHelpersPage.pageInformationForActionLinkPage(objectGraph, productData.id, "info");
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, infoPage, infoPageInformation);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, infoLinkAction, clickOptionsForActionLinkAction(objectGraph, productData.id, shelfMetrics));
+ infoLinkAction.pageData = infoPage;
+ items.push(infoLinkAction);
+ // Capabilities
+ const capabilities = productCapabilities.productCapabilitiesFromData(objectGraph, productData, isFreeProduct);
+ if (capabilities.length > 0) {
+ const capabilitiesShelf = new models.Shelf("productCapability");
+ capabilitiesShelf.items = capabilities;
+ const capabilitiesPage = new models.GenericPage([capabilitiesShelf]);
+ const capabilitiesLinkAction = new models.FlowAction("page");
+ capabilitiesLinkAction.title = objectGraph.loc.string("ProductPage.Section.Supports.Title");
+ capabilitiesPage.title = capabilitiesLinkAction.title;
+ // Metrics
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "supports");
+ const capabilitiesPageInformation = metricsHelpersPage.pageInformationForActionLinkPage(objectGraph, productData.id, "supports");
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, capabilitiesPage, capabilitiesPageInformation);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, capabilitiesLinkAction, clickOptionsForActionLinkAction(objectGraph, productData.id, shelfMetrics));
+ capabilitiesLinkAction.pageData = capabilitiesPage;
+ items.push(capabilitiesLinkAction);
+ }
+ // Privacy Link
+ const privacyPolicyUrl = contentAttributes.contentAttributeAsString(objectGraph, productData, "privacyPolicyUrl");
+ if (privacyPolicyUrl) {
+ const privacyLinkAction = new models.ExternalUrlAction(privacyPolicyUrl, false);
+ // Metrics
+ metricsHelpersClicks.addClickEventToAction(objectGraph, privacyLinkAction, clickOptionsForActionLinkAction(objectGraph, productData.id, shelfMetrics));
+ privacyLinkAction.title = objectGraph.loc.string("PRIVACY_POLICY");
+ items.push(privacyLinkAction);
+ }
+ const traderData = contentAttributes.contentAttributeAsDictionary(objectGraph, productData, "sellerInfo");
+ const safetyAndComplianceURL = serverData.asString(traderData, "safetyAndComplianceUrl");
+ if ((safetyAndComplianceURL === null || safetyAndComplianceURL === void 0 ? void 0 : safetyAndComplianceURL.length) > 0) {
+ const safetyLinkAction = new models.ExternalUrlAction(safetyAndComplianceURL, false);
+ safetyLinkAction.title = objectGraph.loc.string("ProductPage.Section.SafetyAndCompliance.Title");
+ items.push(safetyLinkAction);
+ }
+ shelf.items = items;
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "actionLinks");
+ if (items.length > 0) {
+ return shelf;
+ }
+ else {
+ return null;
+ }
+ });
+}
+//# sourceMappingURL=action-links-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/annotations.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/annotations.js
new file mode 100644
index 0000000..d71ca64
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/annotations.js
@@ -0,0 +1,310 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../../api/models/index";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import { shallowCopyOf } from "../../../../foundation/util/objects";
+import * as productPageUtil from "../../product-page-util";
+import * as categoryAnnotation from "./category-annotation";
+import * as compatibilityAnnotation from "./compatibility-annotation";
+import * as contentRatingAnnotation from "./content-rating-annotation";
+import * as controllerAnnotation from "./controller-annotation";
+import * as copyrightAnnotation from "./copyright-annotation";
+import * as externalPurchasesAnnotation from "./external-purchases-annotation";
+import * as externalBrowserAnnotation from "./external-browser-annotation";
+import * as gameLicenseAnnotation from "./game-license-annotation";
+import * as highMotionAnnotation from "./high-motion-annotation";
+import * as languagesAnnotation from "./languages-annotation";
+import * as locationAnnotation from "./location-annotation";
+import * as productCapabilitiesAnnotation from "./product-capabilities-annotation";
+import * as sellerAnnotation from "./seller-annotation";
+import * as sizeAnnotation from "./size-annotation";
+import * as spatialControllerAnnotation from "./spatial-controller-annotation";
+import * as storefrontContentRatingAnnotation from "./storefront-content-rating-annotation";
+import * as topInAppPurchasesAnnotation from "./top-in-app-purchases-annotation";
+import * as versionAnnotation from "./version-annotation";
+import * as contentAttributes from "../../../content/attributes";
+import { isSome } from "@jet/environment";
+import { externalPurchasesPlacementIsEnabled } from "../../../offers/external-purchases";
+const standardList = [
+ sellerAnnotation.createAnnotation,
+ sizeAnnotation.createAnnotation,
+ categoryAnnotation.createAnnotation,
+ controllerAnnotation.createAnnotation,
+ spatialControllerAnnotation.createAnnotation,
+ compatibilityAnnotation.createAnnotation,
+ externalBrowserAnnotation.createAnnotation,
+ locationAnnotation.createAnnotation,
+ languagesAnnotation.createAnnotation,
+ highMotionAnnotation.createAnnotation,
+ contentRatingAnnotation.createAnnotation,
+ topInAppPurchasesAnnotation.createAnnotation,
+ externalPurchasesAnnotation.createAnnotation,
+ gameLicenseAnnotation.createAnnotation,
+ copyrightAnnotation.createAnnotation,
+];
+const sellerPositionAdjustedList = [
+ sizeAnnotation.createAnnotation,
+ categoryAnnotation.createAnnotation,
+ controllerAnnotation.createAnnotation,
+ spatialControllerAnnotation.createAnnotation,
+ compatibilityAnnotation.createAnnotation,
+ externalBrowserAnnotation.createAnnotation,
+ locationAnnotation.createAnnotation,
+ languagesAnnotation.createAnnotation,
+ highMotionAnnotation.createAnnotation,
+ contentRatingAnnotation.createAnnotation,
+ topInAppPurchasesAnnotation.createAnnotation,
+ externalPurchasesAnnotation.createAnnotation,
+ sellerAnnotation.createAnnotation,
+ gameLicenseAnnotation.createAnnotation,
+ copyrightAnnotation.createAnnotation,
+];
+const tvInformationList = [
+ sellerAnnotation.createAnnotation,
+ categoryAnnotation.createAnnotation,
+ versionAnnotation.createAnnotation,
+ sizeAnnotation.createAnnotation,
+ gameLicenseAnnotation.createAnnotation,
+ copyrightAnnotation.createAnnotation,
+];
+const tvSupportsList = [
+ compatibilityAnnotation.createAnnotation,
+ languagesAnnotation.createAnnotation,
+ contentRatingAnnotation.createAnnotation,
+ storefrontContentRatingAnnotation.createAnnotation,
+];
+const gamesList = [
+ contentRatingAnnotation.createAnnotation,
+ categoryAnnotation.createAnnotation,
+ controllerAnnotation.createAnnotation,
+ languagesAnnotation.createAnnotation,
+ sizeAnnotation.createAnnotation,
+];
+function createAnnotations(objectGraph, annotationList, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics, metadataHandler) {
+ const annotations = [];
+ for (const annotationFactory of annotationList) {
+ const annotation = annotationFactory(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics);
+ if (isSome(annotation)) {
+ // Don't overwrite existing items
+ if (annotation.items_V3.length === 0) {
+ annotation.items_V3 = translateItemsToV3(objectGraph, annotation);
+ }
+ annotation.expandAction = createExpandActionForAnnotation(objectGraph, annotation);
+ annotations.push(annotation);
+ if (metadataHandler) {
+ metadataHandler(annotationFactory, annotation, annotations.length - 1);
+ }
+ }
+ }
+ return annotations;
+}
+export function translateItemsToV3(objectGraph, annotation) {
+ const items_V3 = [];
+ for (const item of annotation.items) {
+ if (items_V3.length > 0) {
+ items_V3.push({
+ $kind: "spacer",
+ });
+ }
+ if (isSome(item.headingArtworks)) {
+ for (const artwork of item.headingArtworks) {
+ items_V3.push({
+ $kind: "artwork",
+ artwork: artwork,
+ });
+ }
+ }
+ // Linkable Text section: covers heading, main text, and lists
+ let markdownStyledText = "";
+ if (isSome(item.heading)) {
+ markdownStyledText += `**${item.heading}**`;
+ }
+ if (isSome(item.text)) {
+ if (markdownStyledText.length > 0) {
+ markdownStyledText += "\n";
+ }
+ markdownStyledText += item.text;
+ }
+ if (isSome(item.listText)) {
+ if (markdownStyledText.length > 0) {
+ markdownStyledText += "\n";
+ }
+ markdownStyledText += item.listText;
+ }
+ if (markdownStyledText.length > 0) {
+ items_V3.push({
+ $kind: "linkableText",
+ linkableText: new models.LinkableText(new models.StyledText(markdownStyledText, "text/markdown")),
+ });
+ }
+ if (isSome(item.textPairs)) {
+ for (const textPair of item.textPairs) {
+ items_V3.push({
+ $kind: "textPair",
+ leadingText: textPair[0],
+ trailingText: textPair[1],
+ });
+ }
+ }
+ }
+ if (isSome(annotation.linkAction) && !objectGraph.client.isTV) {
+ items_V3.push({
+ $kind: "button",
+ action: annotation.linkAction,
+ });
+ }
+ return items_V3;
+}
+function standardInformationShelf(objectGraph, data, isFirstPartyHideableApp, shelfMetrics, isArcadeApp, metadataHandler) {
+ return validation.context("standardInformationShelf", () => {
+ let shelf;
+ const list = shouldUseSellerPositionAdjustedList(objectGraph, data) ? sellerPositionAdjustedList : standardList;
+ const informationItems = createAnnotations(objectGraph, list, data, isFirstPartyHideableApp, isArcadeApp, false, shelfMetrics, metadataHandler);
+ if (informationItems.length > 0) {
+ shelf = new models.Shelf("annotation");
+ shelf.title = objectGraph.loc.string("ProductPage.Section.Information.Title");
+ shelf.items = informationItems;
+ if (!serverData.isNull(shelfMetrics)) {
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "information");
+ }
+ }
+ return shelf;
+ });
+}
+function gamesInformationShelf(objectGraph, data, isFirstPartyHideableApp, shelfMetrics, isArcadeApp, metadataHandler) {
+ return validation.context("standardInformationShelf", () => {
+ const informationItems = createAnnotations(objectGraph, gamesList, data, isFirstPartyHideableApp, isArcadeApp, true, shelfMetrics, metadataHandler);
+ if (informationItems.length === 0) {
+ return undefined;
+ }
+ const shelf = new models.Shelf("annotation");
+ shelf.items = informationItems;
+ return shelf;
+ });
+}
+/** Checks if we should use the alternate position arrangement of seller info
+ * - The Seller ICP Annotation feature is enabled
+ * - The client platform is iOS
+ * - The Internet Content Provider info is available
+ * Or Seller info is available
+ * @returns true if either of the checks above are true
+ */
+function shouldUseSellerPositionAdjustedList(objectGraph, data) {
+ const icpInfo = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "internetContentProviderInfo");
+ const hasICPInfo = objectGraph.bag.enableSellerICPAnnotation && objectGraph.client.isiOS && isSome(icpInfo);
+ const sellerInfo = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "sellerInfo");
+ const hasSellerInfo = objectGraph.bag.enableSellerInfo && isSome(sellerInfo);
+ return hasICPInfo || hasSellerInfo;
+}
+function watchInformationShelf(objectGraph, data, isFirstPartyHideableApp, shelfMetrics) {
+ return validation.context("standardInformationShelf", () => {
+ let shelf;
+ const list = shouldUseSellerPositionAdjustedList(objectGraph, data) ? sellerPositionAdjustedList : standardList;
+ const informationItems = createAnnotations(objectGraph, list, data, isFirstPartyHideableApp, false, false, shelfMetrics, undefined);
+ if (informationItems.length > 0) {
+ shelf = new models.Shelf("annotation");
+ shelf.items = informationItems;
+ if (!serverData.isNull(shelfMetrics)) {
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "information");
+ }
+ }
+ return shelf;
+ });
+}
+function tvInformationShelf(objectGraph, data, isFirstPartyHideableApp, shelfMetrics, forceExpanded, isFreeProduct, metadataHandler) {
+ return validation.context("tvInformationShelf", () => {
+ let shelf;
+ const informationItems = createAnnotations(objectGraph, tvInformationList, data, isFirstPartyHideableApp, false, false, shelfMetrics, metadataHandler);
+ const supportsItems = productCapabilitiesAnnotation
+ .createAnnotations(objectGraph, data, isFreeProduct)
+ .concat(createAnnotations(objectGraph, tvSupportsList, data, isFirstPartyHideableApp, false, false, shelfMetrics, metadataHandler));
+ const externalPurchasesEnabled = externalPurchasesPlacementIsEnabled(objectGraph, "product-page-info-section");
+ const tvTopInAppPurchasesList = externalPurchasesEnabled
+ ? [externalPurchasesAnnotation.createAnnotation]
+ : [topInAppPurchasesAnnotation.createAnnotation];
+ const topInAppPurchaseItems = createAnnotations(objectGraph, tvTopInAppPurchasesList, data, isFirstPartyHideableApp, false, true, shelfMetrics, metadataHandler);
+ const annotationsGroups = [];
+ if (informationItems.length > 0) {
+ const annotationGroup = new models.AnnotationGroup(objectGraph.loc.string("ProductPage.Section.Information.Title"), informationItems, forceExpanded);
+ annotationsGroups.push(annotationGroup);
+ }
+ if (supportsItems.length > 0) {
+ const annotationGroup = new models.AnnotationGroup(objectGraph.loc.string("ProductPage.Section.Supports.Title"), supportsItems, forceExpanded);
+ annotationsGroups.push(annotationGroup);
+ }
+ if (topInAppPurchaseItems.length > 0) {
+ const inAppPurchasesTitle = externalPurchasesEnabled
+ ? objectGraph.loc.string("ProductPage.Section.ExternalPurchases.Title")
+ : objectGraph.loc.string("ProductPage.Section.TopInAppPurchases.Title");
+ const annotationGroup = new models.AnnotationGroup(inAppPurchasesTitle, topInAppPurchaseItems, true);
+ annotationsGroups.push(annotationGroup);
+ }
+ if (annotationsGroups.length > 0) {
+ if (productPageUtil.isShelfBased(objectGraph)) {
+ shelf = new models.Shelf("annotationGroup");
+ shelf.items = annotationsGroups;
+ shelf.background = {
+ type: "darkOverlay",
+ };
+ if (!serverData.isNull(shelfMetrics)) {
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "information");
+ }
+ }
+ else {
+ shelf = new models.Shelf("informationContainer");
+ shelf.items = [new models.InformationContainer(annotationsGroups)];
+ if (!serverData.isNull(shelfMetrics)) {
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "information");
+ }
+ }
+ }
+ return shelf;
+ });
+}
+/**
+ * Creates the information shelf for the current client's deviceType.
+ *
+ * @param productData The product data
+ * @param isFirstPartyHideableApp Indicates whether this app is first party and/or hideable
+ * @param shelfMetrics The shelf metrics
+ * @param metadataHandler The metadata handler used to associate an annotation with another element.
+ * @param isArcadeApp Whether or not the product is an Arcade app.
+ * @param isFreeProduct Whether the buy is for a free product.
+ * @param forceExpanded Whether the items in the shelf should begin expanded
+ */
+export function create(objectGraph, productData, isFirstPartyHideableApp, shelfMetrics, metadataHandler, isArcadeApp, isFreeProduct, forceExpanded) {
+ return validation.context("create", () => {
+ if (preprocessor.GAMES_TARGET) {
+ return gamesInformationShelf(objectGraph, productData, isFirstPartyHideableApp, shelfMetrics, isArcadeApp, metadataHandler);
+ }
+ let shelf;
+ switch (objectGraph.client.deviceType) {
+ case "watch":
+ shelf = watchInformationShelf(objectGraph, productData, isFirstPartyHideableApp, shelfMetrics);
+ break;
+ case "tv":
+ shelf = tvInformationShelf(objectGraph, productData, isFirstPartyHideableApp, shelfMetrics, forceExpanded, isFreeProduct, metadataHandler);
+ break;
+ default:
+ shelf = standardInformationShelf(objectGraph, productData, isFirstPartyHideableApp, shelfMetrics, isArcadeApp, metadataHandler);
+ }
+ return shelf;
+ });
+}
+/**
+ * Creates a flow action for navigating to the expanded view of an annotation.
+ *
+ * @param objectGraph The current object graph
+ * @param annotation The source annotation
+ * @returns A flow action or null
+ */
+export function createExpandActionForAnnotation(objectGraph, annotation) {
+ if (!objectGraph.client.isVision) {
+ return undefined;
+ }
+ const flowAction = new models.FlowAction("annotationDetail");
+ const detailAnnotation = shallowCopyOf(annotation);
+ detailAnnotation.shouldAlwaysPresentExpanded = true;
+ flowAction.pageData = detailAnnotation;
+ return flowAction;
+}
+//# sourceMappingURL=annotations.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/category-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/category-annotation.js
new file mode 100644
index 0000000..8158739
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/category-annotation.js
@@ -0,0 +1,33 @@
+import { isNothing } from "@jet/environment";
+import * as models from "../../../../api/models/index";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import { contentAttributeAsDictionary } from "../../../content/attributes";
+import { artworkFromApiArtwork } from "../../../content/content";
+import * as lockups from "../../../lockups/lockups";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ var _a;
+ if (isArcadeApp && !preprocessor.GAMES_TARGET) {
+ // Design prefers we do not show the category annotation for Arcade apps
+ return null;
+ }
+ const categoryName = lockups.categoryFromData(objectGraph, data);
+ if (isNothing(categoryName)) {
+ return null;
+ }
+ const categoryAnnotationItem = new models.AnnotationItem(categoryName);
+ const title = suppressTitle ? "" : objectGraph.loc.string("InfoList.Category.Title");
+ const summary = preprocessor.GAMES_TARGET ? categoryName : undefined;
+ const annotation = new models.Annotation(title, [categoryAnnotationItem], summary);
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ const artworkData = contentAttributeAsDictionary(objectGraph, data, "editorialArtwork.brandLogo");
+ if (serverData.isDefinedNonNull(artworkData)) {
+ annotation.leadingArtwork =
+ (_a = artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 0 /* ArtworkUseCase.Default */,
+ allowingTransparency: true,
+ })) !== null && _a !== void 0 ? _a : undefined;
+ }
+ }
+ return annotation;
+}
+//# sourceMappingURL=category-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/compatibility-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/compatibility-annotation.js
new file mode 100644
index 0000000..e259165
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/compatibility-annotation.js
@@ -0,0 +1,169 @@
+import { isNothing } from "@jet/environment";
+import * as models from "../../../../api/models/index";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import { createArtworkForResource } from "../../../content/artwork/artwork";
+import * as contentAttributes from "../../../content/attributes";
+import * as content from "../../../content/content";
+import * as contentDeviceFamily from "../../../content/device-family";
+import * as lockups from "../../../lockups/lockups";
+import { translateItemsToV3 } from "./annotations";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ const requirementsByDeviceFamily = mediaAttributes.attributeAsDictionary(data, "requirementsByDeviceFamily");
+ let annotationItems = [];
+ let requirementsSummary = defaultRequirementsSummaryFromData(objectGraph, data);
+ const requiresRosetta = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "requiresRosetta", contentAttributes.defaultAttributePlatform(objectGraph));
+ if (serverData.isDefinedNonNullNonEmpty(requirementsByDeviceFamily)) {
+ // Create an annotation item for each of the device families in "requirementsByDeviceFamily"
+ const deviceFamilies = sortedDeviceFamilies(objectGraph);
+ for (const deviceFamily of deviceFamilies) {
+ const deviceFamilyRequirements = serverData.asDictionary(requirementsByDeviceFamily, deviceFamily);
+ if (serverData.isDefinedNonNullNonEmpty(deviceFamilyRequirements)) {
+ // When Rosetta is not available, use a more specific requirements string explaining it's not available for the region.
+ const preferRosettaUnavailableRequirementsString = objectGraph.appleSilicon.isSupportEnabled &&
+ deviceFamily === "mac" &&
+ requiresRosetta &&
+ !objectGraph.appleSilicon.isRosettaAvailable;
+ const annotationItem = compatibilityAnnotationItemFromDeviceFamilyRequirements(objectGraph, deviceFamilyRequirements, preferRosettaUnavailableRequirementsString);
+ if (serverData.isDefinedNonNull(annotationItem)) {
+ annotationItems.push(annotationItem);
+ }
+ }
+ }
+ // If `requirementsSummary` is null, this means we don't meet all the required capabilities
+ // In this case, use the requirementsString for the first device family as the summary
+ if (!serverData.isDefinedNonNull(requirementsSummary) && annotationItems.length > 0) {
+ requirementsSummary = annotationItems[0].text;
+ }
+ }
+ else {
+ // If "requirementsByDeviceFamily" is empty or otherwise invalid, fall back to the legacy "requirementsString"
+ const annotationItem = legacyCompatibilityAnnotationItemFromData(objectGraph, data);
+ if (serverData.isDefinedNonNull(annotationItem)) {
+ annotationItems = [annotationItem];
+ }
+ }
+ if (serverData.isNullOrEmpty(annotationItems)) {
+ return null;
+ }
+ const title = suppressTitle ? "" : objectGraph.loc.string("Requirements");
+ const annotation = new models.Annotation(title, annotationItems, requirementsSummary);
+ if (annotation.items_V3.length === 0) {
+ annotation.items_V3 = translateItemsToV3(objectGraph, annotation);
+ }
+ if (preprocessor.GAMES_TARGET) {
+ const supportedPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ const isRunnableOnCurrentDevice = content.supportsPlatform(supportedPlatforms, content.currentAppPlatform(objectGraph));
+ let systemImageName;
+ if (!isRunnableOnCurrentDevice && supportedPlatforms.length > 0) {
+ systemImageName = content.systemImageNameForAppPlatform(supportedPlatforms[0]);
+ }
+ else {
+ systemImageName = content.systemImageNameForAppPlatform(content.currentAppPlatform(objectGraph));
+ }
+ annotation.leadingArtwork = createArtworkForResource(objectGraph, `systemimage://${systemImageName}`);
+ annotation.prefersSmallLeadingArtwork = true;
+ }
+ else if (objectGraph.client.isVision) {
+ annotation.leadingArtwork = createArtworkForResource(objectGraph, "systemimage://visionpro");
+ annotation.prefersSmallLeadingArtwork = true;
+ }
+ return annotation;
+}
+/**
+ * Returns a `DeviceFamily` list, in the order the device families should be displayed
+ * This is based on a pre-defined sort order, and the current device type / model
+ * @returns {contentDeviceFamily.DeviceFamily[]} A list of device families
+ */
+function sortedDeviceFamilies(objectGraph) {
+ // Start with the default display order
+ const deviceFamilies = [
+ "iphone",
+ "ipad",
+ "ipod",
+ "mac",
+ "realityDevice",
+ "tvos",
+ "watch",
+ ];
+ // Now move our current device family to the start of the array
+ const clientDeviceFamily = contentDeviceFamily.deviceFamilyFromDeviceType(objectGraph, objectGraph.client.deviceType, objectGraph.host.deviceModel);
+ if (serverData.isDefinedNonNull(clientDeviceFamily)) {
+ const indexOfClientDeviceFamily = deviceFamilies.indexOf(clientDeviceFamily);
+ if (indexOfClientDeviceFamily > 0) {
+ deviceFamilies.splice(indexOfClientDeviceFamily, 1);
+ deviceFamilies.unshift(clientDeviceFamily);
+ }
+ }
+ return deviceFamilies;
+}
+/**
+ * Returns the default requirements summary, eg. "Works on this iPhone"
+ * Currently only returns a value if the device has all required capabilities
+ *
+ * @param data MAPI data to build the summary with.
+ * @returns {string} The requirements summary
+ */
+function defaultRequirementsSummaryFromData(objectGraph, data) {
+ let requirementsSummary;
+ if (lockups.deviceHasCapabilitiesFromData(objectGraph, data)) {
+ const supportsMacOSCompatibleIOSBinary = content.supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, objectGraph.appleSilicon.isSupportEnabled);
+ const supportsVisionOSCompatibleIOSBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ const supportedAppPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
+ const isMacOSAppRunnable = content.isMacOSAppBuyableAndRunnableFromData(objectGraph, data, objectGraph.appleSilicon.isSupportEnabled, objectGraph.appleSilicon.isRosettaAvailable);
+ const isThisDeviceSupported = content.runnableOnDevice(objectGraph, supportedAppPlatforms, objectGraph.client.deviceType, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary, isMacOSAppRunnable);
+ const deviceName = objectGraph.loc.deviceDisplayName(objectGraph);
+ if (isThisDeviceSupported && deviceName) {
+ const requirementsSummaryReplacement = objectGraph.loc.string("InfoList.Requirements.Summary");
+ requirementsSummary = requirementsSummaryReplacement.replace("{deviceName}", deviceName);
+ }
+ }
+ return requirementsSummary;
+}
+/**
+ * Creates an annotation item from a set of requirements for a specific device family
+ *
+ * @param {JSONData} deviceFamilyData data to build the annotation item with
+ * @param {boolean} preferRosettaUnavailableRequirementsString Use more specific Rosetta unavailable requirements string.
+ * @returns {models.AnnotationItem} The annotation item
+ */
+function compatibilityAnnotationItemFromDeviceFamilyRequirements(objectGraph, deviceFamilyData, preferRosettaUnavailableRequirementsString) {
+ var _a;
+ const deviceFamilyName = (_a = serverData.asString(deviceFamilyData, "deviceFamily")) !== null && _a !== void 0 ? _a : undefined;
+ let requirementsString = serverData.asString(deviceFamilyData, "requirementString");
+ const devices = serverData.asArrayOrEmpty(deviceFamilyData, "devices");
+ // When Rosetta is not available, use a more specific requirements string explaining it's not available for the region.
+ if (preferRosettaUnavailableRequirementsString) {
+ const rosettaUnavailableRequirementsString = serverData.asString(deviceFamilyData, "rosettaUnavailableRequirementString");
+ if (serverData.isDefinedNonNullNonEmpty(rosettaUnavailableRequirementsString)) {
+ requirementsString = rosettaUnavailableRequirementsString;
+ }
+ }
+ if (isNothing(requirementsString)) {
+ return null;
+ }
+ // Combine list of devices into a bullet list
+ const deviceListItems = [];
+ for (const device of devices) {
+ // Wrap the device name in a FSI/PDI unicode pair, to ensure it maintains it's correct direction
+ // in both LTR and RTL environments
+ deviceListItems.push("\u2022\t\u2068" + device + "\u2069");
+ }
+ const devicesList = deviceListItems.length > 0 ? deviceListItems.join("\n") : undefined;
+ return new models.AnnotationItem(requirementsString, { heading: deviceFamilyName, listText: devicesList });
+}
+/**
+ * Creates an annotation item from the "requirementsString" text string
+ * This is only used in fallback scenarios, where "requirementsByDeviceFamily" is empty or invalid
+ *
+ * @param data MAPI data to build the annotation item with
+ * @returns {models.AnnotationItem} The annotation item
+ */
+function legacyCompatibilityAnnotationItemFromData(objectGraph, data) {
+ const requirements = contentAttributes.contentAttributeAsString(objectGraph, data, "requirementsString");
+ if (serverData.isNull(requirements)) {
+ return null;
+ }
+ return new models.AnnotationItem(requirements);
+}
+//# sourceMappingURL=compatibility-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/content-rating-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/content-rating-annotation.js
new file mode 100644
index 0000000..da7dc7b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/content-rating-annotation.js
@@ -0,0 +1,368 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../../api/models/index";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as ageRatings from "../../../content/age-ratings";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import * as mediaPlatformAttributes from "../../../../foundation/media/platform-attributes";
+import * as mediaRelationship from "../../../../foundation/media/relationships";
+import * as contentAttributes from "../../../content/attributes";
+import * as storefrontContentRatingAnnotation from "./storefront-content-rating-annotation";
+import { artworkTemplateForBundleImage, createArtworkForResource } from "../../../content/artwork/artwork";
+import { makeRoutableArticlePageIntent } from "../../../../api/intents/routable-article-page-intent";
+import { getLocale } from "../../../../common/locale";
+/**
+ * Attempts to create an age rating annotation, preferring the new "ageRating"
+ * MAPI data. Falls back to "contentRatingsBySystem.appsApple" if needed.
+ */
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ var _a;
+ return ((_a = createModernAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics)) !== null && _a !== void 0 ? _a : createLegacyAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics));
+}
+/**
+ * Returns an `Annotation` model from the new style "ageRating" MAPI age rating
+ * data.
+ */
+function createModernAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ // The textual name of the rating, e.g. "18+"
+ const name = ageRatings.name(objectGraph, data);
+ if (isNothing(name)) {
+ return null;
+ }
+ // "Learn More" link
+ const learnMoreTitle = objectGraph.loc.string("InfoList.AgeRating.LearnMore");
+ const learnMoreAction = createLearnMoreAction(objectGraph, learnMoreTitle);
+ let annotationTitle;
+ let annotationSummary;
+ if (preprocessor.GAMES_TARGET) {
+ if (ageRatings.hasInAppControls(objectGraph, data)) {
+ annotationTitle = objectGraph.localizer.string("GameDetails.Annotation.AgeRating.Title", {
+ ageRating: name,
+ });
+ annotationSummary = objectGraph.loc.string("GameDetails.Annotation.AgeRating.InAppControls.Summary");
+ }
+ else {
+ annotationTitle = "";
+ annotationSummary = objectGraph.localizer.string("GameDetails.Annotation.AgeRating.Title", {
+ ageRating: name,
+ });
+ }
+ }
+ else {
+ annotationTitle = objectGraph.loc.string("InfoList.AgeRating.Title");
+ annotationSummary = name;
+ }
+ const annotation = new models.Annotation(annotationTitle, [], annotationSummary, learnMoreAction);
+ // Set text for visionOS collapsed annotation
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ annotation.leadingText = name;
+ }
+ // Push either a pictogram representing the rating (currently just Korea
+ // and Brazil use this), or a text encapsulation of the rating name.
+ const pictogramResource = ageRatings.pictogramResource(objectGraph, data);
+ if (isSome(pictogramResource)) {
+ const pictogramArtwork = createArtworkForResource(objectGraph, artworkTemplateForBundleImage(pictogramResource), 30, 30);
+ if (preprocessor.GAMES_TARGET) {
+ annotation.expandedArtwork = pictogramArtwork;
+ }
+ else {
+ annotation.items_V3.push({
+ $kind: "artwork",
+ artwork: pictogramArtwork,
+ });
+ }
+ }
+ else {
+ annotation.items_V3.push({
+ $kind: "textEncapsulation",
+ text: name,
+ });
+ }
+ // An optional description of the app's rating, potentially with some
+ // information about the storefront's rating agency.
+ const description = ageRatings.description(objectGraph, data);
+ let ageRatingText = "";
+ if (isSome(description)) {
+ ageRatingText += description;
+ }
+ const linkedSubstrings = {};
+ if ((objectGraph.client.isiOS || objectGraph.client.isMac || objectGraph.client.isVision) &&
+ isSome(learnMoreAction)) {
+ // iOS, macOS, and visionOS show the "Learn More" link as LinkableText.
+ linkedSubstrings[learnMoreTitle] = learnMoreAction;
+ if (ageRatingText.length > 0) {
+ ageRatingText += "\n";
+ }
+ ageRatingText += learnMoreTitle;
+ }
+ const ageRatingLinkableText = new models.LinkableText(new models.StyledText(ageRatingText, "text/markdown"), linkedSubstrings);
+ annotation.items_V3.push({
+ $kind: "linkableText",
+ linkableText: ageRatingLinkableText,
+ });
+ // Watch and Web use a button for the "Learn More" link.
+ if ((objectGraph.client.isWatch || objectGraph.client.isWeb) && isSome(learnMoreAction)) {
+ annotation.items_V3.push({
+ $kind: "button",
+ action: learnMoreAction,
+ style: "infer",
+ });
+ }
+ const contentRestrictionsAnnotation = contentRestrictionsAnnotationFromData(objectGraph, data);
+ if (isSome(contentRestrictionsAnnotation)) {
+ annotation.items_V3.push({
+ $kind: "spacer",
+ });
+ annotation.items_V3.push(contentRestrictionsAnnotation);
+ }
+ // Optional external website where developer can provide more context on
+ // their app's rating and parental controls. This link is omitted for TV
+ // since there is no web browser.
+ const developerAgeGuidanceURL = ageRatings.developerAgeGuidanceURL(objectGraph, data);
+ if (isSome(developerAgeGuidanceURL) && !objectGraph.client.isTV) {
+ const openDeveloperAgeGuidanceURLAction = new models.ExternalUrlAction(developerAgeGuidanceURL);
+ openDeveloperAgeGuidanceURLAction.title = objectGraph.loc.string("InfoList.AgeRating.DeveloperInfo");
+ annotation.items_V3.push({
+ $kind: "spacer",
+ });
+ annotation.items_V3.push({
+ $kind: "button",
+ action: openDeveloperAgeGuidanceURLAction,
+ });
+ }
+ return annotation;
+}
+/**
+ * Returns an `Annotation` model from the old style
+ * "contentRatingsBySystem.appsApple" MAPI data.
+ */
+function createLegacyAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ const contentRating = mediaAttributes.attributeAsDictionary(data, "contentRatingsBySystem.appsApple");
+ if (serverData.isNull(contentRating)) {
+ return null;
+ }
+ const name = serverData.asString(contentRating, "name");
+ if (serverData.isNull(name)) {
+ return null;
+ }
+ const rank = serverData.asNumber(contentRating, "rank");
+ if (rank === 99) {
+ // This indicates the app is not yet rated, which should only happen in test environments.
+ return null;
+ }
+ const advisories = serverData.asArrayOrEmpty(contentRating, "advisories");
+ // Build the summary.
+ let summary;
+ const ageBand = mediaPlatformAttributes.platformAttributeAsDictionary(data, contentAttributes.bestAttributePlatformFromData(objectGraph, data), "ageBand");
+ const minAge = serverData.asNumber(ageBand, "minAge");
+ const maxAge = serverData.asNumber(ageBand, "maxAge");
+ if (serverData.isDefinedNonNull(minAge) && serverData.isDefinedNonNull(maxAge)) {
+ summary = objectGraph.loc
+ .string("InfoList.AgeRating.Summary")
+ .replace("{ageRating}", name)
+ .replace("{minAgeRating}", objectGraph.loc.decimal(minAge))
+ .replace("{maxAgeRating}", objectGraph.loc.decimal(maxAge));
+ }
+ else {
+ summary = name;
+ }
+ const ageRatingAnnotationItems = [];
+ // AnnotationItem will insert vertical space between the summary and each
+ // advisory if they are separate items, so keep them as a single item.
+ let appleRatingString = "";
+ if (isSome(summary)) {
+ // Always use the Apple rating summary as our first item.
+ appleRatingString += summary;
+ }
+ for (const advisory of advisories) {
+ // Tack on the advisories.
+ appleRatingString += "\n";
+ appleRatingString += advisory;
+ }
+ ageRatingAnnotationItems.push(new models.AnnotationItem(appleRatingString));
+ // Display the storefront-specific content rating if present under the
+ // App Store content rating (currently this is just for South Korea).
+ // TV displays the storefront-specific content rating in a separate
+ // annotation section.
+ if (!objectGraph.client.isTV) {
+ const pictogramRow = storefrontContentRatingAnnotation.contentRatingPictogramRowIfNeeded(objectGraph, data);
+ if (isSome(pictogramRow)) {
+ ageRatingAnnotationItems.push(pictogramRow);
+ }
+ // Check if we are required to display the age verification annotations (e.g. this is true for the South Korean storefront)
+ const ageVerificationAnnotationItem = ageVerificationAnnotationItemIfNeeded(objectGraph, data);
+ if (isSome(ageVerificationAnnotationItem)) {
+ ageRatingAnnotationItems.push(ageVerificationAnnotationItem);
+ }
+ }
+ let finalSummaryToUse;
+ if (objectGraph.client.isVision) {
+ finalSummaryToUse = objectGraph.loc.string("InfoList.AgeRating.Years").replace("{age}", name);
+ }
+ else if (preprocessor.GAMES_TARGET) {
+ finalSummaryToUse = objectGraph.loc.string("InfoList.AgeRating.Title");
+ }
+ else if (ageRatingAnnotationItems.length > 1 || ageRatingAnnotationItems[0].text !== summary) {
+ // Use an explicit summary unless we're just showing a simple rating
+ // without advisories. E.g. a rating of "4+" does not need a summary.
+ finalSummaryToUse = summary;
+ }
+ const ageRatingTitle = suppressTitle ? "" : objectGraph.loc.string("InfoList.AgeRating.Title");
+ // Learn More link
+ const learnMoreTitle = objectGraph.loc.string("InfoList.AgeRating.LearnMore");
+ const learnMoreAction = createLearnMoreAction(objectGraph, learnMoreTitle);
+ const annotation = new models.Annotation(ageRatingTitle, ageRatingAnnotationItems, finalSummaryToUse, learnMoreAction);
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ annotation.leadingText = name;
+ }
+ return annotation;
+}
+/**
+ * Creates an `Action` that navigates to the App Store Age Ratings editorial item.
+ * @param objectGraph The App Store Object Graph.
+ * @param title The title for the action.
+ * @returns A platform-appropriate `Action` linking to the Age Ratings editorial page.
+ */
+function createLearnMoreAction(objectGraph, title) {
+ let learnMoreAction;
+ if (isSome(objectGraph.bag.ageRatingLearnMoreEditorialItemId) &&
+ objectGraph.bag.ageRatingLearnMoreEditorialItemId.length > 0) {
+ const flowAction = new models.FlowAction("article");
+ flowAction.title = title;
+ flowAction.pageUrl = `https://apps.apple.com/story/id${objectGraph.bag.ageRatingLearnMoreEditorialItemId}`;
+ const routableArticlePageIntent = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ id: objectGraph.bag.ageRatingLearnMoreEditorialItemId,
+ });
+ flowAction.destination = routableArticlePageIntent;
+ if (objectGraph.client.isVision) {
+ const dismissAction = new models.FlowBackAction("sheetDismiss");
+ learnMoreAction = new models.CompoundAction([dismissAction, flowAction]);
+ learnMoreAction.title = title;
+ }
+ else {
+ learnMoreAction = flowAction;
+ }
+ }
+ return learnMoreAction;
+}
+// #region Age Verification
+/**
+ * Returns a special annotation item when age verification is necessary to download an app.
+ * @param objectGraph The App Store object graph.
+ * @param data Media API data representing an app.
+ */
+export function ageVerificationAnnotationItemIfNeeded(objectGraph, data) {
+ if (objectGraph.bag.requireAgeVerification &&
+ checkContentRating(objectGraph, data, "seventeenPlus") &&
+ isProductGenreGame(objectGraph, data)) {
+ let ageVerificationString;
+ const isBundle = data.type === "app-bundles";
+ if (isBundle) {
+ // If this bundle contains a GRAC-registered product, show a message for GRAC bundles.
+ // Search through the bundle's apps and stop if a GRAC-registered app is found.
+ const childrenRelationship = mediaRelationship.relationship(data, "apps");
+ if (serverData.isDefinedNonNull(childrenRelationship)) {
+ for (const childApp of childrenRelationship.data) {
+ const gracRegistrationNumber = mediaAttributes.attributeAsString(childApp, "gracRegistrationNumber");
+ if (serverData.isDefinedNonNull(gracRegistrationNumber)) {
+ ageVerificationString = objectGraph.loc.string("AgeVerification.ProductPage.Rating.BundleUnsuitableForJuveniles");
+ break;
+ }
+ }
+ }
+ }
+ else if (serverData.isDefinedNonNull(mediaAttributes.attributeAsString(data, "gracRegistrationNumber"))) {
+ // If this product is GRAC-registered, show a message for GRAC products.
+ ageVerificationString = objectGraph.loc.string("AgeVerification.ProductPage.Rating.UnsuitableForJuveniles");
+ }
+ if (isNothing(ageVerificationString)) {
+ return null;
+ }
+ return new models.AnnotationItem(ageVerificationString);
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Returns a boolean indicating if a content rating is at least as high as
+ * the supplied rating rank.
+ * @param data Media API data representing an app.
+ * @param isAtLeast A content rating rank name.
+ */
+function checkContentRating(objectGraph, data, isAtLeast) {
+ const rating = mediaAttributes.attributeAsNumber(data, "contentRatingsBySystem.appsApple.rank");
+ if (isNothing(rating)) {
+ return false;
+ }
+ let contentRatingRankValue;
+ switch (isAtLeast) {
+ case "twelvePlus":
+ contentRatingRankValue = 3;
+ break;
+ case "seventeenPlus":
+ contentRatingRankValue = 4;
+ break;
+ case "adultOnly":
+ contentRatingRankValue = 5;
+ break;
+ default:
+ contentRatingRankValue = 1;
+ }
+ return rating >= contentRatingRankValue;
+}
+/**
+ * Returns a boolean indicating if the product is classified as game.
+ * @param data The product data.
+ */
+function isProductGenreGame(objectGraph, data) {
+ var _a;
+ const genreDicts = (_a = mediaRelationship.relationshipCollection(data, "genres")) !== null && _a !== void 0 ? _a : [];
+ for (const candidateDict of genreDicts) {
+ if (serverData.asString(candidateDict, "id") === 6014 /* constants.GenreIds.Games */.toString()) {
+ return true;
+ }
+ }
+ return false;
+}
+/**
+ * Returns an `AnnotationItemLinkableText` model representing the content descriptors.
+ * @param objectGraph The App Store object graph
+ * @param data The age rating object pulled from the app data.
+ */
+function contentRestrictionsAnnotationFromData(objectGraph, data) {
+ const contentLevels = mediaAttributes.attributeAsArrayOrEmpty(data, "ageRating.contentLevels");
+ let contentDescriptorsString = "";
+ for (const contentLevel of contentLevels) {
+ const contentLevelHeading = serverData.asString(contentLevel, "level");
+ const contentDescriptors = serverData.asArrayOrEmpty(contentLevel, "contentDescriptors");
+ // Skip this content level if it somehow doesn't have descriptors.
+ if (contentDescriptors.length === 0) {
+ continue;
+ }
+ // Add newline between proceeding content level
+ if (contentDescriptorsString.length > 0) {
+ contentDescriptorsString += "\n\n";
+ }
+ // Heading
+ contentDescriptorsString += `**${contentLevelHeading}**`;
+ // Descriptors
+ for (const contentDescriptor of contentDescriptors) {
+ const description = serverData.asString(contentDescriptor, "description");
+ if (isSome(description)) {
+ contentDescriptorsString += "\n";
+ contentDescriptorsString += description;
+ }
+ }
+ }
+ if (contentDescriptorsString.length === 0) {
+ return undefined;
+ }
+ const linkableText = new models.LinkableText(new models.StyledText(contentDescriptorsString, "text/markdown"));
+ return {
+ $kind: "linkableText",
+ linkableText: linkableText,
+ };
+}
+// #endregion
+//# sourceMappingURL=content-rating-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/controller-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/controller-annotation.js
new file mode 100644
index 0000000..b74159b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/controller-annotation.js
@@ -0,0 +1,68 @@
+import { isSome } from "@jet/environment";
+import * as models from "../../../../api/models/index";
+import { isFeatureEnabledForCurrentUser } from "../../../../common/util/lottery";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import { createArtworkForResource } from "../../../content/artwork/artwork";
+import * as gameController from "./../../../content/game-controller";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ if (objectGraph.client.isVision && gameController.isSpatialControllerRequired(objectGraph, data)) {
+ // Controllers and spatial controllers annotations are mutually exclusive
+ return null;
+ }
+ const isFeatureEnabled = isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.gameControllerRecommendedRolloutRate);
+ const isRecommended = gameController.isGameControllerRecommended(objectGraph, data);
+ const isRequired = objectGraph.client.isVision && gameController.isGameControllerRequired(objectGraph, data);
+ const isSupported = preprocessor.GAMES_TARGET && gameController.isGameControllerSupported(objectGraph, data);
+ if (isFeatureEnabled && (isRecommended || isRequired || isSupported)) {
+ const title = suppressTitle ? "" : objectGraph.loc.string("ProductPage.Info.GameController.Title"); // Game Controller
+ // Summary
+ let summaryKey;
+ if (preprocessor.GAMES_TARGET) {
+ summaryKey = isRecommended
+ ? "GameDetails.Annotation.Controller.Recommended.Title"
+ : isRequired
+ ? "GameDetails.Annotation.Controller.Required.Title"
+ : "GameDetails.Annotation.Controller.Supported.Title";
+ }
+ else {
+ summaryKey = isRecommended
+ ? "ProductPage.Info.GameController.Recommended"
+ : "ProductPage.Info.GameController.Required";
+ }
+ const summary = objectGraph.loc.string(summaryKey);
+ // Items
+ const artistName = mediaAttributes.attributeAsString(data, "artistName");
+ const itemKey = isRecommended
+ ? "ProductPage.Info.GameController.Recommended.Summary.v2"
+ : "ProductPage.Info.GameController.Required.Summary.v2";
+ const itemText = objectGraph.loc.string(itemKey).replace("{developerName}", artistName);
+ const items = [new models.AnnotationItem(itemText)];
+ // Learn More link
+ let linkAction;
+ const storyId = objectGraph.bag.gameControllerLearnMoreEditorialItemId;
+ if (isSome(storyId) && storyId.length > 0) {
+ const flowAction = new models.FlowAction("article");
+ const titleKey = isRecommended
+ ? "ProductPage.Info.GameController.Recommended.LearnMore"
+ : "ProductPage.Info.GameController.Required.LearnMore";
+ flowAction.title = objectGraph.loc.string(titleKey);
+ flowAction.pageUrl = `https://apps.apple.com/story/id${storyId}`;
+ if (objectGraph.client.isVision) {
+ const dismissAction = new models.FlowBackAction("sheetDismiss");
+ linkAction = new models.CompoundAction([dismissAction, flowAction]);
+ linkAction.title = flowAction.title;
+ }
+ else {
+ linkAction = flowAction;
+ }
+ }
+ const annotation = new models.Annotation(title, items, summary, linkAction);
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ annotation.leadingArtwork = createArtworkForResource(objectGraph, "systemimage://gamecontroller.fill");
+ annotation.prefersSmallLeadingArtwork = true;
+ }
+ return annotation;
+ }
+ return null;
+}
+//# sourceMappingURL=controller-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/copyright-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/copyright-annotation.js
new file mode 100644
index 0000000..adc274f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/copyright-annotation.js
@@ -0,0 +1,18 @@
+import { isNothing } from "@jet/environment";
+import * as models from "../../../../api/models/index";
+import { createArtworkForResource } from "../../../content/artwork/artwork";
+import * as contentAttributes from "../../../content/attributes";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ const copyrightString = contentAttributes.contentAttributeAsString(objectGraph, data, "copyright");
+ if (isNothing(copyrightString)) {
+ return null;
+ }
+ const copyrightItem = new models.AnnotationItem(copyrightString);
+ const title = suppressTitle ? "" : objectGraph.loc.string("InfoList.Copyright");
+ const annotation = new models.Annotation(title, [copyrightItem]);
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ annotation.leadingArtwork = createArtworkForResource(objectGraph, "resource://copyright");
+ }
+ return annotation;
+}
+//# sourceMappingURL=copyright-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/external-browser-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/external-browser-annotation.js
new file mode 100644
index 0000000..19cc906
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/external-browser-annotation.js
@@ -0,0 +1,54 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../../api/models/index";
+import { createArtworkForResource } from "../../../content/artwork/artwork";
+import { hasExternalBrowserForData } from "../../../content/content";
+import * as metricsHelpersClicks from "../../../metrics/helpers/clicks";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+/**
+ * Creates the external browser annotation, which is only present if an app is using an external browser engine.
+ */
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ var _a;
+ if (!hasExternalBrowserForData(objectGraph, data)) {
+ return null;
+ }
+ const learnMoreAction = (_a = createExternalBrowserLearnMoreAction(objectGraph, shelfMetrics.metricsPageInformation, shelfMetrics.locationTracker)) !== null && _a !== void 0 ? _a : undefined;
+ let linkAction = learnMoreAction;
+ if (objectGraph.client.isVision && isSome(learnMoreAction)) {
+ const dismissAction = new models.FlowBackAction("sheetDismiss");
+ linkAction = new models.CompoundAction([dismissAction, learnMoreAction]);
+ linkAction.title = learnMoreAction.title;
+ }
+ const title = suppressTitle ? "" : objectGraph.loc.string("Annotations.ExternalBrowser.Title");
+ const summary = objectGraph.loc.string("Annotations.ExternalBrowser.Value.Summary");
+ const itemText = objectGraph.loc.string("Annotations.ExternalBrowser.Value.Expanded");
+ const items = [new models.AnnotationItem(itemText)];
+ const annotation = new models.Annotation(title, items, summary, linkAction);
+ if (objectGraph.client.isVision) {
+ annotation.leadingArtwork = createArtworkForResource(objectGraph, "systemimage://exclamationmark.triangle");
+ }
+ return annotation;
+}
+/**
+ * Creates a flow action for the external browser learn more story.
+ * @param objectGraph Current object graph
+ * @returns A flow action for the story, or null
+ */
+function createExternalBrowserLearnMoreAction(objectGraph, metricsPageInformation, metricsLocationTracker) {
+ const editorialItemId = objectGraph.bag.externalBrowserLearnMoreEditorialItemId;
+ if (serverData.isNullOrEmpty(editorialItemId)) {
+ return null;
+ }
+ const flowAction = new models.FlowAction("article");
+ flowAction.title = objectGraph.loc.string("Action.LearnMore");
+ flowAction.pageUrl = `https://apps.apple.com/story/id${editorialItemId}`;
+ metricsHelpersClicks.addClickEventToAction(objectGraph, flowAction, {
+ id: "LearnMore",
+ targetType: "link",
+ actionType: "navigate",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ });
+ return flowAction;
+}
+//# sourceMappingURL=external-browser-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/external-purchases-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/external-purchases-annotation.js
new file mode 100644
index 0000000..a955b00
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/external-purchases-annotation.js
@@ -0,0 +1,39 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../../api/models/index";
+import { externalPurchasesLearnMoreAction, externalPurchasesPlacementIsEnabled, hasExternalPurchasesForData, } from "../../../offers/external-purchases";
+import { createArtworkForResource } from "../../../content/artwork/artwork";
+/**
+ * Creates the external purchases annotation, which is only present if an app is using external purchases, and this feature is enabled in the bag.
+ */
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ const hasExternalPurchases = hasExternalPurchasesForData(objectGraph, data);
+ const externalPurchasesEnabled = externalPurchasesPlacementIsEnabled(objectGraph, "product-page-info-section");
+ if (!hasExternalPurchases || !externalPurchasesEnabled) {
+ return null;
+ }
+ const title = suppressTitle ? "" : objectGraph.loc.string("Annotations.ExternalPurchases.Title");
+ const summary = objectGraph.loc.string("YES");
+ let itemText;
+ if (isSome(objectGraph.bag.externalPurchasesProductPageAnnotationVariant)) {
+ const key = `Annotations.ExternalPurchases.Expanded.Variant${objectGraph.bag.externalPurchasesProductPageAnnotationVariant}`;
+ itemText = objectGraph.loc.string(key);
+ }
+ else {
+ itemText = objectGraph.loc.string("Annotations.ExternalPurchases.Expanded");
+ }
+ const item = new models.AnnotationItem(itemText);
+ const learnMoreTitle = objectGraph.loc.string("Annotations.ExternalPurchases.Expanded.LearnMore");
+ const learnMoreAction = externalPurchasesLearnMoreAction(objectGraph, learnMoreTitle, shelfMetrics.metricsPageInformation, shelfMetrics.locationTracker);
+ let wrappedLearnMoreAction = learnMoreAction;
+ if (objectGraph.client.isVision && isSome(learnMoreAction)) {
+ const dismissAction = new models.FlowBackAction("sheetDismiss");
+ wrappedLearnMoreAction = new models.CompoundAction([dismissAction, learnMoreAction]);
+ wrappedLearnMoreAction.title = learnMoreAction.title;
+ }
+ const annotation = new models.Annotation(title, [item], summary, wrappedLearnMoreAction !== null && wrappedLearnMoreAction !== void 0 ? wrappedLearnMoreAction : undefined);
+ if (objectGraph.client.isVision) {
+ annotation.leadingArtwork = createArtworkForResource(objectGraph, "systemimage://exclamationmark.triangle");
+ }
+ return annotation;
+}
+//# sourceMappingURL=external-purchases-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/game-license-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/game-license-annotation.js
new file mode 100644
index 0000000..4153f08
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/game-license-annotation.js
@@ -0,0 +1,73 @@
+import * as models from "../../../../api/models/index";
+import { asString, attributeAsDictionary } from "@apple-media-services/media-api";
+import { isSome } from "@jet/environment";
+import { isNotEmpty } from "../../../../foundation/util/array-util";
+import { createArtworkForResource } from "../../../content/artwork/artwork";
+/**
+ * Create an annotation for a game license, currently supported for the Vietnamese storefront.
+ */
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ const gameLicense = attributeAsDictionary(data, "licenses.gameLicenseVietnam");
+ const items = [plainText(objectGraph.loc.string("InfoList.GameLicense.Disclaimer"))];
+ // Add license id
+ const gameLicenseId = asString(gameLicense, "licenseId");
+ if (isSome(gameLicenseId) && isNotEmpty(gameLicenseId)) {
+ items.push(spacer(), heading(objectGraph.loc.string("InfoList.GameLicense.Id.Title")), plainText(gameLicenseId));
+ }
+ else {
+ // The license id is required. If this is missing, we should not display this annotation.
+ return null;
+ }
+ // Add license url
+ const gameLicenseUrl = asString(gameLicense, "licenseUrl");
+ if (isSome(gameLicenseUrl) && isNotEmpty(gameLicenseUrl)) {
+ if (objectGraph.client.isWatch) {
+ items.push(buttonLink(objectGraph.loc.string("InfoList.GameLicense.Url.ButtonTitle"), gameLicenseUrl));
+ }
+ else {
+ items.push(spacer(), heading(objectGraph.loc.string("InfoList.GameLicense.Url.Title")), externalLink(gameLicenseUrl));
+ }
+ }
+ const title = suppressTitle ? "" : objectGraph.loc.string("InfoList.GameLicense.Title");
+ const annotation = new models.Annotation(title, [], gameLicenseId);
+ annotation.items_V3 = items;
+ if (objectGraph.client.isVision) {
+ annotation.leadingArtwork = createArtworkForResource(objectGraph, "systemimage://text.page");
+ }
+ return annotation;
+}
+// region Annotation Helpers
+function spacer() {
+ return {
+ $kind: "spacer",
+ };
+}
+function heading(text) {
+ return {
+ $kind: "linkableText",
+ linkableText: new models.LinkableText(new models.StyledText(`**${text}**`)),
+ };
+}
+function plainText(text) {
+ return {
+ $kind: "linkableText",
+ linkableText: new models.LinkableText(new models.StyledText(text)),
+ };
+}
+function externalLink(url) {
+ const linkedSubstrings = {};
+ linkedSubstrings[url] = new models.ExternalUrlAction(url, false);
+ return {
+ $kind: "linkableText",
+ linkableText: new models.LinkableText(new models.StyledText(url), linkedSubstrings),
+ };
+}
+function buttonLink(title, url) {
+ const action = new models.ExternalUrlAction(url, false);
+ action.title = title;
+ return {
+ $kind: "button",
+ action: action,
+ };
+}
+//# sourceMappingURL=game-license-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/high-motion-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/high-motion-annotation.js
new file mode 100644
index 0000000..4c3c5d8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/high-motion-annotation.js
@@ -0,0 +1,53 @@
+import * as models from "../../../../api/models/index";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import { createArtworkForResource } from "../../../content/artwork/artwork";
+import { contentAttributeAsBooleanOrFalse } from "../../../content/attributes";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ // The "High Motion" flag shouldn't appear in data for other platform apps, but to be sure
+ // we don't want the annotation appearing anywhere else accidentally.
+ if (!objectGraph.client.isVision) {
+ return null;
+ }
+ const isHighMotion = contentAttributeAsBooleanOrFalse(objectGraph, data, "isHighMotion");
+ if (!isHighMotion) {
+ return null;
+ }
+ const artwork = createArtworkForResource(objectGraph, "resource://motion.circle.fill");
+ const highMotionAnnotationItem = new models.AnnotationItem(objectGraph.loc.string("ProductPage.Info.HighMotion.Item.Description"), { heading: objectGraph.loc.string("ProductPage.Info.HighMotion.Item.Heading") });
+ const highMotionAnnotationItemHeading = objectGraph.loc.string("ProductPage.Info.HighMotion.Item.Heading");
+ const highMotionAnnotationItemDescription = objectGraph.loc.string("ProductPage.Info.HighMotion.Item.Description");
+ const highMotionAnnotationItems_V3 = [
+ {
+ $kind: "linkableText",
+ linkableText: new models.LinkableText(new models.StyledText(`**${highMotionAnnotationItemHeading}**\n\n${highMotionAnnotationItemDescription}`)),
+ },
+ ];
+ const title = suppressTitle ? "" : objectGraph.loc.string("Annotations.HighMotion.Title");
+ const summary = objectGraph.loc.string("Annotations.HighMotion.Summary");
+ // Learn More link
+ let learnMoreAction;
+ if (serverData.isDefinedNonNullNonEmpty(objectGraph.bag.highMotionLearnMoreEditorialItemId)) {
+ const flowAction = new models.FlowAction("article");
+ flowAction.title = objectGraph.loc.string("ProductPage.Info.HighMotion.LearnMore");
+ flowAction.pageUrl = `https://apps.apple.com/story/id${objectGraph.bag.highMotionLearnMoreEditorialItemId}`;
+ if (objectGraph.client.isVision) {
+ const dismissAction = new models.FlowBackAction("sheetDismiss");
+ learnMoreAction = new models.CompoundAction([dismissAction, flowAction]);
+ learnMoreAction.title = flowAction.title;
+ }
+ else {
+ learnMoreAction = flowAction;
+ }
+ highMotionAnnotationItems_V3.push({
+ $kind: "button",
+ action: learnMoreAction,
+ });
+ }
+ const annotation = new models.Annotation(title, [highMotionAnnotationItem], summary, learnMoreAction);
+ annotation.leadingArtwork = artwork;
+ annotation.expandedArtwork = artwork;
+ annotation.prefersSmallLeadingArtwork = true;
+ annotation.items_V3 = highMotionAnnotationItems_V3;
+ return annotation;
+}
+//# sourceMappingURL=high-motion-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/languages-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/languages-annotation.js
new file mode 100644
index 0000000..91aecc3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/languages-annotation.js
@@ -0,0 +1,61 @@
+import * as models from "../../../../api/models/index";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as stringUtil from "../../../../foundation/util/string-util";
+import * as contentAttributes from "../../../content/attributes";
+import { primaryLanguageLocaleFromLocales } from "../../../content/content";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ var _a;
+ const languageList = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, data, "languageList");
+ if (serverData.isNullOrEmpty(languageList)) {
+ return null;
+ }
+ const languageCount = languageList.length;
+ if (languageCount < 1) {
+ return null;
+ }
+ let summaryString;
+ let expandedString;
+ switch (languageCount) {
+ case 0:
+ break;
+ case 1:
+ expandedString = languageList[0];
+ if (preprocessor.GAMES_TARGET) {
+ summaryString = expandedString;
+ }
+ break;
+ case 2:
+ expandedString = objectGraph.loc
+ .string("InfoList.Two.Languages.Summary")
+ .replace("{first}", languageList[0])
+ .replace("{second}", languageList[1]);
+ if (preprocessor.GAMES_TARGET) {
+ summaryString = expandedString;
+ }
+ break;
+ default:
+ const additionalLanguagesCount = languageCount - 1;
+ summaryString = objectGraph.loc
+ .string("InfoList.More.Languages.Summary")
+ .replace("{language}", languageList[0])
+ .replace("{count}", objectGraph.loc.formattedCount(additionalLanguagesCount));
+ expandedString =
+ (_a = stringUtil.join(languageList, objectGraph.loc.string("Conjunction.Separator"))) !== null && _a !== void 0 ? _a : undefined;
+ break;
+ }
+ if (!serverData.isDefinedNonNull(expandedString)) {
+ return null;
+ }
+ const expandedItem = new models.AnnotationItem(expandedString);
+ const title = suppressTitle ? "" : objectGraph.loc.string("Supported.Languages");
+ const annotation = new models.Annotation(title, [expandedItem], summaryString);
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ const supportedLocales = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, data, "supportedLocales");
+ const primaryLocale = primaryLanguageLocaleFromLocales(objectGraph, supportedLocales);
+ if (serverData.isDefinedNonNull(primaryLocale)) {
+ annotation.leadingText = primaryLocale.tag;
+ }
+ }
+ return annotation;
+}
+//# sourceMappingURL=languages-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/location-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/location-annotation.js
new file mode 100644
index 0000000..eb07202
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/location-annotation.js
@@ -0,0 +1,18 @@
+import * as models from "../../../../api/models/index";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import { createArtworkForResource } from "../../../content/artwork/artwork";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ const usesLocationBackgroundMode = mediaAttributes.attributeAsBooleanOrFalse(data, "usesLocationBackgroundMode");
+ if (!usesLocationBackgroundMode) {
+ return null;
+ }
+ const locationTitle = suppressTitle ? "" : objectGraph.loc.string("InfoList.Location.Title");
+ const locationDescription = objectGraph.loc.string("InfoList.Location.Description");
+ const locationItem = new models.AnnotationItem(locationDescription);
+ const annotation = new models.Annotation(locationTitle, [locationItem]);
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ annotation.leadingArtwork = createArtworkForResource(objectGraph, "systemimage://location.circle");
+ }
+ return annotation;
+}
+//# sourceMappingURL=location-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/product-capabilities-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/product-capabilities-annotation.js
new file mode 100644
index 0000000..08fc843
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/product-capabilities-annotation.js
@@ -0,0 +1,25 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../../api/models/index";
+import { productCapabilitiesFromData } from "../../../content/product-capabilities";
+import { translateItemsToV3 } from "./annotations";
+export function createAnnotations(objectGraph, productData, isFreeProduct) {
+ return validation.context("productCapabilitiesAsAnnotations", () => {
+ const annotations = [];
+ const capabilities = productCapabilitiesFromData(objectGraph, productData, isFreeProduct);
+ for (const capability of capabilities) {
+ const title = capability.title;
+ const text = capability.caption.styledText.rawText;
+ if ((title === null || title === void 0 ? void 0 : title.length) > 0 && (text === null || text === void 0 ? void 0 : text.length) > 0) {
+ const annotationItem = new models.AnnotationItem(text);
+ const annotation = new models.Annotation(title, [annotationItem]);
+ // Don't overwrite existing items
+ if (annotation.items_V3.length === 0) {
+ annotation.items_V3 = translateItemsToV3(objectGraph, annotation);
+ }
+ annotations.push(annotation);
+ }
+ }
+ return annotations;
+ });
+}
+//# sourceMappingURL=product-capabilities-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/seller-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/seller-annotation.js
new file mode 100644
index 0000000..e779e6c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/seller-annotation.js
@@ -0,0 +1,117 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../../api/models/index";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import { createArtworkForResource } from "../../../content/artwork/artwork";
+import * as contentAttributes from "../../../content/attributes";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ if (isArcadeApp) {
+ return null;
+ }
+ const sellerData = contentAttributes.contentAttributeAsString(objectGraph, data, "seller");
+ const sellerInfoData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "sellerInfo");
+ const icpInfo = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "internetContentProviderInfo");
+ const enableSellerICP = objectGraph.bag.enableSellerICPAnnotation && objectGraph.client.isiOS;
+ if (isNothing(sellerData)) {
+ return null;
+ }
+ const sellerLabel = contentAttributes.contentAttributeAsString(objectGraph, data, "sellerLabel") ||
+ objectGraph.loc.string("Seller");
+ let annotation;
+ let annotationItems = [];
+ const sellerDataName = serverData.asString(sellerInfoData, "name");
+ const sellerName = isSome(sellerDataName) && (sellerDataName === null || sellerDataName === void 0 ? void 0 : sellerDataName.length) > 0 ? sellerDataName : sellerData;
+ const title = suppressTitle ? "" : sellerLabel;
+ let annotationSummary = null;
+ if (isSome(sellerInfoData)) {
+ const [newItems, summary] = createSellerInfoAnnotationItemsAndSummary(objectGraph, sellerInfoData);
+ annotationSummary = summary;
+ annotationItems = annotationItems.concat(newItems);
+ }
+ if (enableSellerICP && isSome(icpInfo)) {
+ const annotationItem = setupAnnotationForKey(objectGraph, "filingNumber", objectGraph.loc.string("ProductPage.Info.ICPNumber.Title"), icpInfo);
+ // If no other seller info, include seller title in ICP annotation
+ if (annotationItems.length === 0) {
+ const annotationTitle = new models.AnnotationItem(sellerName);
+ annotationItems.push(annotationTitle);
+ }
+ if (isSome(annotationItem)) {
+ annotationItems.push(annotationItem);
+ }
+ }
+ if (annotationItems.length > 0) {
+ if (isNothing(annotationSummary)) {
+ annotationSummary = annotationItems.length > 1 ? sellerName : undefined;
+ }
+ annotation = new models.Annotation(title, annotationItems, annotationSummary, undefined);
+ }
+ else {
+ const sellerItem = new models.AnnotationItem(sellerData);
+ annotation = new models.Annotation(title, [sellerItem]);
+ }
+ if (isSome(annotation) && (objectGraph.client.isVision || preprocessor.GAMES_TARGET)) {
+ annotation.leadingArtwork = createArtworkForResource(objectGraph, "resource://person.crop.app");
+ }
+ return annotation;
+}
+/**
+ * Create the seller info annotation items on the product page.
+ *
+ * @param sellerData The sellerInfo map from the product page JSON fetch response.
+ * @returns A tuple consisting of a list of seller info annotation items and an optional string for the summary, if necessary to provide.
+ */
+function createSellerInfoAnnotationItemsAndSummary(objectGraph, sellerData) {
+ const sellerInfoAnnotationItems = [];
+ const locKeysMap = {
+ name: "ProductPage.SellerInfo.Name.Title",
+ dunsNumber: "ProductPage.SellerInfo.DunsNumber.Title",
+ address: "ProductPage.SellerInfo.Address.Title",
+ brn: "ProductPage.SellerInfo.BusinessRegistrationNumber.Title",
+ phoneNumber: "ProductPage.SellerInfo.PhoneNumber.Title",
+ email: "ProductPage.SellerInfo.Email.Title",
+ usci: "ProductPage.SellerInfo.UnifiedSocialCreditIdentifier.Title",
+ };
+ const sellerDataName = serverData.asString(sellerData, "name");
+ const sellerName = isSome(sellerDataName) && (sellerDataName === null || sellerDataName === void 0 ? void 0 : sellerDataName.length) > 0 ? sellerDataName : objectGraph.loc.string("Seller");
+ const traderBooleanProvided = isSome(serverData.asString(sellerData, "isTrader"));
+ if (traderBooleanProvided) {
+ const hasTraderInfo = serverData.asBoolean(sellerData, "isTrader");
+ // Trader Identified
+ const traderVerificationString = hasTraderInfo
+ ? "ProductPage.SellerInfo.VerifiedSeller.Subtitle"
+ : "ProductPage.SellerInfo.UnverifiedSeller.Subtitle";
+ sellerInfoAnnotationItems.push(new models.AnnotationItem(objectGraph.loc.string(traderVerificationString).replace("{seller}", sellerName)));
+ if (!hasTraderInfo) {
+ return [sellerInfoAnnotationItems, sellerName];
+ }
+ }
+ // List remaining annotations from seller info
+ // https://developer.apple.com/help/app-store-connect/manage-compliance-information/manage-european-union-digital-services-act-trader-requirements/
+ let summary = null;
+ // If there's only one key in seller data and it's not the seller name, put "See Details" summary.
+ if (Object.keys(sellerData).length === 1 && !Object.keys(sellerData).includes("name")) {
+ summary = objectGraph.loc.string("ProductPage.SellerInfo.SeeDetails.Title");
+ }
+ for (const [key, val] of Object.entries(locKeysMap)) {
+ // Don't need a redundant name field if already included in DSA trader verification subtitle.
+ if (key === "name" && traderBooleanProvided) {
+ continue;
+ }
+ const annotation = setupAnnotationForKey(objectGraph, key, objectGraph.loc.string(val), sellerData);
+ if (isSome(annotation)) {
+ sellerInfoAnnotationItems.push(annotation);
+ }
+ }
+ return [sellerInfoAnnotationItems, summary];
+}
+function setupAnnotationForKey(objectGraph, key, title, sellerData) {
+ let itemText = serverData.asString(sellerData, key);
+ if (isSome(itemText)) {
+ if (key === "brn") {
+ itemText =
+ itemText + "\n" + objectGraph.loc.string("ProductPage.SellerInfo.BusinessRegistrationNumber.Subtitle");
+ }
+ return new models.AnnotationItem(itemText, { heading: title });
+ }
+ return null;
+}
+//# sourceMappingURL=seller-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/size-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/size-annotation.js
new file mode 100644
index 0000000..7dd9905
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/size-annotation.js
@@ -0,0 +1,61 @@
+import { isSome } from "@jet/environment";
+import * as models from "../../../../api/models/index";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as content from "../../../content/content";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ const combinedFileSize = content.combinedFileSizeFromData(objectGraph, data);
+ if (isFirstPartyHideableApp ||
+ serverData.isNull(combinedFileSize) ||
+ serverData.isNull(combinedFileSize.fileSizeByDevice)) {
+ return null;
+ }
+ const sizeString = objectGraph.loc.fileSize(combinedFileSize.fileSizeByDevice);
+ const title = suppressTitle ? "" : objectGraph.loc.string("File.Size");
+ let totalFileSize = combinedFileSize.fileSizeByDevice;
+ let annotation;
+ if (serverData.isDefinedNonNull(combinedFileSize.maxEssentialInstallSizeInBytes)) {
+ const combinedBinaryAndEssentialSize = combinedFileSize.fileSizeByDevice + combinedFileSize.maxEssentialInstallSizeInBytes;
+ const maxEssentialInstallSizeInBytesString = objectGraph.loc
+ .string("Install.Size.String")
+ .replace("{fileSize}", objectGraph.loc.fileSize(combinedBinaryAndEssentialSize));
+ const sizeItem = new models.AnnotationItem(maxEssentialInstallSizeInBytesString);
+ const summary = preprocessor.GAMES_TARGET
+ ? objectGraph.loc.fileSize(combinedBinaryAndEssentialSize)
+ : undefined;
+ annotation = new models.Annotation(title, [sizeItem], summary);
+ }
+ else if (serverData.isDefinedNonNull(combinedFileSize.maxInstallSizeInBytes)) {
+ const sizeListItems = [];
+ const initialAppSizeItem = new models.AnnotationItem(sizeString, {
+ heading: objectGraph.loc.string("Initial.App.Size"),
+ });
+ sizeListItems.push(initialAppSizeItem);
+ const sizeStringInstall = objectGraph.loc
+ .string("Install.Size.String")
+ .replace("{fileSize}", objectGraph.loc.fileSize(combinedFileSize.maxInstallSizeInBytes));
+ const maxInstallSizeInBytesItem = new models.AnnotationItem(sizeStringInstall, {
+ heading: objectGraph.loc.string("Additional.Content.After.App.Install"),
+ });
+ sizeListItems.push(maxInstallSizeInBytesItem);
+ totalFileSize = totalFileSize + combinedFileSize.maxInstallSizeInBytes;
+ const summarySizeString = preprocessor.GAMES_TARGET
+ ? objectGraph.loc.fileSize(totalFileSize)
+ : objectGraph.loc
+ .string("Install.Size.String")
+ .replace("{fileSize}", objectGraph.loc.fileSize(totalFileSize));
+ annotation = new models.Annotation(title, sizeListItems, summarySizeString);
+ }
+ else {
+ const sizeItem = new models.AnnotationItem(sizeString);
+ const summary = preprocessor.GAMES_TARGET ? sizeString : undefined;
+ annotation = new models.Annotation(title, [sizeItem], summary);
+ }
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ const fileSizeAndUnit = content.fileSizeAndUnitFromCombinedFileSize(objectGraph, combinedFileSize);
+ if (isSome(fileSizeAndUnit)) {
+ annotation.leadingText = fileSizeAndUnit.unit;
+ }
+ }
+ return annotation;
+}
+//# sourceMappingURL=size-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/spatial-controller-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/spatial-controller-annotation.js
new file mode 100644
index 0000000..3915c5b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/spatial-controller-annotation.js
@@ -0,0 +1,60 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../../api/models/index";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import { createArtworkForResource } from "../../../content/artwork/artwork";
+import * as gameController from "./../../../content/game-controller";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ if (!objectGraph.client.isVision) {
+ return null;
+ }
+ const isRequired = gameController.isSpatialControllerRequired(objectGraph, data);
+ if (!isRequired) {
+ return null;
+ }
+ const title = suppressTitle ? "" : objectGraph.loc.string("ProductPage.Info.SpatialController.Title");
+ const summary = objectGraph.loc.string("ProductPage.Info.SpatialController.Required.Summary");
+ // Items
+ const artistName = mediaAttributes.attributeAsString(data, "artistName");
+ if (isNothing(artistName) || artistName.length === 0) {
+ return null;
+ }
+ const itemText = objectGraph.loc
+ .string("ProductPage.Info.SpatialController.Required.Item")
+ .replace("{developerName}", artistName);
+ const styledText = new models.StyledText(itemText);
+ const items = [
+ {
+ $kind: "linkableText",
+ linkableText: new models.LinkableText(styledText),
+ },
+ ];
+ // Learn More link
+ const storyId = objectGraph.bag.spatialControlsLearnMoreEditorialItemId;
+ if (isSome(storyId) && storyId.length > 0) {
+ // The native support for the `small` spacer was introduced in 2025B. Older clients will just ignore this
+ // and show the standard size space, which is still preferable over no space at all.
+ items.push({
+ $kind: "spacer",
+ size: "small",
+ });
+ const actionTitle = objectGraph.loc.string("Action.LearnMore");
+ const flowAction = new models.FlowAction("article");
+ flowAction.title = actionTitle;
+ flowAction.pageUrl = `https://apps.apple.com/story/id${storyId}`;
+ const dismissAction = new models.FlowBackAction("sheetDismiss");
+ const linkAction = new models.CompoundAction([dismissAction, flowAction]);
+ linkAction.title = actionTitle;
+ const linkedSubstrings = {};
+ linkedSubstrings[actionTitle] = linkAction;
+ items.push({
+ $kind: "linkableText",
+ linkableText: new models.LinkableText(new models.StyledText(actionTitle), linkedSubstrings),
+ });
+ }
+ const annotation = new models.Annotation(title, [], summary);
+ annotation.items_V3 = items;
+ annotation.leadingArtwork = createArtworkForResource(objectGraph, "resource://SpatialControllers");
+ annotation.prefersSmallLeadingArtwork = true;
+ return annotation;
+}
+//# sourceMappingURL=spatial-controller-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/storefront-content-rating-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/storefront-content-rating-annotation.js
new file mode 100644
index 0000000..4c8bd56
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/storefront-content-rating-annotation.js
@@ -0,0 +1,53 @@
+import * as models from "../../../../api/models/index";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import * as artwork from "../../../content/artwork/artwork";
+import { isSome, isNothing } from "@jet/environment";
+import { ageVerificationAnnotationItemIfNeeded } from "./content-rating-annotation";
+import { storefrontContentRatingResourceForRank } from "../../../content/content";
+/**
+ * Only used by TV. Shows the full annotation by default without needing to expand.
+ */
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ const annotationItems = [];
+ const storefrontContentRating = contentRatingPictogramRowIfNeeded(objectGraph, data);
+ if (isSome(storefrontContentRating)) {
+ annotationItems.push(storefrontContentRating);
+ }
+ const ageVerificationAnnotationItem = ageVerificationAnnotationItemIfNeeded(objectGraph, data);
+ if (isSome(ageVerificationAnnotationItem)) {
+ annotationItems.push(ageVerificationAnnotationItem);
+ }
+ if (annotationItems.length === 0) {
+ return null;
+ }
+ const title = objectGraph.loc.string("InfoList.AgeRating.Title.KR");
+ const annotation = new models.Annotation(title, annotationItems);
+ annotation.shouldAlwaysPresentExpanded = true;
+ return annotation;
+}
+export function contentRatingPictogramRowIfNeeded(objectGraph, data) {
+ const krContentRating = mediaAttributes.attributeAsDictionary(data, "contentRatingsBySystem.appsKorea");
+ if (isNothing(krContentRating)) {
+ return null;
+ }
+ const krContentRank = serverData.asNumber(krContentRating, "rank");
+ if (isNothing(krContentRank)) {
+ return null;
+ }
+ const krContentAdvisory = serverData.asArrayOrEmpty(krContentRating, "advisories")[0];
+ if (isNothing(krContentAdvisory)) {
+ return null;
+ }
+ // Make sure we have a content rank, and the rank is not 19+. 19+ pictogram is not displayed.
+ const pictogramResource = storefrontContentRatingResourceForRank(objectGraph, krContentRank);
+ const krRatingPictograms = [];
+ if (isSome(pictogramResource)) {
+ krRatingPictograms.push(artwork.createArtworkForResource(objectGraph, `resource://${pictogramResource}`));
+ }
+ const pictogramRow = new models.AnnotationItem(krContentAdvisory, {
+ headingArtworks: krRatingPictograms,
+ });
+ return pictogramRow;
+}
+//# sourceMappingURL=storefront-content-rating-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/top-in-app-purchases-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/top-in-app-purchases-annotation.js
new file mode 100644
index 0000000..ecfa58c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/top-in-app-purchases-annotation.js
@@ -0,0 +1,142 @@
+import * as models from "../../../../api/models/index";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import { isNothing, isSome } from "@jet/environment";
+import * as mediaAttributes from "../../../../foundation/media/attributes";
+import * as mediaRelationship from "../../../../foundation/media/relationships";
+import { createArtworkForResource } from "../../../content/artwork/artwork";
+import * as offerFormatting from "../../../offers/offer-formatting";
+import * as offers from "../../../offers/offers";
+import { isEmpty } from "../../../../foundation/util/array-util";
+import { makeRoutableArticlePageIntent } from "../../../../api/intents/routable-article-page-intent";
+import { getLocale } from "../../../locale";
+import { makeRoutableArticlePageCanonicalUrl } from "../../../today/routable-article-page-url-utils";
+import { getPlatform } from "../../../preview-platform";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ const topInApps = mediaRelationship.relationshipCollection(data, "top-in-apps");
+ if (isNothing(topInApps) || isEmpty(topInApps)) {
+ return null;
+ }
+ const textPairs = [];
+ const items_V3 = [];
+ // Create IAP items
+ for (const inAppData of topInApps) {
+ const name = mediaAttributes.attributeAsString(inAppData, "name");
+ if (isNothing(name)) {
+ continue;
+ }
+ const offer = offers.offerDataFromData(objectGraph, inAppData);
+ if (isNothing(offer)) {
+ continue;
+ }
+ const price = serverData.asString(offer, "priceFormatted");
+ if (isNothing(price)) {
+ continue;
+ }
+ const duration = serverData.asString(offer, "recurringSubscriptionPeriod");
+ const numberOfPeriods = serverData.asNumber(offer, "numOfPeriods");
+ let nameAndDuration = objectGraph.loc.string("InfoList.IAP.Duration");
+ if (objectGraph.client.isiOS && isSome(duration)) {
+ nameAndDuration = nameAndDuration.replace("{name}", name);
+ const subscriptionRecurrence = offerFormatting.subscriptionRecurrenceForServerRecurrence(objectGraph, duration, numberOfPeriods);
+ let timeUnit;
+ switch (subscriptionRecurrence === null || subscriptionRecurrence === void 0 ? void 0 : subscriptionRecurrence.type) {
+ case "D":
+ timeUnit = "days";
+ break;
+ case "W":
+ timeUnit = "weeks";
+ break;
+ case "M":
+ timeUnit = "months";
+ break;
+ case "Y":
+ timeUnit = "years";
+ break;
+ default:
+ nameAndDuration = `${name}`;
+ break;
+ }
+ if (isSome(timeUnit) && isSome(subscriptionRecurrence)) {
+ const formattedDuration = objectGraph.loc.formatDuration(subscriptionRecurrence.periodDuration, timeUnit);
+ if (isSome(formattedDuration)) {
+ nameAndDuration = nameAndDuration.replace("{duration}", formattedDuration);
+ }
+ }
+ }
+ else {
+ nameAndDuration = `${name}`;
+ }
+ textPairs.push([nameAndDuration, price]);
+ items_V3.push({
+ $kind: "textPair",
+ leadingText: nameAndDuration,
+ trailingText: price,
+ });
+ }
+ // Learn More link
+ const learnMoreTitle = objectGraph.loc.string("ProductPage.Info.InAppPurchases.LearnMore");
+ const learnMoreAction = makeLearnMoreAction(objectGraph, learnMoreTitle);
+ if (isSome(learnMoreAction)) {
+ if (objectGraph.client.isiOS || objectGraph.client.isMac || objectGraph.client.isVision) {
+ // The native support for the `small` spacer was introduced in 2025B. Older clients will just ignore this
+ // and show the standard size space, which is still preferable over no space at all.
+ items_V3.push({
+ $kind: "spacer",
+ size: "small",
+ });
+ // On iOS, macOS & visionOS, we want this to appear as a link.
+ const linkedSubstrings = {};
+ linkedSubstrings[learnMoreTitle] = learnMoreAction;
+ items_V3.push({
+ $kind: "linkableText",
+ linkableText: new models.LinkableText(new models.StyledText(learnMoreTitle), linkedSubstrings),
+ });
+ }
+ else if (objectGraph.client.isWatch || objectGraph.client.isWeb) {
+ // On watchOS & web, we want this to appear as a button.
+ items_V3.push({
+ $kind: "button",
+ action: learnMoreAction,
+ style: "infer",
+ });
+ }
+ // We intentionally leave out tvOS
+ }
+ const title = suppressTitle ? "" : objectGraph.loc.string("InfoList.InAppPurchases");
+ const items = [new models.AnnotationItem(undefined, { textPairs: textPairs })];
+ const annotation = new models.Annotation(title, items, objectGraph.loc.string("YES"));
+ annotation.items_V3 = items_V3;
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ annotation.leadingArtwork = createArtworkForResource(objectGraph, "resource://in.app.purchase");
+ }
+ return annotation;
+}
+function makeLearnMoreAction(objectGraph, title) {
+ const iapLearnMoreEditorialItemID = objectGraph.bag.inAppPurchasesLearnMoreEditorialItemId;
+ if (isNothing(iapLearnMoreEditorialItemID) || isEmpty(iapLearnMoreEditorialItemID)) {
+ return null;
+ }
+ const flowAction = new models.FlowAction("article");
+ flowAction.title = title;
+ flowAction.pageUrl = `https://apps.apple.com/story/id${objectGraph.bag.inAppPurchasesLearnMoreEditorialItemId}`;
+ if (objectGraph.client.isVision) {
+ const dismissAction = new models.FlowBackAction("sheetDismiss");
+ const compoundAction = new models.CompoundAction([dismissAction, flowAction]);
+ compoundAction.title = title;
+ return compoundAction;
+ }
+ else if (objectGraph.client.isWeb) {
+ const routableArticlePageIntent = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: iapLearnMoreEditorialItemID,
+ });
+ flowAction.pageUrl = makeRoutableArticlePageCanonicalUrl(objectGraph, routableArticlePageIntent);
+ flowAction.destination = routableArticlePageIntent;
+ return flowAction;
+ }
+ else {
+ return flowAction;
+ }
+}
+//# sourceMappingURL=top-in-app-purchases-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/version-annotation.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/version-annotation.js
new file mode 100644
index 0000000..60341d4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/annotations/version-annotation.js
@@ -0,0 +1,41 @@
+import * as models from "../../../../api/models/index";
+import * as serverData from "../../../../foundation/json-parsing/server-data";
+import * as contentAttributes from "../../../content/attributes";
+export function createAnnotation(objectGraph, data, isFirstPartyHideableApp, isArcadeApp, suppressTitle, shelfMetrics) {
+ const versions = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, data, "versionHistory");
+ if (versions.length === 0) {
+ return null;
+ }
+ const latestVersion = versions[0];
+ let releaseDate = null;
+ const releaseDateRaw = serverData.asString(latestVersion, "releaseDate");
+ if (releaseDateRaw) {
+ const releaseDateFormat = objectGraph.loc.string("ProductPage.VersionAnnotation.Value.DateFormat");
+ releaseDate = objectGraph.loc.formatDate(releaseDateFormat, new Date(releaseDateRaw));
+ }
+ const versionDisplay = serverData.asString(latestVersion, "versionDisplay");
+ if (versions.length === 0) {
+ return null;
+ }
+ let versionItemString;
+ if (versionDisplay && releaseDate) {
+ versionItemString = objectGraph.loc
+ .string("ProductPage.VersionAnnotation.Value")
+ .replace("@@version@@", versionDisplay)
+ .replace("@@date@@", releaseDate);
+ }
+ else if (versionDisplay) {
+ versionItemString = versionDisplay;
+ }
+ else {
+ return null;
+ }
+ const versionItem = new models.AnnotationItem(versionItemString);
+ const title = suppressTitle ? "" : objectGraph.loc.string("ProductPage.VersionAnnotation.Title");
+ const version = new models.Annotation(title, [versionItem]);
+ if (version && !isArcadeApp) {
+ return version;
+ }
+ return null;
+}
+//# sourceMappingURL=version-annotation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/bundle-children-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/bundle-children-shelf.js
new file mode 100644
index 0000000..0b87c87
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/bundle-children-shelf.js
@@ -0,0 +1,52 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as productPageCommon from "./../product-page-common";
+/**
+ * Create a shelf of bundle children for the product page.
+ *
+ * @param productData The raw product data response for a product page JSON fetch.
+ * @param dataContainer The raw data container response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @returns A shelf of bundle children.
+ */
+export function create(objectGraph, productData, dataContainer, shelfMetrics) {
+ if (!dataContainer) {
+ return null;
+ }
+ return validation.context("bundleChildrenShelf", () => {
+ const shelfContentType = "smallLockup";
+ const shelf = new models.Shelf(shelfContentType);
+ shelf.isHorizontal = true;
+ const numberOfChildren = dataContainer.data.length;
+ if (!numberOfChildren) {
+ return null;
+ }
+ shelf.title = objectGraph.loc.stringWithCount("ProductPage.AppsInBundleShelf.Title", numberOfChildren);
+ const containerItems = productPageCommon.lockupsFromDataContainer(objectGraph, dataContainer, shelfMetrics, 1 /* content.ArtworkUseCase.LockupIconSmall */, shelfContentType, null, 76286 /* filtering.Filter.Bundles */, null, objectGraph.featureFlags.isEnabled("voyager_bundles_2025A"));
+ if (containerItems.remainingItems.length) {
+ const shelfToken = new productPageCommon.ProductPageShelfToken(productData.id, containerItems.remainingItems, shelf.title, false, undefined, "smallLockup", "infer", null);
+ shelfToken.isBundleShelf = true;
+ shelf.url = productPageCommon.shelfContentUrl(objectGraph, shelfToken, shelfMetrics);
+ }
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "swoosh",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ }, shelf.title);
+ shelf.items = containerItems.items;
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "bundleChildren");
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ if (numberOfChildren < 2 || objectGraph.client.isTV) {
+ shelf.rowsPerColumn = 1;
+ }
+ else {
+ shelf.rowsPerColumn = 2;
+ }
+ return shelf;
+ });
+}
+//# sourceMappingURL=bundle-children-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/bundle-parents-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/bundle-parents-shelf.js
new file mode 100644
index 0000000..5ab9524
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/bundle-parents-shelf.js
@@ -0,0 +1,46 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import { isDefinedNonNullNonEmpty } from "../../../foundation/json-parsing/server-data";
+import * as productPageCommon from "../product-page-common";
+/**
+ * Create a shelf of bundle parents for the product page.
+ *
+ * @param productData The product data response for a product page JSON fetch.
+ * @param dataContainer The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @returns A shelf of bundle parents.
+ */
+export function create(objectGraph, productData, dataContainer, shelfMetrics) {
+ if (!dataContainer) {
+ return null;
+ }
+ return validation.context("bundleParentsShelf", () => {
+ const numberOfParents = dataContainer.data.length;
+ if (!numberOfParents) {
+ return null;
+ }
+ const contentType = "smallLockup";
+ const shelf = new models.Shelf(contentType);
+ shelf.isHorizontal = true;
+ shelf.title = objectGraph.loc.string("ProductPage.Section.IncludedInBundles.Title");
+ const containerItems = productPageCommon.lockupsFromDataContainer(objectGraph, dataContainer, shelfMetrics, 1 /* content.ArtworkUseCase.LockupIconSmall */, contentType);
+ if (containerItems.remainingItems.length === 0 && containerItems.items.length === 0) {
+ return null;
+ }
+ shelf.items = containerItems.items;
+ if (isDefinedNonNullNonEmpty(containerItems.remainingItems)) {
+ const shelfToken = new productPageCommon.ProductPageShelfToken(productData.id, containerItems.remainingItems, shelf.title, false, undefined, contentType, "infer", null);
+ shelfToken.isBundleShelf = true;
+ shelf.url = productPageCommon.shelfContentUrl(objectGraph, shelfToken, shelfMetrics);
+ }
+ if (numberOfParents < 2 || objectGraph.client.isTV) {
+ shelf.rowsPerColumn = 1;
+ }
+ else {
+ shelf.rowsPerColumn = 2;
+ }
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "bundleParent");
+ return shelf;
+ });
+}
+//# sourceMappingURL=bundle-parents-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/capabilities-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/capabilities-shelf.js
new file mode 100644
index 0000000..41a2f46
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/capabilities-shelf.js
@@ -0,0 +1,41 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as productCapabilities from "../../content/product-capabilities";
+import * as contentAttributes from "../../content/attributes";
+import * as mediaPlatformAttributes from "../../../foundation/media/platform-attributes";
+/**
+ * Create a shelf of capabilities for the product page.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @param isFreeProduct Whether the offer for the given product is free.
+ * @param shelfMetrics The product page shelf metrics.
+ * @returns A shelf of capabilities.
+ */
+export function create(objectGraph, data, isFreeProduct, shelfMetrics) {
+ return validation.context("capabilitiesShelf", () => {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
+ const bundleId = mediaPlatformAttributes.platformAttributeAsString(data, attributePlatform, "bundleId");
+ if (serverData.isNullOrEmpty(bundleId)) {
+ return null;
+ }
+ const capabilities = productCapabilities.productCapabilitiesFromData(objectGraph, data, isFreeProduct);
+ if (serverData.isNullOrEmpty(capabilities)) {
+ return null;
+ }
+ const shelf = new models.Shelf("productCapability");
+ shelf.title = objectGraph.loc.string("ProductPage.Section.Supports.Title");
+ shelf.items = capabilities;
+ shelf.mergeWhenFetched = true;
+ shelf.batchGroup = "gameCenter";
+ if (objectGraph.client.isVision && capabilities.length < 3) {
+ shelf.presentationHints = { isLowDensity: true };
+ }
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "supports");
+ return shelf;
+ });
+}
+//# sourceMappingURL=capabilities-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/description-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/description-shelf.js
new file mode 100644
index 0000000..90f9daa
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/description-shelf.js
@@ -0,0 +1,144 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { attributeAsString } from "@apple-media-services/media-api";
+import { hrefToRoutableUrl } from "../../builders/url-mapping";
+import { isNothing, isSome } from "@jet/environment";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { pageRouter } from "../../builders/routing";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as productPageVariants from "../product-page-variants";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+/**
+ * Create a shelf for the product page description.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @param shelfContext A collection of any other variables used when creating this shelf.
+ * @returns A description shelf.
+ */
+export function create(objectGraph, data, shelfMetrics, shelfContext) {
+ return validation.context("descriptionShelf", () => {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ const shelf = new models.Shelf("productDescription");
+ const links = linksFromData(objectGraph, data);
+ const tagRibbonItems = createTagsRibbonItems(objectGraph, data, shelfMetrics.metricsPageInformation, shelfMetrics.locationTracker);
+ const paragraph = paragraphFromData(objectGraph, data);
+ if (paragraph === null) {
+ return null;
+ }
+ const productDescription = new models.ProductDescription(paragraph, links, tagRibbonItems, shelfContext.developerAction);
+ shelf.items = [productDescription];
+ if (objectGraph.client.isWatch) {
+ shelf.title = objectGraph.loc.string("PRODUCT_DESCRIPTION");
+ }
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "description");
+ return shelf;
+ });
+}
+export function createTagsRibbonItems(objectGraph, data, metricsPageInformation, metricsLocationTracker) {
+ const tagsData = mediaRelationship.relationshipViewsCollection(data, "categorizations");
+ const ribbonItems = [];
+ for (const tagData of tagsData) {
+ const name = attributeAsString(tagData, "name");
+ if (isSome(name)) {
+ let pageUrl = null;
+ switch (tagData.type) {
+ case "genres":
+ pageUrl = attributeAsString(tagData, "url");
+ break;
+ case "tags":
+ const href = serverData.asString(tagData, "href");
+ if (href) {
+ pageUrl = hrefToRoutableUrl(objectGraph, href);
+ }
+ break;
+ default:
+ break;
+ }
+ if (isNothing(pageUrl)) {
+ continue;
+ }
+ const flowPage = objectGraph.required(pageRouter).fetchFlowPage(pageUrl);
+ const flowAction = new models.FlowAction(flowPage);
+ flowAction.pageUrl = pageUrl;
+ flowAction.title = name;
+ const ribbonItem = new models.RibbonBarItem(name, flowAction);
+ // Configure impressions
+ const metricsOptions = {
+ targetType: "facet",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ recoMetricsData: null,
+ };
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForTagRibbonItem(objectGraph, tagData, name, metricsOptions);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, ribbonItem, impressionOptions);
+ const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, data, metricsOptions);
+ metricsClickOptions.targetType = metricsOptions.targetType;
+ metricsHelpersClicks.addClickEventToAction(objectGraph, flowAction, metricsClickOptions);
+ ribbonItems.push(ribbonItem);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ }
+ }
+ return ribbonItems;
+}
+/**
+ * Create a description for the product.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @returns The raw description as a paragraph.
+ */
+export function paragraphFromData(objectGraph, data, productVariantData = null) {
+ return validation.context("descriptionFromData", () => {
+ if (serverData.isNull(productVariantData)) {
+ productVariantData = productPageVariants.productVariantDataForData(objectGraph, data); // create variant data if not provided by caller.
+ }
+ let promotionalText = content.promotionalTextFromData(objectGraph, data, productVariantData);
+ if (!promotionalText) {
+ promotionalText = "";
+ }
+ let mainDescription = contentAttributes.contentAttributeAsString(objectGraph, data, "description.standard");
+ if (!mainDescription) {
+ mainDescription = "";
+ }
+ // Join the promotional text and app description, if both are present.
+ let separator = "";
+ if (promotionalText.length > 0 && mainDescription.length > 0) {
+ separator = "\n\n";
+ }
+ const descriptionString = promotionalText + separator + mainDescription;
+ if (descriptionString.length > 0) {
+ const paragraph = new models.Paragraph(descriptionString);
+ paragraph.isCollapsed = true;
+ return paragraph;
+ }
+ return null;
+ });
+}
+function linksFromData(objectGraph, data) {
+ if (!data) {
+ return null;
+ }
+ return validation.context("productDescriptionLinksFromData", () => {
+ const links = [];
+ const developerWebsiteUrl = contentAttributes.contentAttributeAsString(objectGraph, data, "websiteUrl");
+ if (developerWebsiteUrl) {
+ const action = new models.ExternalUrlAction(developerWebsiteUrl, false);
+ const text = objectGraph.loc.string("PRODUCT_DEVELOPER_WEBSITE");
+ links.push(new models.ProductPageLink(text, action, "safari" /* models.ProductPageLinkImageName.developer */));
+ }
+ const developerSupportUrl = contentAttributes.contentAttributeAsString(objectGraph, data, "supportURLForLanguage");
+ if (developerSupportUrl) {
+ const action = new models.ExternalUrlAction(developerSupportUrl, false);
+ const text = objectGraph.loc.string("DEVELOPER_SUPPORT");
+ links.push(new models.ProductPageLink(text, action, "questionmark.circle" /* models.ProductPageLinkImageName.support */));
+ }
+ return links;
+ });
+}
+//# sourceMappingURL=description-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/editorial-quote-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/editorial-quote-shelf.js
new file mode 100644
index 0000000..0df96ed
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/editorial-quote-shelf.js
@@ -0,0 +1,36 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as color from "../../../foundation/util/color-util";
+import * as contentAttributes from "../../content/attributes";
+/**
+ * Create a shelf for the editorial quote on the product page.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @returns An editorial quote shelf.
+ */
+export function create(objectGraph, data, shelfMetrics) {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ const editorialNotes = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialNotes");
+ const standardNotes = serverData.asString(editorialNotes, "standard");
+ if (standardNotes) {
+ const attribution = objectGraph.loc.string("APP_STORE_EDITORS_ATTRIBUTION");
+ // Create the quote
+ const editorialQuote = new models.EditorialQuote(standardNotes, attribution);
+ // Create the shelf
+ const shelf = new models.Shelf("editorialQuote");
+ shelf.items = [editorialQuote];
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "editorialQuote");
+ if (objectGraph.client.isiOS) {
+ shelf.background = {
+ type: "color",
+ color: color.named("componentBackgroundStandout"),
+ };
+ }
+ return shelf;
+ }
+ return null;
+}
+//# sourceMappingURL=editorial-quote-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/editors-choice-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/editors-choice-shelf.js
new file mode 100644
index 0000000..5c0e16b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/editors-choice-shelf.js
@@ -0,0 +1,63 @@
+import * as validation from "@jet/environment/json/validation";
+import { isNothing } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as contentAttributes from "../../content/attributes";
+import { supportsVisionOSCompatibleIOSBinaryFromData } from "../../content/content";
+/**
+ * Create an `EditorsChoice` model from the data.
+ *
+ * @param objectGraph
+ * @param data
+ * @returns A model containing editors choice information.
+ */
+export function createEditorsChoiceModel(objectGraph, data) {
+ return validation.context("editorsChoiceItem", () => {
+ const supportsVisionOSCompatibleIOSBinary = supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
+ if (supportsVisionOSCompatibleIOSBinary) {
+ return null;
+ }
+ const editorialNotes = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialNotes");
+ const editorsNotes = serverData.asString(editorialNotes, "standard");
+ if (editorsNotes) {
+ const editorsChoice = new models.EditorsChoice(editorsNotes);
+ const editorialBadgeInfo = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialBadgeInfo");
+ if (editorialBadgeInfo) {
+ const badgeType = serverData.asString(editorialBadgeInfo, "editorialBadgeType");
+ editorsChoice.showsBadge = badgeType && badgeType === "editorialPriority";
+ }
+ if (!editorsChoice.showsBadge) {
+ editorsChoice.title = objectGraph.loc.string("APP_STORE_EDITORS_NOTES");
+ }
+ return editorsChoice;
+ }
+ return null;
+ });
+}
+/**
+ * Create an `editorsChoice` shelf using the data provided.
+ *
+ * @param objectGraph
+ * @param data
+ * @returns A shelf for editors choice.
+ */
+export function createEditorsChoiceShelf(objectGraph, data) {
+ const model = createEditorsChoiceModel(objectGraph, data);
+ if (isNothing(model)) {
+ return null;
+ }
+ const shelf = new models.Shelf("editorsChoice");
+ shelf.items = [model];
+ if (model.showsBadge) {
+ // Appears as editors choice, with the "Editors' Choice" badge title and background asset
+ shelf.background = {
+ type: "editorsChoice",
+ };
+ }
+ else {
+ // Appears as editors notes, which is less prominent than editors choice.
+ shelf.title = objectGraph.loc.string("ProductPage.Section.EditorsNotes.Title");
+ }
+ return shelf;
+}
+//# sourceMappingURL=editors-choice-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/featured-in-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/featured-in-shelf.js
new file mode 100644
index 0000000..b13fd67
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/featured-in-shelf.js
@@ -0,0 +1,53 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as todayHorizontalCardUtil from "../../today/today-horizontal-card-util";
+import * as productPageCommon from "../product-page-common";
+/**
+ * Create a shelf for the `featured in` product page shelf.
+ *
+ * @param productData The product data response for a product page JSON fetch.
+ * @param dataContainer The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @returns A shelf to display the featured in items.
+ */
+export function create(objectGraph, productData, dataContainer, shelfMetrics) {
+ function featuredInItemsFromData(innerObjectGraph, container, metricsOptions, contentUnavailable) {
+ return todayHorizontalCardUtil.featuredInTodayCardsFromData(innerObjectGraph, container.data, metricsOptions.pageInformation, metricsOptions.locationTracker, productPageCommon.includeTodayCardOnProductPage, contentUnavailable);
+ }
+ return validation.context("featuredInShelf", () => {
+ const data = mediaDataStructure.dataFromDataContainer(objectGraph, dataContainer);
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ const metricsOptions = {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "swoosh",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ };
+ const shelf = new models.Shelf("todayBrick");
+ const title = objectGraph.loc.string("ProductPage.Section.FeaturedIn.Title");
+ shelf.title = title;
+ shelf.isHorizontal = false;
+ const remainingItems = [];
+ const cardUnavailable = function (cardData) {
+ remainingItems.push(cardData);
+ return false;
+ };
+ shelf.items = [featuredInItemsFromData(objectGraph, dataContainer, metricsOptions, cardUnavailable)];
+ if (serverData.isDefinedNonNullNonEmpty(remainingItems)) {
+ const shelfToken = new productPageCommon.ProductPageShelfToken(productData.id, remainingItems, shelf.title, false, undefined, "todayBrick");
+ shelf.url = productPageCommon.shelfContentUrl(objectGraph, shelfToken, shelfMetrics);
+ }
+ metricsHelpersLocation.pushContentLocation(objectGraph, metricsOptions, title);
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "featuredIn");
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ return shelf;
+ });
+}
+//# sourceMappingURL=featured-in-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/friends-playing-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/friends-playing-shelf.js
new file mode 100644
index 0000000..61c47a5
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/friends-playing-shelf.js
@@ -0,0 +1,177 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as contentAttributes from "../../content/attributes";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { platformPrefersLargeTitles } from "../../room/room-common";
+import * as productPageCommon from "../product-page-common";
+import { makeGameCenterHeader, openGamesUIAction } from "../../arcade/arcade-common";
+/**
+ * Create a shelf of Game Center friends who play this game.
+ *
+ * @param productData The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @param pageInformation
+ * @returns A `Friends who played this game` shelf.
+ */
+export function create(objectGraph, productData, shelfMetrics, shelfContext) {
+ return validation.context("friendsPlayingShelf", () => {
+ if (serverData.isNullOrEmpty(productData)) {
+ return null;
+ }
+ const adamId = productData.id;
+ if (serverData.isNullOrEmpty(adamId)) {
+ return null;
+ }
+ if (objectGraph.client.isWeb) {
+ return null;
+ }
+ const isGameCenterEnabled = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, productData, "isGameCenterEnabled");
+ if (!isGameCenterEnabled) {
+ return null;
+ }
+ // rdar://70124826 (N104/18C23a: friends shown as playing a game when it\u2019s not available yet)
+ if (shelfContext.isPreorder) {
+ return null;
+ }
+ const title = objectGraph.loc.string("ProductPage.Section.FriendsPlaying.Title", "Playing This Game");
+ const metricsOptions = {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "swoosh",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ };
+ metricsHelpersLocation.pushContentLocation(objectGraph, metricsOptions, title);
+ const shelf = new models.Shelf("largeGameCenterPlayer");
+ shelf.header = makeGameCenterHeader(objectGraph, title);
+ shelf.isHidden = true;
+ shelf.isHorizontal = true;
+ // This shelf is always empty initially; it is populated natively and then updated in JS using the function `createShelfWithFriends`.
+ shelf.items = [];
+ shelf.mergeWhenFetched = true;
+ const token = new productPageCommon.ProductPageShelfToken(productData.id, [], shelf.title, false, undefined, null, null, null);
+ shelf.url = `${Protocol.internal}:/${Path.product}/${Path.shelf}/?${Parameters.isGameCenterPlayerShelf}=true&${Parameters.id}=${adamId}&${Parameters.token}=${productPageCommon.encodedShelfToken(token, shelfMetrics, shelfMetrics.metricsPageInformation)}`;
+ shelf.batchGroup = "gameCenter";
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "friendsPlaying");
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ const seeAllAction = new models.FlowAction("page");
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, null, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ });
+ return shelf;
+ });
+}
+export function createShelfWithFriends(objectGraph, gameCenterPlayers, overflowToSeeAll, token) {
+ const maxNumberOfPlayersBeforeSeeAll = objectGraph.client.isiOS ? 8 : 20;
+ const friendPrefix = "FRIEND";
+ const shelf = new models.Shelf("largeGameCenterPlayer");
+ shelf.isHorizontal = true;
+ let visiblePlayers = null;
+ if (overflowToSeeAll) {
+ visiblePlayers = gameCenterPlayers.slice(0, maxNumberOfPlayersBeforeSeeAll);
+ }
+ else {
+ visiblePlayers = gameCenterPlayers;
+ }
+ const shelfItems = [];
+ for (let index = 0; index < visiblePlayers.length; index++) {
+ const player = visiblePlayers[index];
+ player.action = objectGraph.client.isTV
+ ? new models.GameCenterPlayerProfileAction(player.playerId)
+ : new models.OpenGamesUIAction({ player: { playerID: player.playerId } });
+ if (serverData.isDefinedNonNullNonEmpty(token)) {
+ // Content metrics
+ const contentKind = "friendPlayer";
+ const contentMetricsOptions = {
+ pageInformation: token.destinationPageInformation,
+ locationTracker: token.sourceLocationTracker,
+ id: "",
+ idType: "sequential",
+ anonymizationOptions: {
+ anonymizationString: `${friendPrefix}${index + 1}`,
+ },
+ };
+ // Impression metrics
+ const impressionMetricsOptions = {
+ ...contentMetricsOptions,
+ kind: contentKind,
+ title: "",
+ softwareType: null,
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, player, impressionMetricsOptions);
+ if (objectGraph.bag.productPageFriendsPlayingClickEventsEnabled) {
+ // Click metrics
+ const clickOptions = {
+ ...contentMetricsOptions,
+ kind: contentKind,
+ targetType: "lockup",
+ actionType: "navigate",
+ softwareType: null,
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, player.action, clickOptions);
+ }
+ }
+ shelfItems.push(player);
+ if (serverData.isDefinedNonNullNonEmpty(token)) {
+ metricsHelpersLocation.nextPosition(token.sourceLocationTracker);
+ }
+ }
+ shelf.items = shelfItems;
+ shelf.mergeWhenFetched = true;
+ shelf.isHidden = shelf.items.length === 0;
+ shelf.batchGroup = "gameCenter";
+ if (overflowToSeeAll && gameCenterPlayers.length > maxNumberOfPlayersBeforeSeeAll) {
+ // The shelf for the see all page
+ const shelfForAllPlayers = new models.Shelf("smallGameCenterPlayer");
+ shelfForAllPlayers.title = objectGraph.loc.stringWithCount("ProductPage.FriendsPlaying.SeeAllTitle", gameCenterPlayers.length);
+ shelfForAllPlayers.items = gameCenterPlayers.map((player) => {
+ player.action = new models.GameCenterPlayerProfileAction(player.playerId);
+ return player;
+ });
+ shelfForAllPlayers.rowsPerColumn = 1;
+ // See all page
+ const seeAllPage = new models.GenericPage([shelfForAllPlayers]);
+ seeAllPage.title = token === null || token === void 0 ? void 0 : token.title;
+ if (platformPrefersLargeTitles(objectGraph)) {
+ seeAllPage.presentationOptions = ["prefersLargeTitle"];
+ }
+ // The action that opens the see all page
+ const seeAllAction = new models.FlowAction("page");
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ seeAllAction.pageData = seeAllPage;
+ // Connect the shelf's seeAllAction
+ shelf.seeAllAction = seeAllAction;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(token)) {
+ const shelfMetricsOptions = {
+ id: "",
+ kind: null,
+ softwareType: null,
+ targetType: "swoosh",
+ title: token.title,
+ pageInformation: token.sourcePageInformation,
+ locationTracker: token.sourceLocationTracker,
+ idType: "sequential",
+ badges: {
+ gameCenter: true,
+ },
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ }
+ shelf.footerTitle = objectGraph.loc.string("Lockup.Footer.GamesApp");
+ shelf.footerAction = openGamesUIAction(objectGraph, { friends: {} });
+ shelf.footerStyle = {
+ $kind: "games",
+ bundleID: "com.apple.games",
+ width: 16,
+ height: 16,
+ };
+ return shelf;
+}
+//# sourceMappingURL=friends-playing-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/in-app-purchases-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/in-app-purchases-shelf.js
new file mode 100644
index 0000000..a49c349
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/in-app-purchases-shelf.js
@@ -0,0 +1,198 @@
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { isNothing } from "@jet/environment";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { shallowCopyOf } from "../../../foundation/util/objects";
+import { tryAwait } from "../../../foundation/util/promise-util";
+import { withAsyncValidationContext } from "../../../foundation/util/validation-util";
+import * as appPromotionsCommon from "../../app-promotions/app-promotions-common";
+import * as contentAttributes from "../../content/attributes";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as productPageCommon from "../product-page-common";
+import { ProductPageSectionMapping } from "../shelf-based/product-page-section-mapping";
+/**
+ * Create a shelf for the in-app purchase items for this product page.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @param shelfContext A collection of any other variables used when creating this shelf.
+ * @returns An in-app purchase shelf.
+ */
+export async function create(objectGraph, productData, shelfMetrics, shelfContext) {
+ return await withAsyncValidationContext("inAppPurchasesShelf", async () => {
+ var _a;
+ if (!shouldShowIAPShelf(objectGraph, objectGraph.host.platform)) {
+ return null;
+ }
+ if (serverData.isNullOrEmpty(productData)) {
+ return null;
+ }
+ let inAppsContainer;
+ if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
+ inAppsContainer = mediaRelationship.relationshipViewsContainer(productData, "standalone-merchandised-in-apps");
+ }
+ else {
+ inAppsContainer = mediaRelationship.relationship(productData, "merchandised-in-apps");
+ }
+ if (isNothing(inAppsContainer) || inAppsContainer.data.length === 0) {
+ return null;
+ }
+ // Consider only the In-App Purchase lockups that are appropriate for this type (i.e. subscription or not).
+ const merchandisedInApps = inAppsContainer.data.filter(function (inAppData) {
+ const isDataForSubscription = mediaAttributes.attributeAsBooleanOrFalse(inAppData, "isSubscription");
+ return isDataForSubscription === shelfContext.isForSubscriptions;
+ });
+ if (merchandisedInApps.length === 0) {
+ return null;
+ }
+ const metricsOptions = {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "swoosh",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ };
+ const contentType = "inAppPurchaseLockup";
+ const shelf = new models.Shelf(contentType);
+ // Disable iAP offer button for preorders
+ const isPreorder = mediaAttributes.attributeAsBoolean(productData, "isPreorder");
+ const offerStyle = isPreorder ? "disabled" : "infer";
+ const appBundleId = contentAttributes.contentAttributeAsString(objectGraph, productData, "bundleId");
+ const spotlightId = (_a = shelfContext.options) === null || _a === void 0 ? void 0 : _a.spotlightInAppProductIdentifier;
+ injectParentIntoInAppsContainer(objectGraph, productData, inAppsContainer);
+ const remainingItems = [];
+ const contentUnavailable = function (index, dataItem) {
+ remainingItems.push(dataItem);
+ return false;
+ };
+ const iapLockups = inAppPurchaseLockupsFromData(objectGraph, inAppsContainer.data, offerStyle, metricsOptions, spotlightId, contentUnavailable).filter(function (item) {
+ if (item.productIdentifier === spotlightId) {
+ shelfContext.options.spotlightSection = new ProductPageSectionMapping("shelf", shelfContext.isForSubscriptions ? "subscriptions" : "inAppPurchases");
+ }
+ return item.isSubscription === shelfContext.isForSubscriptions;
+ });
+ if (iapLockups.length === 0 && remainingItems.length === 0) {
+ return null;
+ }
+ shelf.items = await applyClientOrderingToIAPShelfItems(objectGraph, iapLockups, appBundleId, spotlightId);
+ if (serverData.isDefinedNonNullNonEmpty(remainingItems)) {
+ const shelfToken = new productPageCommon.ProductPageShelfToken(productData.id, remainingItems, shelf.title, false, undefined, contentType, offerStyle, null);
+ shelfToken.appBundleId = appBundleId;
+ shelfToken.iapShelfContext = shelfContext;
+ if (shelfContext.isForSubscriptions) {
+ shelfToken.inAppShelfId = "subscriptions";
+ }
+ else {
+ shelfToken.inAppShelfId = "inAppPurchases";
+ }
+ shelf.url = productPageCommon.shelfContentUrl(objectGraph, shelfToken, shelfMetrics);
+ }
+ let targetType;
+ if (shelfContext.isForSubscriptions) {
+ shelf.title = objectGraph.loc.string("ProductPage.Section.Subscriptions.Title", "Subscriptions");
+ targetType = "subscription";
+ }
+ else {
+ shelf.title = objectGraph.loc.string("ProductPage.Section.InAppPurchases.Title");
+ targetType = "iAP";
+ }
+ metricsHelpersLocation.pushContentLocation(objectGraph, metricsOptions, shelf.title);
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ shelf.isHorizontal = true;
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, targetType);
+ return shelf;
+ });
+}
+/**
+ * In-App Purchases have a relationship back to the parent app. However, this specific relationship is not included
+ * when the In-App Purchase itself is a relationship on the parent app resource. Therefore, we need to do some work to
+ * inject the parent app into each IAP on the IAP relationship. This is the path of least resistance, as it will allow
+ * us to keep our core lockup builder lean and consistent across all call sites (after all, on everywhere else except
+ * the product page, the parent app will be included in the relationship).
+ * @param {Data} productData The data for the parent ap.
+ * @param {DataContainer} inAppsContainer The container for the In-App Purchases relationship.
+ */
+function injectParentIntoInAppsContainer(objectGraph, productData, inAppsContainer) {
+ const parentAppRelationshipType = "app";
+ for (const data of inAppsContainer.data) {
+ let existingParentRelationship = mediaRelationship.relationship(data, parentAppRelationshipType);
+ if (!existingParentRelationship) {
+ existingParentRelationship = {
+ data: [productData],
+ };
+ }
+ if (serverData.isNull(data.relationships)) {
+ data.relationships = {};
+ }
+ data.relationships[parentAppRelationshipType] = existingParentRelationship;
+ }
+}
+function inAppPurchaseLockupsFromData(objectGraph, inAppsData, offerStyle, metricsOptions, spotlightId, contentUnavailable) {
+ const lockupOptions = {
+ metricsOptions: metricsOptions,
+ offerStyle: offerStyle,
+ skipDefaultClickAction: true,
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ };
+ const options = {
+ lockupOptions: lockupOptions,
+ contentUnavailable: contentUnavailable,
+ };
+ const lockupModels = lockups.lockupsFromData(objectGraph, inAppsData, options);
+ if (spotlightId) {
+ productPageCommon.moveLockupToFront(objectGraph, spotlightId, lockupModels);
+ }
+ return lockupModels;
+}
+/**
+ * Whether or not IAP shelf should be shown
+ */
+function shouldShowIAPShelf(objectGraph, platform) {
+ if (platform === "macOS" || objectGraph.client.isCompanionVisionApp) {
+ return false;
+ }
+ return true;
+}
+async function applyClientOrderingToIAPShelfItems(objectGraph, currentItems, appBundleId, spotlightInAppProductIdentifier) {
+ if (isNothing(appBundleId)) {
+ return currentItems;
+ }
+ const allInAppIds = [];
+ const defaultVisibleInAppIds = [];
+ for (const inAppLockup of currentItems) {
+ const productIdentifier = inAppLockup.productIdentifier;
+ // We must ensure this item is appropriate for the shelf type.
+ allInAppIds.push(productIdentifier);
+ if (inAppLockup.isVisibleByDefault) {
+ defaultVisibleInAppIds.push(productIdentifier);
+ }
+ }
+ // FIXME: <rdar://99419136> ESLint rule disabled temporarily but code should be fixed. If `orderedVisibleIAPs()` is rejected, nothing will happen.
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ const finalOrderingResult = await tryAwait(objectGraph.clientOrdering.orderedVisibleIAPs(appBundleId, allInAppIds, defaultVisibleInAppIds, spotlightInAppProductIdentifier));
+ if (!finalOrderingResult.success) {
+ return currentItems;
+ }
+ return filteredInAppLockupsUsingOrdering(currentItems, finalOrderingResult.value);
+}
+function filteredInAppLockupsUsingOrdering(iAPLockups, iAPOrdering) {
+ // Use a map to avoid nested iteration.
+ const identifiersToLockups = {};
+ for (const lockup of iAPLockups) {
+ identifiersToLockups[lockup.productIdentifier] = lockup;
+ }
+ // Filter by those present in the ordering.
+ const filteredOrderedLockups = [];
+ for (const productIdentifier of iAPOrdering) {
+ const foundLockup = identifiersToLockups[productIdentifier];
+ if (foundLockup) {
+ const copy = shallowCopyOf(foundLockup);
+ filteredOrderedLockups.push(copy);
+ }
+ }
+ return filteredOrderedLockups;
+}
+//# sourceMappingURL=in-app-purchases-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/links-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/links-shelf.js
new file mode 100644
index 0000000..0b2d907
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/links-shelf.js
@@ -0,0 +1,269 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import { Path, Protocol } from "../../../foundation/network/url-constants";
+import * as contentAttributes from "../../content/attributes";
+import * as sad from "../../content/sad";
+import * as productPageUtil from "../product-page-util";
+import * as informationShelf from "./annotations/annotations";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import { isSome } from "@jet/environment/types/optional";
+import { makeEulaPageIntent } from "../../../api/intents/eula-page-intent";
+/**
+ * Create a links shelf for the product page.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @param isPurchased Whether the links are for a user who has purchased the product.
+ * @param isFreeProduct Whether the offer is for a free product.
+ * @param isArcadeProduct Whether the product is an Arcade product.
+ * @returns A links shelf.
+ */
+export function create(objectGraph, data, shelfMetrics, isPurchased, isFreeProduct, isArcadeProduct) {
+ return validation.context("create", () => {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ if (objectGraph.client.isTV) {
+ return tvLinksShelf(objectGraph, data, isFreeProduct, shelfMetrics);
+ }
+ return standardLinksShelf(objectGraph, data, shelfMetrics, isPurchased, isArcadeProduct);
+ });
+}
+/**
+ * Determines the URL to use for the EULA page.
+ * @param {Data} data The data for the product.
+ * @returns {string} The string form of the URL for the EULA page, or `null` if the data is undefined or
+ * null.
+ */
+function eulaUrlFromData(objectGraph, data) {
+ const resourceId = data.id;
+ const resourceType = data.type;
+ if (!(serverData.isDefinedNonNull(data) &&
+ serverData.isDefinedNonNull(resourceId) &&
+ serverData.isDefinedNonNull(resourceType))) {
+ return null;
+ }
+ return `${Protocol.internal}:/${Path.eula}?resourceType=${resourceType}&resourceId=${resourceId}`;
+}
+export function privacyPolicyUrlFromData(objectGraph, data) {
+ const resourceId = data.id;
+ if (!(serverData.isDefinedNonNull(data) && serverData.isDefinedNonNull(resourceId))) {
+ return null;
+ }
+ return `${Protocol.internal}:/${Path.privacyPolicy}?resourceId=${resourceId}`;
+}
+function tvEulaUrlFromData(objectGraph, data) {
+ const resourceId = data.id;
+ const resourceType = data.type;
+ if (!(serverData.isDefinedNonNull(data) &&
+ serverData.isDefinedNonNull(resourceId) &&
+ serverData.isDefinedNonNull(resourceType))) {
+ return null;
+ }
+ return `${Protocol.internal}:/${Path.tvEula}?resourceType=${resourceType}&resourceId=${resourceId}`;
+}
+function safetyComplianceUrlForContent(objectGraph, pageContent) {
+ if (serverData.isNullOrEmpty(pageContent)) {
+ return null;
+ }
+ return `${Protocol.internal}:/${Path.safetyCompliance}?pageContent=${encodeURIComponent(pageContent)}`;
+}
+function reportAProblemUrlFromData(objectGraph, data) {
+ const resourceId = data.id;
+ const reportAProblemURL = objectGraph.bag.productPageReportProblemURL;
+ if (!(serverData.isDefinedNonNull(resourceId) && serverData.isDefinedNonNull(reportAProblemURL))) {
+ return null;
+ }
+ return reportAProblemURL.replace("{productId}", resourceId);
+}
+/**
+ * The links shelf for TV deviceTypes.
+ * @param data The product data to use
+ * @param isFreeProduct Whether the offer is for a free product.
+ * @param shelfMetrics The current product page shelf metrics
+ * @returns {models.Shelf} The shelf of links.
+ */
+function tvLinksShelf(objectGraph, data, isFreeProduct, shelfMetrics) {
+ return validation.context("tvLinksShelf", () => {
+ const shelf = new models.Shelf("titledButtonStack");
+ const links = [];
+ const moreInformationShelf = informationShelf.create(objectGraph, data, false, shelfMetrics, null, false, isFreeProduct, true);
+ moreInformationShelf.presentationHints = { isSeeAllContext: true };
+ const moreInformationPage = new models.GenericPage([moreInformationShelf]);
+ moreInformationPage.title = mediaAttributes.attributeAsString(data, "name");
+ if (objectGraph.client.isTV) {
+ moreInformationPage.presentationOptions.push("prefersIndirectTouch");
+ }
+ const moreInformationAction = new models.FlowAction("page");
+ moreInformationAction.presentationContext = "presentModalBlur";
+ moreInformationAction.pageData = moreInformationPage;
+ const moreInformationButton = new models.TitledButton(objectGraph.loc.string("MORE_INFORMATION"), moreInformationAction);
+ links.push(moreInformationButton);
+ const hasPrivacyPolicy = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "hasPrivacyPolicyText");
+ if (hasPrivacyPolicy) {
+ const url = privacyPolicyUrlFromData(objectGraph, data);
+ if (url) {
+ const text = objectGraph.loc.string("PRIVACY_POLICY");
+ const action = new models.FlowAction("unknown");
+ action.pageUrl = url;
+ const button = new models.TitledButton(text, action);
+ links.push(button);
+ }
+ }
+ const hasLicenseAgreement = mediaAttributes.attributeAsBoolean(data, "hasEula");
+ if (hasLicenseAgreement) {
+ const url = tvEulaUrlFromData(objectGraph, data);
+ if (url) {
+ const text = objectGraph.loc.string("LICENSE_AGREEMENT");
+ const action = new models.FlowAction("unknown");
+ action.pageUrl = url;
+ const button = new models.TitledButton(text, action);
+ links.push(button);
+ }
+ }
+ const traderData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "sellerInfo");
+ const safetyAndComplianceURL = serverData.asString(traderData, "safetyAndComplianceUrl");
+ if ((safetyAndComplianceURL === null || safetyAndComplianceURL === void 0 ? void 0 : safetyAndComplianceURL.length) > 0) {
+ const url = safetyComplianceUrlForContent(objectGraph, safetyAndComplianceURL);
+ if (isSome(url)) {
+ const text = objectGraph.loc.string("ProductPage.Section.SafetyAndCompliance.Title");
+ const action = new models.FlowAction("unknown");
+ action.pageUrl = url;
+ const button = new models.TitledButton(text, action);
+ links.push(button);
+ }
+ }
+ const stack = new models.TitledButtonStack(links);
+ // On compact width, we want to place a line break after each button.
+ stack.compactLineBreaks = stack.buttons.map((value, index) => index);
+ shelf.items = [stack];
+ if (productPageUtil.isShelfBased(objectGraph)) {
+ shelf.background = {
+ type: "darkOverlay",
+ };
+ }
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "links");
+ return shelf;
+ });
+}
+/**
+ * The links shelf for all deviceTypes except TV.
+ * @param data The product data to use
+ * @param shelfMetrics The current product page shelf metrics
+ * @param isPurchased Whether the links are to be displayed on a purchased product page
+ * @param isArcadeProduct Whether the product is an Arcade product
+ * @returns {models.Shelf} The shelf of links.
+ */
+function standardLinksShelf(objectGraph, data, shelfMetrics, isPurchased, isArcadeProduct) {
+ return validation.context("standardLinksShelf", () => {
+ const shelf = new models.Shelf("productPageLink");
+ const { metricsPageInformation, locationTracker } = shelfMetrics;
+ const links = [];
+ const isWeb = objectGraph.client.isWeb;
+ if (objectGraph.client.deviceType !== "mac") {
+ const developerWebsiteUrl = contentAttributes.contentAttributeAsString(objectGraph, data, "websiteUrl");
+ if (developerWebsiteUrl) {
+ const action = new models.ExternalUrlAction(developerWebsiteUrl, false);
+ const text = objectGraph.loc.string("DEVELOPER_WEBSITE");
+ if (isWeb) {
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, {
+ id: "DeveloperWebsite",
+ actionDetails: {
+ type: "developer",
+ },
+ actionType: "navigate",
+ locationTracker,
+ pageInformation: metricsPageInformation,
+ }, false, "link");
+ }
+ links.push(new models.ProductPageLink(text, action, "safari" /* models.ProductPageLinkImageName.developer */));
+ }
+ }
+ const privacyPolicyUrl = contentAttributes.contentAttributeAsString(objectGraph, data, "privacyPolicyUrl");
+ if (privacyPolicyUrl) {
+ const action = new models.ExternalUrlAction(privacyPolicyUrl, false);
+ if (isWeb) {
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, {
+ id: "LinkToPrivacyPolicy",
+ actionType: "navigate",
+ locationTracker,
+ pageInformation: metricsPageInformation,
+ }, false, "link");
+ }
+ const text = objectGraph.loc.string("PRIVACY_POLICY");
+ links.push(new models.ProductPageLink(text, action, "hand.raised.fill" /* models.ProductPageLinkImageName.privacy */));
+ }
+ const hasLicenseAgreement = mediaAttributes.attributeAsBoolean(data, "hasEula");
+ if (hasLicenseAgreement) {
+ const url = eulaUrlFromData(objectGraph, data);
+ if (url) {
+ const flowPage = isWeb ? "licenseAgreement" : "page";
+ const action = new models.FlowAction(flowPage);
+ action.pageUrl = url;
+ if (isWeb) {
+ const resourceId = data.id;
+ const resourceType = data.type;
+ // web presents this data in a modal
+ action.presentationContext = "presentModal";
+ action.destination = makeEulaPageIntent({
+ resourceId,
+ resourceType: resourceType,
+ });
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, {
+ id: "LicenseAgreement",
+ actionType: "navigate",
+ locationTracker,
+ pageInformation: metricsPageInformation,
+ }, false, "link");
+ }
+ const text = objectGraph.loc.string("LICENSE_AGREEMENT");
+ links.push(new models.ProductPageLink(text, action, "doc.plaintext" /* models.ProductPageLinkImageName.eula */));
+ }
+ }
+ const traderData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "sellerInfo");
+ const safetyAndComplianceURL = serverData.asString(traderData, "safetyAndComplianceUrl");
+ if ((safetyAndComplianceURL === null || safetyAndComplianceURL === void 0 ? void 0 : safetyAndComplianceURL.length) > 0) {
+ const action = new models.ExternalUrlAction(safetyAndComplianceURL, false);
+ const text = objectGraph.loc.string("ProductPage.Section.SafetyAndCompliance.Title");
+ links.push(new models.ProductPageLink(text, action, "checkmark.seal" /* models.ProductPageLinkImageName.safetyAndCompliance */));
+ }
+ // We want to show the report-a-problem link for apps that are purchased and only SAD apps that are specified by the bag.
+ // Because isPurchased contains both SAD and non SAD apps, we need a few additional checks.
+ const clientSupportsReportAProblem = objectGraph.client.isMac || objectGraph.client.isiOS || objectGraph.client.isVision;
+ const reportProblemSADSubscriptionArray = objectGraph.bag.productPageReportProblemSADSubscriptionArray;
+ const adamID = data.id;
+ const isDeletableSystemAppWithSub = reportProblemSADSubscriptionArray.includes(adamID);
+ const isSystemApp = sad.systemApps(objectGraph).isSystemAppFromData(data);
+ // Check if the adamID is a bundle id.
+ const isBundle = data.type === "app-bundles";
+ // We only want purchased apps that are not system apps
+ const isPurchasedAppNotSAD = isPurchased && !isSystemApp;
+ // We do not support report a problem for second party apps
+ const isSecondPartyApp = objectGraph.bag.productPageReportProblemSecondPartyAppArray.includes(adamID);
+ const shouldShowRAPLinkInShelf = !isBundle &&
+ !isSecondPartyApp &&
+ !isArcadeProduct &&
+ clientSupportsReportAProblem &&
+ objectGraph.bag.reportProblemEnabled &&
+ (isDeletableSystemAppWithSub || isPurchasedAppNotSAD);
+ if (shouldShowRAPLinkInShelf) {
+ const reportAProblemUrl = reportAProblemUrlFromData(objectGraph, data);
+ if ((reportAProblemUrl === null || reportAProblemUrl === void 0 ? void 0 : reportAProblemUrl.length) > 0) {
+ const action = new models.ExternalUrlAction(reportAProblemUrl, false);
+ const text = objectGraph.loc.string("REPORT_A_PROBLEM");
+ // We never want to filter out SAD apps based on purchase history
+ const adamIdForPurchaseHistoryFilter = isDeletableSystemAppWithSub ? null : adamID;
+ const link = new models.ProductPageLink(text, action, "exclamationmark.triangle" /* models.ProductPageLinkImageName.reportAProblem */, adamIdForPurchaseHistoryFilter);
+ links.push(link);
+ }
+ }
+ if (links && links.length > 0) {
+ shelf.items = links;
+ }
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "links");
+ return shelf;
+ });
+}
+//# sourceMappingURL=links-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/more-by-developer-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/more-by-developer-shelf.js
new file mode 100644
index 0000000..2788fb6
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/more-by-developer-shelf.js
@@ -0,0 +1,192 @@
+import { isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as content from "../../content/content";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { insertPlaceholdersIntoProductPageShelfIfRequired } from "../../placeholders/placeholders";
+import * as productPageCommon from "../product-page-common";
+import { ProductPageOnDemandShelfType } from "../product-page-common";
+import { ProductPageShelfMetrics } from "../product-page-shelf-metrics";
+import { getLocale } from "../../locale";
+import { makeDeveloperPageIntent } from "../../../api/intents/developer-page-intent";
+/**
+ * Create a shelf of developers other apps for the product page.
+ *
+ * @param productData The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @returns A `More by developer` shelf.
+ */
+export function createInitialShelf(objectGraph, productData, shelfMetrics) {
+ return validation.context("moreByDeveloperShelf", () => {
+ if (serverData.isNullOrEmpty(productData)) {
+ return null;
+ }
+ const developer = mediaRelationship.relationshipData(objectGraph, productData, "developer");
+ const developerUrl = content.developerUrlFromDeveloperData(objectGraph, developer);
+ const developerName = mediaAttributes.attributeAsString(productData, "artistName");
+ let otherAppsRelationship;
+ let otherApps;
+ if (preprocessor.GAMES_TARGET) {
+ otherApps = mediaRelationship.relationshipViewsCollection(productData, "developer-other-games");
+ if (otherApps.length === 0) {
+ return null;
+ }
+ }
+ else {
+ otherAppsRelationship = mediaRelationship.relationship(productData, "developer-other-apps");
+ if (!otherAppsRelationship || otherAppsRelationship.data.length === 0) {
+ return null;
+ }
+ otherApps = otherAppsRelationship.data;
+ }
+ const title = objectGraph.loc
+ .string("ProductPage.Section.MoreByDeveloper.TitleTemplate")
+ .replace("{developer}", developerName);
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "swoosh",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ }, title);
+ let contentType;
+ switch (objectGraph.client.deviceType) {
+ case "mac":
+ case "tv":
+ contentType = "mediumLockup";
+ break;
+ default:
+ contentType = "smallLockup";
+ break;
+ }
+ const shelf = new models.Shelf(contentType);
+ shelf.title = title;
+ const hasShelfBackground = !objectGraph.client.isTV && !objectGraph.client.isiOS;
+ const offerStyle = hasShelfBackground ? "white" : null;
+ const filter = objectGraph.client.isCompanionVisionApp ? 32768 /* Filter.VisionOSCompatibility */ : undefined;
+ let shelfItems;
+ if (isSome(otherAppsRelationship)) {
+ shelfItems = productPageCommon.lockupsFromDataContainer(objectGraph, otherAppsRelationship, shelfMetrics, content.artworkUseCaseFromShelfStyle(objectGraph, contentType), contentType, offerStyle, filter);
+ }
+ else if (isSome(otherApps)) {
+ shelfItems = productPageCommon.lockupsFromData(objectGraph, otherApps, shelfMetrics, content.artworkUseCaseFromShelfStyle(objectGraph, contentType), contentType, offerStyle, filter);
+ }
+ if (!shelfItems) {
+ return null;
+ }
+ shelf.items = shelfItems.items;
+ shelf.isHorizontal = true;
+ if (otherApps.length < 2 || objectGraph.client.isTV) {
+ shelf.rowsPerColumn = 1;
+ }
+ else {
+ shelf.rowsPerColumn = 2;
+ }
+ if (!objectGraph.client.isTV) {
+ const developerSeeAll = new models.FlowAction("page");
+ developerSeeAll.pageUrl = developerUrl;
+ developerSeeAll.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, developerSeeAll, developerSeeAll.pageUrl, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ });
+ if (objectGraph.client.isWeb) {
+ developerSeeAll.destination = makeDeveloperPageIntent({
+ ...getLocale(objectGraph),
+ id: developer.id,
+ });
+ }
+ shelf.seeAllAction = developerSeeAll;
+ }
+ if (hasShelfBackground) {
+ shelf.background = {
+ type: "color",
+ color: productPageCommon.grayShelfBackgroundColor,
+ };
+ }
+ if (objectGraph.client.isiOS && objectGraph.bag.isOnDemandShelfFetchingEnabled) {
+ shelf.fetchStrategy = models.IncompleteShelfFetchStrategy.OnShelfWillAppear;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(shelfItems.remainingItems)) {
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "developer");
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ const shelfToken = new productPageCommon.ProductPageShelfToken(productData.id, shelfItems.remainingItems, shelf.title, false, undefined, contentType, offerStyle, null, null, null, null, ProductPageOnDemandShelfType.MoreByDeveloper);
+ shelfToken.developerUrl = developerUrl;
+ insertPlaceholdersIntoProductPageShelfIfRequired(objectGraph, shelf, shelfToken);
+ shelf.url = productPageCommon.shelfContentUrl(objectGraph, shelfToken, shelfMetrics);
+ }
+ else {
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "developer");
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ }
+ return shelf;
+ });
+}
+/**
+ * Create a shelf of more by developer items for the secondary look up.
+ *
+ * @param dataItems The raw data items from the secondary fetch.
+ * @param shelfToken The shelf token for this shelf.
+ * @returns A more by developer items shelf.
+ */
+export function createSecondaryShelf(objectGraph, dataItems, shelfToken) {
+ return validation.context("moreByDeveloperSecondaryShelf", () => {
+ if (serverData.isNullOrEmpty(dataItems)) {
+ return null;
+ }
+ const shelfMetrics = new ProductPageShelfMetrics(shelfToken.sourcePageInformation, shelfToken.sourceLocationTracker, shelfToken.sourceSequenceId);
+ const metricsOptions = {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "swoosh",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ excludeAttribution: true,
+ recoMetricsData: shelfToken.recoMetricsData,
+ };
+ metricsHelpersLocation.pushContentLocation(objectGraph, metricsOptions, shelfToken.title);
+ const shelf = new models.Shelf(shelfToken.contentType);
+ shelf.title = shelfToken.title;
+ const hasShelfBackground = !objectGraph.client.isTV && !objectGraph.client.isiOS;
+ const filter = objectGraph.client.isCompanionVisionApp ? 32768 /* Filter.VisionOSCompatibility */ : undefined;
+ const shelfItems = productPageCommon.lockupsFromData(objectGraph, dataItems, shelfMetrics, content.artworkUseCaseFromShelfStyle(objectGraph, shelfToken.contentType), shelfToken.contentType, shelfToken.offerStyle, filter);
+ if (serverData.isNullOrEmpty(shelfItems)) {
+ return null;
+ }
+ shelf.items = shelfItems.items;
+ shelf.isHorizontal = true;
+ if (dataItems.length < 2 || objectGraph.client.isTV) {
+ shelf.rowsPerColumn = 1;
+ }
+ else {
+ shelf.rowsPerColumn = 2;
+ }
+ if (!objectGraph.client.isTV && isSome(shelfToken.developerUrl)) {
+ const developerSeeAll = new models.FlowAction("page");
+ developerSeeAll.pageUrl = shelfToken.developerUrl;
+ developerSeeAll.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, developerSeeAll, developerSeeAll.pageUrl, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ });
+ shelf.seeAllAction = developerSeeAll;
+ }
+ if (hasShelfBackground) {
+ shelf.background = {
+ type: "color",
+ color: productPageCommon.grayShelfBackgroundColor,
+ };
+ }
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "developer");
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ return shelf;
+ });
+}
+//# sourceMappingURL=more-by-developer-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/product-media-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/product-media-shelf.js
new file mode 100644
index 0000000..3cc927c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/product-media-shelf.js
@@ -0,0 +1,244 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as videoDefaults from "../../../common/constants/video-constants";
+import * as metricsHelpersMedia from "../../../common/metrics/helpers/media";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { shallowCopyOf } from "../../../foundation/util/objects";
+import * as content from "../../content/content";
+import { addMetricsEventsToPageWithInformation, fakeMetricsPageInformation } from "../../metrics/helpers/page";
+import * as productPageVariants from "../product-page-variants";
+import { ProductPageSectionMapping } from "../shelf-based/product-page-section-mapping";
+import * as videoShelf from "./video-shelf";
+import { isSome } from "@jet/environment";
+const productMediaShelfPrefix = "product_media";
+export function isProductMediaShelf(shelf) {
+ var _a;
+ return isSome(shelf) && ((_a = shelf.id) === null || _a === void 0 ? void 0 : _a.indexOf(productMediaShelfPrefix)) > -1;
+}
+export function create(objectGraph, data, clientIdentifierOverride, shelfMetrics) {
+ return validation.context("productMediaShelves", () => {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ // <rdar://problem/50189280> REG: tvOS: Prevent tvOS Product Pages from creating media shelves for other platforms.
+ // For certain platforms, we only show app media for that platform only.
+ const includedAppPlatformsForProductMedia = includedAppPlatformsForProductMediaOnDevice(objectGraph, objectGraph.client.deviceType);
+ // rdar://59908940 (CrashTracer: AppStore at AppStore: specialized static AnyComponentView.shelfLayout)
+ // On TV / Watch, restrict the video previews to the client platform only.
+ const includedAppPlatformsForPlatformVideoPreviews = includedAppPlatformsForPlatformVideoPreviewsOnDevice(objectGraph, objectGraph.client.deviceType);
+ // Find all media
+ const productVariantData = productPageVariants.productVariantDataForData(objectGraph, data);
+ const allProductMedia = content.productMediaFromData(objectGraph, data, 11 /* content.ArtworkUseCase.ProductPageScreenshots */, includedAppPlatformsForProductMedia, productVariantData, clientIdentifierOverride);
+ const platformVideoPreviews = content.platformVideoPreviewFromData(objectGraph, data, videoDefaults.defaultVideoConfiguration(objectGraph), includedAppPlatformsForPlatformVideoPreviews);
+ // Find the first platform that has a video.
+ let screenshotMediaWithCorrespondingVideo = null;
+ if (platformVideoPreviews) {
+ for (const screenshotMedia of allProductMedia) {
+ if (screenshotMedia.mediaPlatform.isEqualTo(platformVideoPreviews.mediaPlatform)) {
+ screenshotMediaWithCorrespondingVideo = screenshotMedia;
+ break;
+ }
+ }
+ }
+ // For phone screenshots or on visionOS, we show the video(s) inline with the screenshots if-and-only-if the aspect ratios are
+ // the same. Therefore, we store the prevailing aspect ratio of the screenshots that has a video.
+ let overridingAspectRatio = null;
+ if (screenshotMediaWithCorrespondingVideo) {
+ const targetAppPlatform = screenshotMediaWithCorrespondingVideo.mediaPlatform.appPlatform;
+ if (objectGraph.client.isVision || targetAppPlatform === "phone" || targetAppPlatform === "messages") {
+ for (const item of screenshotMediaWithCorrespondingVideo.items) {
+ const screenshot = item.screenshot;
+ overridingAspectRatio = screenshot.width / screenshot.height;
+ if (overridingAspectRatio < 1) {
+ break;
+ }
+ }
+ }
+ }
+ // Determine which videos are eligible to be collapsed with screenshots.
+ const collapsedVideoItems = [];
+ const separatedVideos = [];
+ if (screenshotMediaWithCorrespondingVideo && platformVideoPreviews && platformVideoPreviews.videos) {
+ for (const video of platformVideoPreviews.videos) {
+ const videoItem = new models.ProductMediaItem();
+ videoItem.video = video;
+ const videoAspectRatio = video.preview.width / video.preview.height;
+ const isMatchingAspectRatio = overridingAspectRatio === null || Math.abs(videoAspectRatio - overridingAspectRatio) < 0.01;
+ const isMatchingAppPlatform = screenshotMediaWithCorrespondingVideo.mediaPlatform.isEqualTo(platformVideoPreviews.mediaPlatform);
+ if (isMatchingAspectRatio && isMatchingAppPlatform) {
+ collapsedVideoItems.push(videoItem);
+ }
+ else {
+ separatedVideos.push(video);
+ }
+ const metricsOptions = {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ id: data.id,
+ isAdvert: shelfMetrics.metricsPageInformation.iAdInfo
+ ? shelfMetrics.metricsPageInformation.iAdInfo.iAdIsPresent
+ : false,
+ };
+ metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, video, metricsOptions);
+ }
+ }
+ // Collapse the eligible videos into the first media row.
+ if (screenshotMediaWithCorrespondingVideo) {
+ screenshotMediaWithCorrespondingVideo.items = collapsedVideoItems.concat(screenshotMediaWithCorrespondingVideo.items);
+ }
+ let sectionMappings = [];
+ let shelfMapping = {};
+ for (let index = 0; index < allProductMedia.length; index++) {
+ const productMedia = allProductMedia[index];
+ const shelfId = `${productMediaShelfPrefix}_${productMedia.mediaPlatform.appPlatform}_${productMedia.mediaPlatform.supplementaryAppPlatforms.join("_")}`;
+ sectionMappings.push(new ProductPageSectionMapping("shelf", shelfId));
+ const mediaShelf = new models.Shelf("productMediaItem", null, productMedia.items);
+ mediaShelf.isHorizontal = true;
+ let hasPortraitMedia = false;
+ let mediaAspectRatio = aspectRatioForMediaItem(objectGraph, productMedia.items[0]);
+ for (const mediaItem of productMedia.items) {
+ if ((serverData.isDefinedNonNullNonEmpty(mediaItem.screenshot) && mediaItem.screenshot.isPortrait()) ||
+ (serverData.isDefinedNonNullNonEmpty(mediaItem.video) && mediaItem.video.preview.isPortrait())) {
+ mediaAspectRatio = aspectRatioForMediaItem(objectGraph, mediaItem);
+ hasPortraitMedia = true;
+ break;
+ }
+ }
+ mediaShelf.contentsMetadata = {
+ type: "productMedia",
+ platform: productMedia.mediaPlatform,
+ allPlatforms: productMedia.allPlatforms,
+ platformDescription: productMedia.platformDescription,
+ allPlatformsDescription: productMedia.allPlatformsDescription,
+ allPlatformsDescriptionPlacement: productMedia.allPlatformsDescriptionPlacement,
+ hasPortraitMedia: hasPortraitMedia,
+ expandProductMediaAction: null,
+ viewProductMediaGalleryAction: createViewProductMediaGalleryAction(objectGraph, productMedia, data.id),
+ aspectRatio: mediaAspectRatio,
+ };
+ shelfMetrics.addImpressionsToShelf(objectGraph, mediaShelf, "screenshots");
+ if (index === 0 && objectGraph.client.deviceType !== "tv" && !objectGraph.client.isWeb) {
+ mediaShelf.title = objectGraph.loc.string("ProductPage.Section.ScreenshotsPreview.Title");
+ }
+ shelfMapping[shelfId] = mediaShelf;
+ }
+ if (objectGraph.client.isVision && sectionMappings.length > 1) {
+ const firstSectionMapping = sectionMappings[0];
+ const mediaShelf = shelfMapping[firstSectionMapping.shelfId];
+ if (mediaShelf.contentsMetadata.type === "productMedia") {
+ mediaShelf.contentsMetadata.expandProductMediaAction = createExpandProductMediaAction(objectGraph, sectionMappings, shelfMapping);
+ }
+ // Now that we've created the expand action, we can strip out the other product media shelves,
+ // as we will only be displaying the first in the non-expanded state.
+ sectionMappings = [firstSectionMapping];
+ const firstShelf = shelfMapping[firstSectionMapping.shelfId];
+ shelfMapping = {
+ [firstSectionMapping.shelfId]: firstShelf,
+ };
+ }
+ // Videos that stay in the Videos Shelf
+ if (separatedVideos.length > 0) {
+ shelfMapping["videos"] = videoShelf.create(objectGraph, separatedVideos, shelfMetrics);
+ }
+ return {
+ sectionMappings: sectionMappings,
+ shelfMapping: shelfMapping,
+ allProductMedia: allProductMedia,
+ };
+ });
+}
+/**
+ * Returns the list of included app platforms for the product media shelves.
+ * This is used to constrain some platforms to media type of just that platform, e.g. tvOS can only show tvOS screenshots and video.
+ *
+ * @param deviceType Device type to find the included app platforms for product media for.
+ * @returns models.AppPlatform[] | null An array of supported app platforms for product media, or `null` if there are no constraints and should fallback to sorting logic in content.ts
+ */
+export function includedAppPlatformsForProductMediaOnDevice(objectGraph, deviceType) {
+ // We want to return all product media platforms, so that we can generate
+ // the appropriate platform selector description text.
+ return null;
+}
+/**
+ * Returns the list of included app platforms for the platform video previews
+ * This is used to constrain some platforms to video previews of just that platform, e.g. tvOS can only show tvOS video previews.
+ *
+ * @param deviceType Device type to determine to app platforms we allow video previews for.
+ * @returns models.AppPlatform[] | null An array of supported app platforms for video previews, or `null` if there are no constraints
+ */
+function includedAppPlatformsForPlatformVideoPreviewsOnDevice(objectGraph, deviceType) {
+ if (objectGraph.client.isTV || objectGraph.client.isWatch) {
+ return [content.currentAppPlatform(objectGraph)];
+ }
+ return null;
+}
+/**
+ * Creates a flow action for navigating to the expanded product media page.
+ *
+ * @param objectGraph
+ * @param sectionMappings The product media section mappings array
+ * @param shelfMapping The product media shelf mapping
+ * @returns A flow action
+ */
+function createExpandProductMediaAction(objectGraph, sectionMappings, shelfMapping) {
+ if (!objectGraph.client.isVision || sectionMappings.length <= 1) {
+ return null;
+ }
+ const expandedShelves = [];
+ for (const sectionMapping of sectionMappings) {
+ const shelf = shelfMapping[sectionMapping.shelfId];
+ const expandedShelf = shallowCopyOf(shelf);
+ expandedShelf.title = null;
+ expandedShelf.contentsMetadata = shallowCopyOf(shelf.contentsMetadata);
+ if (expandedShelf.contentsMetadata.type === "productMedia") {
+ expandedShelf.contentsMetadata.expandProductMediaAction = null;
+ }
+ expandedShelves.push(expandedShelf);
+ }
+ const page = new models.GenericPage(expandedShelves);
+ page.title = objectGraph.loc.string("PRODUCT_PREVIEWS_TITLE");
+ const flowAction = new models.FlowAction("page");
+ flowAction.pageData = page;
+ flowAction.title = page.title;
+ return flowAction;
+}
+/**
+ * Creates a flow action for navigating to the product media gallery page.
+ *
+ * @param objectGraph Current object graph
+ * @param productMedia The product media object that contains the media items to display
+ * @param productId The id of the owning product
+ * @returns A flow action
+ */
+function createViewProductMediaGalleryAction(objectGraph, productMedia, productId) {
+ if (!objectGraph.client.isVision && !objectGraph.featureFlags.isEnabled("tv_product_page_2024E")) {
+ return null;
+ }
+ const page = new models.ProductMediaGalleryPage(productMedia);
+ const pageInformation = fakeMetricsPageInformation(objectGraph, "ProductMediaGallery", productId, "");
+ addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation);
+ const flowAction = new models.FlowAction("productMediaGallery");
+ flowAction.pageData = page;
+ return flowAction;
+}
+/**
+ * Determines the aspect ratio for a given ProductMediaItem
+ *
+ * @param objectGraph Current object graph
+ * @param mediaItem The media item
+ * @returns An aspect ratio determined by width / height
+ */
+function aspectRatioForMediaItem(objectGraph, mediaItem) {
+ var _a;
+ if (serverData.isNull(mediaItem)) {
+ return 1.0;
+ }
+ if (serverData.isDefinedNonNull((_a = mediaItem.video) === null || _a === void 0 ? void 0 : _a.preview) && mediaItem.video.preview.height > 0) {
+ return mediaItem.video.preview.width / mediaItem.video.preview.height;
+ }
+ if (serverData.isDefinedNonNull(mediaItem.screenshot) && mediaItem.screenshot.height > 0) {
+ return mediaItem.screenshot.width / mediaItem.screenshot.height;
+ }
+ return 1.0;
+}
+//# sourceMappingURL=product-media-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/ribbon-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/ribbon-shelf.js
new file mode 100644
index 0000000..d3064a5
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/ribbon-shelf.js
@@ -0,0 +1,40 @@
+import * as models from "../../../api/models";
+import * as productBadges from "../badges/badges";
+import * as productPageUtil from "../product-page-util";
+/**
+ * Create a shelf for the product page ribbon to display badges.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @returns A ribbon shelf.
+ */
+export function create(objectGraph, data, shelfMetrics, shelfContext) {
+ if (productPageUtil.isShelfBased(objectGraph)) {
+ const badgeShelf = new models.Shelf("productBadge");
+ const badges = productBadges.badgesFromResponse(objectGraph, data, true, {
+ locationTracker: shelfMetrics.locationTracker,
+ pageInformation: shelfMetrics.metricsPageInformation,
+ });
+ badgeShelf.items = badges;
+ badgeShelf.isHorizontal = true;
+ shelfMetrics.addImpressionsToShelf(objectGraph, badgeShelf, "informationRibbon");
+ return badgeShelf;
+ }
+ else {
+ const ribbonShelf = new models.Shelf("informationRibbon");
+ const badges = productBadges.badgesFromResponse(objectGraph, data, true, {
+ locationTracker: shelfMetrics.locationTracker,
+ pageInformation: shelfMetrics.metricsPageInformation,
+ });
+ const hasTopSeparator = !shelfContext.useInlineUberStyle;
+ const hasBottomSeparator = false;
+ const separatorsAreFullWidth = shelfContext.useInlineUberStyle;
+ const alignment = "justified";
+ const informationRibbon = new models.InformationRibbon(badges, hasTopSeparator, hasBottomSeparator, separatorsAreFullWidth, alignment);
+ ribbonShelf.items = [informationRibbon];
+ ribbonShelf.isHorizontal = true;
+ shelfMetrics.addImpressionsToShelf(objectGraph, ribbonShelf, "informationRibbon");
+ return ribbonShelf;
+ }
+}
+//# sourceMappingURL=ribbon-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/shelf-based-reviews-shelves.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/shelf-based-reviews-shelves.js
new file mode 100644
index 0000000..5f66054
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/shelf-based-reviews-shelves.js
@@ -0,0 +1,544 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { shallowCopyOf } from "../../../foundation/util/objects";
+import { createArtworkForResource } from "../../content/artwork/artwork";
+import * as contentAttributes from "../../content/attributes";
+import * as content from "../../content/content";
+import * as reviews from "../reviews";
+import { isNothing, isSome } from "@jet/environment";
+/**
+ * Create a shelf for the product page reviews.
+ *
+ * @param objectGraph
+ * @param productData
+ * @param shelfMetrics The product page shelf metrics.
+ * @param shelfContext A collection of any other variables used when creating this shelf.
+ * @returns A reviews shelf.
+ */
+export function createReviewsShelves(objectGraph, productData, shelfMetrics, shelfContext, lockupSubtitle) {
+ return validation.context("createReviewsShelves", () => {
+ const productReviewsShelves = {};
+ if (objectGraph.client.isWatch) {
+ return productReviewsShelves;
+ }
+ if (serverData.isNullOrEmpty(productData)) {
+ return productReviewsShelves;
+ }
+ const isReviewable = reviews.isAppReviewable(objectGraph, productData);
+ const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, productData);
+ const reviewsData = mediaRelationship.relationship(productData, "reviews");
+ const reviewItems = mediaDataStructure.dataCollectionFromDataContainer(reviewsData);
+ const ratingsData = mediaAttributes.attributeAsDictionary(productData, "userRating");
+ ratingsData.ratingAverage = serverData.asNumber(ratingsData, "value");
+ ratingsData.adamId = productData.id;
+ ratingsData.isBundle = shelfContext.isBundle;
+ ratingsData.supportUrl = contentAttributes.contentAttributeAsString(objectGraph, productData, "supportURLForLanguage");
+ const appIcon = content.iconFromData(objectGraph, productData, {
+ useCase: 2 /* content.ArtworkUseCase.LockupIconMedium */,
+ });
+ const tvOnlyApp = appPlatforms.length === 1 && appPlatforms[0] === "tv";
+ const reviewsTitle = reviews.sectionTitleForPlatform(objectGraph);
+ const productRatingsWereReset = serverData.asBooleanOrFalse(ratingsData, "wasReset");
+ const productRatings = createProductRatings(objectGraph, ratingsData, reviewItems.length, productRatingsWereReset);
+ const tapToRateProductReviewAction = createTapToRateProductReviewAction(objectGraph, productData, shelfContext.productTitle, ratingsData, shelfContext.isBundle, tvOnlyApp);
+ const writeAReview = createWriteAReview(objectGraph, productData, ratingsData, tvOnlyApp, appIcon, lockupSubtitle, shelfContext.productTitle);
+ const editorsChoiceReview = reviews.createEditorsChoiceReview(objectGraph, productData);
+ const reviewSummary = reviews.createReviewSummaryProductReview(objectGraph, productData, shelfMetrics);
+ const reviewSummaryForReviewsData = reviews.reviewSummaryFromData(objectGraph, productData, shelfMetrics);
+ const allProductReviews = reviews.createProductReviewsList(objectGraph, ratingsData, reviewItems, true, editorsChoiceReview, reviewSummary, shelfMetrics);
+ const userProductReviews = reviews.createProductReviewsList(objectGraph, ratingsData, reviewItems, true, null, null, shelfMetrics);
+ const productReviewActions = createProductReviewActions(objectGraph, productData, shelfContext.productTitle, ratingsData, shelfContext.isBundle, tvOnlyApp, appIcon, lockupSubtitle);
+ const includeMoreAction = objectGraph.client.isVision;
+ const includeFeedbackActions = objectGraph.client.isVision;
+ const seeAllAction = tvOnlyApp || reviewItems.length === 0 || (objectGraph.client.isVision && reviewsData.next == null)
+ ? null
+ : reviews.reviewsPageActionFromReviewsData(objectGraph, objectGraph.client.guid, ratingsData, reviewItems, reviewsData.next, shelfContext.productTitle, appIcon, includeMoreAction, includeFeedbackActions, productData, shelfMetrics);
+ if (shelfContext.shouldShowRatingsAndReviews) {
+ const ratingsShelf = new models.Shelf("productRatings");
+ ratingsShelf.title = reviewsTitle;
+ ratingsShelf.items = [productRatings];
+ productReviewsShelves.ratingsShelf = ratingsShelf;
+ shelfMetrics.addImpressionsToShelf(objectGraph, ratingsShelf, "ratingsOverview", null, null, null, objectGraph.loc.string("ProductPage.Section.Reviews.Title"));
+ const allReviewActionsShelfItems = [];
+ if (serverData.isDefinedNonNullNonEmpty(tapToRateProductReviewAction) && isReviewable) {
+ allReviewActionsShelfItems.push(tapToRateProductReviewAction);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(writeAReview) && isReviewable) {
+ allReviewActionsShelfItems.push(writeAReview);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(allReviewActionsShelfItems)) {
+ const allReviewActionsShelf = new models.Shelf("productReviewAction");
+ allReviewActionsShelf.items = allReviewActionsShelfItems;
+ productReviewsShelves.allReviewActionsShelf = allReviewActionsShelf;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(reviewSummaryForReviewsData)) {
+ const reviewSummaryShelf = new models.Shelf("reviewSummary");
+ reviewSummaryShelf.items = [reviewSummaryForReviewsData];
+ productReviewsShelves.reviewSummaryShelf = reviewSummaryShelf;
+ }
+ let tapToRateProductReviewActionsShelf = null;
+ if (serverData.isDefinedNonNullNonEmpty(tapToRateProductReviewAction) && isReviewable) {
+ tapToRateProductReviewActionsShelf = new models.Shelf("productReviewAction");
+ tapToRateProductReviewActionsShelf.items = [tapToRateProductReviewAction];
+ productReviewsShelves.tapToRateActionsShelf = tapToRateProductReviewActionsShelf;
+ }
+ let writeAReviewActionsShelf = null;
+ if (serverData.isDefinedNonNullNonEmpty(writeAReview) && isReviewable) {
+ writeAReviewActionsShelf = new models.Shelf("productReviewAction");
+ writeAReviewActionsShelf.items = [writeAReview];
+ productReviewsShelves.writeAReviewActionsShelf = writeAReviewActionsShelf;
+ }
+ const userReviewsTitle = objectGraph.loc.string("ProductPage.Section.Reviews.MostHelpfulReviews.Title");
+ const reviewSummaryTitle = objectGraph.loc.string("ProductPage.ReviewSummary.Body.Title");
+ const hasUserReviews = isSome(userProductReviews) && userProductReviews.length > 0;
+ let allProductReviewsShelf;
+ if (isSome(allProductReviews) && allProductReviews.length > 0) {
+ allProductReviewsShelf = new models.Shelf("productReview");
+ allProductReviewsShelf.items = allProductReviews;
+ allProductReviewsShelf.isHorizontal = true;
+ if (objectGraph.client.isiOS) {
+ if (hasUserReviews) {
+ allProductReviewsShelf.title = userReviewsTitle;
+ allProductReviewsShelf.contentsMetadata = {
+ type: "productReviewsSection",
+ hasReviewSummary: isSome(reviewSummary),
+ reviewSummaryTitle: reviewSummaryTitle,
+ };
+ shelfMetrics.addImpressionsToShelf(objectGraph, allProductReviewsShelf, "mostHelpfulReviews", null, null, null, objectGraph.loc.string("ProductPage.Section.Reviews.MostHelpfulReviews.Title"));
+ }
+ else {
+ allProductReviewsShelf.title = reviewSummaryTitle;
+ }
+ }
+ productReviewsShelves.allProductReviewsShelf = allProductReviewsShelf;
+ }
+ let userProductReviewsShelf;
+ if (hasUserReviews) {
+ userProductReviewsShelf = new models.Shelf("productReview");
+ userProductReviewsShelf.items = userProductReviews;
+ userProductReviewsShelf.isHorizontal = true;
+ if (objectGraph.client.isiOS) {
+ userProductReviewsShelf.title = userReviewsTitle;
+ }
+ shelfMetrics.addImpressionsToShelf(objectGraph, userProductReviewsShelf, "mostHelpfulReviews", null, null, null, objectGraph.loc.string("ProductPage.Section.Reviews.MostHelpfulReviews.Title"));
+ productReviewsShelves.userProductReviewsShelf = userProductReviewsShelf;
+ }
+ if (objectGraph.client.deviceType === "tv") {
+ const shelf = new models.Shelf("productRatingsAndReviewsComponent");
+ shelf.title = reviewsTitle;
+ const shelfItems = [];
+ if (serverData.isDefinedNonNullNonEmpty(tapToRateProductReviewAction) &&
+ tapToRateProductReviewAction.action instanceof models.TapToRate &&
+ isReviewable) {
+ shelfItems.push(tapToRateProductReviewAction.action);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(productRatings.status)) {
+ if (objectGraph.featureFlags.isEnabled("tv_product_page_2024E")) {
+ shelfItems.push(new models.ProductRatingsAndReviewsMessage(productRatings.status));
+ }
+ else {
+ shelfItems.push(reviews.noRatingsFromRatings(productRatings));
+ }
+ }
+ else {
+ shelfItems.push(reviews.starRatingsFromRatings(productRatings));
+ shelfItems.push(reviews.starRatingsHistogramFromRatings(productRatings));
+ }
+ shelf.items = shelfItems;
+ productReviewsShelves.purchasedRatingsAndReviewsComponentShelf = shelf;
+ }
+ if (objectGraph.client.deviceType === "tv") {
+ const shelf = new models.Shelf("productRatingsAndReviewsComponent");
+ shelf.title = reviewsTitle;
+ const shelfItems = [];
+ if (serverData.isDefinedNonNullNonEmpty(productRatings.status)) {
+ if (objectGraph.featureFlags.isEnabled("tv_product_page_2024E")) {
+ shelfItems.push(new models.ProductRatingsAndReviewsMessage(productRatings.status));
+ }
+ else {
+ shelfItems.push(reviews.noRatingsFromRatings(productRatings));
+ }
+ }
+ else {
+ shelfItems.push(reviews.starRatingsFromRatings(productRatings));
+ shelfItems.push(reviews.starRatingsHistogramFromRatings(productRatings));
+ }
+ shelf.items = shelfItems;
+ productReviewsShelves.notPurchasedRatingsAndReviewsComponentShelf = shelf;
+ }
+ if (objectGraph.client.isVision) {
+ const productReviewsHeader = createProductReviewsLearnMoreShelf(objectGraph);
+ if (isSome(productReviewsHeader)) {
+ productReviewsHeader.title = reviewsTitle;
+ productReviewsHeader.seeAllAction = seeAllAction;
+ productReviewsShelves.productReviewsHeader = productReviewsHeader;
+ }
+ // If there is a learn more shelf, the title and see all action is applied to the learn more shelf,
+ // not the actual ratings and reviews shelf, due to the learn more text being placed directly
+ // underneath the shelf title.
+ const shelfTitle = isSome(productReviewsHeader) ? null : reviewsTitle;
+ const shelfSeeAllAction = isSome(productReviewsHeader) ? null : seeAllAction;
+ const purchasedShelf = createVisionRatingsAndReviewsShelf(objectGraph, true, shelfTitle, productRatings, productRatingsWereReset, productReviewActions, allProductReviews, shelfSeeAllAction);
+ const notPurchasedShelf = createVisionRatingsAndReviewsShelf(objectGraph, false, shelfTitle, productRatings, productRatingsWereReset, productReviewActions, allProductReviews, seeAllAction);
+ productReviewsShelves.purchasedRatingsAndReviewsComponentShelf = purchasedShelf;
+ productReviewsShelves.notPurchasedRatingsAndReviewsComponentShelf = notPurchasedShelf;
+ }
+ let editorsChoiceProductReviewsShelf = null;
+ if (serverData.isDefinedNonNullNonEmpty(editorsChoiceReview)) {
+ editorsChoiceProductReviewsShelf = new models.Shelf("productReview");
+ editorsChoiceProductReviewsShelf.items = [editorsChoiceReview];
+ if (!shelfContext.shouldShowRatingsAndReviews) {
+ editorsChoiceProductReviewsShelf.title = reviewsTitle;
+ }
+ else if (objectGraph.client.isiOS &&
+ !editorsChoiceReview.review.showsBadge) {
+ editorsChoiceProductReviewsShelf.title = objectGraph.loc.string("ProductPage.Section.EditorsNotes.Title");
+ }
+ productReviewsShelves.editorsChoiceProductReviewsShelf = editorsChoiceProductReviewsShelf;
+ }
+ // Web does not currently support a "See All" page for Bundles
+ const isBundleOnWeb = shelfContext.isBundle && objectGraph.client.isWeb;
+ if (!tvOnlyApp && !isBundleOnWeb && reviewItems.length > 0) {
+ productReviewsShelves.ratingsShelf.seeAllAction = objectGraph.client.isiOS
+ ? createReviewsPageSeeAllAction(objectGraph, objectGraph.client.guid, productData.id, ratingsData, reviewItems, reviewsData.next, includeMoreAction, includeFeedbackActions, productReviewsShelves)
+ : reviews.reviewsPageActionFromReviewsData(objectGraph, objectGraph.client.guid, ratingsData, reviewItems, reviewsData.next, shelfContext.productTitle, appIcon, includeMoreAction, includeFeedbackActions, productData, shelfMetrics);
+ }
+ }
+ return productReviewsShelves;
+ });
+}
+function createProductRatings(objectGraph, ratingsData, reviewsCount, wereReset) {
+ return validation.context("createProductRatings", () => {
+ const ratings = reviews.ratingsFromApiResponses(objectGraph, objectGraph.client.guid, "productPage", ratingsData);
+ const hasRatings = ratings.ratingAverage > 0 && ratings.ratingCounts;
+ if (!hasRatings && !wereReset) {
+ ratings.status = objectGraph.loc.string("RATINGS_STATUS_NOT_ENOUGH_RATINGS_OR_REVIEWS");
+ }
+ return ratings;
+ });
+}
+function createTapToRateProductReviewAction(objectGraph, productData, productTitle, ratingsData, isBundle, tvOnlyApp) {
+ return validation.context("createTapToRateProductReviewAction", () => {
+ const tapToRate = createTapToRate(objectGraph, productData, productTitle, ratingsData, isBundle, tvOnlyApp);
+ if (tapToRate) {
+ const productReviewAction = new models.ProductReviewAction();
+ productReviewAction.actionType = "tapToRate";
+ productReviewAction.action = tapToRate;
+ productReviewAction.id = "ProductReviewAction.TapToRate";
+ return productReviewAction;
+ }
+ else {
+ return null;
+ }
+ });
+}
+/**
+ * Creates the TapToRate object, for tapping on a star to rate the app
+ * @param objectGraph Current object graph
+ * @param productData The data blob for the product
+ * @param productTitle The title of the product
+ * @param ratingsData The data blobs for the ratings
+ * @param isBundle Whether this product is for a bundle
+ * @param tvOnlyApp Whether this product is for a TV only app
+ * @returns The built TapToRate object
+ */
+function createTapToRate(objectGraph, productData, productTitle, ratingsData, isBundle, tvOnlyApp) {
+ return validation.context("createTapToRateProductReviewAction", () => {
+ // Tap to Rate
+ if (!tvOnlyApp || objectGraph.client.isTV) {
+ return reviews.tapToRateWithAdamId(objectGraph, productData.id, isBundle, productTitle, null);
+ }
+ else {
+ return null;
+ }
+ });
+}
+/**
+ * Creates an action for writing a product review
+ * @param objectGraph Current object graph
+ * @param productData The data blob for the product
+ * @param ratingsData The data blobs for the ratings
+ * @param tvOnlyApp Whether this product is for a TV only app
+ * @param appIcon The icon artwork for the product
+ * @returns A product review action
+ */
+function createWriteAReview(objectGraph, productData, ratingsData, tvOnlyApp, appIcon, lockupSubtitle, lockupTitle) {
+ return validation.context("createWriteAReview", () => {
+ // Tap to Rate
+ if (!tvOnlyApp) {
+ const productWriteAReview = new models.ProductWriteAReview();
+ productWriteAReview.writeReviewAction = reviews.writeReviewActionFromData(objectGraph, productData, lockupSubtitle, lockupTitle, appIcon);
+ productWriteAReview.supportAction = createSupportAction(objectGraph, ratingsData);
+ const productReviewAction = new models.ProductReviewAction();
+ productReviewAction.actionType = "writeAReview";
+ productReviewAction.action = productWriteAReview;
+ return productReviewAction;
+ }
+ else {
+ return null;
+ }
+ });
+}
+/**
+ * Creates an action for contacting support
+ * @param objectGraph Current object graph
+ * @param ratingsData The data blobs for the ratings
+ * @returns An external URL action
+ */
+function createSupportAction(objectGraph, ratingsData) {
+ const supportUrl = serverData.asString(ratingsData, "supportUrl");
+ if (supportUrl) {
+ const supportAction = new models.ExternalUrlAction(supportUrl, false);
+ supportAction.title = objectGraph.loc.string("APP_SUPPORT");
+ supportAction.artwork = createArtworkForResource(objectGraph, "systemimage://questionmark.circle");
+ return supportAction;
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Creates a ProductReviewActions object, which includes tap to rate, write a review, and support
+ * @param objectGraph Current object graph
+ * @param productData The data blob for the product
+ * @param productTitle The title of the product
+ * @param ratingsData The data blobs for the ratings
+ * @param isBundle Whether this product is for a bundle
+ * @param tvOnlyApp Whether this product is for a TV only app
+ * @param appIcon The icon artwork for the product
+ * @returns The built ReviewActions object
+ */
+export function createProductReviewActions(objectGraph, productData, productTitle, ratingsData, isBundle, tvOnlyApp, appIcon, lockupSubtitle) {
+ return validation.context("createProductReviewActions", () => {
+ const productReviewActions = new models.ProductReviewActions();
+ productReviewActions.tapToRate = createTapToRate(objectGraph, productData, productTitle, ratingsData, isBundle, tvOnlyApp);
+ productReviewActions.writeReviewAction = reviews.writeReviewActionFromData(objectGraph, productData, lockupSubtitle, productTitle, appIcon);
+ productReviewActions.supportAction = createSupportAction(objectGraph, ratingsData);
+ return productReviewActions;
+ });
+}
+/**
+ * Creates a ratings and reviews shelf for visionOS, which is composed of a
+ * number of different ProductRatingsAndReviewsComponent items
+ * @param objectGraph Current object graph
+ * @param isPurchasedShelf Whether this shelf is for display for a purchased app
+ * @param shelfTitle The title to use for the shelf
+ * @param productRatings The ratings for the product
+ * @param productRatingsWereReset Whether the product ratings were reset by the developer
+ * @param reviewActions A review actions object
+ * @param productReviews An array of product reviews
+ * @param seeAllAction The see all action for the shelf
+ * @returns The built shelf
+ */
+function createVisionRatingsAndReviewsShelf(objectGraph, isPurchasedShelf, shelfTitle, productRatings, productRatingsWereReset, productReviewActions, productReviews, seeAllAction) {
+ const shelf = new models.Shelf("productRatingsAndReviewsComponent");
+ shelf.title = shelfTitle;
+ let shelfItems = [];
+ const hasRatings = productRatings.ratingAverage > 0 && productRatings.ratingCounts;
+ const hasReviews = productReviews.length > 0;
+ if (hasRatings) {
+ shelfItems.push(reviews.starRatingsFromRatings(productRatings));
+ }
+ else if (productRatingsWereReset) {
+ const messageText = objectGraph.loc.string("RATINGS_STATUS_DEVELOPER_RESET");
+ shelfItems.push(new models.ProductRatingsAndReviewsMessage(messageText));
+ }
+ else if (hasReviews) {
+ const messageText = objectGraph.loc.string("RATINGS_STATUS_NOT_ENOUGH_RATINGS");
+ shelfItems.push(new models.ProductRatingsAndReviewsMessage(messageText));
+ }
+ if (isPurchasedShelf && serverData.isDefinedNonNullNonEmpty(productReviewActions)) {
+ shelfItems.push(productReviewActions);
+ }
+ if (hasReviews) {
+ shelfItems = shelfItems.concat(productReviews);
+ }
+ else if (!hasRatings && !productRatingsWereReset) {
+ const messageText = objectGraph.loc.string("RATINGS_STATUS_NOT_ENOUGH_RATINGS_OR_REVIEWS");
+ shelfItems.push(new models.ProductRatingsAndReviewsMessage(messageText));
+ }
+ else {
+ const messageText = objectGraph.loc.string("RATINGS_STATUS_NO_REVIEWS");
+ shelfItems.push(new models.ProductRatingsAndReviewsMessage(messageText));
+ }
+ shelf.items = shelfItems;
+ shelf.isHorizontal = true;
+ shelf.seeAllAction = seeAllAction;
+ if (shelfItems.filter((shelfItem) => shelfItem instanceof models.ProductRatingsAndReviewsMessage).length >= 1) {
+ if (shelfItems.length === 1) {
+ shelf.presentationHints = { isSingleDensity: true };
+ }
+ else if (shelfItems.length === 2) {
+ shelf.presentationHints = { isLowDensity: true };
+ }
+ }
+ return shelf;
+}
+/**
+ * Creates a flow action to display a reviews page.
+ * @param deviceId The UUID for the user's device.
+ * @param adamId The adamid of the app
+ * @param ratingsData The ratings data response
+ * @param reviewsData The review row data response.
+ * @param nextPageHref The Media API href for the next page.
+ * @param appName The name of the app
+ * @param appIcon The app's icon.
+ * @param productData The data for the product
+
+ * @returns A `FlowAction` object pointing to a reviews page.
+ */
+function createReviewsPageSeeAllAction(objectGraph, deviceId, adamId, ratingsData, reviewItems, nextPageHref = null, includeMoreAction, includeFeedbackActions, reviewsShelves) {
+ if (!ratingsData) {
+ return null;
+ }
+ return validation.context("reviewsPageActionFromReviewsData", () => {
+ var _a;
+ const pageData = new models.ReviewsPage();
+ let reviewActionsShelf;
+ if (isSome(reviewsShelves === null || reviewsShelves === void 0 ? void 0 : reviewsShelves.allReviewActionsShelf)) {
+ reviewActionsShelf = shallowCopyOf(reviewsShelves.allReviewActionsShelf);
+ const existingReviewActionsShelfPresentationHints = (_a = reviewActionsShelf.presentationHints) !== null && _a !== void 0 ? _a : {};
+ reviewActionsShelf.presentationHints = {
+ ...existingReviewActionsShelfPresentationHints,
+ displayIfReviewable: true,
+ };
+ addSeeAllContextPresentationHint(reviewActionsShelf);
+ }
+ let ratingsShelf;
+ if (isSome(reviewsShelves === null || reviewsShelves === void 0 ? void 0 : reviewsShelves.ratingsShelf)) {
+ ratingsShelf = shallowCopyOf(reviewsShelves.ratingsShelf);
+ const ratings = ratingsShelf.items[0];
+ if (isSome(ratings) && ratingsShelf.items.length === 1) {
+ const ratingsCopy = shallowCopyOf(ratings);
+ ratingsCopy.context = "details";
+ ratingsShelf.items = [ratingsCopy];
+ }
+ ratingsShelf.seeAllAction = null;
+ ratingsShelf.title = null;
+ addSeeAllContextPresentationHint(ratingsShelf);
+ }
+ let reviewSummaryShelf;
+ if (isSome(reviewsShelves === null || reviewsShelves === void 0 ? void 0 : reviewsShelves.reviewSummaryShelf)) {
+ reviewSummaryShelf = shallowCopyOf(reviewsShelves.reviewSummaryShelf);
+ addSeeAllContextPresentationHint(reviewSummaryShelf);
+ }
+ const pageComponents = reviews.createReviewsPageComponents(objectGraph, deviceId, adamId, ratingsData, reviewItems, nextPageHref, includeMoreAction, includeFeedbackActions, "helpful", {
+ ratingsShelf: ratingsShelf,
+ reviewSummaryShelf: reviewSummaryShelf,
+ reviewActionsShelf: reviewActionsShelf,
+ });
+ pageData.title = reviews.pageTitleForPlatform(objectGraph);
+ pageData.adamId = serverData.asString(ratingsData, "adamId");
+ pageData.nextPage = pageComponents.paginationToken;
+ pageData.initialSortOptionIdentifier = pageComponents.initialSortId;
+ pageData.sortActionSheetTitle = pageComponents.sortActionSheetTitle;
+ pageData.sortOptions = pageComponents.sorts;
+ if (isSome(ratingsShelf)) {
+ pageData.shelves.push(ratingsShelf);
+ }
+ if (isSome(reviewSummaryShelf)) {
+ pageData.shelves.push(reviewSummaryShelf);
+ }
+ if (isSome(reviewActionsShelf)) {
+ pageData.shelves.push(reviewActionsShelf);
+ }
+ pageData.shelves.push(pageComponents.reviewsShelf);
+ const reviewSummaryLearnMoreAction = reviews.reviewSummaryLearnMoreAction(objectGraph);
+ if (isSome(reviewSummaryLearnMoreAction)) {
+ pageData.trailingNavBarAction = reviewSummaryLearnMoreAction;
+ }
+ const action = new models.FlowAction("reviews");
+ action.pageData = pageData;
+ action.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ return action;
+ });
+}
+/**
+ * Sets `isSeeAllContext` to `true` in the presentation hints of the provided shelf.
+ * @param shelf The shelf to add the presentation hint to
+ */
+function addSeeAllContextPresentationHint(shelf) {
+ var _a;
+ if (isSome(shelf)) {
+ const existingPresentationHints = (_a = shelf.presentationHints) !== null && _a !== void 0 ? _a : {};
+ shelf.presentationHints = {
+ ...existingPresentationHints,
+ isSeeAllContext: true,
+ };
+ }
+}
+/**
+ * Creates a "Learn More" shelf for product reviews, which includes transparency info about the feature.
+ * This can be placed in different locations on a page based on the platform.
+ * @param objectGraph Current object graph
+ * @returns The built shelf
+ */
+export function createProductReviewsLearnMoreShelf(objectGraph) {
+ if (serverData.isNullOrEmpty(objectGraph.bag.ratingsAndReviewsLearnMoreEditorialId)) {
+ return null;
+ }
+ if (!objectGraph.client.isiOS && !objectGraph.client.isVision) {
+ return null;
+ }
+ const shelf = new models.Shelf("linkableText");
+ const bodyText = bodyTextForProductReviewsLearnMore(objectGraph);
+ if (isNothing(bodyText)) {
+ return null;
+ }
+ shelf.items = [bodyText];
+ return shelf;
+}
+/**
+ * Creates the linkable body text for the product reviews learn more shelf.
+ * @param objectGraph Current object graph
+ * @returns The built body text
+ */
+function bodyTextForProductReviewsLearnMore(objectGraph) {
+ const learnMoreLinkText = objectGraph.loc.string("ProductPage.RatingsAndReviews.Footer.LearnMore");
+ const linkedSubstrings = {};
+ const learnMoreAction = createProductReviewsLearnMoreAction(objectGraph);
+ if (isNothing(learnMoreAction)) {
+ return null;
+ }
+ linkedSubstrings[learnMoreLinkText] = learnMoreAction;
+ let text = objectGraph.loc.string("ProductPage.RatingsAndReviews.Footer");
+ text = text.replace("{learnMoreLink}", learnMoreLinkText);
+ const textType = "text/plain";
+ const styledText = new models.StyledText(text, textType);
+ return new models.LinkableText(styledText, linkedSubstrings);
+}
+/**
+ * Creates the FlowAction to navigate to the article related to the product reviews learn more shelf.
+ * @param objectGraph Current object graph
+ * @returns The built FlowAction
+ */
+function createProductReviewsLearnMoreAction(objectGraph) {
+ const editorialItemId = objectGraph.bag.ratingsAndReviewsLearnMoreEditorialId;
+ if (serverData.isNullOrEmpty(editorialItemId)) {
+ return null;
+ }
+ const learnMoreAction = new models.FlowAction("article");
+ learnMoreAction.title = objectGraph.loc.string("ProductPage.RatingsAndReviews.Footer.LearnMore");
+ learnMoreAction.pageUrl = `https://apps.apple.com/story/id${editorialItemId}`;
+ return learnMoreAction;
+}
+/**
+ * Creates the Review Summary Shelf
+ * @param objectGraph Current object graph
+ * @param data The current product page data
+ * @returns The review summary shelf
+ */
+export function createReviewSummary(objectGraph, data, shelfMetrics) {
+ const reviewSummary = reviews.reviewSummaryFromData(objectGraph, data, shelfMetrics);
+ if (isNothing(reviewSummary)) {
+ return null;
+ }
+ const shelf = new models.Shelf("reviewSummary");
+ shelf.items = [reviewSummary];
+ return shelf;
+}
+//# sourceMappingURL=shelf-based-reviews-shelves.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/similar-items-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/similar-items-shelf.js
new file mode 100644
index 0000000..70468a0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/similar-items-shelf.js
@@ -0,0 +1,253 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { Parameters, Path, Protocol, ShelfRefreshType } from "../../../foundation/network/url-constants";
+import { URL } from "../../../foundation/network/urls";
+import { applySearchAdMissedOpportunityToShelvesIfNeeded, isAdPlacementEnabled } from "../../ads/ad-common";
+import * as content from "../../content/content";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as metricsHelpersPage from "../../metrics/helpers/page";
+import { insertPlaceholdersIntoProductPageShelfIfRequired } from "../../placeholders/placeholders";
+import * as productPageCommon from "../product-page-common";
+import { ProductPageOnDemandShelfType } from "../product-page-common";
+import { ProductPageShelfMetrics } from "../product-page-shelf-metrics";
+import * as adIncidents from "../../../common/ads/ad-incident-recorder";
+import { adStitcherForOnDeviceProductPageYMALAdvertData } from "../../ads/on-device-ad-stitch";
+import * as adStitch from "../../ads/ad-stitcher";
+import { makeSeeAllPageURL } from "../intent-controller-routing";
+import { makeSeeAllPageIntent } from "../../../api/intents/see-all-page-intent";
+import { getPlatform } from "../../preview-platform";
+import { getLocale } from "../../locale";
+/**
+ * Create a shelf of similar items for the product page.
+ *
+ * @param productData The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @returns A similar items shelf.
+ */
+export function createInitialShelf(objectGraph, productData, shelfMetrics) {
+ return validation.context("similarItemsShelf", () => {
+ var _a;
+ if (serverData.isNullOrEmpty(productData)) {
+ return null;
+ }
+ if (objectGraph.client.isCompanionVisionApp) {
+ return null;
+ }
+ const relationship = mediaRelationship.relationship(productData, "customers-also-bought-apps");
+ if (serverData.isNullOrEmpty(relationship)) {
+ return null;
+ }
+ const supportsArcade = content.isArcadeSupported(objectGraph, productData);
+ const title = supportsArcade
+ ? objectGraph.loc.string("Arcade.ProductPage.MoreFromAppleArcade")
+ : objectGraph.loc.string("ProductPage.Section.SimilarItems.Title");
+ const metricsTargetType = "similarItems";
+ const metricsIdType = "relationship";
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: metricsTargetType,
+ id: ProductPageShelfMetrics.similarItemsShelfId,
+ idType: metricsIdType,
+ }, title);
+ let contentType;
+ let shouldInferSeeAllFromFetchedItems;
+ switch (objectGraph.client.deviceType) {
+ case "tv":
+ contentType = "mediumLockup";
+ shouldInferSeeAllFromFetchedItems = false;
+ break;
+ case "mac":
+ contentType = "mediumLockup";
+ shouldInferSeeAllFromFetchedItems = true;
+ break;
+ default:
+ contentType = "smallLockup";
+ shouldInferSeeAllFromFetchedItems = true;
+ break;
+ }
+ const shelf = new models.Shelf(contentType);
+ shelf.title = title;
+ const recoMetricsData = mediaDataStructure.metricsFromMediaApiObject(relationship);
+ const hasShelfBackground = !objectGraph.client.isTV && !objectGraph.client.isiOS;
+ const offerStyle = hasShelfBackground ? "white" : null;
+ const shelfItems = productPageCommon.lockupsFromDataContainer(objectGraph, relationship, shelfMetrics, content.artworkUseCaseFromShelfStyle(objectGraph, contentType), contentType, offerStyle, null, recoMetricsData);
+ if (!shelfItems) {
+ return null;
+ }
+ shelf.items = shelfItems.items;
+ const isAdEnabled = isAdPlacementEnabled(objectGraph, "productPageYMAL");
+ const destinationPageInformation = metricsHelpersPage.fakeMetricsPageInformation(objectGraph, "SimilarItems", productData.id, "");
+ // Add all the ids on the shelf to attach to the refreshUrl as a fallback in case the shelf can't be refreshed with new content.
+ // Create a `refreshUrl` for this shelf so it can be refreshed by native during downloading.
+ const refreshShelfToken = new productPageCommon.ProductPageShelfToken(productData.id, relationship.data, shelf.title, shouldInferSeeAllFromFetchedItems, undefined, contentType, null, null, null, null, null, ProductPageOnDemandShelfType.SimilarItems);
+ if (objectGraph.client.isWeb) {
+ const action = new models.FlowAction("page");
+ action.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ const destination = makeSeeAllPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ "id": productData.id,
+ "see-all": "customers-also-bought-apps",
+ });
+ action.destination = destination;
+ action.pageUrl = makeSeeAllPageURL(objectGraph, destination);
+ shelf.seeAllAction = action;
+ }
+ else {
+ const shelfRefreshUrl = refreshUrl(objectGraph, productData.id, refreshShelfToken, shelfMetrics, destinationPageInformation);
+ shelf.refreshUrl = shelfRefreshUrl;
+ }
+ shelf.isHorizontal = true;
+ if (relationship.data.length < 2 || objectGraph.client.isTV) {
+ shelf.rowsPerColumn = 1;
+ }
+ else if (supportsArcade || !objectGraph.client.isiOS) {
+ // Arcade and other platforms (except TV) show 2 apps per column.
+ shelf.rowsPerColumn = 2;
+ }
+ else {
+ // iOS regular product pages show 3 apps per column.
+ shelf.rowsPerColumn = 3;
+ }
+ if (hasShelfBackground) {
+ shelf.background = {
+ type: "color",
+ color: productPageCommon.grayShelfBackgroundColor,
+ };
+ }
+ if (objectGraph.client.isiOS && objectGraph.bag.isOnDemandShelfFetchingEnabled) {
+ shelf.fetchStrategy = models.IncompleteShelfFetchStrategy.OnShelfWillAppear;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(shelfItems.remainingItems) || isAdEnabled) {
+ // Add impressions and pop the location here so we're at the original state for the shelf token / url
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ shelfMetrics.addImpressionsFieldsToSimilarItemsShelf(objectGraph, shelf, metricsTargetType, metricsIdType);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ const shelfToken = new productPageCommon.ProductPageShelfToken(productData.id, (_a = shelfItems.remainingItems) !== null && _a !== void 0 ? _a : [], shelf.title, shouldInferSeeAllFromFetchedItems, undefined, contentType, offerStyle, null, shelf.refreshUrl, recoMetricsData, supportsArcade, ProductPageOnDemandShelfType.SimilarItems);
+ insertPlaceholdersIntoProductPageShelfIfRequired(objectGraph, shelf, shelfToken);
+ shelf.url = productPageCommon.shelfContentUrl(objectGraph, shelfToken, shelfMetrics, destinationPageInformation);
+ if (isAdEnabled) {
+ const url = new URL(shelf.url);
+ url.param(Parameters.isShelfWithAd, "true");
+ url.param(Parameters.shelfWithAdPlacementType, "productPageYMAL");
+ url.param(Parameters.id, productData.id);
+ shelf.url = url.build();
+ }
+ }
+ else {
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ shelfMetrics.addImpressionsFieldsToSimilarItemsShelf(objectGraph, shelf, metricsTargetType, metricsIdType);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ }
+ return shelf;
+ });
+}
+/**
+ * Create a shelf of similar items for the secondary look up.
+ *
+ * @param dataItems The raw data items from the secondary fetch.
+ * @param shelfToken The shelf token for this shelf.
+ * @returns A similar items shelf.
+ */
+export function createSecondaryShelf(objectGraph, dataItems, shelfToken, adResponse) {
+ return validation.context("similarItemsSecondaryShelf", () => {
+ var _a, _b;
+ if (serverData.isNullOrEmpty(dataItems)) {
+ return null;
+ }
+ if (objectGraph.client.isCompanionVisionApp) {
+ return null;
+ }
+ const shelfMetrics = new ProductPageShelfMetrics(shelfToken.sourcePageInformation, shelfToken.sourceLocationTracker, shelfToken.sourceSequenceId);
+ const metricsTargetType = "similarItems";
+ const metricsIdType = "relationship";
+ const metricsOptions = {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: metricsTargetType,
+ id: ProductPageShelfMetrics.similarItemsShelfId,
+ idType: metricsIdType,
+ excludeAttribution: true,
+ recoMetricsData: shelfToken.recoMetricsData,
+ };
+ metricsHelpersLocation.pushContentLocation(objectGraph, metricsOptions, shelfToken.title);
+ const shelf = new models.Shelf(shelfToken.contentType);
+ let adIncidentRecorder;
+ if (isAdPlacementEnabled(objectGraph, "productPageYMAL") ||
+ isAdPlacementEnabled(objectGraph, "productPageYMALDuringDownload")) {
+ adIncidentRecorder = adIncidents.newRecorder(objectGraph, shelfToken.sourcePageInformation.iAdInfo);
+ adIncidents.recordAdResponseEventsIfNeeded(objectGraph, adIncidentRecorder, adResponse);
+ const adStitcher = adStitcherForOnDeviceProductPageYMALAdvertData(objectGraph, adResponse);
+ // Update metrics data for a new ad request.
+ (_a = shelfMetrics.metricsPageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.updateForAdResponse(objectGraph, adResponse);
+ /**
+ * *** Ad Stitching ***
+ * Get all the tasks for the current shelf, and iterate over the tasks, inserting them in the specified position.
+ */
+ const adTasks = adStitch.consumeTasksForShelfIdentifier(adStitcher, (_b = metricsHelpersLocation.currentLocation(shelfMetrics.locationTracker)) === null || _b === void 0 ? void 0 : _b.id);
+ adTasks.forEach((task) => {
+ // Remove any organic dupes of the ad we're about to insert.
+ dataItems = dataItems.filter((data) => data.id !== task.data.id);
+ dataItems.splice(task.positionInfo.slot, 0, task.data);
+ });
+ shelf.adIncidents = adIncidents.recordedIncidents(objectGraph, adIncidentRecorder);
+ }
+ const shelfItems = productPageCommon.lockupsFromData(objectGraph, dataItems, shelfMetrics, content.artworkUseCaseFromShelfStyle(objectGraph, shelfToken.contentType), shelfToken.contentType, shelfToken.offerStyle, null, shelfToken.recoMetricsData);
+ if (serverData.isNullOrEmpty(shelfItems)) {
+ return null;
+ }
+ shelf.title = shelfToken.title;
+ shelf.items = shelfItems.items;
+ shelf.isHorizontal = true;
+ const hasShelfBackground = !objectGraph.client.isTV && !objectGraph.client.isiOS;
+ if (hasShelfBackground) {
+ shelf.background = {
+ type: "color",
+ color: productPageCommon.grayShelfBackgroundColor,
+ };
+ }
+ // Add all the ids on the shelf to attach to the refreshUrl as a fallback in case the shelf can't be refreshed with new content.
+ // Create a `refreshUrl` for this shelf so it can be refreshed by native during downloading.
+ const refreshShelfToken = new productPageCommon.ProductPageShelfToken(shelfToken.productId, dataItems, shelfToken.title, shelfToken.shouldInferSeeAllFromFetchesItems, undefined, shelfToken.contentType, null, null, null, null, null, ProductPageOnDemandShelfType.SimilarItems);
+ const shelfRefreshUrl = refreshUrl(objectGraph, shelfToken.productId, refreshShelfToken, shelfMetrics, shelfToken.destinationPageInformation);
+ shelf.refreshUrl = shelfRefreshUrl;
+ if (dataItems.length < 2 || objectGraph.client.isTV) {
+ shelf.rowsPerColumn = 1;
+ }
+ else if (shelfToken.supportsArcade || !objectGraph.client.isiOS) {
+ // Arcade and other platforms (except TV) show 2 apps per column.
+ shelf.rowsPerColumn = 2;
+ }
+ else {
+ // iOS regular product pages show 3 apps per column.
+ shelf.rowsPerColumn = 3;
+ }
+ shelfMetrics.addImpressionsFieldsToSimilarItemsShelf(objectGraph, shelf, metricsTargetType, metricsIdType);
+ applySearchAdMissedOpportunityToShelvesIfNeeded(objectGraph, [shelf], "productPageYMAL", metricsOptions.id, metricsOptions.pageInformation);
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ return shelf;
+ });
+}
+/**
+ * Returns a refresh URL for the similar items shelf.
+ * @param objectGraph - The object graph.
+ * @param id - The ID of the product this shelf appears on.
+ * @param token - The constructed ProductPageShelfToken.
+ * @param shelfMetrics - Metrics for the shelf.
+ * @param destinationPageInformation - Information for the destination page.
+ * @returns A string containing a URL.
+ */
+function refreshUrl(objectGraph, id, token, shelfMetrics, destinationPageInformation) {
+ const urlString = `${Protocol.internal}:/${Path.product}/${Path.shelf}/` +
+ productPageCommon.encodedShelfToken(token, shelfMetrics, destinationPageInformation);
+ const url = new URL(urlString);
+ url.param(Parameters.shelfRefreshType, ShelfRefreshType.productPageSimilarItems);
+ url.param(Parameters.id, id);
+ return url.build();
+}
+//# sourceMappingURL=similar-items-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/small-story-card-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/small-story-card-shelf.js
new file mode 100644
index 0000000..7881975
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/small-story-card-shelf.js
@@ -0,0 +1,137 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import { isDefinedNonNullNonEmpty, isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { insertPlaceholdersIntoProductPageShelfIfRequired } from "../../placeholders/placeholders";
+import * as todayHorizontalCardUtil from "../../today/today-horizontal-card-util";
+import { TodayParseContext } from "../../today/today-types";
+import * as productPageCommon from "../product-page-common";
+import { ProductPageOnDemandShelfType } from "../product-page-common";
+import { ProductPageShelfMetrics } from "../product-page-shelf-metrics";
+/**
+ * Create a shelf for the product page's small story cards.
+ *
+ * @param productData The raw product data response for a product page JSON fetch.
+ * @param data The raw data items for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @returns A small story card shelf.
+ */
+export function createInitialShelf(objectGraph, productData, data, shelfMetrics) {
+ return validation.context("smallStoryShelf", () => {
+ var _a;
+ if (isNullOrEmpty(data)) {
+ return null;
+ }
+ const metricsOptions = {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "swoosh",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ };
+ const title = objectGraph.loc.string("ProductPage.Section.FeaturedIn.Title");
+ metricsHelpersLocation.pushContentLocation(objectGraph, metricsOptions, title);
+ const remainingItems = [];
+ const cardUnavailable = function (cardData) {
+ remainingItems.push(cardData);
+ return false;
+ };
+ let shelf;
+ if (areMiniTodayCardsEnabled(objectGraph)) {
+ const todayParseContext = new TodayParseContext(metricsOptions.pageInformation, metricsOptions.locationTracker);
+ shelf = todayHorizontalCardUtil.shelfForMiniTodayCards(objectGraph, data, title, null, todayParseContext, cardUnavailable);
+ }
+ else {
+ const context = {
+ metricsLocationTracker: metricsOptions.locationTracker,
+ metricsPageInformation: metricsOptions.pageInformation,
+ filterOutMediaCardKinds: new Set(["appIcon"]), // policy decision to filter out these types on the featured in shelf of a product page.
+ };
+ shelf = todayHorizontalCardUtil.shelfForHorizontalCardItems(objectGraph, data, "smallStoryCard", title, null, context, cardUnavailable);
+ if (Array.isArray(shelf.items)) {
+ shelf.items = shelf.items.filter((item) => {
+ if (!(item instanceof models.TodayCard)) {
+ return true;
+ }
+ return todayHorizontalCardUtil.isHorizontalCardSupportedForKind(objectGraph, item.media.kind, "smallStoryCard");
+ });
+ }
+ }
+ shelf.isHorizontal = true;
+ if (!((_a = shelf.items) === null || _a === void 0 ? void 0 : _a.length) && remainingItems.length === 0) {
+ return null;
+ }
+ if (objectGraph.client.isiOS && objectGraph.bag.isOnDemandShelfFetchingEnabled) {
+ shelf.fetchStrategy = models.IncompleteShelfFetchStrategy.OnShelfWillAppear;
+ }
+ if (isDefinedNonNullNonEmpty(remainingItems)) {
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "featuredIn");
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ const shelfToken = new productPageCommon.ProductPageShelfToken(productData.id, remainingItems, shelf.title, false, undefined, shelf.contentType, null, null, null, null, null, ProductPageOnDemandShelfType.SmallStory);
+ insertPlaceholdersIntoProductPageShelfIfRequired(objectGraph, shelf, shelfToken);
+ shelf.url = productPageCommon.shelfContentUrl(objectGraph, shelfToken, shelfMetrics);
+ }
+ else {
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "featuredIn");
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ }
+ return shelf;
+ });
+}
+/**
+ * Create a shelf of small store card items for the secondary look up.
+ *
+ * @param dataItems The raw data items from the secondary fetch.
+ * @param shelfToken The shelf token for this shelf.
+ * @returns A small story card items shelf.
+ */
+export function createSecondaryShelf(objectGraph, dataItems, shelfToken) {
+ return validation.context("smallStorySecondaryShelf", () => {
+ if (isNullOrEmpty(dataItems)) {
+ return null;
+ }
+ const shelfMetrics = new ProductPageShelfMetrics(shelfToken.sourcePageInformation, shelfToken.sourceLocationTracker, shelfToken.sourceSequenceId);
+ const metricsOptions = {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ targetType: "swoosh",
+ id: `${shelfMetrics.getSequenceId()}`,
+ idType: "sequential",
+ excludeAttribution: true,
+ recoMetricsData: shelfToken.recoMetricsData,
+ };
+ metricsHelpersLocation.pushContentLocation(objectGraph, metricsOptions, shelfToken.title);
+ let shelf;
+ if (areMiniTodayCardsEnabled(objectGraph)) {
+ const todayParseContext = new TodayParseContext(metricsOptions.pageInformation, metricsOptions.locationTracker);
+ shelf = todayHorizontalCardUtil.shelfForMiniTodayCards(objectGraph, dataItems !== null && dataItems !== void 0 ? dataItems : [], shelfToken.title, null, todayParseContext);
+ }
+ else {
+ const context = {
+ metricsLocationTracker: metricsOptions.locationTracker,
+ metricsPageInformation: metricsOptions.pageInformation,
+ filterOutMediaCardKinds: productPageCommon.filteredMediaCardKindsForSmallStoryCardOnPlatform(objectGraph.host.platform),
+ };
+ shelf = todayHorizontalCardUtil.shelfForHorizontalCardItems(objectGraph, dataItems, "smallStoryCard", shelfToken.title, null, context);
+ if (Array.isArray(shelf === null || shelf === void 0 ? void 0 : shelf.items)) {
+ shelf.items = shelf.items.filter((item) => {
+ if (!(item instanceof models.TodayCard)) {
+ return true;
+ }
+ return todayHorizontalCardUtil.isHorizontalCardSupportedForKind(objectGraph, item.media.kind, "smallStoryCard");
+ });
+ }
+ }
+ shelf.isHorizontal = true;
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "featuredIn");
+ metricsHelpersLocation.popLocation(shelfMetrics.locationTracker);
+ metricsHelpersLocation.nextPosition(shelfMetrics.locationTracker);
+ return shelf;
+ });
+}
+function areMiniTodayCardsEnabled(objectGraph) {
+ return objectGraph.client.isiOS && objectGraph.featureFlags.isEnabled("mini_today_cards_product");
+}
+//# sourceMappingURL=small-story-card-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/text-links-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/text-links-shelf.js
new file mode 100644
index 0000000..a17dabd
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/text-links-shelf.js
@@ -0,0 +1,59 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as contentAttributes from "../../content/attributes";
+import * as productPageUtil from "../product-page-util";
+/**
+ * Creates a shelf of text links
+ * @param data The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The current product page shelf metrics
+ * @returns The shelf of text links. If objectGraph.client.deviceType is not tv, returns null.
+ */
+export function create(objectGraph, data, shelfMetrics) {
+ return validation.context("create", () => {
+ if (objectGraph.client.deviceType !== "tv") {
+ return null;
+ }
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ const shelf = new models.Shelf("paragraph");
+ shelf.isHorizontal = false;
+ const links = externalProductLinksAsParagraphsFromData(objectGraph, data);
+ if (links && links.length > 0) {
+ shelf.items = links;
+ }
+ if (productPageUtil.isShelfBased(objectGraph)) {
+ shelf.background = {
+ type: "darkOverlay",
+ };
+ }
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "links");
+ return shelf;
+ });
+}
+/**
+ * Product links displayable as text
+ * @param data The product data to use
+ * @returns {models.Paragraph[]} Product links displayed as paragraphs
+ */
+function externalProductLinksAsParagraphsFromData(objectGraph, data) {
+ if (!data) {
+ return null;
+ }
+ return validation.context("externalProductLinksAsParagraphsFromData", () => {
+ const links = [];
+ const developerWebsiteUrl = contentAttributes.contentAttributeAsString(objectGraph, data, "websiteUrl");
+ if (developerWebsiteUrl) {
+ const text = objectGraph.loc.string("DEVELOPER_WEBSITE_WITH_URL").replace("{URL}", developerWebsiteUrl);
+ const paragraph = new models.Paragraph(text);
+ if (objectGraph.featureFlags.isEnabled("tv_product_page_2024E")) {
+ paragraph.style = "unadorned";
+ }
+ paragraph.alignment = "center";
+ links.push(paragraph);
+ }
+ return links;
+ });
+}
+//# sourceMappingURL=text-links-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/textcard-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/textcard-shelf.js
new file mode 100644
index 0000000..b41eabd
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/textcard-shelf.js
@@ -0,0 +1,226 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import { shallowCopyOf } from "../../../foundation/util/objects";
+import * as contentAttributes from "../../content/attributes";
+import { developerUrlFromDeveloperData } from "../../content/content";
+import * as lockups from "../../lockups/lockups";
+import { addClickEventToAction } from "../../metrics/helpers/clicks";
+import { addMetricsEventsToPageWithInformation, fakeMetricsPageInformation } from "../../metrics/helpers/page";
+import * as badgesCommon from "../badges/badges-common";
+import * as descriptionShelf from "./description-shelf";
+import * as versionHistoryShelves from "./version-history-shelves";
+const tvTextCardList = [aboutTextCard, editorsNoteTextCard, whatsNewTextCard];
+const visionTextCardList = [aboutTextCard, whatsNewTextCard];
+/**
+ * Create a shelf for the text cards displayed on the tv product page.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @returns A textcard shelf.
+ */
+export function create(objectGraph, data, shelfMetrics) {
+ return validation.context("textCardShelfFromResponse", () => {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ let textCardList;
+ if (objectGraph.client.isTV) {
+ textCardList = tvTextCardList;
+ }
+ else if (objectGraph.client.isVision) {
+ textCardList = visionTextCardList;
+ }
+ else {
+ return null;
+ }
+ const shelf = new models.Shelf("textCard");
+ if (!objectGraph.client.isVision) {
+ shelf.title = objectGraph.loc.string("PRODUCT_SECTION_TEXT_CARDS");
+ shelf.isHorizontal = true;
+ }
+ shelf.items = createTextCards(objectGraph, textCardList, data, shelfMetrics);
+ if (objectGraph.client.isVision && shelf.items.length === 1) {
+ shelf.presentationHints = {
+ isSingleDensity: true,
+ };
+ }
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "links");
+ return shelf;
+ });
+}
+/**
+ * Creates a text card object
+ *
+ * @param objectGraph The current object graph
+ * @param style The style of the text card
+ * @param title The title text
+ * @param subtitle The subtitle text
+ * @param descriptionText The description text
+ * @param alertTitle The title to show when presenting as an alert
+ * @param captionTitle The caption title text
+ * @param captionSubtitle The caption subtitle text
+ * @param captionArtwork The artwork to display alongside caption text
+ * @param includeCaptionWhenExpanded Whether to display the caption when in an expanded state
+ * @param captionAction An action to run when selecting the caption
+ * @returns A built text card
+ */
+function createTextCard(objectGraph, style, title, subtitle, descriptionText, alertTitle, captionTitle, captionSubtitle, captionArtwork, includeCaptionWhenExpanded, captionAction) {
+ const textCard = new models.TextCard(style, title, subtitle);
+ if (objectGraph.host.isTV || objectGraph.client.isVision) {
+ const bodyParagraph = new models.Paragraph(descriptionText);
+ if (objectGraph.featureFlags.isEnabled("tv_product_page_2024E")) {
+ bodyParagraph.style = "unadorned";
+ }
+ bodyParagraph.isCollapsed = true;
+ textCard.bodyParagraph = bodyParagraph;
+ }
+ const paragraph = new models.Paragraph(descriptionText);
+ paragraph.wantsCollapsedNewlines = false;
+ if (objectGraph.client.isVision) {
+ textCard.captionTitle = captionTitle;
+ textCard.captionSubtitle = captionSubtitle;
+ textCard.captionArtwork = captionArtwork;
+ textCard.includeCaptionWhenExpanded = includeCaptionWhenExpanded;
+ textCard.captionAction = captionAction;
+ }
+ else {
+ textCard.regularAction = new models.ScrollingAlertAction(alertTitle, paragraph);
+ }
+ return textCard;
+}
+function aboutTextCard(objectGraph, data, metricsContext) {
+ const paragraph = descriptionShelf.paragraphFromData(objectGraph, data);
+ if (serverData.isNull(paragraph)) {
+ return null;
+ }
+ const descriptionText = paragraph.text;
+ if (objectGraph.client.isVision) {
+ const title = objectGraph.loc.string("PRODUCT_SECTION_DESCRIPTION");
+ const captionTitle = objectGraph.loc.string("PRODUCT_DEVELOPER_TITLE");
+ const captionSubtitle = mediaAttributes.attributeAsString(data, "artistName");
+ const developer = mediaRelationship.relationshipData(objectGraph, data, "developer");
+ const developerArtworkData = mediaAttributes.attributeAsDictionary(developer, "editorialArtwork.brandLogo");
+ const developerArtwork = badgesCommon.badgeArtwork(objectGraph, developerArtworkData);
+ // Developer action
+ let captionAction;
+ const developerUrl = developerUrlFromDeveloperData(objectGraph, developer);
+ if (serverData.isDefinedNonNull(developerUrl)) {
+ const developerAction = new models.FlowAction("page");
+ developerAction.title = mediaAttributes.attributeAsString(data, "artistName");
+ developerAction.pageUrl = developerUrl;
+ const flowBackAction = new models.FlowBackAction("sheetDismiss");
+ captionAction = new models.CompoundAction([flowBackAction, developerAction]);
+ addClickEventToAction(objectGraph, captionAction, {
+ targetType: "button",
+ id: developer.id,
+ actionType: "navigate",
+ pageInformation: metricsContext.pageInformation,
+ locationTracker: metricsContext.locationTracker,
+ });
+ }
+ const textCard = createTextCard(objectGraph, "text", title, null, descriptionText, title, captionTitle, captionSubtitle, developerArtwork, true, captionAction);
+ textCard.regularAction = createTextCardDetailPageFlowAction(objectGraph, textCard, data.id);
+ return textCard;
+ }
+ else {
+ const title = mediaAttributes.attributeAsString(data, "name");
+ const subtitle = lockups.subtitleFromData(objectGraph, data);
+ return createTextCard(objectGraph, "text", title, subtitle, descriptionText, title);
+ }
+}
+function editorsNoteTextCard(objectGraph, data, metricsContext) {
+ const editorialNotes = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialNotes");
+ const editorsNotes = serverData.asString(editorialNotes, "standard");
+ if (editorsNotes) {
+ let title = null;
+ let alertTitle = null;
+ let style = "text";
+ const editorialBadgeInfo = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialBadgeInfo");
+ if (editorialBadgeInfo) {
+ const badgeType = serverData.asString(editorialBadgeInfo, "editorialBadgeType");
+ if (badgeType && badgeType === "editorialPriority") {
+ style = "editorsChoice";
+ }
+ const alertTitleFromServer = serverData.asString(editorialBadgeInfo, "nameForDisplay");
+ if (alertTitleFromServer) {
+ alertTitle = alertTitleFromServer;
+ }
+ else {
+ alertTitle = objectGraph.loc.string("APP_STORE_EDITORS_NOTES");
+ }
+ }
+ else {
+ title = objectGraph.loc.string("APP_STORE_EDITORS_NOTES");
+ alertTitle = objectGraph.loc.string("APP_STORE_EDITORS_NOTES");
+ }
+ return createTextCard(objectGraph, style, title, "", editorsNotes, alertTitle);
+ }
+ return null;
+}
+function whatsNewTextCard(objectGraph, data, metricsContext) {
+ const isBundle = data.type === "app-bundles";
+ const isFirstPartyHideableApp = mediaAttributes.attributeAsBooleanOrFalse(data, "isFirstPartyHideableApp");
+ const isPreorder = mediaAttributes.attributeAsBoolean(data, "isPreorder");
+ const mostRecentVersionNotes = versionHistoryShelves.mostRecentTitledParagraphFromVersionHistory(objectGraph, data, isFirstPartyHideableApp, isBundle, isPreorder);
+ if (mostRecentVersionNotes) {
+ const versionNumber = mostRecentVersionNotes.primarySubtitle;
+ const releaseNotes = mostRecentVersionNotes.text;
+ const title = objectGraph.loc.string("ProductPage.Section.MostRecentVersion.Title");
+ if (objectGraph.client.isVision) {
+ const releaseDate = mostRecentVersionNotes.secondarySubtitle;
+ const textCard = createTextCard(objectGraph, "text", title, null, releaseNotes, title, releaseDate, versionNumber);
+ const versions = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, data, "versionHistory");
+ if (versions.length > 0 && !isFirstPartyHideableApp && !isBundle && !isPreorder) {
+ textCard.regularAction = versionHistoryShelves.versionHistoryPageAction(objectGraph, versions, data.id);
+ }
+ return textCard;
+ }
+ else {
+ return createTextCard(objectGraph, "text", title, versionNumber, releaseNotes, title);
+ }
+ }
+ return null;
+}
+function createTextCards(objectGraph, textCardList, data, shelfMetrics) {
+ const textCards = [];
+ const metricsContext = {
+ locationTracker: shelfMetrics.locationTracker,
+ pageInformation: shelfMetrics.metricsPageInformation,
+ };
+ for (const f of textCardList) {
+ const textCard = f(objectGraph, data, metricsContext);
+ if (textCard) {
+ textCards.push(textCard);
+ }
+ }
+ return textCards;
+}
+/**
+ * Creates a flow action for navigating to the detail page for a text card.
+ *
+ * @param objectGraph The current object graph
+ * @param textCard The source text card
+ * @param productId The id of the owning product
+ * @returns A flow action
+ */
+function createTextCardDetailPageFlowAction(objectGraph, textCard, productId) {
+ const detailTextCard = shallowCopyOf(textCard);
+ detailTextCard.isExpanded = true;
+ const shelf = new models.Shelf("textCard");
+ shelf.items = [detailTextCard];
+ shelf.presentationHints = {
+ isSingleDensity: true,
+ };
+ const page = new models.GenericPage([shelf]);
+ page.title = textCard.title;
+ const pageInformation = fakeMetricsPageInformation(objectGraph, "DescriptionDetails", productId, "");
+ addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation);
+ const flowAction = new models.FlowAction("textCardDetail");
+ flowAction.title = textCard.title;
+ flowAction.pageData = page;
+ return flowAction;
+}
+//# sourceMappingURL=textcard-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/version-history-shelves.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/version-history-shelves.js
new file mode 100644
index 0000000..41615fb
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/version-history-shelves.js
@@ -0,0 +1,149 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as contentAttributes from "../../content/attributes";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersPage from "../../metrics/helpers/page";
+import { isSome } from "@jet/environment";
+/**
+ * Create a shelf for the product page's version history.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @param shelfMetrics The product page shelf metrics.
+ * @param shelfContext A collection of any other variables used when creating this shelf.
+ * @returns A version history shelf.
+ */
+export function create(objectGraph, data, shelfMetrics, shelfContext) {
+ return validation.context("create", () => {
+ return standardMostRecentVersionShelf(objectGraph, data, shelfContext.isFirstPartyHideableApp, shelfContext.isBundle, shelfContext.isPreorder, shelfMetrics);
+ });
+}
+/**
+ * Create a shelf for the product page description.
+ *
+ * @param data The raw data response for a product page JSON fetch.
+ * @param isFirstPartyHideableApp Indicates whether this app is first party and/or hideable.
+ * @param isBundle Whether this is a bundle id
+ * @param isPreorder Whether the buy is for a preorder.
+ * @returns A description shelf.
+ */
+export function mostRecentTitledParagraphFromVersionHistory(objectGraph, data, isFirstPartyHideableApp, isBundle, isPreorder) {
+ if (!isFirstPartyHideableApp && !isBundle && !isPreorder) {
+ const versions = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, data, "versionHistory");
+ if (versions.length > 1) {
+ const versionHistory = versionHistoryFromApiResponse(objectGraph, versions);
+ if (versionHistory.length > 0) {
+ return titledParagraphFromVersionHistoryEntry(objectGraph, versions[0], "overview");
+ }
+ }
+ }
+ return null;
+}
+/**
+ * A flow action to navigate to the version history.
+ *
+ * @param versions The raw data response for the version history of a product.
+ * @param productId The product ID.
+ * @returns A flow action for navigating to the version history.
+ */
+export function versionHistoryPageAction(objectGraph, versions, productId) {
+ return validation.context("versionHistoryPageAction", () => {
+ const versionHistory = versionHistoryFromApiResponse(objectGraph, versions);
+ let action;
+ if (versionHistory.length > 0) {
+ // Create version history page
+ const allVersionHistoryShelf = new models.Shelf("titledParagraph");
+ allVersionHistoryShelf.items = versionHistory;
+ const page = new models.GenericPage([allVersionHistoryShelf]);
+ page.title = objectGraph.loc.string("VERSION_HISTORY_PAGE_TITLE", "Version History");
+ if (objectGraph.client.deviceType !== "watch") {
+ page.presentationOptions = ["prefersLargeTitle"];
+ }
+ const pageInformation = metricsHelpersPage.fakeMetricsPageInformation(objectGraph, "VersionHistory", productId, "");
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation);
+ // Create version history action
+ const versionHistorySeeAll = new models.FlowAction("versionHistory");
+ versionHistorySeeAll.title = objectGraph.loc.string("ACTION_VERSION_HISTORY_SEE_ALL");
+ versionHistorySeeAll.pageData = page;
+ action = versionHistorySeeAll;
+ }
+ return action;
+ });
+}
+function standardMostRecentVersionShelf(objectGraph, productData, isFirstPartyHideableApp, isBundle, isPreorder, shelfMetrics) {
+ return validation.context("standardMostRecentVersionShelf", () => {
+ if (!isFirstPartyHideableApp && !isBundle && !isPreorder) {
+ const versions = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, productData, "versionHistory");
+ if (versions.length > 1) {
+ const versionHistory = versionHistoryFromApiResponse(objectGraph, versions);
+ if (versionHistory.length > 0) {
+ const shelf = new models.Shelf("titledParagraph");
+ shelf.title = objectGraph.loc.string("ProductPage.Section.MostRecentVersion.Title");
+ // Add recent version
+ const recentVersion = titledParagraphFromVersionHistoryEntry(objectGraph, versions[0], "overview");
+ shelf.items = [recentVersion];
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "versionHistory");
+ if (shelf) {
+ if (objectGraph.client.deviceType !== "watch") {
+ shelf.seeAllAction = versionHistorySeeAllAction(objectGraph, versions, productData.id, shelfMetrics);
+ }
+ return shelf;
+ }
+ }
+ }
+ }
+ return null;
+ });
+}
+function versionHistorySeeAllAction(objectGraph, versions, productId, shelfMetrics) {
+ return validation.context("versionHistorySeeAllAction", () => {
+ const action = versionHistoryPageAction(objectGraph, versions, productId);
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, action, null, {
+ pageInformation: shelfMetrics.metricsPageInformation,
+ locationTracker: shelfMetrics.locationTracker,
+ });
+ return action;
+ });
+}
+/**
+ * Create an array of product releases from an array of version history data objects.
+ * @param versions The data objects to convert.
+ * @return An array of titled paragraph model objects.
+ */
+function versionHistoryFromApiResponse(objectGraph, versions) {
+ if (!versions) {
+ return [];
+ }
+ return versions.map((version) => {
+ return titledParagraphFromVersionHistoryEntry(objectGraph, version, "detail");
+ });
+}
+function titledParagraphFromVersionHistoryEntry(objectGraph, versionEntry, style) {
+ return validation.context("titledParagraphFromVersionHistoryEntry", () => {
+ // Notes
+ let releaseNotes = serverData.asString(versionEntry, "releaseNotes");
+ if (isSome(releaseNotes)) {
+ releaseNotes = releaseNotes.trim();
+ }
+ const release = new models.TitledParagraph(releaseNotes, style);
+ // Version
+ const versionString = serverData.asString(versionEntry, "versionDisplay");
+ const versionTemplate = objectGraph.loc.string("VERSION_STRING_TEMPLATE");
+ switch (style) {
+ case "detail":
+ release.primarySubtitle = versionString;
+ break;
+ default:
+ release.primarySubtitle = versionTemplate.replace("{version}", versionString);
+ break;
+ }
+ // Release date
+ const releaseDateString = serverData.asString(versionEntry, "releaseTimestamp");
+ if (releaseDateString) {
+ const releaseDate = new Date(releaseDateString);
+ release.secondarySubtitle = objectGraph.loc.relativeDate(releaseDate);
+ }
+ return release;
+ });
+}
+//# sourceMappingURL=version-history-shelves.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/video-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/video-shelf.js
new file mode 100644
index 0000000..0d58160
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/product-page/shelves/video-shelf.js
@@ -0,0 +1,38 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../../api/models";
+import * as productPageUtil from "../product-page-util";
+/**
+ * Create a shelf for the product page's version history.
+ *
+ * @param videos An array of videos to present in this shelf.
+ * @param shelfMetrics The product page shelf metrics.
+ * @returns A video shelf.
+ */
+export function create(objectGraph, videos, shelfMetrics) {
+ return validation.context("videoShelf", () => {
+ if (!videos || videos.length === 0) {
+ return null;
+ }
+ let shelf;
+ if (productPageUtil.isShelfBased(objectGraph)) {
+ shelf = new models.Shelf("productMediaItem");
+ shelf.title = objectGraph.loc.string("ProductPage.Section.Videos.Title");
+ shelf.items = videos.map((video) => {
+ const productMediaItem = new models.ProductMediaItem();
+ productMediaItem.video = video;
+ return productMediaItem;
+ });
+ }
+ else {
+ shelf = new models.Shelf("framedVideo");
+ shelf.title = objectGraph.loc.string("ProductPage.Section.Videos.Title");
+ shelf.items = videos.map((video) => {
+ return new models.FramedVideo(video, true, "text/plain", null, null, true);
+ });
+ }
+ shelf.isHorizontal = true;
+ shelfMetrics.addImpressionsToShelf(objectGraph, shelf, "videos");
+ return shelf;
+ });
+}
+//# sourceMappingURL=video-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/refresh/page-refresh-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/refresh/page-refresh-controller.js
new file mode 100644
index 0000000..a7c51d7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/refresh/page-refresh-controller.js
@@ -0,0 +1,123 @@
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { ResponseMetadata } from "../../foundation/network/network";
+/**
+ * Creates a new `PageRefreshController` with the provided information.
+ * @param response the response from the network request.
+ * @param refreshWhileVisibleForNextPreferredContentRefreshDate indicates that the page supports refreshing while visible, but only where the nextPreferredContentRefreshDate is used as the refresh date.
+ * @returns a `PageRefreshController`.
+ */
+export function newPageRefreshControllerFromResponse(response, refreshWhileVisibleForNextPreferredContentRefreshDate = false) {
+ return {
+ timeToLive: timeToLiveFromResponse(response),
+ nextPreferredContentRefreshDate: null,
+ refreshWhileVisibleForNextPreferredContentRefreshDate: refreshWhileVisibleForNextPreferredContentRefreshDate,
+ };
+}
+/**
+ * Creates a new `PageRefreshController`.
+ * This is primarily used for shelf hydration where we don't have a full page response to use for TTL data.
+ * @returns an empty `PageRefreshController`.
+ */
+export function newPageRefreshController() {
+ return {
+ timeToLive: null,
+ nextPreferredContentRefreshDate: null,
+ refreshWhileVisibleForNextPreferredContentRefreshDate: false,
+ };
+}
+/**
+ * Adds a preferredContentRefreshDate to the refreshController if it's sooner than the currently known nextPreferredContentRefreshDate.
+ * Provided date must be in the future.
+ * @param nextPreferredContentRefreshDate the new date to check and add.
+ * @param refreshController the refreshController to add the new date to
+ */
+export function addNextPreferredContentRefreshDate(newPreferredContentRefreshDate, refreshController) {
+ if (serverData.isNull(refreshController) || serverData.isNull(newPreferredContentRefreshDate)) {
+ return;
+ }
+ if ((serverData.isNull(refreshController.nextPreferredContentRefreshDate) ||
+ newPreferredContentRefreshDate.getTime() < refreshController.nextPreferredContentRefreshDate.getTime()) &&
+ newPreferredContentRefreshDate.getTime() > new Date().getTime()) {
+ refreshController.nextPreferredContentRefreshDate = newPreferredContentRefreshDate;
+ }
+}
+/**
+ * Generates a `PageRefreshPolicy` that can be attached to a page to define the refresh logic of that page.
+ * @param refreshController the object that has collected data to create a suitable refresh policy
+ * @returns the refresh policy, if a content TTL or nextPreferredContentRefreshDate has been provided.
+ */
+export function pageRefreshPolicyForController(objectGraph, refreshController) {
+ if (!isAutomaticPageRefreshEnabled(objectGraph)) {
+ return null;
+ }
+ if (serverData.isNull(refreshController)) {
+ return null;
+ }
+ let date;
+ let usingNextPreferredContentRefreshDate = false;
+ if (serverData.isDefinedNonNull(refreshController.timeToLive) &&
+ serverData.isDefinedNonNull(refreshController.nextPreferredContentRefreshDate)) {
+ const timeToLiveDate = dateByAddingTimeToNow(refreshController.timeToLive);
+ if (timeToLiveDate.getTime() < refreshController.nextPreferredContentRefreshDate.getTime() &&
+ timeToLiveDate.getTime() > new Date().getTime()) {
+ date = timeToLiveDate;
+ }
+ else {
+ date = refreshController.nextPreferredContentRefreshDate;
+ usingNextPreferredContentRefreshDate = true;
+ }
+ }
+ else if (serverData.isDefinedNonNull(refreshController.timeToLive)) {
+ date = dateByAddingTimeToNow(refreshController.timeToLive);
+ }
+ else if (serverData.isDefinedNonNull(refreshController.nextPreferredContentRefreshDate)) {
+ date = refreshController.nextPreferredContentRefreshDate;
+ usingNextPreferredContentRefreshDate = true;
+ }
+ else {
+ return null;
+ }
+ if (date.getTime() <= new Date().getTime()) {
+ return null;
+ }
+ const refreshWhileVisible = usingNextPreferredContentRefreshDate && refreshController.refreshWhileVisibleForNextPreferredContentRefreshDate;
+ return new models.PageRefreshPolicy("timeToLive", 0.0, null, date, refreshWhileVisible);
+}
+/**
+ * Returns the nextPreferredContentRefreshDate for the provided refresh controller.
+ * Primarily used when fetching individual shelves so we can attach a refresh date, rather
+ * than a `PageRefreshPolicy`, which sits up at a page level.
+ * @param refreshController the object that has collected refresh data.
+ * @returns the nextPreferredContentRefreshDate, if it has been provided.
+ */
+export function nextPreferredContentRefreshDateForController(refreshController) {
+ return refreshController === null || refreshController === void 0 ? void 0 : refreshController.nextPreferredContentRefreshDate;
+}
+/**
+ * Returns the time to live value from the response data.
+ *
+ * @param dataContainer The response data container from which to extract the content TTL
+ * @returns a content TTL, if any, or null.
+ */
+function timeToLiveFromResponse(response) {
+ return response[ResponseMetadata.contentMaxAge];
+}
+/**
+ * Returns a new date, adjusted by the number of seconds.
+ *
+ * @param seconds The number of seconds to add to now
+ * @returns The adjusted Date
+ */
+function dateByAddingTimeToNow(seconds) {
+ const date = new Date();
+ date.setSeconds(date.getSeconds() + seconds);
+ return date;
+}
+/**
+ * Returns whether refreshing pages automatically is enabled in the bag.
+ */
+function isAutomaticPageRefreshEnabled(objectGraph) {
+ return objectGraph.bag.enableAutomaticPageRefresh;
+}
+//# sourceMappingURL=page-refresh-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/room/room-common.js b/node_modules/@jet-app/app-store/tmp/src/common/room/room-common.js
new file mode 100644
index 0000000..d1ab4a0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/room/room-common.js
@@ -0,0 +1,139 @@
+import { makeRoomPageIntent } from "../../api/intents/room-page-intent";
+import * as models from "../../api/models";
+import * as mediaDataFetching from "../../foundation/media/data-fetching";
+import { AbstractMediaApiPageBuilder } from "../builders/abstract-media-api-page-builder";
+import * as pagination from "../builders/pagination";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import { generateRoutes } from "../util/generate-routes";
+import { defaultAttributesForRoomRequest } from "./room-request";
+import { withAsyncValidationContext } from "../../foundation/util/validation-util";
+import { defaultRoomShelfContentType, roomPageWithContent } from "./room-page";
+export class RoomPageToken {
+ constructor() {
+ /// The store platform data profile to use.
+ this.profile = "lockup";
+ /// The maximum number of ids to fetch per load more request.
+ this.maxPerPage = pagination.suggestedMaxPerPage;
+ }
+}
+export class AbstractRoomBuilder extends AbstractMediaApiPageBuilder {
+ //
+ // Begin Region: Common
+ //
+ defaultAttributes(objectGraph) {
+ return defaultAttributesForRoomRequest(objectGraph);
+ }
+ defaultPlatforms(objectGraph) {
+ return mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph);
+ }
+ pageType() {
+ return "page";
+ }
+ //
+ // Begin Region: Load More
+ //
+ generatePaginationRequest(objectGraph, url, parameters, token) {
+ const pageToken = token;
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph, pageToken.remainingContent);
+ return mediaApiRequest;
+ }
+ async renderPaginatedPage(objectGraph, data, token) {
+ const pageToken = token;
+ return await this.pageWithContent(objectGraph, data, pageToken);
+ }
+ //
+ // Begin Region: Internal API
+ //
+ /**
+ * Generates a room page given a list of lockups
+ *
+ * @param {DataContainer} roomContents
+ * @param {RoomPageToken} token The token representing the content to load
+ * @param {boolean} shouldUpdateRemainingContent Whether to update the remaining content in the token,
+ * false when being handled outside of this method
+ * @returns {GenericPage}
+ */
+ async pageWithContent(objectGraph, roomContents, token, shouldUpdateRemainingContent = true) {
+ return await withAsyncValidationContext("pageWithContent", async () => {
+ return roomPageWithContent(objectGraph, roomContents, token, shouldUpdateRemainingContent);
+ });
+ }
+}
+/**
+ * Creates a page that can be used for side-packing see all pages into a room.
+ *
+ * @param objectGraph
+ * @param {string} title The title of the destination page
+ * @param {ShelfContentType} preferredShelfContentType The content type to use for the page
+ * @returns {GenericPage} A GenericPage which will use the parentShelfItems from the see all to render the initial room
+ */
+export function seeAllPage(objectGraph, title, preferredShelfContentType) {
+ const shelf = new models.Shelf(preferredShelfContentType || defaultRoomShelfContentType);
+ shelf.isHorizontal = false;
+ shelf.items = "parentShelfItems";
+ const page = new models.GenericPage([shelf]);
+ page.isIncomplete = true;
+ page.title = title;
+ if (platformPrefersLargeTitles(objectGraph)) {
+ page.presentationOptions = ["prefersLargeTitle"];
+ }
+ return page;
+}
+export function platformPrefersLargeTitles(objectGraph) {
+ return objectGraph.client.isWatch || objectGraph.client.isMac;
+}
+export class AbstractActionRoomBuilder extends AbstractRoomBuilder {
+ async handlePage(objectGraph, url, parameters, matchedRuleIdentifier, referrerData, isIncomingURL) {
+ return this.action();
+ }
+ generatePageRequest(objectGraph, url, parameters) {
+ throw new Error(`generatePageRequest is not supported on: ${this.builderClass}`);
+ }
+ async renderPage(objectGraph, data, parameters, additionalPageRequirements) {
+ throw new Error(`renderPage is not supported on: ${this.builderClass}`);
+ }
+}
+export class AbstractFilteredRoom extends AbstractRoomBuilder {
+ requestWithFilter(objectGraph, type, value) {
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph)
+ .forType("apps")
+ .includingMacOSCompatibleIOSAppsWhenSupported(true)
+ .withFilter(type, value);
+ return mediaApiRequest;
+ }
+ async renderPage(objectGraph, data, parameters, additionalPageRequirements) {
+ return await withAsyncValidationContext("renderPage", async () => {
+ // Create the token
+ const token = new RoomPageToken();
+ token.url = this.paginationUrl;
+ token.metricsPageInformation = this.pageInformation(objectGraph, data, parameters);
+ token.shouldFilter = false;
+ token.metricsLocationTracker = metricsHelpersLocation.newLocationTracker();
+ const page = await this.pageWithContent(objectGraph, data, token);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, token.metricsPageInformation);
+ return page;
+ });
+ }
+}
+/// MARK: `RoomPageIntentController` Routing
+const { routes: roomPageRoutesWithoutPlatform, makeCanonicalUrl: makeCanonicalRoomPageUrlWithoutPlatform } = generateRoutes(makeRoomPageIntent, "/room/{id}");
+const { routes: roomPageRoutesWithPlatform, makeCanonicalUrl: makeCanonicalRoomPageUrlWithPlatform } = generateRoutes(makeRoomPageIntent, "/{platform}/room/{id}");
+/**
+ * Define the `RouteProvider` routes for the `RoomPageIntentController`
+ */
+export function roomPageRoutes(objectGraph) {
+ return [...roomPageRoutesWithoutPlatform(objectGraph), ...roomPageRoutesWithPlatform(objectGraph)];
+}
+/**
+ * Generate the URL used by the "web" client to route to a {@linkcode RoomPageIntent}
+ */
+export function makeCanonicalRoomPageUrl(objectGraph, intent) {
+ if ("platform" in intent) {
+ return makeCanonicalRoomPageUrlWithPlatform(objectGraph, intent);
+ }
+ else {
+ return makeCanonicalRoomPageUrlWithoutPlatform(objectGraph, intent);
+ }
+}
+//# sourceMappingURL=room-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/room/room-page.js b/node_modules/@jet-app/app-store/tmp/src/common/room/room-page.js
new file mode 100644
index 0000000..a54ca0e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/room/room-page.js
@@ -0,0 +1,105 @@
+import { unwrapOptional as unwrap } from "@jet/environment/types/optional";
+import { GenericPage, Shelf } from "../../api/models";
+import { attributeAsBooleanOrFalse, attributeAsString } from "../../foundation/media/attributes";
+import { metricsFromMediaApiObject, dataCollectionFromDataContainer, dataFromDataContainer, } from "../../foundation/media/data-structure";
+import { relationship } from "../../foundation/media/relationships";
+import { addMetricsEventsToPageWithInformation, metricsPageInformationFromMediaApiResponse, } from "../metrics/helpers/page";
+import { addImpressionFields } from "../metrics/helpers/impressions";
+import { pushContentLocation, newLocationTracker } from "../metrics/helpers/location";
+import { lockupsFromData } from "../lockups/lockups";
+import { clientIdentifierForEditorialContextInData } from "../lockups/editorial-context";
+import { artworkUseCaseFromShelfStyle } from "../content/content";
+import { nextDataRange } from "../builders/pagination";
+import { platformPrefersLargeTitles, RoomPageToken } from "./room-common";
+export const defaultRoomShelfContentType = "mediumLockup";
+/**
+ * Provides the shelf content type for featured content kinds that require a particular shelf content type that differs
+ * from the default.
+ *
+ * @param fcKind The featured content ID.
+ * @returns {ShelfContentType} The content type for the shelf.
+ */
+export function preferredShelfStyleForFcKind(fcKind) {
+ if (fcKind === null || fcKind === undefined) {
+ return null;
+ }
+ switch (fcKind) {
+ case 431 /* FeaturedContentID.AppStore_iAPShelf */:
+ return "inAppPurchaseTiledLockup";
+ default:
+ return null;
+ }
+}
+export function renderRoomPage(objectGraph, data) {
+ const roomData = unwrap(dataFromDataContainer(objectGraph, data));
+ const roomContents = unwrap(relationship(roomData, "contents"));
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "Room", roomData.id, data);
+ // Create the token
+ const token = new RoomPageToken();
+ token.remainingContent = roomContents.data;
+ token.shouldFilter = !attributeAsBooleanOrFalse(roomData, "doNotFilter");
+ token.metricsPageInformation = pageInformation;
+ token.metricsLocationTracker = newLocationTracker();
+ const fcKindString = attributeAsString(roomData, "editorialElementKind");
+ token.preferredShelfContentType = preferredShelfStyleForFcKind(Number(fcKindString));
+ token.clientIdentifierOverride = clientIdentifierForEditorialContextInData(objectGraph, roomData);
+ // Render the room
+ token.title = unwrap(attributeAsString(roomData, "title"));
+ const page = roomPageWithContent(objectGraph, roomContents, token);
+ page.title = token.title;
+ if (platformPrefersLargeTitles(objectGraph)) {
+ page.presentationOptions = ["prefersLargeTitle"];
+ }
+ addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation);
+ return page;
+}
+/**
+ * Shared rendering logic for all kinds of "room" pages
+ */
+export function roomPageWithContent(objectGraph, roomContents, token, shouldUpdateRemainingContent = true) {
+ var _a;
+ const shelfStyle = token.preferredShelfContentType || defaultRoomShelfContentType;
+ const shelf = new Shelf(shelfStyle);
+ const shelfMetricsOptions = {
+ id: null,
+ kind: null,
+ softwareType: null,
+ targetType: "swoosh",
+ title: token.title,
+ pageInformation: token.metricsPageInformation,
+ locationTracker: token.metricsLocationTracker,
+ idType: "its_contentId",
+ recoMetricsData: (_a = metricsFromMediaApiObject(roomContents)) !== null && _a !== void 0 ? _a : {},
+ };
+ /// add impression fields
+ addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ pushContentLocation(objectGraph, shelfMetricsOptions, token.title);
+ shelf.isHorizontal = false;
+ shelf.shouldFilterApps = token.shouldFilter;
+ const contents = dataCollectionFromDataContainer(roomContents);
+ if (shouldUpdateRemainingContent) {
+ token.remainingContent = [];
+ }
+ shelf.items = lockupsFromData(objectGraph, contents, {
+ contentUnavailable: (index, _dataItem) => {
+ if (shouldUpdateRemainingContent) {
+ token.remainingContent = nextDataRange(objectGraph, contents, index);
+ }
+ return true;
+ },
+ lockupOptions: {
+ metricsOptions: {
+ pageInformation: token.metricsPageInformation,
+ locationTracker: token.metricsLocationTracker,
+ },
+ clientIdentifierOverride: token.clientIdentifierOverride,
+ artworkUseCase: artworkUseCaseFromShelfStyle(objectGraph, shelfStyle),
+ },
+ });
+ const page = new GenericPage([shelf]);
+ if (token.remainingContent.length) {
+ page.nextPage = token;
+ }
+ return page;
+}
+//# sourceMappingURL=room-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/room/room-request.js b/node_modules/@jet-app/app-store/tmp/src/common/room/room-request.js
new file mode 100644
index 0000000..ba0337b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/room/room-request.js
@@ -0,0 +1,48 @@
+import { Request, defaultAdditionalPlatformsForClient } from "../../foundation/media/data-fetching";
+import { shouldFetchCustomAttributes } from "../product-page/product-page-variants";
+import { shouldUsePrerenderedIconArtwork } from "../content/content";
+import { setPreviewPlatform } from "../preview-platform";
+export function createRoomRequest(objectGraph, roomId) {
+ const mediaApiRequest = new Request(objectGraph)
+ .withIdOfType(roomId, "rooms")
+ .includingAgeRestrictions()
+ .includingMacOSCompatibleIOSAppsWhenSupported(true);
+ if (objectGraph.client.isWeb) {
+ mediaApiRequest.includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph));
+ setPreviewPlatform(objectGraph, mediaApiRequest);
+ }
+ return mediaApiRequest;
+}
+export function prepareRoomRequest(objectGraph, request) {
+ return request
+ .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph))
+ .includingAttributes(defaultAttributesForRoomRequest(objectGraph))
+ .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph))
+ .includingAgeRestrictions()
+ .includingMacOSCompatibleIOSAppsWhenSupported(true);
+}
+export function defaultAttributesForRoomRequest(objectGraph) {
+ const attributes = [
+ "editorialArtwork",
+ "editorialVideo",
+ "isAppleWatchSupported",
+ "requiredCapabilities",
+ ];
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.client.isVision) {
+ attributes.push("compatibilityControllerRequirement");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ return attributes;
+}
+//# sourceMappingURL=room-request.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-categories.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-categories.js
new file mode 100644
index 0000000..927de7c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-categories.js
@@ -0,0 +1,334 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaRelationships from "../../../foundation/media/relationships";
+import { pageRouter } from "../../builders/routing";
+import { hrefToRoutableUrl } from "../../builders/url-mapping";
+import * as content from "../../content/content";
+import { addClickEventToAction, addClickEventToSeeAllAction } from "../../metrics/helpers/clicks";
+import { addImpressionFields } from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { addMetricsEventsToPageWithInformation, metricsPageInformationFromMediaApiResponse, } from "../../metrics/helpers/page";
+import { createClickMetricsOptionsForChartOrCategory, createMetricsOptionsForChartOrCategory, } from "../../metrics/helpers/search/search-shelves";
+import { combinedRecoMetricsDataFromMetricsData } from "../../metrics/helpers/util";
+import * as onDevicePersonalization from "../../personalization/on-device-personalization";
+import * as searchShelves from "./search-shelves";
+import * as groupingShelfControllerCommon from "../../grouping/shelf-controllers/grouping-shelf-controller-common";
+/**
+ * Takes the raw page data and creates a charts and categories page
+ * @param objectGraph The App Store Object Graph
+ * @param resultData The data representing the charts and categories page
+ * @returns A charts and categories page
+ */
+export function searchChartsAndCategoriesPageFromData(objectGraph, resultData) {
+ var _a, _b;
+ /// Get the page's metadata
+ const pageTitle = mediaAttributes.attributeAsString(resultData, "title");
+ const selectedTabId = serverData.asString(resultData, "meta.autoSelectedTabId");
+ const sourceShelfCanonicalId = serverData.asString(resultData, "meta.sourceShelfCanonicalId");
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "SearchLanding", sourceShelfCanonicalId, resultData);
+ const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph);
+ pageInformation.recoMetricsData = combinedRecoMetricsDataFromMetricsData(pageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData);
+ /// Generate a new SearchPageContext (mainly for making shelf contexts)
+ const searchPageContext = {
+ shelves: [],
+ metricsLocationTracker: metricsHelpersLocation.newLocationTracker(),
+ metricsPageInformation: pageInformation,
+ adStitcher: null,
+ adIncidentRecorder: null,
+ pageType: searchShelves.SearchPageType.ChartsAndCategories,
+ };
+ /// Prep the shelves data, mappings, orderings, and page tabs list
+ const shelvesData = serverData.asArrayOrEmpty(resultData, "data");
+ const shelvesMapping = {};
+ const shelfOrdering = [];
+ const pageTabsCollector = [];
+ const pageTabsLocationTracker = metricsHelpersLocation.newLocationTracker();
+ for (const shelfData of shelvesData) {
+ /// Generate the shelf attributes for the shelf
+ const shelfAttributes = searchShelves.shelfAttributesFromData(objectGraph, shelfData, models.SearchLandingPageContentKind.CategoriesAndCharts, models.SearchPageKind.CategoriesAndCharts);
+ /// Since the shelves act as their own page, we need to set a new location tracker for each shelf
+ const shelfPageContext = {
+ ...searchPageContext,
+ metricsLocationTracker: metricsHelpersLocation.newLocationTracker(),
+ };
+ /// Generate the shelf context
+ const shelfContext = searchShelves.baseShelfContext(objectGraph, shelfData, shelfAttributes, shelfPageContext);
+ /// Push the shelf content location so each shelf has the correct parent and starting index
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfContext.metricsOptions, shelfAttributes.title);
+ /// Make the shelf
+ const shelf = createChartsCategoryShelf(objectGraph, shelfData, true, shelfAttributes, shelfPageContext, shelfContext);
+ metricsHelpersLocation.popLocation(shelfContext.metricsOptions.locationTracker);
+ if (isNothing(shelf)) {
+ continue;
+ }
+ /// Add the shelf to the shelves mapping for the shelf id
+ shelvesMapping[shelf.id] = shelf;
+ /// Add the shelf id to the shelf ordering (to maintain ordered shelves)
+ shelfOrdering.push(shelf.id);
+ /// Make a pageTab metadata model from the shelf and action
+ const pageTab = new models.PageTab();
+ const action = new models.PageTabChangeAction(shelfData.id, shelfAttributes.title);
+ /// Generate the click actions
+ const pageTabClickActionOptions = {
+ id: shelfAttributes.title,
+ canonicalId: (_a = serverData.asString(shelfData.meta, "canonicalId")) !== null && _a !== void 0 ? _a : undefined,
+ actionType: "navigate",
+ targetType: "button",
+ pageInformation: searchPageContext.metricsPageInformation,
+ locationTracker: pageTabsLocationTracker,
+ };
+ addClickEventToAction(objectGraph, action, pageTabClickActionOptions);
+ pageTab.action = action;
+ pageTab.id = shelf.id;
+ pageTab.title = `${(_b = shelf.title) !== null && _b !== void 0 ? _b : ""}`; /// We need a deep copy of the string as we will remove the reference possibly later.
+ pageTabsCollector.push(pageTab);
+ metricsHelpersLocation.nextPosition(pageTabsLocationTracker);
+ }
+ /// Return an empty page if there are no real shelves to show
+ if (!serverData.isDefinedNonNullNonEmpty(shelfOrdering)) {
+ return new models.SearchChartsAndCategoriesPage();
+ }
+ /// Create the page tabs
+ const pageTabs = new models.PageTabs();
+ /// The id needs to be static but it isn't tied to the payload
+ pageTabs.id = objectGraph.random.nextUUID();
+ /// Add the pageTabs as its own shelf so it can be independent of the chart and category shelves
+ /// as well as to control the segmented control for swapping between category shelf orderings (e.g. category containers)
+ const pageTabsShelf = new models.Shelf("pageTabs");
+ pageTabsShelf.items = [pageTabs];
+ shelvesMapping[pageTabs.id] = pageTabsShelf;
+ /// Make the page and add the shelf mappings (which includes our pageTabs shelf)
+ const page = new models.SearchChartsAndCategoriesPage();
+ page.shelfMapping = shelvesMapping;
+ const shelfOrderings = {};
+ /** Normally, we would have the shelf orderings be based on some identifier in the payload on a container which contains
+ an array of shelves. E.g.
+ {
+ Container {
+ containerId: string
+ shelves: [
+ {
+ shelfId1: string,
+ ...
+ },
+ ...
+ ]
+ }
+ }
+ shelf orderings = { containerId: [shelfId1, ...]}
+
+ However, the current protocol has no container, just a loose array of shelves.
+ {
+ shelves: [
+ {
+ shelfId1: string,
+ ...
+ },
+ ...
+ ]
+ }
+
+ Therefore, we have to fake our ordering by having the ordering be the shelves' own identifiers.
+
+ shelf orderings = { shelfId1: [shelfId1], shelfId2: [shelfId2], ...}
+
+ We also will append the pageTabs shelf if we have multiple shelves representing multiple tabs on the page.
+ */
+ for (const shelfOrderingElement of shelfOrdering) {
+ if (pageTabsCollector.length > 1) {
+ shelfOrderings[shelfOrderingElement] = [pageTabs.id, shelfOrderingElement];
+ }
+ else {
+ shelfOrderings[shelfOrderingElement] = [shelfOrderingElement];
+ }
+ }
+ for (const shelf of Object.values(shelvesMapping)) {
+ shelf.title = undefined;
+ }
+ /// Set the data on the page
+ page.title = pageTitle;
+ page.pageTabs = pageTabs;
+ page.columnCount = 2;
+ page.shelfOrderings = shelfOrderings;
+ /// If we don't have a reco selected default tab, just default to the first one
+ page.defaultShelfOrdering = shelfOrdering.includes(selectedTabId) ? selectedTabId : shelfOrdering[0];
+ /// Set the data on the page tabs
+ pageTabs.tabs = pageTabsCollector;
+ pageTabs.selectedTabId = page.defaultShelfOrdering;
+ /// Add the page metrics
+ addMetricsEventsToPageWithInformation(objectGraph, page, searchPageContext.metricsPageInformation);
+ return page;
+}
+/**
+ * Creates a charts and categories shelf from the shelf data
+ * @param objectGraph The App Store Object Graph
+ * @param data The shelf data object
+ * @param isForSeeAllPage Whether or not this shelf will be displayed on the see-all page
+ * @param shelfAttributes The shelf's attributes
+ * @param searchPageContext The context for the page this shelf belongs to
+ * @param searchShelfContext The shelf's context
+ * @returns A charts and categories shelf
+ */
+export function createChartsCategoryShelf(objectGraph, data, isForSeeAllPage, shelfAttributes, searchPageContext, searchShelfContext) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
+ const items = [];
+ const chartsAndCategoriesData = mediaRelationships.relationshipCollection(data, "contents");
+ const shelf = new models.Shelf("searchChartsAndCategories");
+ shelf.isHorizontal = false;
+ shelf.id = data.id;
+ shelf.title = shelfAttributes.title;
+ shelf.presentationHints = { isWidthConstrained: true };
+ if (serverData.isNumber((_a = shelfAttributes.displayStyle) === null || _a === void 0 ? void 0 : _a.layoutSize)) {
+ shelf.contentsMetadata = {
+ type: "searchLandingChartsAndCategoriesSection",
+ numberOfColumns: shelfAttributes.displayStyle.layoutSize,
+ };
+ }
+ if (shelfAttributes.hasSeeAll) {
+ const action = new models.FlowAction("searchChartsAndCategories");
+ action.pageUrl = shelfAttributes.seeAllLink;
+ action.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ const seeAllMetricsOptions = {
+ ...searchShelfContext.metricsOptions,
+ targetType: "button",
+ };
+ addClickEventToSeeAllAction(objectGraph, action, action.pageUrl, seeAllMetricsOptions);
+ shelf.seeAllAction = action;
+ }
+ addImpressionFields(objectGraph, shelf, searchShelfContext.metricsOptions);
+ const brickUseCase = isForSeeAllPage
+ ? content.SearchChartOrCategoryBrickUseCase.seeAllPage
+ : content.SearchChartOrCategoryBrickUseCase.other;
+ for (const chartOrCategory of chartsAndCategoriesData) {
+ let name = null;
+ let badge = null;
+ if (chartOrCategory.type === "tags") {
+ const brickName = (_b = mediaAttributes.attributeAsString(chartOrCategory, "name")) !== null && _b !== void 0 ? _b : "tagbrick";
+ name = brickName;
+ }
+ else {
+ const editorialNotes = mediaAttributes.attributeAsDictionary(chartOrCategory, "editorialNotes");
+ if (serverData.isDefinedNonNullNonEmpty(editorialNotes)) {
+ name = serverData.asString(editorialNotes, "name");
+ badge = serverData.asString(editorialNotes, "badge");
+ }
+ }
+ const artwork = content.searchChartOrCategoryArtworkFromData(objectGraph, chartOrCategory, brickUseCase, (_c = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _c === void 0 ? void 0 : _c.layoutDensity);
+ const kind = mediaAttributes.attributeAsString(chartOrCategory, "kind");
+ const linkData = mediaAttributes.attributeAsDictionary(chartOrCategory, "link");
+ const linkUrl = serverData.asString(linkData, "url");
+ const dataCollection = mediaRelationships.relationshipCollection(chartOrCategory, "primary-content");
+ let isTitleRequired = true;
+ let action = null;
+ if (chartOrCategory.type === "tags") {
+ const href = serverData.asString(chartOrCategory, "href");
+ const url = hrefToRoutableUrl(objectGraph, href);
+ const flowPage = objectGraph.required(pageRouter).fetchFlowPage(url);
+ const flowAction = new models.FlowAction(flowPage);
+ flowAction.pageUrl = url;
+ flowAction.title = name;
+ action = flowAction;
+ }
+ else if (isSome(linkUrl)) {
+ switch (kind) {
+ case "CategoryChart":
+ const topChartAction = new models.FlowAction("topCharts");
+ topChartAction.pageUrl = linkUrl;
+ topChartAction.title = name;
+ action = topChartAction;
+ break;
+ case "External":
+ // For external links, we don't require a title we can just show an image
+ isTitleRequired = false;
+ const target = serverData.asString(linkData, "target");
+ if (target === "external") {
+ action = new models.ExternalUrlAction(linkUrl);
+ action.title = name !== null && name !== void 0 ? name : "";
+ }
+ else {
+ const flowPage = objectGraph.required(pageRouter).fetchFlowPage(linkUrl);
+ const linkAction = new models.FlowAction(flowPage);
+ linkAction.pageUrl = linkUrl;
+ linkAction.title = name !== null && name !== void 0 ? name : "";
+ action = linkAction;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(dataCollection)) {
+ const chartMetadata = dataCollection[0];
+ const chartHref = chartMetadata.href;
+ const url = hrefToRoutableUrl(objectGraph, chartHref);
+ if ((url === null || url === void 0 ? void 0 : url.length) > 0) {
+ const flowAction = new models.FlowAction("page");
+ flowAction.pageUrl = url;
+ flowAction.title = name;
+ action = flowAction;
+ }
+ else {
+ continue;
+ }
+ }
+ else {
+ continue;
+ }
+ const chartClickOptions = createClickMetricsOptionsForChartOrCategory(objectGraph, (_d = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _d === void 0 ? void 0 : _d.layoutDensity, chartOrCategory, searchShelfContext);
+ addClickEventToAction(objectGraph, action, chartClickOptions);
+ if (isTitleRequired && serverData.isNullOrEmpty(name)) {
+ continue;
+ }
+ let chartOrCategoryModel = new models.SearchChartOrCategory(name, artwork, null, null, badge, action, (_e = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _e === void 0 ? void 0 : _e.layoutDensity, artworkSafeArea((_f = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _f === void 0 ? void 0 : _f.layoutDensity), textSafeArea((_g = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _g === void 0 ? void 0 : _g.layoutDensity));
+ let chartModelMetricsOptions = createMetricsOptionsForChartOrCategory(objectGraph, chartOrCategoryModel, chartOrCategory, searchShelfContext);
+ const artworkOptions = {
+ useCase: 18 /* content.ArtworkUseCase.GroupingBrick */,
+ };
+ const collectionIcons = groupingShelfControllerCommon.artworkForTags(objectGraph, chartOrCategory, 1060, 520, artworkOptions, chartModelMetricsOptions);
+ if (isSome(collectionIcons) && collectionIcons.length > 0) {
+ const collectionIconBackgroundColor = collectionIcons[0].backgroundColor;
+ if (isSome(collectionIconBackgroundColor) && (collectionIconBackgroundColor === null || collectionIconBackgroundColor === void 0 ? void 0 : collectionIconBackgroundColor.type) === "rgb") {
+ chartOrCategoryModel = new models.SearchChartOrCategory(name, null, collectionIcons, (_h = content.closestTagBackgroundColorForIcon(collectionIconBackgroundColor)) !== null && _h !== void 0 ? _h : undefined, badge, action, (_j = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _j === void 0 ? void 0 : _j.layoutDensity, artworkSafeArea((_k = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _k === void 0 ? void 0 : _k.layoutDensity), textSafeArea((_l = shelfAttributes === null || shelfAttributes === void 0 ? void 0 : shelfAttributes.displayStyle) === null || _l === void 0 ? void 0 : _l.layoutDensity));
+ chartModelMetricsOptions = createMetricsOptionsForChartOrCategory(objectGraph, chartOrCategoryModel, chartOrCategory, searchShelfContext);
+ }
+ }
+ addImpressionFields(objectGraph, chartOrCategoryModel, chartModelMetricsOptions);
+ items.push(chartOrCategoryModel);
+ metricsHelpersLocation.nextPosition(searchShelfContext.metricsOptions.locationTracker);
+ }
+ if (serverData.isNullOrEmpty(items)) {
+ return null;
+ }
+ shelf.items = items;
+ return shelf;
+}
+function artworkSafeArea(density) {
+ switch (density) {
+ // Tile
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density1:
+ return models.ChartOrCategorySafeArea.defaultTileArtworkSafeArea;
+ // Pill
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density2:
+ return models.ChartOrCategorySafeArea.defaultPillArtworkSafeArea;
+ // Round
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density3:
+ return undefined;
+ default:
+ return undefined;
+ }
+}
+function textSafeArea(density) {
+ switch (density) {
+ // Tile
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density1:
+ return models.ChartOrCategorySafeArea.defaultTileTextSafeArea;
+ // Pill
+ case models.GenericSearchPageShelfDisplayStyleDensity.Density2:
+ return models.ChartOrCategorySafeArea.defaultPillTextSafeArea;
+ default:
+ return undefined;
+ }
+}
+//# sourceMappingURL=search-categories.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-content-common.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-content-common.js
new file mode 100644
index 0000000..59b2fe5
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-content-common.js
@@ -0,0 +1,31 @@
+/**
+ * Common functions for search results content.
+ */
+import { isDefinedNonNullNonEmpty } from "../../../foundation/json-parsing/server-data";
+import { attributeAsBooleanOrFalse, attributeAsString } from "../../../foundation/media/attributes";
+// region Editorial Content
+/**
+ * Returns the headline / tagline for Editorial Search Results
+ * @param resultData Data to determine tagline for.
+ */
+export function editorialSearchResultTagline(objectGraph, resultData) {
+ // Flag to disable showing specific headings, e.g. "App of the Day" that may be unnatural in search result
+ const showLabelInSearch = attributeAsBooleanOrFalse(resultData, "showLabelInSearch");
+ if (!showLabelInSearch) {
+ return null;
+ }
+ // <rdar://problem/40468686> LOC: AOTD & GOTD badges in Search result for the Editorial Item Card AOTD & GOTD stories do not show up correctly for JA-JP and TH-TH
+ // Always use alternative label, if one is provided.
+ const alternateLabel = attributeAsString(resultData, "alternateLabel");
+ if (isDefinedNonNullNonEmpty(alternateLabel)) {
+ return alternateLabel; // No newline flattening needed for alternativeLabel
+ }
+ // Otherwise, fallback to franchise label, if any.
+ const label = attributeAsString(resultData, "label");
+ if (isDefinedNonNullNonEmpty(label)) {
+ return label.replace(/\n/g, " ");
+ }
+ return null;
+}
+// endregion
+//# sourceMappingURL=search-content-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-lockup-collection.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-lockup-collection.js
new file mode 100644
index 0000000..c5c85e7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-lockup-collection.js
@@ -0,0 +1,171 @@
+import { TodayCardDisplayStyle } from "./../../today/today-types";
+/**
+ * Builder for SearchLockupCollection.
+ */
+import { SearchLockupCollection } from "../../../api/models";
+import { isDefinedNonNullNonEmpty, isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { attributeAsArrayOrEmpty, attributeAsString } from "../../../foundation/media/attributes";
+import { relationshipCollection } from "../../../foundation/media/relationships";
+import { createArtworkForResource } from "../../content/artwork/artwork";
+import { notesFromData } from "../../content/content";
+import { currentEditorialCollectionTreatment, } from "../../../foundation/experimentation/search-results-experiments";
+import { actionFromData, lockupsFromData } from "../../lockups/lockups";
+import { addImpressionFields, impressionOptions } from "../../metrics/helpers/impressions";
+import { popLocation, pushContentLocation } from "../../metrics/helpers/location";
+import { cardDisplayStyleFromData } from "../../today/today-card-util";
+import { editorialSearchResultTagline } from "./search-content-common";
+/**
+ * Whether or not a given result data should be rendered as `SearchLockupCollection` model.
+ * Semantically, every `collection` should be rendered as a lockup collection, but collections are currently backed by many different types of articles.
+ * @param resultData Data from results endpoint.
+ * @param searchResponseMetadata Meta blob from initial search request.
+ */
+export function resultDataShouldRenderLockupCollection(objectGraph, resultData, searchResponseMetadata) {
+ // Filter supported platforms
+ if (!(objectGraph.host.isiOS || objectGraph.host.isVision)) {
+ return false;
+ }
+ if (currentEditorialCollectionTreatment(objectGraph, searchResponseMetadata) !==
+ 1 /* EditorialCollectionExperimentType.Swoosh */) {
+ return false;
+ }
+ switch (resultData.type) {
+ case "groupings":
+ // Groupings can only be rendered if `contentIds` is present
+ const collectionAdamIds = attributeAsArrayOrEmpty(resultData, "contentIds");
+ return isDefinedNonNullNonEmpty(collectionAdamIds);
+ case "rooms":
+ case "multirooms":
+ // Rooms, and multirooms always render as collection.
+ return true;
+ case "editorial-items":
+ // Some subtypes of editorial items render as collection.
+ const cardDisplayStyle = cardDisplayStyleFromData(resultData);
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.List:
+ case TodayCardDisplayStyle.NumberedList:
+ case TodayCardDisplayStyle.Grid:
+ case TodayCardDisplayStyle.River:
+ return true;
+ default:
+ return false;
+ }
+ default:
+ return false;
+ }
+}
+/**
+ * Create a `SearchLockupCollection` from search results.
+ * @param resultData Data from results endpoint.
+ * @param lockupOptions Options for lockups shared throughout search results page.
+ * @param metricsOptions Metrics context.
+ */
+export function lockupCollectionFromResultData(objectGraph, resultData, metricsOptions) {
+ // Text
+ const heading = lockupCollectionHeadingFromResultData(objectGraph, resultData);
+ const headingArtwork = lockupCollectionHeadingArtworkFromResultData(objectGraph, resultData);
+ const title = lockupCollectionTitleFromResultData(objectGraph, resultData);
+ // Metrics for impression + location
+ const lockupCollectionMetrics = impressionOptions(objectGraph, resultData, title, {
+ targetType: "card",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ });
+ pushContentLocation(objectGraph, lockupCollectionMetrics, title);
+ // Click Action ("See All")
+ const clickMetrics = {
+ actionType: "navigate",
+ targetType: "button",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ id: "See All",
+ idType: "sequential",
+ };
+ const seeAllAction = actionFromData(objectGraph, resultData, clickMetrics, objectGraph.host.clientIdentifier);
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ // Lockups
+ const listOptions = {
+ lockupOptions: {
+ metricsOptions: {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "lockup",
+ },
+ skipDefaultClickAction: objectGraph.client.isVision,
+ artworkUseCase: 8 /* ArtworkUseCase.SearchIcon */,
+ hideCompatibilityBadge: objectGraph.client.isVision,
+ },
+ filter: 128 /* Filter.UnsupportedPlatform */,
+ };
+ let lockupData = relationshipCollection(resultData, "card-contents");
+ if (isNullOrEmpty(lockupData)) {
+ lockupData = relationshipCollection(resultData, "top-apps");
+ }
+ const items = lockupsFromData(objectGraph, lockupData, listOptions);
+ popLocation(metricsOptions.locationTracker);
+ // Lockup Collection
+ const lockupCollection = new SearchLockupCollection(heading, title, items, seeAllAction, headingArtwork);
+ addImpressionFields(objectGraph, lockupCollection, lockupCollectionMetrics);
+ if (isNullOrEmpty(items)) {
+ return null;
+ }
+ return lockupCollection;
+}
+// endregion
+// region Heading
+/**
+ * Returns the title for given editorial results search results data.
+ * @param resultData The result data to get a title for.
+ */
+function lockupCollectionTitleFromResultData(objectGraph, resultData) {
+ const resultType = resultData.type;
+ switch (resultType) {
+ case "developers":
+ return attributeAsString(resultData, "name");
+ default:
+ return notesFromData(objectGraph, resultData, "name");
+ }
+}
+/**
+ * Returns the heading for given editorial results search results data.
+ * This builds on `editorialSearchResultTagline` with some additional search-specific logic
+ * @param resultData The result data to get a heading for.
+ */
+function lockupCollectionHeadingFromResultData(objectGraph, resultData) {
+ const resultType = resultData.type;
+ if (resultType === "developers") {
+ return objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_DEVELOPER_TITLE_CASE");
+ }
+ const editorialTagline = editorialSearchResultTagline(objectGraph, resultData);
+ if (isDefinedNonNullNonEmpty(editorialTagline)) {
+ return editorialTagline;
+ }
+ if (objectGraph.client.isVision) {
+ return objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_COLLECTION_TITLE_CASE");
+ }
+ else {
+ return objectGraph.loc.string("Search.EditorialSearchResultType.Heading.Collection");
+ }
+}
+/**
+ * Returns the heading artwork to use for a given editorial search results data.
+ * @param resultData The result data to get heading artwork for.
+ */
+function lockupCollectionHeadingArtworkFromResultData(objectGraph, resultData) {
+ if (!objectGraph.client.isVision) {
+ return null;
+ }
+ const resultType = resultData.type;
+ let imageName;
+ switch (resultType) {
+ case "developers":
+ imageName = "person.crop.square";
+ break;
+ default:
+ imageName = "appstore";
+ break;
+ }
+ return createArtworkForResource(objectGraph, `systemimage://${imageName}`);
+}
+// endregion
+//# sourceMappingURL=search-lockup-collection.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-results.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-results.js
new file mode 100644
index 0000000..5f363b3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-results.js
@@ -0,0 +1,723 @@
+/**
+ * A builder for all SearchResultsContainers variants except for `SearchLockupCollection`.
+ * To be cleaned in the future.
+ */
+import * as validation from "@jet/environment/json/validation";
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import { EditorialSearchResult } from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as errors from "../../../foundation/util/errors";
+import * as client from "../../../foundation/wrappers/client";
+import * as appEvents from "../../app-promotions/app-event";
+import * as appPromotionsCommon from "../../app-promotions/app-promotions-common";
+import { createArtworkForResource } from "../../content/artwork/artwork";
+import * as content from "../../content/content";
+import { editorialDisplayOptionsFromClientParams, extractEditorialClientParams, } from "../../editorial-pages/editorial-data-util";
+import * as filtering from "../../filtering";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersUtil from "../../metrics/helpers/util";
+import * as onDevicePersonalization from "../../personalization/on-device-personalization";
+import { defaultTodayCardConfiguration, todayCardFromData } from "../../today/today-card-util";
+import { TodayCardDisplayStyle, TodayParseContext } from "../../today/today-types";
+import * as common from "./search-content-common";
+import * as searchLockupCollection from "./search-lockup-collection";
+export function searchResultFromData(objectGraph, resultData, searchResponseMetadata, personalizationDataContainer, metricsOptions, isNetworkConstrained, searchEntity, searchExperimentsData) {
+ return validation.context("searchResultFromData", () => {
+ let searchResult = null;
+ const resultType = resultData.type;
+ const standardLockupOptions = {
+ metricsOptions: {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "card",
+ createUniqueImpressionId: true,
+ },
+ hideZeroRatings: true,
+ artworkUseCase: 8 /* content.ArtworkUseCase.SearchIcon */,
+ isNetworkConstrained: isNetworkConstrained,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "mixedMediaLockup"),
+ clientIdentifierOverride: clientIdentifierOverrideForSearchEntity(objectGraph, searchEntity),
+ isMultilineTertiaryTitleAllowed: false,
+ };
+ const condensedBehavior = condensedBehaviorForData(objectGraph, resultData, searchExperimentsData);
+ switch (resultType) {
+ case "rooms":
+ case "multirooms":
+ case "developers":
+ case "editorial-items":
+ case "groupings":
+ if (resultType !== "editorial-items" && resultType !== "developers" && objectGraph.client.isVision) {
+ return null;
+ }
+ const todayCardConfig = defaultTodayCardConfiguration(objectGraph);
+ todayCardConfig.isSearchContext = true;
+ const todayCard = todayCardFromData(objectGraph, resultData, todayCardConfig, new TodayParseContext(metricsOptions.pageInformation, metricsOptions.locationTracker));
+ if (todayCard && todayCard.media && todayCard.media.kind === "inAppPurchase") {
+ if (objectGraph.client.isVision) {
+ return null;
+ }
+ const iapMedia = todayCard.media;
+ const todayCardInAppPurchaseLockup = iapMedia.lockup;
+ todayCardInAppPurchaseLockup.theme = "dark";
+ searchResult = new models.InAppPurchaseSearchResult(todayCardInAppPurchaseLockup);
+ }
+ else if (searchLockupCollection.resultDataShouldRenderLockupCollection(objectGraph, resultData, searchResponseMetadata)) {
+ const lockupCollection = searchLockupCollection.lockupCollectionFromResultData(objectGraph, resultData, metricsOptions);
+ if (lockupCollection) {
+ searchResult = lockupCollection;
+ }
+ }
+ else {
+ const editorialSearchResult = editorialSearchResultFromData(objectGraph, resultData, standardLockupOptions.metricsOptions, condensedBehavior);
+ if (editorialSearchResult) {
+ if (editorialSearchResult.title) {
+ editorialSearchResult.title = editorialSearchResult.title.replace(/\n/g, " ");
+ }
+ if (editorialSearchResult instanceof EditorialSearchResult && editorialSearchResult.subtitle) {
+ editorialSearchResult.subtitle = editorialSearchResult.subtitle.replace(/\n/g, " ");
+ }
+ searchResult = editorialSearchResult;
+ }
+ }
+ break;
+ case "in-apps":
+ if (objectGraph.client.isVision || objectGraph.client.isWeb) {
+ return null;
+ }
+ // Ensure the parent app data is available before proceeding with building the lockup.
+ const parentData = lockups.parentDataFromInAppData(objectGraph, resultData);
+ if (serverData.isNullOrEmpty(parentData)) {
+ return null;
+ }
+ const inAppPurchaseLockup = lockups.inAppPurchaseLockupFromData(objectGraph, resultData, standardLockupOptions);
+ inAppPurchaseLockup.theme = "dark";
+ modifyMetadataBadgeForSearchExperiment(objectGraph, inAppPurchaseLockup, searchExperimentsData);
+ searchResult = new models.InAppPurchaseSearchResult(inAppPurchaseLockup);
+ break;
+ case "apps":
+ case "app-bundles":
+ default:
+ // There should never be an iad in the non-ad search results, so remove it here
+ // before creating the lockups.
+ delete resultData.attributes["iad"];
+ if (resultType === "app-bundles") {
+ if (objectGraph.client.isVision) {
+ return null;
+ }
+ standardLockupOptions.shouldIncludeScreenshotsForChildren =
+ objectGraph.featureFlags.isEnabled("voyager_bundles_2025A");
+ const bundleLockup = lockups.lockupFromData(objectGraph, resultData, standardLockupOptions);
+ bundleLockup.showMetadataInformationInLockup = true;
+ modifyMetadataBadgeForSearchExperiment(objectGraph, bundleLockup, searchExperimentsData);
+ searchResult = new models.BundleSearchResult(bundleLockup);
+ }
+ else {
+ const lockup = lockups.mixedMediaLockupFromData(objectGraph, resultData, standardLockupOptions, {
+ canPlayFullScreen: false,
+ playbackControls: {},
+ }, searchExperimentsData);
+ modifyMetadataBadgeForSearchExperiment(objectGraph, lockup, searchExperimentsData);
+ // Extract out any associated app event from meta
+ const appEventSearchResult = appEventSearchResultFromData(objectGraph, resultData, lockup, standardLockupOptions, personalizationDataContainer, metricsOptions);
+ if (serverData.isDefinedNonNull(appEventSearchResult)) {
+ searchResult = appEventSearchResult;
+ searchResult.condensedBehavior = "never";
+ }
+ else {
+ // If no app event in meta, fallback to a regular mixed media lockup
+ searchResult = new models.AppSearchResult(lockup);
+ }
+ }
+ break;
+ }
+ if (serverData.isDefinedNonNull(searchResult) && serverData.isNull(searchResult.condensedBehavior)) {
+ searchResult.condensedBehavior = condensedBehavior;
+ }
+ return searchResult;
+ });
+}
+/**
+ * Indicates whether a search result will display an app event
+ * @param objectGraph The object graph
+ * @param searchResultData The data for the search result
+ * @param installStates A mapping of adamIDs to their respective install states that is used to determine if the app is currently installed by the user
+ * @param appStates A mapping of adamIDs to their respective app states that is used to determine if the app has been installed by the user in the past
+ * @param metricsOptions Metrics options for built lockups
+ * @param personalizationDataContainer Personalization criteria used for sorting and filtering app events
+ * @returns a boolean result
+ */
+export function searchResultWillUseAppEventDisplay(objectGraph, searchResultData, installStates, appStates, metricsOptions, personalizationDataContainer) {
+ const appEventEligibleToDisplay = searchResultIsEligibleToDisplayAppEvent(objectGraph, searchResultData);
+ if (!appEventEligibleToDisplay) {
+ return false;
+ }
+ const { dataItems } = selectedAppEventDataItems(objectGraph, searchResultData, personalizationDataContainer);
+ let appEvent;
+ for (const appEventDataItem of dataItems) {
+ appEvent = sanitizedAppEvent(objectGraph, appEventDataItem, searchResultData, metricsOptions, undefined, undefined);
+ if (serverData.isDefinedNonNull(appEvent)) {
+ break;
+ }
+ }
+ const appIsInstalled = serverData.isDefinedNonNull(installStates) && serverData.isDefinedNonNull(installStates[searchResultData.id])
+ ? installStates[searchResultData.id]
+ : false;
+ const appHasBeenInstalled = serverData.isDefinedNonNull(appStates) && serverData.isDefinedNonNull(appStates[searchResultData.id])
+ ? ["downloadable"].includes(appStates[searchResultData.id])
+ : false;
+ // App Events are only displayed in native when the user has installed the app previously and there is a valid app event
+ return (appIsInstalled || appHasBeenInstalled) && serverData.isDefinedNonNull(appEvent);
+}
+/**
+ * Derive the condensed behavior from a search result
+ * @param objectGraph The global object graph instance
+ * @param resultData The data blob for this specific search result
+ * @param pageSearchExperimentsData The page level search experiments data object
+ */
+function condensedBehaviorForData(objectGraph, resultData, pageSearchExperimentsData) {
+ var _a, _b;
+ /// Guard early against incompatible client devices
+ if (!canHaveCondensedBehaviorForClient(objectGraph)) {
+ return "never";
+ }
+ const itemSearchExperimentData = resultData.meta;
+ const itemCondensedStyle = (_a = itemSearchExperimentData === null || itemSearchExperimentData === void 0 ? void 0 : itemSearchExperimentData.displayStyle) === null || _a === void 0 ? void 0 : _a.condensed;
+ if (serverData.isDefinedNonNull(itemCondensedStyle)) {
+ return condensedBehaviorFromStyle(objectGraph, itemCondensedStyle);
+ }
+ const pageCondensedStyle = (_b = pageSearchExperimentsData === null || pageSearchExperimentsData === void 0 ? void 0 : pageSearchExperimentsData.displayStyle) === null || _b === void 0 ? void 0 : _b.condensed;
+ if (serverData.isDefinedNonNull(pageCondensedStyle)) {
+ return condensedBehaviorFromStyle(objectGraph, pageCondensedStyle);
+ }
+ return defaultCondensedBehaviorForClient(objectGraph);
+}
+/**
+ * Gets the condensed behavior from a given condensed style, or a default behavior based on the client type
+ * @param objectGraph The global object graph instance
+ * @param condensedStyle The condensed style on the data model
+ * @returns The condensed behavior for the native search result view
+ */
+function condensedBehaviorFromStyle(objectGraph, condensedStyle) {
+ switch (condensedStyle) {
+ case "always":
+ return "always";
+ case "never":
+ return "never";
+ case "when-installed":
+ return "whenInstalled";
+ default:
+ return defaultCondensedBehaviorForClient(objectGraph);
+ }
+}
+/**
+ * Determines the default condensed behavior based for a given client type
+ * @param objectGraph The global object graph instance
+ * @param condensedStyle The condensed style on the data model
+ * @returns The condensed behavior for the native search result view
+ */
+function defaultCondensedBehaviorForClient(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return "whenInstalled";
+ default:
+ return "never";
+ }
+}
+/**
+ * Determines if the current client type supports condensed behavior
+ * @param objectGraph The global object graph instance
+ * @returns Whether condensed behavior is allowed
+ */
+function canHaveCondensedBehaviorForClient(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return true;
+ default:
+ return false;
+ }
+}
+/**
+ * Modifies the editor's choice badge based on whether the search experiment overrides the metadataPrecendence
+ * for the badge
+ * @param objectGraph App Store ObjectGraph
+ * @param lockup The lockup we need to modify for the experiment
+ * @param searchExperimentData The experiment data to pull the metadata info from
+ */
+function modifyMetadataBadgeForSearchExperiment(objectGraph, lockup, searchExperimentData) {
+ const shouldShowEditorsChoice = metadataPrecedenceTypePreceedsType(objectGraph, searchExperimentData, "editorialBadgeInfo", "userRating");
+ if (serverData.isDefinedNonNull(shouldShowEditorsChoice)) {
+ lockup.isEditorsChoice = lockup.isEditorsChoice && shouldShowEditorsChoice;
+ }
+}
+/**
+ * Determines whether the first metadata type should precede the second type for the given experiment data
+ * @param objectGraph App Store ObjectGraph
+ * @param experimentData The experiment data to pull the metadata info from
+ * @param firstType Does this precede the second type in the experiment order
+ * @param secondType Does this succeed the second type in the experiment order
+ * @returns whether the first type precedes the second type
+ */
+function metadataPrecedenceTypePreceedsType(objectGraph, experimentData, firstType, secondType) {
+ var _a;
+ if (serverData.isNull(experimentData) || !objectGraph.client.isPhone) {
+ return null;
+ }
+ const order = (_a = experimentData === null || experimentData === void 0 ? void 0 : experimentData.displayStyle) === null || _a === void 0 ? void 0 : _a.metadataPrecedenceOrder;
+ if (!serverData.isDefinedNonNullNonEmpty(order)) {
+ return null;
+ }
+ const firstIndex = order.indexOf(firstType);
+ const secondIndex = order.indexOf(secondType);
+ if (firstIndex === -1 && secondIndex === -1) {
+ return null;
+ }
+ if (firstIndex === -1) {
+ return false;
+ }
+ if (secondIndex === -1) {
+ return true;
+ }
+ return firstIndex < secondIndex;
+}
+function editorialSearchResultFromData(objectGraph, resultData, metricsOptions, resultCondensedBehavior) {
+ return validation.context("editorialSearchResultFromData", () => {
+ let searchResult;
+ const title = mediaAttributes.attributeAsString(resultData, "name");
+ const resultType = resultData.type;
+ switch (resultType) {
+ case "groupings": {
+ const editorialSearchResult = new models.EditorialSearchResult(title);
+ const collectionAdamIds = mediaAttributes.attributeAsArrayOrEmpty(resultData, "contentIds");
+ if (serverData.isDefinedNonNullNonEmpty(collectionAdamIds)) {
+ editorialSearchResult.collectionAdamIds = collectionAdamIds;
+ }
+ else {
+ const iconArtwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "artwork"), {
+ useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */,
+ allowingTransparency: true,
+ });
+ editorialSearchResult.iconArtwork = iconArtwork;
+ }
+ editorialSearchResult.type = "category";
+ searchResult = editorialSearchResult;
+ break;
+ }
+ case "rooms":
+ case "multirooms": {
+ const editorialSearchResult = new models.EditorialSearchResult(title);
+ editorialSearchResult.artwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "artwork"), {
+ useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */,
+ cropCode: "sr",
+ });
+ editorialSearchResult.collectionAdamIds = mediaAttributes.attributeAsArrayOrEmpty(resultData, "contentIds");
+ editorialSearchResult.type = "collection";
+ searchResult = editorialSearchResult;
+ break;
+ }
+ case "editorial-items": {
+ if (objectGraph.bag.searchFilterEditorialItemIds.has(resultData.id)) {
+ return null;
+ }
+ // Bridge objects to Today builder.
+ const todayParseContext = new TodayParseContext(metricsOptions.pageInformation, metricsOptions.locationTracker);
+ const shouldResultBeCondensed = resultCondensedBehavior === "always";
+ const editorialSearchResult = editorialSearchResultFromTodayCardData(objectGraph, resultData, todayParseContext, shouldResultBeCondensed);
+ searchResult = editorialSearchResult;
+ break;
+ }
+ case "developers": {
+ const editorialSearchResult = new models.EditorialSearchResult(title);
+ editorialSearchResult.artwork = content.artworkFromApiArtwork(objectGraph, mediaAttributes.attributeAsDictionary(resultData, "editorialArtwork.bannerUber"), {
+ useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */,
+ cropCode: "sr",
+ });
+ editorialSearchResult.type = "developer";
+ if (isSome(editorialSearchResult.artwork)) {
+ searchResult = editorialSearchResult;
+ }
+ else {
+ let topApps = mediaRelationship.relationshipCollection(resultData, "top-apps");
+ topApps = topApps.filter((topApp) => {
+ return !filtering.shouldFilter(objectGraph, topApp, 76532 /* filtering.Filter.DeveloperPage */);
+ });
+ const topAppIds = [];
+ const topAppArtwork = [];
+ topApps.forEach((app) => {
+ topAppIds.push(app.id);
+ const appIcon = content.iconFromData(objectGraph, app, {
+ useCase: 9 /* content.ArtworkUseCase.SearchEditorialResult */,
+ });
+ if (serverData.isDefinedNonNull(appIcon)) {
+ topAppArtwork.push(appIcon);
+ }
+ });
+ editorialSearchResult.collectionAdamIds = topAppIds;
+ editorialSearchResult.collectionAppIcons = topAppArtwork;
+ // On visionOS, the developer result falls back to a lockup collection if no art is available.
+ if (objectGraph.client.isVision) {
+ const lockupCollection = searchLockupCollection.lockupCollectionFromResultData(objectGraph, resultData, metricsOptions);
+ searchResult = lockupCollection;
+ }
+ else {
+ searchResult = editorialSearchResult;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ if (serverData.isNull(searchResult)) {
+ return null;
+ }
+ if (searchResult instanceof EditorialSearchResult) {
+ if (searchResult.collectionAdamIds != null && searchResult.collectionAdamIds.length) {
+ const lockupCount = searchResult.collectionAdamIds.length;
+ if (lockupCount <= 5) {
+ searchResult.artworkGridType = "extraLarge";
+ }
+ else if (lockupCount <= 8) {
+ searchResult.artworkGridType = "large";
+ }
+ else if (lockupCount <= 16) {
+ searchResult.artworkGridType = "mixed";
+ }
+ else {
+ searchResult.artworkGridType = "small";
+ }
+ }
+ if (objectGraph.client.isVision) {
+ let badgeText;
+ let badgeArtwork = createArtworkForResource(objectGraph, "systemimage://appstore");
+ switch (searchResult.type) {
+ case "developer":
+ badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_DEVELOPER_TITLE_CASE");
+ badgeArtwork = createArtworkForResource(objectGraph, "systemimage://person.crop.square");
+ break;
+ case "category":
+ badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_CATEGORY_TITLE_CASE");
+ break;
+ case "collection":
+ badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_COLLECTION_TITLE_CASE");
+ break;
+ case "story":
+ badgeText = objectGraph.loc.string("EDITORIAL_SEARCH_RESULT_TYPE_STORY_TITLE_CASE");
+ break;
+ default:
+ break;
+ }
+ searchResult.badgeText = badgeText;
+ searchResult.badgeArtwork = badgeArtwork;
+ }
+ }
+ const impressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, resultData, searchResult.title, metricsOptions);
+ searchResult.clickAction = lockups.actionFromData(objectGraph, resultData, impressionOptions, null);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchResult, impressionOptions);
+ return searchResult;
+ });
+}
+function editorialSearchResultFromTodayCardData(objectGraph, cardData, todayParseContext, shouldResultBeCondensed) {
+ // Card configuration to use for building TodayCards that will be converted into editorial search results.
+ const cardConfig = defaultTodayCardConfiguration(objectGraph);
+ cardConfig.isSearchContext = true;
+ if (!objectGraph.client.isVision) {
+ cardConfig.prevailingCropCodes =
+ shouldResultBeCondensed && objectGraph.client.isPhone
+ ? { defaultCrop: "DMGE.AppST01" }
+ : { defaultCrop: "fo" };
+ }
+ // If we are on visionOS, drop any EIs that have a not-compatible app as the primary content.
+ if (objectGraph.client.isVision) {
+ const primaryContent = content.primaryContentForData(objectGraph, cardData);
+ if (isSome(primaryContent)) {
+ const runnableOnDevice = content.runnableOnDeviceWithData(objectGraph, primaryContent, objectGraph.client.deviceType, objectGraph.appleSilicon.isSupportEnabled);
+ if (!runnableOnDevice) {
+ return null;
+ }
+ }
+ }
+ const todayCard = todayCardFromData(objectGraph, cardData, cardConfig, todayParseContext);
+ if (!todayCard) {
+ return null;
+ }
+ const editorialSearchResult = new models.EditorialSearchResult(todayCard.title);
+ editorialSearchResult.type = "story";
+ editorialSearchResult.clickAction = todayCard.clickAction;
+ let collectionLockups = null;
+ if (todayCard.media) {
+ switch (todayCard.media.kind) {
+ case "brandedSingleApp":
+ const mediaSingleApp = todayCard.media;
+ editorialSearchResult.artwork = mediaSingleApp.artworks[0];
+ if (serverData.isNull(editorialSearchResult.artwork)) {
+ editorialSearchResult.iconArtwork = mediaSingleApp.icon;
+ }
+ const cardDisplayStyle = mediaAttributes.attributeAsString(cardData, "cardDisplayStyle");
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.AppOfTheDay:
+ case TodayCardDisplayStyle.GameOfTheDay:
+ const relatedContentData = mediaRelationship.relationshipData(objectGraph, cardData, "card-contents");
+ if (relatedContentData) {
+ editorialSearchResult.title =
+ mediaAttributes.attributeAsString(relatedContentData, "name") ||
+ editorialSearchResult.title;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case "list":
+ const mediaList = todayCard.media;
+ collectionLockups = mediaList.lockups;
+ break;
+ case "river":
+ const mediaRiver = todayCard.media;
+ collectionLockups = mediaRiver.lockups;
+ break;
+ case "artwork":
+ const mediaArtwork = todayCard.media;
+ editorialSearchResult.artwork = mediaArtwork.artworks[0];
+ break;
+ case "grid":
+ const mediaGrid = todayCard.media;
+ collectionLockups = mediaGrid.lockups;
+ break;
+ case "multiApp":
+ const multiApp = todayCard.media;
+ collectionLockups = multiApp.lockups;
+ break;
+ case "video":
+ const mediaVideo = todayCard.media;
+ editorialSearchResult.artwork = mediaVideo.videos[0].preview;
+ editorialSearchResult.video = mediaVideo.videos[0];
+ if (todayCard.overlay instanceof models.TodayCardThreeLineOverlay) {
+ const overlay = todayCard.overlay;
+ editorialSearchResult.title = overlay.title;
+ editorialSearchResult.subtitle = overlay.description;
+ }
+ else {
+ editorialSearchResult.subtitle = mediaVideo.description;
+ }
+ break;
+ case "appEvent":
+ const media = todayCard.media;
+ editorialSearchResult.artwork = media.artworks[0];
+ editorialSearchResult.appEventFormattedDates = media.formattedDates;
+ editorialSearchResult.subtitle = todayCard.inlineDescription;
+ editorialSearchResult.tintColor = media.tintColor;
+ editorialSearchResult.type = "appEventStory";
+ if (serverData.isDefinedNonNull(todayCard.style)) {
+ switch (todayCard.style) {
+ case "light":
+ case "white":
+ editorialSearchResult.mediaOverlayStyle = "light";
+ break;
+ case "dark":
+ editorialSearchResult.mediaOverlayStyle = "dark";
+ break;
+ default:
+ errors.unreachable(todayCard.style);
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (todayCard.overlay) {
+ switch (todayCard.overlay.kind) {
+ case "lockup":
+ const cardOverlayLockup = todayCard.overlay;
+ if (!editorialSearchResult.artwork || objectGraph.client.isVision) {
+ collectionLockups = [cardOverlayLockup.lockup];
+ }
+ break;
+ case "lockupList":
+ const cardOverlayList = todayCard.overlay;
+ collectionLockups = cardOverlayList.lockups;
+ break;
+ case "paragraph":
+ const cardOverlayParagraph = todayCard.overlay;
+ editorialSearchResult.subtitle = cardOverlayParagraph.paragraph.text;
+ break;
+ default:
+ break;
+ }
+ }
+ if (serverData.isDefinedNonNull(collectionLockups)) {
+ editorialSearchResult.collectionAdamIds = [];
+ editorialSearchResult.collectionAppIcons = [];
+ for (const lockup of collectionLockups) {
+ editorialSearchResult.collectionAdamIds.push(lockup.adamId);
+ editorialSearchResult.collectionAppIcons.push(lockup.icon);
+ }
+ if (collectionLockups.length === 1) {
+ editorialSearchResult.lockup = collectionLockups[0];
+ }
+ }
+ const editorialClientParams = extractEditorialClientParams(objectGraph, cardData);
+ editorialSearchResult.editorialDisplayOptions = editorialDisplayOptionsFromClientParams(editorialClientParams);
+ /**
+ * Editorial tagline
+ */
+ const storyTagline = common.editorialSearchResultTagline(objectGraph, cardData);
+ if ((storyTagline === null || storyTagline === void 0 ? void 0 : storyTagline.length) > 0 && storyTagline !== editorialSearchResult.title) {
+ editorialSearchResult.tagline = storyTagline;
+ }
+ const heroMedia = todayCard.heroMedia;
+ if (serverData.isDefinedNonNullNonEmpty(heroMedia)) {
+ if (serverData.isDefinedNonNullNonEmpty(heroMedia.artworks[0])) {
+ editorialSearchResult.artwork = heroMedia.artworks[0];
+ editorialSearchResult.artwork.crop = "em";
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(heroMedia.videos[0])) {
+ editorialSearchResult.video = heroMedia.videos[0];
+ }
+ }
+ if (editorialSearchResult.video) {
+ editorialSearchResult.video.canPlayFullScreen = false;
+ editorialSearchResult.video.playbackControls = {};
+ }
+ if (!editorialSearchResult.collectionAdamIds &&
+ !editorialSearchResult.artwork &&
+ !editorialSearchResult.iconArtwork) {
+ return null;
+ }
+ return editorialSearchResult;
+}
+/// Gets the client identifier (or null) that should be used when building lockups for a given search entity.
+function clientIdentifierOverrideForSearchEntity(objectGraph, searchEntity) {
+ return searchEntity === "watch" ? client.watchIdentifier : null;
+}
+/**
+ * Indicates whether a search result meets basic sanity requirements to display app events. This does not include business rules for massaging a valid app event. For that, see `sanitizedAppEvent` function
+ * @param objectGraph The object graph
+ * @param searchResultData The data for the search result
+ * @returns a boolean result
+ */
+function searchResultIsEligibleToDisplayAppEvent(objectGraph, searchResultData) {
+ if (!appPromotionsCommon.appEventsAreEnabled(objectGraph) || serverData.isNull(searchResultData.meta)) {
+ return false;
+ }
+ // The first organic can't use CPP data from the ad if it has an app event that will be displayed.
+ // In this case, it should continue to use DPP assets
+ const appEventDataItems = serverData.asArrayOrEmpty(searchResultData.meta, "associations.app-events.data");
+ const hasInAppEvents = appEventDataItems.length > 0;
+ // App events can be displayed on most search result types, except for these ones
+ const typesIneligibleToDisplayAppEvent = [
+ "rooms",
+ "multirooms",
+ "developers",
+ "editorial-items",
+ "groupings",
+ "in-apps",
+ "app-bundles",
+ ];
+ const typeIsEligibleToDisplayAppEvent = !typesIneligibleToDisplayAppEvent.includes(searchResultData.type);
+ return typeIsEligibleToDisplayAppEvent && hasInAppEvents;
+}
+/**
+ * Sorts and filters app event for personalization, if applicable. May filter app events, and may select just the best one
+ * @param objectGraph The object graph
+ * @param resultData The data for the search result
+ * @param personalizationDataContainer Personalization criteria used for sorting and filtering app events
+ * @returns an object representing the personalized app events for a user, along with the personalization result
+ */
+function selectedAppEventDataItems(objectGraph, resultData, personalizationDataContainer) {
+ const alwaysShowAppEvent = serverData.asBooleanOrFalse(resultData.meta, "associations.app-events.attributes.forceAppEvent");
+ const appEventDataItems = serverData.asArrayOrEmpty(resultData.meta, "associations.app-events.data");
+ if (alwaysShowAppEvent) {
+ // In this scenario, there should always be only one event to choose from,
+ // and we don't want to do any personalization.
+ return { dataItems: [appEventDataItems[0]] };
+ }
+ const personalizedDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "search", appEventDataItems, false, personalizationDataContainer, false, undefined, resultData.id);
+ const personalizedDataItems = personalizedDataResult.personalizedData;
+ if (personalizedDataItems.length <= 0) {
+ return { dataItems: [] };
+ }
+ return { dataItems: personalizedDataItems, personalizationData: personalizedDataResult };
+}
+/**
+ * Sanitizes a raw app event metadata into an AppEvent model. If the event is not hydratable or has not started, this returns null.
+ * @param objectGraph The object graph
+ * @param appEventDataItem The app event that needs to be processed
+ * @param resultData The data for the search result
+ * @param baseMetricsOptions Metrics options for built lockups
+ * @param offerEnvironment Offer environment for the lockup. Typically comes from lockup options
+ * @param offerStyle Offer style for the lockup. Typically comes from lockup options
+ * @returns the processed app event with all business rules applied, or null if the app event was disqualified
+ */
+function sanitizedAppEvent(objectGraph, appEventDataItem, resultData, baseMetricsOptions, offerEnvironment, offerStyle) {
+ const metricsOptions = {
+ ...baseMetricsOptions,
+ targetType: "eventModule",
+ };
+ const appEventOrDate = appEvents.appEventOrPromotionStartDateFromData(objectGraph, appEventDataItem, resultData, false, true, offerEnvironment, offerStyle, false, metricsOptions, false, true, null, false, false);
+ // Ignore a future AppEvent promotionStartDate here.
+ if (serverData.isNull(appEventOrDate) || appEventOrDate instanceof Date) {
+ return null;
+ }
+ else {
+ return appEventOrDate;
+ }
+}
+/**
+ * Creates an app event search result from the data and associated lockup, if an app event exists in the metadata
+ * @param resultData The data blob
+ * @param lockup The associated mixed media lockup
+ * @param standardLockupOptions: The standard lockup options for search results
+ * @param personalizationDataContainer The data container to use for personalizing the selected app event
+ * @param baseMetricsOptions Base metrics options for this result
+ * @returns {AppEventSearchResult} The app event search result, or null
+ */
+function appEventSearchResultFromData(objectGraph, resultData, lockup, standardLockupOptions, personalizationDataContainer, baseMetricsOptions) {
+ return validation.context("appEventSearchResultFromData", () => {
+ const appEventEligibleToDisplay = searchResultIsEligibleToDisplayAppEvent(objectGraph, resultData);
+ if (!appEventEligibleToDisplay) {
+ return null;
+ }
+ const { dataItems, personalizationData } = selectedAppEventDataItems(objectGraph, resultData, personalizationDataContainer);
+ let appEvent;
+ let parentAppData;
+ for (const appEventDataItem of dataItems) {
+ const appEventOrNull = sanitizedAppEvent(objectGraph, appEventDataItem, resultData, baseMetricsOptions, standardLockupOptions.offerEnvironment, standardLockupOptions.offerStyle);
+ if (serverData.isDefinedNonNull(appEventOrNull)) {
+ appEvent = appEventOrNull;
+ parentAppData = resultData !== null && resultData !== void 0 ? resultData : mediaRelationship.relationshipData(objectGraph, appEventDataItem, "app");
+ break;
+ }
+ }
+ if (serverData.isNull(appEvent)) {
+ return null;
+ }
+ const alwaysShowAppEvent = serverData.asBooleanOrFalse(resultData.meta, "associations.app-events.attributes.forceAppEvent");
+ const searchResult = new models.AppEventSearchResult();
+ searchResult.lockup = lockup;
+ searchResult.appEvent = appEvent;
+ searchResult.alwaysShowAppEvent = alwaysShowAppEvent;
+ searchResult.clickAction = lockup.clickAction;
+ const recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(null, personalizationData === null || personalizationData === void 0 ? void 0 : personalizationData.processingType, null);
+ const impressionOptions = {
+ ...baseMetricsOptions,
+ id: appEvent.appEventId,
+ kind: "inAppEvent",
+ targetType: "eventModule",
+ title: appEvent.title,
+ softwareType: null,
+ recoMetricsData: recoMetricsData,
+ };
+ if (serverData.isDefinedNonNull(parentAppData)) {
+ impressionOptions.relatedSubjectIds = [parentAppData.id];
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchResult, impressionOptions);
+ return searchResult;
+ });
+}
+//# sourceMappingURL=search-results.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-shelves.js b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-shelves.js
new file mode 100644
index 0000000..03df66d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/content/search-shelves.js
@@ -0,0 +1,53 @@
+import * as models from "../../../api/models";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import { asInterface } from "../../../foundation/json-parsing/server-data";
+import { createMetricsOptionsForGenericSearchPageShelf } from "../../metrics/helpers/search/search-shelves";
+import { isNothing } from "@jet/environment";
+// The types of search pages shelves can render in
+export var SearchPageType;
+(function (SearchPageType) {
+ SearchPageType[SearchPageType["Landing"] = 0] = "Landing";
+ SearchPageType[SearchPageType["Results"] = 1] = "Results";
+ SearchPageType[SearchPageType["ChartsAndCategories"] = 2] = "ChartsAndCategories";
+ SearchPageType[SearchPageType["Focus"] = 3] = "Focus";
+})(SearchPageType || (SearchPageType = {}));
+/**
+ * Collections the shelf's attributes
+ * @param objectGraph The App Store Object Graph
+ * @param data The shelf data object
+ * @returns The attributes for the shelf
+ */
+export function shelfAttributesFromData(objectGraph, data, shelfKind = undefined, pageKind = models.SearchPageKind.Default) {
+ var _a, _b, _c;
+ const title = (_a = mediaAttributes.attributeAsString(data, "title")) !== null && _a !== void 0 ? _a : undefined;
+ let displayStyle = (_b = mediaAttributes.attributeAsInterface(data, "displayStyle")) !== null && _b !== void 0 ? _b : undefined;
+ /// If we are on the see-all page, we currently don't get back a displayStyle object, so we need to manually provide one
+ /// for the tile brick type as see-all is hard-coded for that density everywhere else (natively)
+ if (isNothing(displayStyle) && pageKind === models.SearchPageKind.CategoriesAndCharts) {
+ displayStyle = {
+ layoutDensity: models.GenericSearchPageShelfDisplayStyleDensity.Density1,
+ layout: undefined,
+ layoutSize: undefined,
+ };
+ }
+ const itemDisplayStyleAttributes = mediaAttributes.attributeAsDictionary(data, "itemDisplayStyle");
+ const itemDisplayStyle = asInterface(itemDisplayStyleAttributes);
+ const hasSeeAll = mediaAttributes.attributeAsBooleanOrFalse(data, "hasSeeAll");
+ const displayCount = (_c = mediaAttributes.attributeAsNumber(data, "displayCount")) !== null && _c !== void 0 ? _c : undefined;
+ const seeAllURL = hasSeeAll ? data.href : undefined;
+ return new models.SearchShelfAttributes(data.id, title, displayStyle, displayCount, hasSeeAll, seeAllURL, itemDisplayStyle, shelfKind);
+}
+/**
+ * Creates a base shelf context for the search shelf
+ * @param objectGraph The App Store Object Graph
+ * @param data The shelf data object
+ * @param shelfAttributes The shelf's attributes
+ * @param searchPageContext The context for the page containing the shelf
+ * @returns A standard shelf context for the shelf
+ */
+export function baseShelfContext(objectGraph, data, shelfAttributes, searchPageContext) {
+ return {
+ metricsOptions: createMetricsOptionsForGenericSearchPageShelf(objectGraph, data, shelfAttributes, searchPageContext),
+ };
+}
+//# sourceMappingURL=search-shelves.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/custom-creative.js b/node_modules/@jet-app/app-store/tmp/src/common/search/custom-creative.js
new file mode 100644
index 0000000..9b3aa26
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/custom-creative.js
@@ -0,0 +1,71 @@
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { isNothing, isSome } from "@jet/environment";
+import { asDictionary } from "@apple-media-services/media-api";
+import { artworkFromApiArtwork } from "../content/content";
+import { asString } from "../../foundation/json-parsing/server-data";
+import { Video } from "../../api/models";
+export function getSelectedCustomCreativeId(data) {
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ const customCreativeMeta = asDictionary(data, "meta.creativeAttributes");
+ const selectedCustomCreativeId = asString(customCreativeMeta, "creatives.0");
+ return selectedCustomCreativeId;
+ }
+ return null;
+}
+/**
+ * Create a custom creative artwork from the data.
+ * @param objectGraph Object graph
+ * @param data that we use to populate custom creative artwork
+ * @param customCreativeData where we store custom creative data.
+ * @param cropCode for the artwork
+ */
+export function customCreativeArtworkFromData(objectGraph, data, customCreativeData, cropCode) {
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ const selectedCustomCreativeId = getSelectedCustomCreativeId(data);
+ if (isNothing(customCreativeData) || isNothing(selectedCustomCreativeId)) {
+ return null;
+ }
+ const creativeAssetArtwork = artworkFromApiArtwork(objectGraph, serverData.asDictionary(customCreativeData, `${selectedCustomCreativeId}.adCreativeArtwork`), {
+ allowingTransparency: false,
+ useCase: 4 /* ArtworkUseCase.LockupScreenshots */,
+ });
+ if (isSome(cropCode) && isSome(creativeAssetArtwork)) {
+ creativeAssetArtwork.crop = cropCode;
+ }
+ return creativeAssetArtwork;
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Create a custom creative video from the data.
+ * @param objectGraph Object graph
+ * @param data that we use to populate custom creative video and preview.
+ * @param videoConfiguration for the custom creative video.
+ * @param customCreativeData where we store custom creative data.
+ * @param cropCode for the artwork
+ */
+export function customCreativeVideoFromData(objectGraph, data, customCreativeData, videoConfiguration, cropCode) {
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ const selectedCustomCreativeId = getSelectedCustomCreativeId(data);
+ if (isNothing(selectedCustomCreativeId)) {
+ return null;
+ }
+ const creativeVideoData = serverData.asDictionary(customCreativeData, `${selectedCustomCreativeId}.adCreativeVideo`);
+ const videoUrl = serverData.asString(creativeVideoData, "video");
+ const preview = artworkFromApiArtwork(objectGraph, serverData.asDictionary(creativeVideoData, "previewFrame"), {
+ allowingTransparency: false,
+ useCase: 4 /* ArtworkUseCase.LockupScreenshots */,
+ });
+ if (isNothing(videoUrl) || isNothing(preview)) {
+ return null;
+ }
+ if (isSome(cropCode)) {
+ preview.crop = cropCode;
+ }
+ return new Video(videoUrl, preview, videoConfiguration);
+ }
+ return null;
+}
+//# sourceMappingURL=custom-creative.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search-metrics.js b/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search-metrics.js
new file mode 100644
index 0000000..83cdf90
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search-metrics.js
@@ -0,0 +1,132 @@
+/**
+ * Builds metrics entities for Guided Search
+ */
+import { ImpressionMetrics, } from "../../../api/models";
+import { isDefinedNonNullNonEmpty, isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { createMetricsClickData, createMetricsSearchData } from "../../metrics/builder";
+import { createBasicLocation, currentPosition } from "../../metrics/helpers/location";
+// region Action Metrics
+/**
+ * Add click + search metrics data to guided search token action
+ * @param objectGraph The dependency graph for the App Store.
+ * @param action Action to instrument, e.g. toggle or rewrite.
+ * @param targetToken The token display label, which may be toggleable.
+ * @param searchTerm The search term for which this token action was returned for.
+ * @param metricsOptions The metrics option use for adding instrumentation to token toggle.
+ */
+export function addEventsToGuidedSearchTokenAction(objectGraph, action, targetToken, searchTerm, metricsOptions) {
+ // Click Fields
+ const actionType = "guidedSearch";
+ const targetType = "guidedLabel";
+ const clickFields = {
+ actionType: actionType,
+ location: createBasicLocation(objectGraph, {
+ pageInformation: null,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: targetType,
+ }, targetToken),
+ searchTerm: searchTerm,
+ };
+ // Search Fields
+ const searchFields = {
+ targetId: targetToken,
+ };
+ // SSS: Clicks must be before Search
+ const clickData = createMetricsClickData(objectGraph, targetToken, targetType, clickFields, ["guidedSearch"]);
+ action.actionMetrics.addMetricsData(clickData);
+ const searchData = createMetricsSearchData(objectGraph, searchTerm, targetType, actionType, null, searchFields, [
+ "guidedSearch",
+ ]);
+ action.actionMetrics.addMetricsData(searchData);
+}
+export function addEventsToGuidedSearchTokenEntityChangeAction(objectGraph, action, searchTerm, targetEntity, metricsOptions) {
+ // Click Fields
+ const actionType = "hint";
+ const targetType = "hintsEntity";
+ const clickFields = {
+ actionType: actionType,
+ location: createBasicLocation(objectGraph, {
+ pageInformation: null,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: targetType,
+ }, targetEntity),
+ searchTerm: searchTerm,
+ };
+ // Search Fields
+ const searchFields = {
+ targetId: targetEntity,
+ };
+ // SSS: Clicks must be before Search
+ const clickData = createMetricsClickData(objectGraph, targetEntity, targetType, clickFields);
+ action.actionMetrics.addMetricsData(clickData);
+ const searchData = createMetricsSearchData(objectGraph, searchTerm, targetType, actionType, null, searchFields);
+ action.actionMetrics.addMetricsData(searchData);
+}
+// endregion
+// region Page Metrics
+/**
+ * Build a `MetricsFields` for guided search fields used for page and impression events.
+ * This is attached onto `MetricsPageInformation` for consumption during page and impression field generation.
+ */
+export function guidedSearchPageInformationFields(objectGraph, request, guidedSearchData) {
+ const fields = {};
+ // field for what tokens were selected on page.
+ if (isDefinedNonNullNonEmpty(request.guidedSearchTokens)) {
+ fields["searchSelectedGuidedFacets"] = request.guidedSearchTokens;
+ }
+ // field for what the server computed final query was
+ if (guidedSearchData && guidedSearchData.finalTerm) {
+ fields["searchGuidedFinalQuery"] = guidedSearchData.finalTerm;
+ }
+ if (isNullOrEmpty(fields)) {
+ return undefined;
+ }
+ return fields;
+}
+// endregion
+// region Impression Metrics
+export function addImpressionMetricsToGuidedSearchToken(objectGraph, token, type, metricsOptions) {
+ const tokenIndex = currentPosition(metricsOptions.locationTracker);
+ /**
+ * ID for parent is index. This should be explicit but is not in how we do metrics in JS today (sequential IDs are added later)
+ */
+ const impressionFields = {
+ impressionIndex: tokenIndex,
+ id: tokenIndex.toString(),
+ idType: "sequential",
+ name: token.displayName,
+ impressionType: type,
+ parentId: "search-revisions",
+ };
+ token.impressionMetrics = new ImpressionMetrics(impressionFields);
+}
+/**
+ * Add imaginary parent container for search results
+ * <rdar://problem/70515816> Guided Search: Tech Debt: Trim parent impression tech debt off SearchResults (JS Compatibility Breaking)
+ */
+export function addSearchResultParentImpressionMetrics(objectGraph, searchResultsPage, metricsOptions) {
+ const parentIndex = currentPosition(metricsOptions.locationTracker);
+ searchResultsPage.resultsParentImpressionMetrics = new ImpressionMetrics({
+ impressionIndex: parentIndex,
+ impressionType: "SearchResults",
+ idType: "relationship",
+ id: "search-results",
+ name: "Search Results",
+ });
+}
+/**
+ * Add an imaginary parent container for guided search tokens
+ * <rdar://problem/70515816> Guided Search: Tech Debt: Trim parent impression tech debt off SearchResults (JS Compatibility Breaking)
+ */
+export function addGuidedSearchParentImpressionMetrics(objectGraph, searchResultsPage, metricsOptions) {
+ const parentIndex = currentPosition(metricsOptions.locationTracker);
+ searchResultsPage.guidedSearchTokensParentImpressionMetrics = new ImpressionMetrics({
+ impressionIndex: parentIndex,
+ impressionType: "SearchRevisions",
+ idType: "relationship",
+ id: "search-revisions",
+ name: "Search Revisions",
+ });
+}
+// endregion
+//# sourceMappingURL=guided-search-metrics.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search.js b/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search.js
new file mode 100644
index 0000000..3be4ae9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/guided-search/guided-search.js
@@ -0,0 +1,105 @@
+/**
+ * Model Builder for Guided Search (STUB)
+ */
+import { GuidedSearchQuery, GuidedSearchToken, SearchAction, searchEntitySystemImage, } from "../../../api/models";
+import { GuidedSearchTokenToggleAction, SearchEntityChangeAction, } from "../../../api/models/search/guided-search-actions";
+import { isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { unreachable } from "../../../foundation/util/errors";
+import { addEventsToGuidedSearchTokenEntityChangeAction, addEventsToGuidedSearchTokenAction, addImpressionMetricsToGuidedSearchToken, } from "./guided-search-metrics";
+// region exports
+/**
+ * Create a Guided search token from facet data.
+ * @param objectGraph The App Store object graph.
+ * @param selectionBehavior The behavior for click action, e.g. query rewrite versus toggling tokens.
+ * @param requestDescriptor The request descriptor for search request that returned this data.
+ * @param facetData The facet data to build with.
+ * @param metricsOptions The metrics options.
+ */
+export function createGuidedSearchToken(objectGraph, selectionBehavior, requestDescriptor, facetData, metricsOptions) {
+ if (isNullOrEmpty(facetData)) {
+ return null;
+ }
+ // Create click and search metrics for token action.
+ const origin = "guidedToken";
+ const searchTerm = requestDescriptor.term;
+ const targetToken = facetData.displayLabel;
+ const clickAction = selectionBehavior === "rewrite"
+ ? new SearchAction(targetToken, facetData.finalTerm, null, origin)
+ : new GuidedSearchTokenToggleAction(targetToken, origin);
+ addEventsToGuidedSearchTokenAction(objectGraph, clickAction, targetToken, searchTerm, metricsOptions);
+ // Create the token with associated click action and metrics..
+ const token = new GuidedSearchToken(targetToken, facetData.isSelected, undefined, targetToken, clickAction);
+ addImpressionMetricsToGuidedSearchToken(objectGraph, token, "guidedLabel", metricsOptions);
+ return token;
+}
+/**
+ * Create the guided search queries from facet data.
+ * These are stored across a guided search session so client can send down precomputed combinations of search term and guided search facets as an optimization.
+ * @param requestDescriptor The request descriptor for search request that returned this data.
+ * @param facetData The data to generate `GuidedSearchQuery` for.
+ */
+export function createGuidedSearchQueries(objectGraph, requestDescriptor, facetData) {
+ var _a;
+ if (isNullOrEmpty(facetData)) {
+ return null;
+ }
+ // request parameters that returned this `facetData`
+ const requestTerm = requestDescriptor.term;
+ const requestFacets = (_a = requestDescriptor.guidedSearchTokens) !== null && _a !== void 0 ? _a : [];
+ const queries = [];
+ for (const data of facetData) {
+ /**
+ * For each facet data, project the facet selection if that facet was selected / deselected w.r.t. request's facets
+ */
+ const facetValue = data.displayLabel;
+ const lookaheadFacets = Array.from(requestFacets);
+ if (data.isSelected) {
+ const facetIndex = lookaheadFacets.indexOf(facetValue);
+ if (facetIndex !== -1) {
+ lookaheadFacets.splice(facetIndex, 1);
+ }
+ }
+ else {
+ lookaheadFacets.push(facetValue);
+ }
+ const query = new GuidedSearchQuery(requestTerm, lookaheadFacets, data.finalTerm);
+ queries.push(query);
+ }
+ return queries;
+}
+/**
+ * Create an action for **reversing** a selected entity hint. This is so user can reverse an selected entity filter for search.
+ * e.g. If user tapped "Search for Games in Apple Watch Apps", user can deselect the "Apple Watch Apps" entity filter from the Guided Search UI
+ */
+export function createGuidedSearchTokenClearingEntityFilter(objectGraph, requestDescriptor, metricsOptions) {
+ var _a;
+ const selectedEntityFilter = requestDescriptor.searchEntity;
+ if (!selectedEntityFilter) {
+ return null;
+ }
+ const deselectEntityAction = new SearchEntityChangeAction(null, "guidedToken");
+ addEventsToGuidedSearchTokenEntityChangeAction(objectGraph, deselectEntityAction, requestDescriptor.term, selectedEntityFilter, metricsOptions);
+ let displayText;
+ switch (selectedEntityFilter) {
+ case "arcade":
+ displayText = objectGraph.loc.string("GUIDED_SEARCH_TOKEN_ENTITY_ARCADE");
+ break;
+ case "developer":
+ displayText = objectGraph.loc.string("GUIDED_SEARCH_TOKEN_ENTITY_DEVELOPERS");
+ break;
+ case "story":
+ displayText = objectGraph.loc.string("GUIDED_SEARCH_TOKEN_ENTITY_STORIES");
+ break;
+ case "watch":
+ displayText = objectGraph.loc.string("GUIDED_SEARCH_TOKEN_ENTITY_APPLEWATCH");
+ break;
+ default:
+ unreachable(selectedEntityFilter);
+ break;
+ }
+ const token = new GuidedSearchToken(displayText, true, (_a = searchEntitySystemImage(selectedEntityFilter)) !== null && _a !== void 0 ? _a : "magnifyingglass", displayText, deselectEntityAction);
+ addImpressionMetricsToGuidedSearchToken(objectGraph, token, "hintsEntity", metricsOptions);
+ return token;
+}
+// endregion
+//# sourceMappingURL=guided-search.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js
new file mode 100644
index 0000000..6baf947
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-cohort.js
@@ -0,0 +1,84 @@
+/**
+ * Manages storing Cohort IDs for Search Landing Page (SLP)
+ *
+ * # What is Cohort ID?
+ * Cohort IDs (a.k.a. cluster IDs) are used to bucket users into different categories, e.g. a gamer.
+ * This ID can be used to specify a SLP that is suited to that cateogry of users, e.g. a page featuring more games.
+ *
+ * # SLP Endpoint Constraints
+ * Today, SLPs are not personalized, and rely heavily on CDN caching.
+ * We cannot fire a single request to SLP endpoint to have it adapt to a user's cohort based on cookies, etc.
+ *
+ * As a workaround we:
+ * 1. Store the cohort ID for user if we ever load a personalized endpoint, e.g. Today.
+ * 2. Send stored cohort ID as a query param on the SLP endpoint, if we have any.
+ */
+"use strict";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+// region exports
+/**
+ * Should be called whenever we recieve a MAPI response from a personalized endpoint.
+ * Persists the cohort id that may be present in given MAPI respose for specified user.
+ * @param dsid The user's dsid.
+ * @param metaDataProviding MAPI response that may contain cohort id for current user.
+ */
+export function storeCohortIdForUserFromResponse(objectGraph, dsid, metaDataProviding) {
+ if (serverData.isNullOrEmpty(dsid)) {
+ return;
+ }
+ const cohortId = cohortIdFromResponse(metaDataProviding);
+ if (serverData.isNullOrEmpty(cohortId)) {
+ return;
+ }
+ setCohortIdForDSID(objectGraph, dsid, cohortId);
+}
+/**
+ * Return the stored cohort id for given dsid.
+ * @param dsid The DSID of user to fetch cohort id for.
+ */
+export function cohortIdForUser(objectGraph, dsid) {
+ if (serverData.isNullOrEmpty(dsid)) {
+ return null;
+ }
+ return getCohortIdForDSID(objectGraph, dsid);
+}
+/**
+ * Deletes cohort id for given user. For testing.
+ */
+export function deleteCohortIdForUser(objectGraph, dsid) {
+ setCohortIdForDSID(objectGraph, dsid, undefined);
+}
+// endregion
+// region Internals
+/**
+ * Given a top-level MAPI response `metaDataProviding`, returns the cohort ID, if any.
+ * @param metaDataProviding A MAPI Response that may contain a `meta.metrics` blob with `clusterId`
+ */
+function cohortIdFromResponse(metaDataProviding) {
+ return serverData.asString(metaDataProviding, "meta.metrics.clusterId");
+}
+/**
+ * Converts a DSID into a dictionary key for storing cohort ID.
+ * @param dsid DSID to generate storage key for.
+ */
+function cohortIDStorageKeyForDSID(dsid) {
+ return dsid + "-cohort-id";
+}
+/**
+ * Set the stored cohort id for given user (by dsid).
+ * @param dsid DSID to associate cohortId with
+ * @param cohortId The cohort id for user.
+ */
+function setCohortIdForDSID(objectGraph, dsid, cohortId) {
+ const cohortForDSIDKey = cohortIDStorageKeyForDSID(dsid);
+ objectGraph.storage.storeString(cohortForDSIDKey, cohortId);
+}
+/**
+ * Gets the stored cohort id for given user by dsid.
+ * @param dsid DSID to get cohort id for.
+ */
+function getCohortIdForDSID(objectGraph, dsid) {
+ const cohortForDSIDKey = cohortIDStorageKeyForDSID(dsid);
+ return objectGraph.storage.retrieveString(cohortForDSIDKey);
+}
+//# sourceMappingURL=search-landing-cohort.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js
new file mode 100644
index 0000000..808384b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/landing/search-landing-shelf-controller.js
@@ -0,0 +1,836 @@
+import { isNothing, isSome, unwrapOptional } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as filtering from "../../filtering";
+import * as adLockups from "../../lockups/ad-lockups";
+import * as lockups from "../../lockups/lockups";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaDataStructure from "../../../foundation/media/data-structure";
+import * as mediaRelationships from "../../../foundation/media/relationships";
+import { searchLandingPageAdShelfIdentifier, searchLandingPagePositionInfo } from "../../ads/on-device-ad-stitch";
+import { createArtworkForResource } from "../../content/artwork/artwork";
+import * as content from "../../content/content";
+import * as metricsHelpersPage from "../../metrics/helpers/page";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { createChartsCategoryShelf } from "../content/search-categories";
+import { MediumAdLockupWithScreenshotsBackground, } from "../../../api/models";
+import * as adStitch from "../../ads/ad-stitcher";
+import * as adIncidents from "../../ads/ad-incident-recorder";
+import * as searchShelves from "../content/search-shelves";
+import { CollectionShelfDisplayStyle } from "../../editorial-pages/editorial-page-types";
+import { buildBrick } from "../../editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-brick-collection-shelf-builder";
+import { iconFromData } from "../../content/content";
+import * as searchHistoryShelf from "../shelves/search-history-shelf";
+import { adLogger } from "../search-ads";
+import { asString } from "../../../foundation/json-parsing/server-data";
+import { addImpressionsFieldsToAd } from "../../metrics/helpers/impressions";
+import { makeSearchResultsPageIntent } from "../../../api/intents/search-results-page-intent";
+import { getLocale } from "../../locale";
+import { actionFor } from "../../../foundation/runtime/action-provider";
+import * as impressionDemotion from "../../personalization/on-device-impression-demotion";
+import { applySearchAdMissedOpportunityToShelvesIfNeeded } from "../../ads/ad-common";
+// #region Shelf Creation
+export function firstShelfMarkerMatchingUseCase(dataContainer, searchLandingPageContext, onDevicePersonalizationUseCase) {
+ const shelfData = dataContainer.data;
+ if (serverData.isNullOrEmpty(shelfData)) {
+ return null;
+ }
+ for (const dataItem of shelfData) {
+ if (serverData.isNullOrEmpty(dataItem)) {
+ continue;
+ }
+ /// Skip shelf if not valid for context page type.
+ const shelfMetadata = serverData.asDictionary(dataItem, "meta");
+ const shelfPageType = pageTypeFromShelfMetaCategory(shelfMetadata === null || shelfMetadata === void 0 ? void 0 : shelfMetadata.category);
+ if (shelfPageType !== searchLandingPageContext.pageType) {
+ continue;
+ }
+ if (onDevicePersonalizationUseCase ===
+ mediaAttributes.attributeAsString(dataItem, "onDevicePersonalizationUseCase")) {
+ return dataItem;
+ }
+ }
+ return null;
+}
+/**
+ * Inserts the shelf made from the data container into the grouping parse context
+ * @param objectGraph The App Store dependency graph
+ * @param dataContainer The response data
+ * @param searchLandingPageContext The context for the search page, e.g. landing or focus
+ */
+export function insertShelvesIntoSearchPageContext(objectGraph, dataContainer, searchLandingPageContext) {
+ var _a;
+ const shelfData = dataContainer.data;
+ if (serverData.isNullOrEmpty(shelfData)) {
+ return;
+ }
+ // index to compare adPositionInfo
+ let builtShelves = 0;
+ const supportsFocus = objectGraph.bag.mediaAPISearchFocusEnabled && isSome(searchLandingPageContext.pageType);
+ for (const dataItem of shelfData) {
+ if (serverData.isNullOrEmpty(dataItem)) {
+ continue;
+ }
+ /// Skip shelf if not valid for context page type.
+ if (supportsFocus) {
+ const shelfMetadata = serverData.asDictionary(dataItem, "meta");
+ const shelfPageType = pageTypeFromShelfMetaCategory(shelfMetadata === null || shelfMetadata === void 0 ? void 0 : shelfMetadata.category);
+ if (isSome(shelfPageType) && shelfPageType !== searchLandingPageContext.pageType) {
+ continue;
+ }
+ }
+ const adMeta = dataContainer.meta || null;
+ const adUnitShelf = shelfFromAdStitcher(objectGraph, searchLandingPageContext, builtShelves, adMeta);
+ if (isSome(adUnitShelf)) {
+ searchLandingPageContext.shelves.push(adUnitShelf);
+ metricsHelpersLocation.nextPosition(searchLandingPageContext.metricsLocationTracker);
+ }
+ /// Get the necessary metadata for the shelf
+ const shelfKind = mediaAttributes.attributeAsString(dataItem, "contentKind");
+ const shelfAttributes = searchShelves.shelfAttributesFromData(objectGraph, dataItem, shelfKind);
+ const shelfContext = searchLandingPageShelfContext(objectGraph, dataItem, shelfAttributes, searchLandingPageContext, shelfKind);
+ /// Push the shelf content location so each shelf has the correct parent and starting index
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfContext.metricsOptions, (_a = shelfAttributes.title) !== null && _a !== void 0 ? _a : "");
+ /// Create the shelf
+ const shelf = createShelf(objectGraph, dataItem, searchLandingPageContext, shelfContext, shelfAttributes, shelfKind);
+ /// Pop the shelf location
+ metricsHelpersLocation.popLocation(searchLandingPageContext.metricsLocationTracker);
+ /// If the shelf is empty, skip it
+ if (serverData.isNullOrEmpty(shelf)) {
+ continue;
+ }
+ /// Add impressions for the shelf
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfContext.metricsOptions);
+ applySearchAdMissedOpportunityToShelvesIfNeeded(objectGraph, [shelf], "searchLanding", shelfContext.metricsOptions.id, searchLandingPageContext.metricsPageInformation);
+ /// Add the shelf to the page context for processing later
+ searchLandingPageContext.shelves.push(shelf);
+ builtShelves += 1;
+ /// Make sure each shelf is represented by its own impressions index position
+ metricsHelpersLocation.nextPosition(searchLandingPageContext.metricsLocationTracker);
+ }
+}
+function pageTypeFromShelfMetaCategory(category) {
+ switch (category) {
+ case "search-landing":
+ return searchShelves.SearchPageType.Landing;
+ case "search-focus":
+ return searchShelves.SearchPageType.Focus;
+ default:
+ return undefined;
+ }
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfContext The context for the shelf
+ * @param shelfAttributes The attributes for the shelf
+ * @param shelfKind The content kind for the shelf
+ * @returns A shelf if supported, null otherwise
+ */
+function createShelf(objectGraph, data, pageContext, shelfContext, shelfAttributes, shelfKind) {
+ switch (shelfKind) {
+ case models.SearchLandingPageContentKind.Suggestion:
+ if (objectGraph.client.isVision || objectGraph.client.isWeb) {
+ return createSuggestedLinksShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ else if (pageContext.pageType === searchShelves.SearchPageType.Focus) {
+ return createSuggestedSearchesShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ else {
+ return createSuggestedLinkActionsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ case models.SearchLandingPageContentKind.CategoriesAndCharts:
+ return createChartsCategoryShelf(objectGraph, data, false, shelfAttributes, pageContext, shelfContext);
+ case models.SearchLandingPageContentKind.Apps:
+ return createLockupsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ case models.SearchLandingPageContentKind.EditorialCollection:
+ if (objectGraph.client.isVision || objectGraph.client.isWeb) {
+ return buildBrickShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext);
+ }
+ else {
+ return null;
+ }
+ default:
+ return createShelfFromMarker(objectGraph, data, pageContext, shelfContext, shelfAttributes);
+ }
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfContext The context for the shelf
+ * @param shelfAttributes The attributes for the shelf
+ * @returns A shelf if supported, null otherwise
+ */
+function createShelfFromMarker(objectGraph, data, pageContext, shelfContext, shelfAttributes) {
+ if (data.type !== "search-recommendations-marker") {
+ return null;
+ }
+ switch (mediaAttributes.attributeAsString(data, "onDevicePersonalizationUseCase")) {
+ case "recentSearches":
+ return searchHistoryShelf.createShelfWithContext(objectGraph, pageContext, shelfAttributes);
+ default:
+ return null;
+ }
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ */
+function createMediumAdLockupWithScreenshotsBackgroundShelf(objectGraph, data, pageContext) {
+ var _a, _b, _c;
+ const shelf = new models.Shelf("mediumAdLockupWithScreenshotsBackground");
+ shelf.isHorizontal = false;
+ const offerEnvironment = "dark";
+ const offerStyle = "white";
+ const metricsOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, asString(data.attributes.name), {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ targetType: "card",
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ });
+ metricsOptions.kind = "adItem";
+ // Set up iAdInfo
+ metricsOptions.pageInformation.iAdInfo.apply(objectGraph, data);
+ const lockupOptions = {
+ offerEnvironment: offerEnvironment,
+ offerStyle: offerStyle,
+ metricsOptions: {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ disableFastImpressionsForAds: true,
+ },
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, "mediumAdLockupWithScreenshotsBackground"),
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "mediumAdLockupWithScreenshotsBackground"),
+ };
+ const videoConfiguration = {
+ canPlayFullScreen: false,
+ playbackControls: {},
+ };
+ let lockup = lockups.mixedMediaAdLockupFromData(objectGraph, data, lockupOptions, videoConfiguration, null, false);
+ const platformScreenshots = lockup.screenshots[0];
+ const templateString = adLockups.getTemplateTypeForMediumAdFromLockupWithScreenshots(platformScreenshots);
+ pageContext.metricsPageInformation.iAdInfo.setTemplateType(templateString);
+ const iconData = iconFromData(objectGraph, data, {
+ useCase: 0 /* ArtworkUseCase.Default */,
+ withJoeColorPlaceholder: true,
+ overrideTextColorKey: "textColor2",
+ });
+ // Update the lockup value after setting the template type so that the value gets added to the lockup.
+ lockup = lockups.mixedMediaAdLockupFromData(objectGraph, data, lockupOptions, videoConfiguration, null, false);
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_a = lockup.searchAdOpportunity) === null || _a === void 0 ? void 0 : _a.setTemplateType(templateString);
+ }
+ else {
+ (_b = lockup.searchAd) === null || _b === void 0 ? void 0 : _b.setTemplateType(templateString);
+ }
+ const backgroundColor = iconData.backgroundColor;
+ const secondaryTextColor = iconData.textColor;
+ const mediumAd = new MediumAdLockupWithScreenshotsBackground(lockup, [platformScreenshots], true, secondaryTextColor, backgroundColor, (_c = objectGraph.bag.todayAdMediumLockupScreenshotsRiverSpeed) !== null && _c !== void 0 ? _c : 8);
+ addImpressionsFieldsToAd(objectGraph, mediumAd, metricsOptions, metricsOptions.pageInformation.iAdInfo);
+ mediumAd.clickAction = lockups.actionFromData(objectGraph, data, metricsOptions, null);
+ shelf.items = [mediumAd];
+ return shelf;
+}
+/**
+ * Creates the appropriate shelf for the shelf's type
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this shelf
+ * @param pageContext The context for the shelf's page
+ */
+function createCondensedAdLockupWithIconBackgroundShelf(objectGraph, data, pageContext) {
+ var _a, _b, _c;
+ const shelf = new models.Shelf("condensedAdLockupWithIconBackground");
+ shelf.isHorizontal = false;
+ const offerEnvironment = "dark";
+ const offerStyle = "white";
+ const metricsOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, asString(data.attributes.name), {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ targetType: "card",
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ });
+ metricsOptions.kind = "adItem";
+ // Set up iAdInfo
+ metricsOptions.pageInformation.iAdInfo.apply(objectGraph, data);
+ const lockupOptions = {
+ offerEnvironment: offerEnvironment,
+ offerStyle: offerStyle,
+ metricsOptions: {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(data),
+ isAdvert: adLockups.isAdvert(objectGraph, data),
+ disableFastImpressionsForAds: true,
+ },
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, "condensedAdLockupWithIconBackground"),
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "condensedAdLockupWithIconBackground"),
+ };
+ (_a = pageContext.metricsPageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.setTemplateType("APPLOCKUP");
+ const videoConfiguration = {
+ canPlayFullScreen: false,
+ playbackControls: {},
+ };
+ const lockup = lockups.mixedMediaAdLockupFromData(objectGraph, data, lockupOptions, videoConfiguration, null, false);
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_b = lockup.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.setTemplateType("APPLOCKUP");
+ }
+ else {
+ (_c = lockup.searchAd) === null || _c === void 0 ? void 0 : _c.setTemplateType("APPLOCKUP");
+ }
+ const condensedAd = new models.CondensedAdLockupWithIconBackground(lockup, lockup.icon);
+ addImpressionsFieldsToAd(objectGraph, condensedAd, metricsOptions, metricsOptions.pageInformation.iAdInfo);
+ shelf.items = [condensedAd];
+ return shelf;
+}
+// #endregion
+// #region Suggested Links/Actions Shelves
+/**
+ * Creates the suggested links shelf
+ * @param objectGraph The App Store dependency graph
+ * @param data The content items for the shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns A shelf for the suggested links, or null if the shelf would be empty
+ */
+function createSuggestedLinkActionsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a;
+ const linkData = mediaRelationships.relationshipCollection(data, "contents");
+ const items = [];
+ const shelf = new models.Shelf("action");
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ shelf.presentationHints = { isWidthConstrained: true };
+ for (const [linkIndex, link] of linkData.entries()) {
+ const metricsBase = {
+ targetType: "link",
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ };
+ const searchAdAction = trendingSearchLinkActionFromData(objectGraph, link, pageContext, shelfAttributes);
+ if (serverData.isNull(searchAdAction) || serverData.isNull(searchAdAction.action)) {
+ continue;
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchAdAction.action, {
+ ...metricsBase,
+ kind: "link",
+ softwareType: null,
+ title: searchAdAction.action.title,
+ id: `${linkIndex}`,
+ idType: "sequential",
+ });
+ items.push(searchAdAction);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ if (serverData.isNullOrEmpty(items)) {
+ return null;
+ }
+ shelf.items = items;
+ if (serverData.isNumber((_a = shelfAttributes.displayStyle) === null || _a === void 0 ? void 0 : _a.layoutSize)) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: shelfAttributes.displayStyle.layoutSize,
+ };
+ }
+ else if (objectGraph.client.isPhone || objectGraph.client.isPad) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: items.length >= 6 ? 2 : 1,
+ };
+ }
+ return shelf;
+}
+/**
+ * Creates the suggested searches shelf for the focus page.
+ * @param objectGraph The App Store dependency graph
+ * @param data The content items for the shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns A shelf for the suggested links, or null if the shelf would be empty
+ */
+function createSuggestedSearchesShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a, _b, _c;
+ const linkData = mediaRelationships.relationshipCollection(data, "contents");
+ if (isNothing(linkData)) {
+ return null;
+ }
+ const items = [];
+ const shelf = new models.Shelf("singleColumnList");
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ shelf.presentationHints = { isWidthConstrained: true };
+ for (const [linkIndex, link] of linkData.entries()) {
+ const searchTerm = mediaAttributes.attributeAsString(link, "searchTerm");
+ if (isNothing(searchTerm) || searchTerm.length === 0) {
+ continue; // search term is required
+ }
+ const displayTerm = (_a = mediaAttributes.attributeAsString(link, "displayTerm")) !== null && _a !== void 0 ? _a : searchTerm;
+ const searchAction = createFocusPageSearchAction(objectGraph, displayTerm !== null && displayTerm !== void 0 ? displayTerm : "", searchTerm !== null && searchTerm !== void 0 ? searchTerm : "", undefined, pageContext.metricsLocationTracker, "suggested", undefined, /// MAINTAINER'S NOTE: In the future, we could use this to attribute the suggestion source to the search result fetch.
+ pageContext.metricsPageInformation, (_b = shelfAttributes.searchLandingItemDisplayStyle) !== null && _b !== void 0 ? _b : undefined);
+ if (isNothing(searchAction) || serverData.isNullOrEmpty(searchAction)) {
+ continue;
+ }
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchAction, {
+ targetType: "link",
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ kind: "link",
+ softwareType: null,
+ title: (_c = searchAction.title) !== null && _c !== void 0 ? _c : "",
+ id: `${linkIndex}`,
+ idType: "sequential",
+ });
+ items.push(searchAction);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ if (serverData.isNullOrEmpty(items)) {
+ return null;
+ }
+ shelf.items = items;
+ return shelf;
+}
+/**
+ * Creates the suggested links shelf
+ * @param objectGraph The App Store dependency graph
+ * @param data The content items for the shelf
+ * @param pageContext The context for the shelf's page
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns A shelf for the suggested links, or null if the shelf would be empty
+ */
+function createSuggestedLinksShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a;
+ const linkData = mediaRelationships.relationshipCollection(data, "contents");
+ const links = [];
+ const shelf = new models.Shelf("searchLink");
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ shelf.presentationHints = { isWidthConstrained: true };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfContext.metricsOptions);
+ for (const [linkIndex, link] of linkData.entries()) {
+ const metricsBase = {
+ targetType: "link",
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ };
+ const searchLink = trendingSearchLinkFromData(objectGraph, link, pageContext, shelfAttributes);
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchLink, {
+ ...metricsBase,
+ kind: "link",
+ softwareType: null,
+ title: searchLink.clickAction.title,
+ id: `${linkIndex}`,
+ idType: "sequential",
+ });
+ if (serverData.isNullOrEmpty(searchLink)) {
+ continue;
+ }
+ links.push(searchLink);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ if (serverData.isNullOrEmpty(links)) {
+ return null;
+ }
+ shelf.items = links;
+ if (serverData.isNumber((_a = shelfAttributes.displayStyle) === null || _a === void 0 ? void 0 : _a.layoutSize)) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: shelfAttributes.displayStyle.layoutSize,
+ };
+ }
+ else if (objectGraph.client.isPhone) {
+ shelf.contentsMetadata = {
+ type: "searchLandingTrendingSection",
+ numberOfColumns: links.length >= 6 ? 2 : 1,
+ };
+ }
+ return shelf;
+}
+/**
+ * Creates a suggestion link action for the link data
+ * NOTE: This is legacy and the newer SLP protocol uses `trendingSearchLinkFromData`
+ * @param objectGraph The App Store dependency graph
+ * @param link The data for an individual suggestion link
+ * @param pageContext The page context for the search link
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns An action representing the suggestion link
+ */
+function trendingSearchLinkActionFromData(objectGraph, link, pageContext, shelfAttributes) {
+ var _a, _b;
+ /// We should always have search term, but not necessarily displayTerm.
+ const searchTerm = mediaAttributes.attributeAsString(link, "searchTerm");
+ if (isNothing(searchTerm) || searchTerm.length === 0) {
+ return null;
+ }
+ const displayTerm = (_a = mediaAttributes.attributeAsString(link, "displayTerm")) !== null && _a !== void 0 ? _a : searchTerm;
+ /// MAINTAINER'S NOTE: In the future, we could use this to attribute the suggestion source to the search result fetch.
+ const searchAction = new models.SearchAction(displayTerm, searchTerm, null, "suggested", undefined, undefined);
+ searchAction.artwork = createArtworkForSearchAction((_b = shelfAttributes.searchLandingItemDisplayStyle) !== null && _b !== void 0 ? _b : undefined, objectGraph);
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", pageContext.metricsLocationTracker);
+ return isSome(searchAction) ? new models.SearchAdAction(searchAction) : null;
+}
+/**
+ * Creates a search action
+ * @param objectGraph The App Store dependency graph
+ * @param searchTerm The term to search for
+ * @param entity The entity to scope the search to, e.g. apps, stories, arcade
+ * @param metricsLocationTracker The metrics location tracker
+ * @param origin The source the search was fired from
+ * @param isFocusPage Whether the origin is in context of the focus page
+ * @param displayStyle The style to display the action item
+ * @returns An action representing a search
+ */
+export function createFocusPageSearchAction(objectGraph, title, searchTerm, entity, metricsLocationTracker, origin, source, metricsPageInformation = undefined, displayStyle) {
+ if (serverData.isNullOrEmpty(searchTerm)) {
+ return null;
+ }
+ // For Search Focus Page, text uses primary color and icon uses secondary color (instead of tintColor).
+ const searchAction = new models.SearchAction(title, searchTerm, null, origin, entity !== null && entity !== void 0 ? entity : undefined, source, []);
+ searchAction.artwork = createArtworkForSearchAction(displayStyle, objectGraph);
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", metricsLocationTracker, metricsPageInformation);
+ return searchAction;
+}
+function createArtworkForSearchAction(displayStyle, objectGraph) {
+ var _a;
+ if ((displayStyle === null || displayStyle === void 0 ? void 0 : displayStyle.iconKind) === models.SearchLandingPageShelfItemIconKind.Symbol && ((_a = displayStyle === null || displayStyle === void 0 ? void 0 : displayStyle.iconKind) === null || _a === void 0 ? void 0 : _a.length)) {
+ return createArtworkForResource(objectGraph, `systemimage://${displayStyle.iconSymbol}`);
+ }
+ else if (objectGraph.client.isPhone || objectGraph.client.isVision) {
+ return createArtworkForResource(objectGraph, "systemimage://magnifyingglass");
+ }
+ return undefined;
+}
+/**
+ * Creates a suggestion link action for the link data
+ * @param objectGraph The App Store dependency graph
+ * @param link The data for an individual suggestion link
+ * @param pageContext The page context for the search link
+ * @param shelfAttributes The attributes for the shelf and its contents
+ * @returns An action representing the suggestion link
+ */
+function trendingSearchLinkFromData(objectGraph, link, pageContext, shelfAttributes) {
+ var _a, _b, _c;
+ const searchTerm = mediaAttributes.attributeAsString(link, "searchTerm");
+ if (isNothing(searchTerm) || searchTerm.length === 0) {
+ return null;
+ }
+ const displayTerm = (_a = mediaAttributes.attributeAsString(link, "displayTerm")) !== null && _a !== void 0 ? _a : searchTerm; /// we should always have search term, but not necessarily displayTerm
+ let searchAction;
+ if (objectGraph.client.isWeb) {
+ const intent = makeSearchResultsPageIntent({
+ ...getLocale(objectGraph),
+ origin: "suggested",
+ term: displayTerm,
+ platform: (_b = objectGraph.activeIntent) === null || _b === void 0 ? void 0 : _b.previewPlatform,
+ });
+ searchAction = unwrapOptional(actionFor(intent, objectGraph));
+ }
+ else {
+ searchAction = new models.SearchAction(displayTerm, displayTerm, null, "suggested");
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", pageContext.metricsLocationTracker);
+ }
+ const artwork = createArtworkForSearchAction((_c = shelfAttributes.searchLandingItemDisplayStyle) !== null && _c !== void 0 ? _c : undefined, objectGraph);
+ return new models.SearchLink(displayTerm, searchAction, artwork, null);
+}
+// #endregion
+// #region Discover Lockups Shelves
+/**
+ * Creates a shelf composed of lockups
+ * @param objectGraph The App Store Object Graph
+ * @param data The data representing this lockups shelf
+ * @param pageContext The page context for the shelf
+ * @param shelfAttributes The attributes for the shelf
+ * @param shelfContext The context for the shelf
+ * @returns A lockups shelf if any lockups could be made
+ */
+function createLockupsShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ var _a, _b, _c, _d, _e;
+ const filterType = 80894 /* filtering.Filter.All */;
+ const items = [];
+ let hasAdLockup = false;
+ let shelfData = initialShelfContentsFromData(data);
+ // Set metadata
+ const shelf = new models.Shelf(shelfContext.shelfStyle);
+ shelf.isHorizontal = false;
+ shelf.title = shelfAttributes.title;
+ if (objectGraph.client.isVision) {
+ shelf.shouldFilterApps = !mediaAttributes.attributeAsBooleanOrFalse(data, "doNotFilter");
+ }
+ else {
+ shelf.shouldFilterApps = false;
+ }
+ shelf.filteringExcludedItems = shelfContext.filteringExcludedItems;
+ // Stitch First Position Ad (only if lockup array is nonempty)
+ if (serverData.isDefinedNonNullNonEmpty(shelfData)) {
+ const adLockup = lockupFromAdStitcher(objectGraph, pageContext, shelfContext);
+ if (adLockup && adLockup instanceof models.Lockup) {
+ hasAdLockup = true;
+ items.push(adLockup);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ shelfData = shelfData.filter((shelfItem) => shelfItem.id !== adLockup.adamId); // Filter dupe
+ }
+ }
+ // Determines whether all apps are displayed on SLP suggested apps shelf.
+ const hasDisplayCount = isSome(shelfAttributes.displayCount);
+ // If on device personalization is available, we need to personalize the data items. This reorders the shelf.
+ if (serverData.isDefinedNonNullNonEmpty(shelfData)) {
+ shelfData = impressionDemotion.personalizeDataItems(shelfData, (_a = pageContext.recoImpressionData) !== null && _a !== void 0 ? _a : {}, (_c = (_b = shelfContext.metricsOptions) === null || _b === void 0 ? void 0 : _b.recoMetricsData) !== null && _c !== void 0 ? _c : {});
+ }
+ // Build Lockups
+ for (const lockupData of shelfData) {
+ // If we encounter a type of app-events, this means they have been incorrectly programmed,
+ // and we should throw the shelf away.
+ if (lockupData.type === "app-events") {
+ return null;
+ }
+ if (serverData.isNull(lockupData.attributes)) {
+ continue;
+ }
+ // Filter out unwanted content
+ if (filtering.shouldFilter(objectGraph, lockupData, filterType)) {
+ continue;
+ }
+ const lockup = lockupFromData(objectGraph, lockupData, pageContext, shelfContext);
+ if (lockup) {
+ items.push(lockup);
+ metricsHelpersLocation.nextPosition(pageContext.metricsLocationTracker);
+ }
+ }
+ if (hasDisplayCount) {
+ // number of apps displayed on SLP suggested shelf
+ const displayCount = shelfAttributes.displayCount;
+ shelf.items = items.slice(0, displayCount);
+ }
+ else {
+ shelf.items = items;
+ }
+ if (hasDisplayCount) {
+ const seeAllShelf = new models.Shelf(shelfContext.shelfStyle);
+ if (hasAdLockup) {
+ // Ad is the first item in array so we are dropping it here.
+ seeAllShelf.items = items.splice(1, items.length - 1);
+ }
+ else {
+ seeAllShelf.items = items;
+ }
+ // Setup Page
+ const seeAllPage = new models.GenericPage([seeAllShelf]);
+ seeAllPage.title = shelf.title;
+ // Setup action
+ const seeAllAction = new models.FlowAction("page");
+ seeAllAction.pageUrl = shelfAttributes.seeAllLink;
+ seeAllAction.title = objectGraph.loc.string("ACTION_SEE_ALL");
+ seeAllAction.pageData = seeAllPage;
+ // Connect action
+ shelf.seeAllAction = seeAllAction;
+ // Metrics
+ metricsHelpersClicks.addClickEventToSeeAllAction(objectGraph, seeAllAction, seeAllAction.pageUrl, {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ });
+ const seeAllPageInformation = metricsHelpersPage.pageInformationForRoom(objectGraph, data.id);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, seeAllPage, seeAllPageInformation);
+ }
+ // Honor A/B treatment for using horizontal shelf for suggested apps, which may have an optional rowsPerColumn defined.
+ // NOTE: Client will choose an appropriate `rowsPerColumn` at display time, if not provided by server.
+ if (((_d = shelfAttributes.displayStyle) === null || _d === void 0 ? void 0 : _d.layout) === "horizontal" /* models.GenericSearchPageShelfDisplayStyleLayout.Horizontal */) {
+ shelf.isHorizontal = true;
+ shelf.rowsPerColumn = (_e = shelfAttributes.displayStyle) === null || _e === void 0 ? void 0 : _e.layoutSize;
+ }
+ return shelf;
+}
+/**
+ * Create a lockup for shelfContents to display within a grouping shelf
+ * @param objectGraph
+ * @param lockupData shelfContents to create lockup for.
+ * @param pageContext The page context for the lockup
+ * @param shelfContext The shelf context for the lockup
+ */
+function lockupFromData(objectGraph, lockupData, pageContext, shelfContext) {
+ if (serverData.isNullOrEmpty(lockupData)) {
+ return null;
+ }
+ if (shelfContext.shelfStyle !== "smallLockup") {
+ return null;
+ }
+ let offerStyle = null;
+ if (serverData.isDefinedNonNull(shelfContext.shelfBackground) &&
+ (shelfContext.shelfBackground.type === "color" || shelfContext.shelfBackground.type === "interactive")) {
+ offerStyle = "white";
+ }
+ // Create the lockup
+ const lockupOptions = {
+ metricsOptions: {
+ pageInformation: pageContext.metricsPageInformation,
+ locationTracker: pageContext.metricsLocationTracker,
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(lockupData),
+ isAdvert: adLockups.isAdvert(objectGraph, lockupData),
+ },
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfContext.shelfStyle),
+ offerStyle: offerStyle,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfContext.shelfStyle),
+ isContainedInPreorderExclusiveShelf: false,
+ shouldHideArcadeHeader: false,
+ };
+ const lockup = lockups.lockupFromData(objectGraph, lockupData, lockupOptions);
+ if (serverData.isNull(lockup) || !lockup.isValid()) {
+ return null;
+ }
+ return lockup;
+}
+/**
+ * Performs `lockupFromData`, but additional with Ad stitch related side-effects.
+ * @param objectGraph The AppStore dependency graph
+ * @param pageContext The page context for the ad lockup
+ * @param shelfContext The shelf context for the ad lockup
+ */
+function lockupFromAdStitcher(objectGraph, pageContext, shelfContext) {
+ const task = adStitch.consumeTask(pageContext.adStitcher, shelfContext.adPositionInfo);
+ if (serverData.isNull(task)) {
+ return null; // no task for position
+ }
+ // Try to create lockup
+ const lockupData = task.data;
+ try {
+ const lockup = lockupFromData(objectGraph, lockupData, pageContext, shelfContext);
+ if (serverData.isDefinedNonNull(lockup)) {
+ shelfContext.filteringExcludedItems = [lockupData.id];
+ }
+ else {
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, lockupData);
+ }
+ return lockup;
+ }
+ catch (error) {
+ adLogger(objectGraph, `Failed to create SLP ad lockup: ${error}`);
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, lockupData);
+ return null;
+ }
+}
+// #endregion
+// #region Brick Shelves
+function buildBrickShelf(objectGraph, data, pageContext, shelfAttributes, shelfContext) {
+ const items = [];
+ const shelf = new models.Shelf("brick");
+ shelf.isHorizontal = mediaAttributes.attributeAsString(data, "layoutDirection") === "Horizontal";
+ const shelfContents = mediaRelationships.relationshipCollection(data, "contents");
+ for (const itemData of shelfContents) {
+ const metricsOptions = {
+ ...shelfContext.metricsOptions,
+ targetType: "brickMedium",
+ recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(itemData),
+ };
+ // Using the location tracker from the context will cause positions to be unintentionally incremented
+ // by the lockup builder. Since we're only extracting icons we won't use the lockup metrics, so we can
+ // pass in a new, unused, location tracker instead.
+ const lockupMetricsOptions = {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ };
+ const brick = buildBrick(objectGraph, itemData, CollectionShelfDisplayStyle.BrickMedium, metricsOptions, lockupMetricsOptions);
+ brick.clickAction = createPrimaryActionForComponentFromData(objectGraph, itemData, shelfContext);
+ if (!brick.isValid()) {
+ continue;
+ }
+ items.push(brick);
+ metricsHelpersLocation.nextPosition(shelfContext.metricsOptions.locationTracker);
+ }
+ shelf.title = shelfAttributes.title;
+ shelf.items = items;
+ return shelf;
+}
+export function createPrimaryActionForComponentFromData(objectGraph, data, shelfContext) {
+ const clickOptions = createBrickClickOptionsFromData(objectGraph, data, shelfContext);
+ const primaryAction = lockups.actionFromData(objectGraph, data, clickOptions, null);
+ return primaryAction;
+}
+function createBrickClickOptionsFromData(objectGraph, data, shelfContext) {
+ const clickOptions = {
+ ...shelfContext.metricsOptions,
+ id: data.id,
+ targetType: "brickMedium",
+ };
+ return clickOptions;
+}
+// #endregion
+// #region Shelf Data Extraction
+/**
+ * Gets the initial raw shelf contents from the MAPI data object
+ * @param mediaApiData The raw MAPI data
+ * @returns A collection of data objects representing a shelf's contents
+ */
+function initialShelfContentsFromData(mediaApiData) {
+ const shelfContents = mediaRelationships.relationship(mediaApiData, "contents");
+ return shelfContents === null || shelfContents === void 0 ? void 0 : shelfContents.data;
+}
+// #endregion
+// #region Context Generators
+/**
+ * Performs either `createMediumAdLockupWithScreenshotsBackgroundShelf` or `createCondensedAdLockupWithIconBackgroundShelf
+ * depending on the format defined in adDisplayStyle, but with Ad stitch related side-effects.
+ * @param objectGraph The AppStore dependency graph
+ * @param pageContext The page context for the ad lockup
+ * @param builtShelves The index of the current shelf
+ * @param adMeta The SearchLandingPageAdMeta which includes adDisplayStyle
+ */
+function shelfFromAdStitcher(objectGraph, pageContext, builtShelves, adMeta) {
+ var _a;
+ const task = adStitch.consumeTask(pageContext.adStitcher, {
+ shelfIdentifier: searchLandingPageAdShelfIdentifier,
+ slot: builtShelves,
+ });
+ if (serverData.isNull(task)) {
+ return null; // no task for position
+ }
+ const adData = task.data;
+ try {
+ switch ((_a = adMeta === null || adMeta === void 0 ? void 0 : adMeta.adDisplayStyle) === null || _a === void 0 ? void 0 : _a.format) {
+ case "medium":
+ return createMediumAdLockupWithScreenshotsBackgroundShelf(objectGraph, adData, pageContext);
+ case "condensed":
+ return createCondensedAdLockupWithIconBackgroundShelf(objectGraph, adData, pageContext);
+ default:
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, adData);
+ return null;
+ }
+ }
+ catch (error) {
+ adLogger(objectGraph, `Failed to create SLP ad shelf: ${error}`);
+ adIncidents.recordLockupFromDataFailed(objectGraph, pageContext.adIncidentRecorder, adData);
+ return null;
+ }
+}
+/**
+ * Generates a shelf context for a search landing page shelf
+ * @param objectGraph The App Store Object Graph
+ * @param data The shelf's data object
+ * @param shelfAttributes The shelf's attribtues
+ * @param pageContext The context for the page containing the shelf
+ * @param shelfContentKind The type of content the shelf contains
+ * @returns A shelf context for a search landing page shelf
+ */
+function searchLandingPageShelfContext(objectGraph, data, shelfAttributes, pageContext, shelfContentKind = null) {
+ const baseShelfContext = searchShelves.baseShelfContext(objectGraph, data, shelfAttributes, pageContext);
+ switch (shelfContentKind) {
+ case models.SearchLandingPageContentKind.Apps:
+ return {
+ ...baseShelfContext,
+ shelfStyle: "smallLockup",
+ adPositionInfo: searchLandingPagePositionInfo,
+ };
+ default:
+ return baseShelfContext;
+ }
+}
+// #endregion
+//# sourceMappingURL=search-landing-shelf-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/category-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/category-metadata-ribbon-item.js
new file mode 100644
index 0000000..70c47c8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/category-metadata-ribbon-item.js
@@ -0,0 +1,38 @@
+import { isSome } from "@jet/environment";
+import { MetadataRibbonItem } from "../../../api/models";
+import { isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { categoryArtworkData } from "../../categories";
+import * as content from "../../content/content";
+import { categoryFromData } from "../../lockups/lockups";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const artworkData = categoryArtworkData(objectGraph, data, true);
+ const hasArtwork = isSome(artworkData);
+ const labelText = categoryFromData(objectGraph, data);
+ if (isNullOrEmpty(labelText)) {
+ return null;
+ }
+ if (labelText != null) {
+ if (dedupeSet.has(labelText)) {
+ return null;
+ }
+ else {
+ dedupeSet.add(labelText);
+ }
+ }
+ const viewType = hasArtwork ? "imageWithLabel" : "textLabel";
+ const categoryItem = new MetadataRibbonItem(viewType);
+ categoryItem.moduleType = "genreDisplayName";
+ categoryItem.labelText = labelText;
+ if (hasArtwork) {
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 20 /* content.ArtworkUseCase.CategoryIcon */,
+ });
+ artwork.crop = "sr";
+ categoryItem.artwork = artwork;
+ }
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "genreDisplayName", categoryItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, categoryItem, impressionOptions);
+ return [categoryItem];
+}
+//# sourceMappingURL=category-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/chart-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/chart-metadata-ribbon-item.js
new file mode 100644
index 0000000..26f815c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/chart-metadata-ribbon-item.js
@@ -0,0 +1,75 @@
+import { isSome, isNothing } from "@jet/environment/types/optional";
+import { MetadataRibbonItem } from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { contentAttributeAsDictionary } from "../../content/attributes";
+import { badgeChartKeyForClientIdentifier } from "../../content/content";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ var _a, _b;
+ const chartData = chartFromData(objectGraph, data);
+ if (serverData.isNullOrEmpty(chartData)) {
+ return null;
+ }
+ const position = serverData.asNumber(chartData, "position");
+ /// Per product, if the app isn't charting in the top 50, we don't want to use the chart module
+ if (isNothing(position) || position > 50) {
+ return null;
+ }
+ const genreName = (_a = serverData.asString(chartData, "genreShortName")) !== null && _a !== void 0 ? _a : serverData.asString(chartData, "genreName");
+ if (genreName != null) {
+ if (dedupeSet.has(genreName)) {
+ return null;
+ }
+ else {
+ dedupeSet.add(genreName);
+ }
+ }
+ let chartItem;
+ if (objectGraph.bag.isLLMSearchTagsEnabled) {
+ chartItem = new MetadataRibbonItem("highlightedText");
+ }
+ else {
+ chartItem = new MetadataRibbonItem("borderedTextLabel");
+ }
+ chartItem.moduleType = "chartPositions";
+ // Only use an ad override locale if this is an ad.
+ const adsOverrideLanguage = isSome((_b = lockup.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.searchAd) || isSome(lockup.searchAd)
+ ? objectGraph.bag.adsOverrideLanguage
+ : null;
+ const useAdsLocale = isSome(adsOverrideLanguage);
+ const loc = useAdsLocale ? objectGraph.adsLoc : objectGraph.loc;
+ // MAINTAINER'S NOTE: This was previously guarded by the iOS only `search_tags` feature flag that has been enabled by default on iOS only.
+ if (objectGraph.client.isiOS) {
+ const chartPositionText = loc
+ .string("MetadataRibbon.ChartPosition")
+ .replace("@@chartPosition@@", objectGraph.loc.formattedCountForPreferredLocale(objectGraph, position, adsOverrideLanguage));
+ if (objectGraph.bag.isLLMSearchTagsEnabled) {
+ chartItem.highlightedText = chartPositionText;
+ chartItem.labelText = loc
+ .string("MetadataRibbon.ChartPositionAndCategory.Tags")
+ .replace("@@chartPosition@@", objectGraph.loc.formattedCountForPreferredLocale(objectGraph, position, adsOverrideLanguage))
+ .replace("@@category@@", genreName);
+ }
+ else {
+ chartItem.labelText = genreName;
+ chartItem.borderedText = chartPositionText;
+ }
+ }
+ chartItem.secondaryViewPlacement = "leading";
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "chartPosition", chartItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, chartItem, impressionOptions);
+ return [chartItem];
+}
+export function chartFromData(objectGraph, data) {
+ const chartPositionsByStore = contentAttributeAsDictionary(objectGraph, data, "chartPositions");
+ if (serverData.isNullOrEmpty(chartPositionsByStore)) {
+ return null;
+ }
+ const storeChartKey = badgeChartKeyForClientIdentifier(objectGraph, objectGraph.host.clientIdentifier);
+ if (serverData.isNullOrEmpty(storeChartKey)) {
+ return null;
+ }
+ const chartData = serverData.asDictionary(chartPositionsByStore, storeChartKey);
+ return chartData;
+}
+//# sourceMappingURL=chart-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/developer-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/developer-metadata-ribbon-item.js
new file mode 100644
index 0000000..3a451a2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/developer-metadata-ribbon-item.js
@@ -0,0 +1,37 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as models from "../../../api/models";
+import { attributeAsString } from "../../../foundation/media/attributes";
+import * as contentArtwork from "../../content/artwork/artwork";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ var _a;
+ let developerName = lockup.developerName;
+ if (isNothing(developerName)) {
+ developerName = (_a = attributeAsString(data, "artistName")) !== null && _a !== void 0 ? _a : attributeAsString(data, "developerName");
+ }
+ if (developerName != null) {
+ if (dedupeSet.has(developerName)) {
+ return null;
+ }
+ else {
+ dedupeSet.add(developerName);
+ }
+ }
+ if (isSome(developerName) && developerName.length > 0) {
+ const developerItem = new models.MetadataRibbonItem("imageWithLabel");
+ developerItem.moduleType = "developerInfo";
+ developerItem.labelText = developerName;
+ developerItem.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://person.crop.square");
+ const characterCountThreshold = 6;
+ developerItem.maxCharacterCount = 16;
+ developerItem.truncationLegibilityCharacterCountThreshold = Math.min(characterCountThreshold, developerName.length);
+ developerItem.allowsTruncation = developerName.length >= characterCountThreshold;
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "developerInfo", developerItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, developerItem, impressionOptions);
+ return [developerItem];
+ }
+ else {
+ return null;
+ }
+}
+//# sourceMappingURL=developer-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/divider-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/divider-metadata-ribbon-item.js
new file mode 100644
index 0000000..91f5a5f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/divider-metadata-ribbon-item.js
@@ -0,0 +1,8 @@
+import { MetadataRibbonItem } from "../../../api/models";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const dividerItem = new MetadataRibbonItem("divider");
+ dividerItem.moduleType = "divider";
+ dividerItem.labelText = "|";
+ return [dividerItem];
+}
+//# sourceMappingURL=divider-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/editors-choice-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/editors-choice-metadata-ribbon-item.js
new file mode 100644
index 0000000..764624e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/editors-choice-metadata-ribbon-item.js
@@ -0,0 +1,21 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ var _a;
+ if (lockup.isEditorsChoice) {
+ const editorsChoiceItem = new models.MetadataRibbonItem("editorsChoice");
+ editorsChoiceItem.moduleType = "editorialBadgeInfo";
+ // Only use an ad override locale if this is an ad.
+ editorsChoiceItem.useAdsLocale =
+ (isSome((_a = lockup.searchAdOpportunity) === null || _a === void 0 ? void 0 : _a.searchAd) || isSome(lockup.searchAd)) &&
+ isSome(objectGraph.bag.adsOverrideLanguage);
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "editorialBadgeInfo", "Editors Choice", "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, editorsChoiceItem, impressionOptions);
+ return [editorsChoiceItem];
+ }
+ else {
+ return null;
+ }
+}
+//# sourceMappingURL=editors-choice-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/game-controller-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/game-controller-metadata-ribbon-item.js
new file mode 100644
index 0000000..40d18c0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/game-controller-metadata-ribbon-item.js
@@ -0,0 +1,36 @@
+import { isSome } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as contentArtwork from "../../content/artwork/artwork";
+import * as contentAttributes from "../../content/attributes";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ var _a;
+ let isGameControllerSupported = false;
+ switch (contentAttributes.contentAttributeAsString(objectGraph, data, "remoteControllerRequirement")) {
+ case "CONTROLLER_REQUIRED":
+ case "CONTROLLER_OPTIONAL":
+ isGameControllerSupported = true;
+ break;
+ default:
+ break;
+ }
+ if (contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "supportsGameController")) {
+ isGameControllerSupported = true;
+ }
+ if (!isGameControllerSupported) {
+ return null;
+ }
+ const gameControllerItem = new models.MetadataRibbonItem("imageWithLabel");
+ gameControllerItem.moduleType = "supportsGameController";
+ // Only use an ad override locale if this is an ad.
+ const useAdsLocale = (isSome(lockup.searchAd) || isSome((_a = lockup.searchAdOpportunity) === null || _a === void 0 ? void 0 : _a.searchAd)) &&
+ isSome(objectGraph.bag.adsOverrideLanguage);
+ gameControllerItem.labelText = useAdsLocale
+ ? objectGraph.adsLoc.string("BADGE_MFI_SUPPORTED")
+ : objectGraph.loc.string("BADGE_MFI_SUPPORTED");
+ gameControllerItem.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://gamecontroller.fill");
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "supportsGameController", "Supports Game Controller", "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, gameControllerItem, impressionOptions);
+ return [gameControllerItem];
+}
+//# sourceMappingURL=game-controller-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon-item-factory.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon-item-factory.js
new file mode 100644
index 0000000..5c6f3a6
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon-item-factory.js
@@ -0,0 +1,24 @@
+import * as categoryMetadataRibbonItem from "./category-metadata-ribbon-item";
+import * as chartMetadataRibbonItem from "./chart-metadata-ribbon-item";
+import * as developerMetadataRibbonItem from "./developer-metadata-ribbon-item";
+import * as dividerMetadataRibbonItem from "./divider-metadata-ribbon-item";
+import * as editorsChoiceMetadataRibbonItem from "./editors-choice-metadata-ribbon-item";
+import * as gameControllerMetadataRibbonItem from "./game-controller-metadata-ribbon-item";
+import * as secondaryShortCategoriesMetadataRibbonItem from "./secondary-short-categories-metadata-ribbon-item";
+import * as shortCategoryMetadataRibbonItem from "./short-category-metadata-ribbon-item";
+import * as starRatingMetadataRibbonItem from "./star-rating-metadata-ribbon-item";
+import * as tagMetadataRibbonItem from "./tag-metadata-ribbon-item";
+export const standardList = {
+ // every key in Key must be present
+ chartPositions: chartMetadataRibbonItem.createMetadataRibbonItems,
+ genreDisplayName: categoryMetadataRibbonItem.createMetadataRibbonItems,
+ genreShortDisplayName: shortCategoryMetadataRibbonItem.createMetadataRibbonItems,
+ secondaryGenreShortDisplayNames: secondaryShortCategoriesMetadataRibbonItem.createMetadataRibbonItems,
+ developerInfo: developerMetadataRibbonItem.createMetadataRibbonItems,
+ editorialBadgeInfo: editorsChoiceMetadataRibbonItem.createMetadataRibbonItems,
+ userRating: starRatingMetadataRibbonItem.createMetadataRibbonItems,
+ supportsGameController: gameControllerMetadataRibbonItem.createMetadataRibbonItems,
+ tag: tagMetadataRibbonItem.createMetadataRibbonItems,
+ divider: dividerMetadataRibbonItem.createMetadataRibbonItems,
+};
+//# sourceMappingURL=metadata-ribbon-item-factory.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon.js
new file mode 100644
index 0000000..1b487cb
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/metadata-ribbon.js
@@ -0,0 +1,27 @@
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { standardList } from "./metadata-ribbon-item-factory";
+export function createMetadataRibbonItemsForLockup(objectGraph, data, lockup, itemTypes, options, metadataRibbonItemFactory = standardList) {
+ if (serverData.isNullOrEmpty(itemTypes)) {
+ return [];
+ }
+ const metadataRibbonItems = [];
+ const dedupeSet = new Set();
+ for (const itemSlot of itemTypes) {
+ if (serverData.isNullOrEmpty(itemSlot)) {
+ continue;
+ }
+ for (const itemType of itemSlot) {
+ const metadataRibbonFactory = metadataRibbonItemFactory[itemType];
+ if (serverData.isNull(metadataRibbonFactory)) {
+ continue;
+ }
+ const results = metadataRibbonFactory(objectGraph, data, lockup, dedupeSet, options.metricsOptions);
+ if (serverData.isDefinedNonNull(results)) {
+ metadataRibbonItems.push(...results);
+ break;
+ }
+ }
+ }
+ return metadataRibbonItems;
+}
+//# sourceMappingURL=metadata-ribbon.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/ranked-secondary-category-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/ranked-secondary-category-metadata-ribbon-item.js
new file mode 100644
index 0000000..217876d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/ranked-secondary-category-metadata-ribbon-item.js
@@ -0,0 +1,22 @@
+import { MetadataRibbonItem } from "../../../api/models";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import { isNothing, isSome } from "@jet/environment";
+/**
+ * Creates a metadata ribbon item for ranked secondary category metadata ribbon type.
+ * This particular function takes in the type string since we grab it from the data in the search-tags-ribbon.
+ */
+export function createMetadataRibbonItemsForRankedSecondaryCategory(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ if (isNothing(data) || data.length === 0 || dedupeSet.has(data)) {
+ return null;
+ }
+ const tagItem = new MetadataRibbonItem("textLabel");
+ tagItem.moduleType = "rankedSecondaryGenre";
+ if (isSome(data)) {
+ tagItem.labelText = data;
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "rankedSecondaryGenre", tagItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, tagItem, impressionOptions);
+ }
+ dedupeSet.add(data);
+ return [tagItem];
+}
+//# sourceMappingURL=ranked-secondary-category-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/search-tags-ribbon.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/search-tags-ribbon.js
new file mode 100644
index 0000000..3901446
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/search-tags-ribbon.js
@@ -0,0 +1,72 @@
+import { isSome } from "@jet/environment";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import { standardList } from "./metadata-ribbon-item-factory";
+import * as rankedSecondaryCategoryMetadataRibbonItem from "./ranked-secondary-category-metadata-ribbon-item";
+export function createSearchTagsRibbonItemsForLockup(objectGraph, data, lockup, itemSlots, options, metadataRibbonItemFactory = standardList) {
+ if (serverData.isNullOrEmpty(itemSlots)) {
+ return [];
+ }
+ const tagData = serverData.asArrayOrEmpty(data.meta, "associations.tags.data");
+ const metadataRibbonItems = [];
+ // We need to keep track of how many tags we have so we can assign the tag properly
+ let tagIndex = 0;
+ let rankedSecondaryGenreIndex = 0;
+ // We are going to pass in a set of strings for items we have already added to the ribbon so we never duplicate items
+ const dedupeSet = new Set();
+ for (const itemSlot of itemSlots) {
+ const itemTypes = Array.isArray(itemSlot) ? itemSlot : [itemSlot];
+ if (serverData.isNullOrEmpty(itemTypes)) {
+ continue;
+ }
+ for (const itemType of itemTypes) {
+ // If we find a tag, we pass in the tag data specifically
+ // If we find a rankedSecondaryGenre, we want to call the factory function specifically.
+ const isTag = itemType === "tag";
+ const isRankedSecondaryGenre = itemType === "rankedSecondaryGenre";
+ let results;
+ let metadataItemData = data;
+ let metadataItemString = "";
+ if (isRankedSecondaryGenre) {
+ const searchExperimentDataForLockup = serverData.asDictionary(data, "meta");
+ if (isSome(searchExperimentDataForLockup === null || searchExperimentDataForLockup === void 0 ? void 0 : searchExperimentDataForLockup.rankedSecondaryGenreShortDisplayNames)) {
+ metadataItemString =
+ searchExperimentDataForLockup === null || searchExperimentDataForLockup === void 0 ? void 0 : searchExperimentDataForLockup.rankedSecondaryGenreShortDisplayNames[rankedSecondaryGenreIndex];
+ }
+ if (isSome(metadataItemString)) {
+ results =
+ rankedSecondaryCategoryMetadataRibbonItem.createMetadataRibbonItemsForRankedSecondaryCategory(objectGraph, metadataItemString, lockup, dedupeSet, options.metricsOptions);
+ rankedSecondaryGenreIndex = rankedSecondaryGenreIndex + 1;
+ }
+ else {
+ results = [];
+ }
+ }
+ else {
+ const metadataRibbonFactory = metadataRibbonItemFactory[itemType];
+ if (serverData.isNull(metadataRibbonFactory)) {
+ continue;
+ }
+ if (isTag) {
+ metadataItemData = tagData[tagIndex];
+ }
+ else {
+ metadataItemData = data;
+ }
+ results = metadataRibbonFactory(objectGraph, metadataItemData, lockup, dedupeSet, options.metricsOptions);
+ tagIndex = isTag ? tagIndex + 1 : tagIndex;
+ }
+ if (serverData.isDefinedNonNull(results)) {
+ metadataRibbonItems.push(...results);
+ for (const result of results) {
+ if (isSome(result.impressionMetrics)) {
+ metricsHelpersLocation.nextPosition(options.metricsOptions.locationTracker);
+ }
+ }
+ break;
+ }
+ }
+ }
+ return metadataRibbonItems;
+}
+//# sourceMappingURL=search-tags-ribbon.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/secondary-short-categories-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/secondary-short-categories-metadata-ribbon-item.js
new file mode 100644
index 0000000..c5469c7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/secondary-short-categories-metadata-ribbon-item.js
@@ -0,0 +1,27 @@
+import { MetadataRibbonItem } from "../../../api/models";
+import { isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { attributeAsArrayOrEmpty } from "../../../foundation/media/attributes";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const secondaryGenres = attributeAsArrayOrEmpty(data, "secondaryGenreShortDisplayNames");
+ if (isNullOrEmpty(secondaryGenres)) {
+ return null;
+ }
+ const secondaryCategoryItems = secondaryGenres.map((secondaryGenre) => {
+ const categoryItem = new MetadataRibbonItem("textLabel");
+ // Workaround for changing the moduleType to secondaryGenreShortDisplayNames from secondaryGenreShortDisplayName
+ // otherwise native doesnt layout the secondary genres correctly
+ // will be fixed with rdar://127458403 (Allow unknown metadataribbon items)
+ categoryItem.moduleType = "genreShortDisplayName";
+ categoryItem.labelText = secondaryGenre;
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "genreDisplayName", categoryItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, categoryItem, impressionOptions);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ return categoryItem;
+ });
+ return secondaryCategoryItems.filter((category) => {
+ return category.labelText != null && !dedupeSet.has(category.labelText);
+ });
+}
+//# sourceMappingURL=secondary-short-categories-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/short-category-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/short-category-metadata-ribbon-item.js
new file mode 100644
index 0000000..16a148a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/short-category-metadata-ribbon-item.js
@@ -0,0 +1,36 @@
+import { isNothing, isSome } from "@jet/environment";
+import { MetadataRibbonItem } from "../../../api/models";
+import { attributeAsString } from "../../../foundation/media/attributes";
+import { categoryArtworkData } from "../../categories";
+import { artworkFromApiArtwork } from "../../content/content";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const artworkData = categoryArtworkData(objectGraph, data, true);
+ const hasArtwork = isSome(artworkData);
+ const shortGenre = attributeAsString(data, "genreShortDisplayName");
+ if (shortGenre != null) {
+ if (dedupeSet.has(shortGenre)) {
+ return null;
+ }
+ else {
+ dedupeSet.add(shortGenre);
+ }
+ }
+ if (isNothing(shortGenre) || shortGenre.length === 0) {
+ return null;
+ }
+ const viewType = hasArtwork ? "imageWithLabel" : "textLabel";
+ const shortCategoryItem = new MetadataRibbonItem(viewType);
+ shortCategoryItem.moduleType = "genreShortDisplayName";
+ shortCategoryItem.labelText = shortGenre;
+ if (hasArtwork) {
+ shortCategoryItem.artwork = artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 20 /* ArtworkUseCase.CategoryIcon */,
+ cropCode: "sr",
+ });
+ }
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "genreDisplayName", shortCategoryItem.labelText, "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, shortCategoryItem, impressionOptions);
+ return [shortCategoryItem];
+}
+//# sourceMappingURL=short-category-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/star-rating-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/star-rating-metadata-ribbon-item.js
new file mode 100644
index 0000000..81587f8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/star-rating-metadata-ribbon-item.js
@@ -0,0 +1,20 @@
+import { MetadataRibbonItem } from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { contentAttributeAsBooleanOrFalse } from "../../content/attributes";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const isPreorder = contentAttributeAsBooleanOrFalse(objectGraph, data, "isPreorder");
+ if (serverData.isDefinedNonNull(lockup.ratingCount) && serverData.isDefinedNonNull(lockup.rating) && !isPreorder) {
+ const starRatingItem = new MetadataRibbonItem("starRating");
+ starRatingItem.moduleType = "userRating";
+ starRatingItem.starRating = lockup.rating;
+ starRatingItem.labelText = lockup.ratingCount;
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, "userRating", "User Rating", "static");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, starRatingItem, impressionOptions);
+ return [starRatingItem];
+ }
+ else {
+ return null;
+ }
+}
+//# sourceMappingURL=star-rating-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/tag-metadata-ribbon-item.js b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/tag-metadata-ribbon-item.js
new file mode 100644
index 0000000..37877c3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/metadata-ribbon/tag-metadata-ribbon-item.js
@@ -0,0 +1,18 @@
+import { isNothing } from "@jet/environment";
+import { MetadataRibbonItem } from "../../../api/models";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+export function createMetadataRibbonItems(objectGraph, data, lockup, dedupeSet, metricsOptions) {
+ const tagData = data;
+ const tagItem = new MetadataRibbonItem("textLabel");
+ tagItem.moduleType = "tag";
+ tagItem.labelText = mediaAttributes.attributeAsString(tagData, "name");
+ if (isNothing(tagItem.labelText) || tagItem.labelText.length === 0 || dedupeSet.has(tagItem.labelText)) {
+ return null;
+ }
+ const impressionOptions = metricsHelpersImpressions.impressionOptionsForMetadataRibbonItem(metricsOptions, tagData.id, tagItem.labelText, "tag_id");
+ metricsHelpersImpressions.addImpressionFieldsToSearchMetadataRibbonItem(objectGraph, tagItem, impressionOptions);
+ dedupeSet.add(tagItem.labelText);
+ return [tagItem];
+}
+//# sourceMappingURL=tag-metadata-ribbon-item.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads-odml.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads-odml.js
new file mode 100644
index 0000000..aedcef1
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads-odml.js
@@ -0,0 +1,87 @@
+/**
+ * Implements the ODML Treatment (On-device machine learning) for Sponsored search.
+ */
+"use strict";
+import { adLogger } from "../../common/search/search-ads";
+import { isNull, isNullOrEmpty } from "../../foundation/json-parsing/server-data";
+import { shallowCopyOf } from "../../foundation/util/objects";
+import { decorateAdInstanceIdOnData } from "../ads/ad-common";
+import { productVariantDataForData, productVariantIDForVariantData } from "../product-page/product-page-variants";
+// region exports
+/**
+ * Merge the contents of the raw response with the data in `advertData` from `SearchAds.framework`
+ *
+ * @param rawAdverts The adverts returned in the network response.
+ * @param nativeAdvertData The advert data that was returned from native.
+ */
+export function applyNativeAdvertData(objectGraph, rawAdverts, nativeAdvertData) {
+ if (isNull(nativeAdvertData)) {
+ return rawAdverts; // even if `nativeAdvertData` may contain error, we need to perform apply to merge instance ids.
+ }
+ const updated = [];
+ const rawAdvertsMap = rawAdverts.reduce((acc, data) => ({ ...acc, [data.id]: data }), {});
+ for (const nativeAdData of nativeAdvertData.adverts) {
+ const rawAd = rawAdvertsMap[nativeAdData.adamId];
+ if (isNullOrEmpty(rawAd)) {
+ adLogger(objectGraph, `[${nativeAdData.adamId}] skipped - Data was not part of original response`);
+ continue;
+ }
+ if (isNullOrEmpty(rawAd.attributes)) {
+ adLogger(objectGraph, `[${rawAd.id}] skipped - Data is missing attributes`);
+ continue;
+ }
+ const newAd = createCopyWithNativeData(objectGraph, rawAd, nativeAdData);
+ updated.push(newAd);
+ }
+ if (!preprocessor.PRODUCTION_BUILD) {
+ const rawOrder = rawAdverts.map((ad) => ad.id).join(" ");
+ const updatedOrder = updated.map((ad) => ad.id).join(" ");
+ adLogger(objectGraph, `applyNativeAdvertData: [${rawOrder}] => [${updatedOrder}]`);
+ }
+ return updated;
+}
+/**
+ * Returns whether or not ODML treatment was successful
+ */
+export function wasODMLSuccessful(objectGraph, nativeAdvertData) {
+ return nativeAdvertData && nativeAdvertData.odmlSuccess;
+}
+// endregion
+// region internals
+/**
+ * Create a copy of `ad` with the `adData` replacing the "iad" attribute.
+ * @param ad The raw ad to copy
+ * @param adData The ad blob to overrwite wth.
+ */
+function createCopyWithNativeData(objectGraph, ad, nativeAdData) {
+ const copy = shallowCopyOf(ad);
+ const attributes = shallowCopyOf(ad.attributes);
+ attributes["iads"] = nativeAdData.adData;
+ copy.attributes = attributes;
+ overrideCustomProductPageIdIfRequired(objectGraph, copy, nativeAdData);
+ decorateAdInstanceIdOnData(objectGraph, copy, nativeAdData.instanceId);
+ return copy;
+}
+/**
+ * Modifies an `ad` by overriding the `ppid` value if native ODML has dictated a new selection.
+ * Note: This may intentionally replace the `ppid` value with `null` if CPP is disabled for Search Ads.
+ * If we pass native all `null` values for ODML processing (because the feature is disabled in the bag),
+ * we expect to receive `null` back and insert `null` into the `ad` here.
+ * @param ad The ad to modify.
+ * @param adData The ad blob to overrwite with.
+ */
+function overrideCustomProductPageIdIfRequired(objectGraph, ad, nativeAdData) {
+ var _a;
+ const productVariantData = productVariantDataForData(objectGraph, ad);
+ const productVariantId = productVariantIDForVariantData(productVariantData);
+ // If there is a "selected" cppId, and it's different to the `serverCppId`, native has made a new selection.
+ // Confirm we have a cppData of meta to modify
+ if (nativeAdData.selectedCppId === productVariantId || isNullOrEmpty((_a = ad === null || ad === void 0 ? void 0 : ad.meta) === null || _a === void 0 ? void 0 : _a.cppData)) {
+ return;
+ }
+ // Modify the meta data with the new selection.
+ const meta = shallowCopyOf(ad.meta);
+ meta.cppData["ppid"] = nativeAdData.selectedCppId;
+ ad.meta = meta;
+}
+//# sourceMappingURL=search-ads-odml.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js
new file mode 100644
index 0000000..e8377d4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-ads.js
@@ -0,0 +1,1047 @@
+//
+// search-ads.ts
+// AppStoreKit
+//
+// Created by Joel Parsons on 24/October/2019
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import { isSome } from "@jet/environment";
+import * as models from "../../api/models";
+import { ads } from "../../api/typings/constants";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import { shallowCopyOf } from "../../foundation/util/objects";
+import { isAdLocalizationValid } from "../ads/ad-common";
+import * as client from "../../foundation/wrappers/client";
+import * as contentAttributes from "../content/attributes";
+import * as content from "../content/content";
+import * as filtering from "../filtering";
+import * as lockups from "../lockups/lockups";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import { customCreativeArtworkFromData, customCreativeVideoFromData } from "./custom-creative";
+import { platformAttributeAsDictionary } from "../../foundation/media/platform-attributes";
+import { searchResultWillUseAppEventDisplay } from "./content/search-results";
+/**
+ * Data passed from native for requesting Sponsored Search
+ */
+export class SponsoredSearchRequestData {
+ constructor(data, appStoreClientRequestId) {
+ if (!data) {
+ return;
+ }
+ this.appStoreClientRequestId = appStoreClientRequestId;
+ this.iAdId = data["iAdId"];
+ this.sponsoredSearchRequestData = data["dataBlob"];
+ this.routingInfo = data["iAdRoutingInfo"];
+ this.canary = data["canary"];
+ }
+ validAdRequest() {
+ const hasRequestData = this.sponsoredSearchRequestData && this.sponsoredSearchRequestData.length > 0;
+ const hasRoutingInfo = this.routingInfo && this.routingInfo.length > 0;
+ return hasRequestData && hasRoutingInfo;
+ }
+}
+const searchVideoConfiguration = {
+ canPlayFullScreen: false,
+ playbackControls: {},
+};
+export function adsResultFromSearchResults(objectGraph, advertDatum, resultsDatum, requestMetadata, metricsOptions, installedStates, appStates, searchExperimentsData, personalizationDataContainer) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
+ const advertsSearchResult = new models.AdvertsSearchResult();
+ const isNetworkConstrained = (_a = requestMetadata.requestDescriptor.isNetworkConstrained) !== null && _a !== void 0 ? _a : false;
+ const advertMetricsOptions = {
+ id: "ad_container",
+ kind: "iosSoftware",
+ softwareType: null,
+ targetType: null,
+ title: "ad_container",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ idType: "sequential",
+ };
+ /**
+ * # Why do we push ad_container?
+ * We push an identifier for element that doesn't exist (`ad_container`) to prevent impression indices of organic search results
+ * being affected by having multiple ads built for ad-rotation.
+ *
+ * It is expected that:
+ * - `ad_container` will not impresss, since it doesn't exist
+ * - `parentImpressionId` will not be set expected.
+ */
+ metricsHelpersLocation.pushContentLocation(objectGraph, advertMetricsOptions, "ad_container");
+ if (serverData.isNullOrEmpty(advertDatum)) {
+ return {
+ result: advertsSearchResult,
+ };
+ }
+ const firstSearchResult = resultsDatum[0];
+ let firstAdComputedStyle;
+ const adIdsString = advertDatum
+ .filter(serverData.isDefinedNonNull)
+ .map((ad) => `[${ad.id}]`)
+ .join(", ");
+ const adString = `Adverts received from ad server: ${adIdsString}`;
+ adLogger(objectGraph, adString);
+ let isFirstAd = true;
+ for (const ad of advertDatum) {
+ if (serverData.isNull(ad)) {
+ continue;
+ }
+ if (filtering.shouldFilter(objectGraph, ad)) {
+ adLogger(objectGraph, `[${ad.id}] filtered by shouldFilter() - app probably not supported on current os or device`);
+ continue;
+ }
+ const isDupe = adIsDupe(ad.id, firstSearchResult === null || firstSearchResult === void 0 ? void 0 : firstSearchResult.id, installedStates);
+ // Extract the iAd data dictionary based on the first organic search result.
+ const adDataType = iAdDataTypeForAdvert(firstSearchResult, isDupe);
+ ad.attributes["iad"] = iadAttributesForType(ad, adDataType);
+ if (serverData.isNullOrEmpty(ad.attributes["iad"])) {
+ adLogger(objectGraph, `[${ad.id}] filtered because no appropriate iAd dictionary was found. (Probably a server issue if hitting this)`);
+ continue;
+ }
+ const adLockupOptions = {
+ metricsOptions: {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "card",
+ isAdvert: true,
+ },
+ hideZeroRatings: true,
+ artworkUseCase: 8 /* content.ArtworkUseCase.SearchIcon */,
+ isNetworkConstrained: isNetworkConstrained,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "mixedMediaLockup"),
+ };
+ const iAdData = contentAttributes.contentAttributeAsDictionary(objectGraph, ad, "iad");
+ const iAdAllowsMedia = serverData.asBooleanOrFalse(iAdData, "format.images");
+ let creativeHasArtworkToDisplay = false;
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ const customCreativeData = platformAttributeAsDictionary(ad, contentAttributes.bestAttributePlatformFromData(objectGraph, ad), "creativeAttributes");
+ const customCreativeArtwork = customCreativeArtworkFromData(objectGraph, ad, customCreativeData);
+ const customCreativeVideo = customCreativeVideoFromData(objectGraph, ad, customCreativeData, searchVideoConfiguration);
+ creativeHasArtworkToDisplay = isSome(customCreativeArtwork) || isSome(customCreativeVideo);
+ }
+ const noPreviousAdStyle = serverData.isNullOrEmpty(firstAdComputedStyle);
+ const temporaryAdLockup = lockups.mixedMediaAdLockupFromData(objectGraph, ad, adLockupOptions, searchVideoConfiguration, searchExperimentsData);
+ const platformLockupMedia = platformMediaForLockup(temporaryAdLockup);
+ // Adverts should only rotate between the same display style. The first advert dictates the display style
+ // for the rest of the adverts. Once we have an ad display style locked in we try to create the next ads in
+ // a style compatible with the first. If it can't satisfy any compatible styles, it gets thrown away.
+ const screenshotsDisplayStyle = creativeHasArtworkToDisplay
+ ? "four-screenshots"
+ : (_b = searchExperimentsData === null || searchExperimentsData === void 0 ? void 0 : searchExperimentsData.displayStyle) === null || _b === void 0 ? void 0 : _b.screenshots;
+ const computedAdDisplayStyle = resolvedAdDisplayStyleForMedia(objectGraph, platformLockupMedia, ad.id, adDataType, iAdAllowsMedia, firstAdComputedStyle, screenshotsDisplayStyle, firstSearchResult, installedStates, appStates, metricsOptions, personalizationDataContainer);
+ if (serverData.isNull(computedAdDisplayStyle)) {
+ adLogger(objectGraph, `[${ad.id}] will not be displayed because we could not create an ad style compatible with ${debugDescriptionForStyle(firstAdComputedStyle)}`);
+ continue;
+ }
+ // Check the localization is valid for the ad with the selected display style.
+ // We do this _before_ storing the style (if this is the first ad) to avoid setting
+ // a style based on an ad that can't be shown.
+ if (!isAdLocalizationValid(objectGraph, ad, null, computedAdDisplayStyle.style)) {
+ adLogger(objectGraph, `[${ad.id}] filtered because localization is not available`);
+ continue;
+ }
+ if (noPreviousAdStyle) {
+ // For the first advert we calculate a display style based on whether it is a dupe of the first organic result
+ // and what media is available for display for the app
+ adLogger(objectGraph, `[${ad.id}] first ad dictates ad display style of: ${debugDescriptionForStyle(computedAdDisplayStyle)}`);
+ firstAdComputedStyle = computedAdDisplayStyle;
+ }
+ else {
+ adLogger(objectGraph, `[${ad.id}] will be displayed because it is compatible with the display style of: ${debugDescriptionForStyle(computedAdDisplayStyle)}, which is the same height as display style: ${debugDescriptionForStyle(firstAdComputedStyle)}`);
+ }
+ metricsOptions.pageInformation.iAdInfo.apply(objectGraph, ad);
+ // Set the template type before creating the lockup to ensure the click event has the right data.
+ (_c = metricsOptions.pageInformation.iAdInfo) === null || _c === void 0 ? void 0 : _c.setTemplateType(computedAdDisplayStyle.style);
+ let adResultLockup = lockups.mixedMediaAdLockupFromData(objectGraph, ad, adLockupOptions, searchVideoConfiguration, searchExperimentsData);
+ adResultLockup = modifyLockupToMatchAdDisplayStyle(adResultLockup, computedAdDisplayStyle, isDupe, isFirstAd);
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_d = adResultLockup.searchAdOpportunity) === null || _d === void 0 ? void 0 : _d.setTemplateType(computedAdDisplayStyle.style);
+ }
+ else {
+ (_e = adResultLockup.searchAd) === null || _e === void 0 ? void 0 : _e.setTemplateType(computedAdDisplayStyle.style);
+ }
+ if (computedAdDisplayStyle.style === "TEXT" /* SearchAdDisplayStyle.TEXT */) {
+ const iAdTextKey = mediaAttributes.attributeAsString(ad, "iad.format.text");
+ if (iAdTextKey !== "none") {
+ let advertisingText;
+ if (iAdTextKey === "description") {
+ advertisingText = contentAttributes.contentAttributeAsString(objectGraph, ad, "description.standard");
+ }
+ else {
+ advertisingText = contentAttributes.contentAttributeAsString(objectGraph, ad, iAdTextKey);
+ }
+ const searchAd = (_f = adResultLockup.searchAd) !== null && _f !== void 0 ? _f : (_g = adResultLockup.searchAdOpportunity) === null || _g === void 0 ? void 0 : _g.searchAd;
+ if (serverData.isDefinedNonNull(searchAd) && serverData.isDefinedNonNull(advertisingText)) {
+ searchAd.advertisingText = advertisingText;
+ }
+ }
+ advertsSearchResult.displaysScreenshots = false;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(adResultLockup)) {
+ const duplicatePosition = findOrganicLockupPosition(resultsDatum, adResultLockup.adamId);
+ if (isSome(duplicatePosition) && !creativeHasArtworkToDisplay) {
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_h = adResultLockup.searchAdOpportunity) === null || _h === void 0 ? void 0 : _h.setDuplicatePosition(duplicatePosition);
+ }
+ else {
+ (_j = adResultLockup.searchAd) === null || _j === void 0 ? void 0 : _j.setDuplicatePosition(duplicatePosition);
+ }
+ }
+ advertsSearchResult.lockups.push(adResultLockup);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ isFirstAd = false;
+ }
+ }
+ // Always pop the `ad_container` location.
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ // There are situations where all ads are filtered out - check the results have at least one item prior to modifying metrics data.
+ if (serverData.isDefinedNonNullNonEmpty(advertsSearchResult.lockups)) {
+ // Once all the lockups are built re-set the page information to use the iAd Info of the first advert
+ // This (probably) happens to ensure that the page event accurately reflects what's being presented
+ // to the user at first view.
+ const firstAd = advertDatum[0];
+ metricsOptions.pageInformation.iAdInfo.apply(objectGraph, firstAd);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ }
+ if (firstAdComputedStyle) {
+ (_k = metricsOptions.pageInformation.iAdInfo) === null || _k === void 0 ? void 0 : _k.setTemplateType(firstAdComputedStyle.style);
+ }
+ else {
+ (_l = metricsOptions.pageInformation.iAdInfo) === null || _l === void 0 ? void 0 : _l.setTemplateType(null);
+ }
+ advertsSearchResult.condensedBehavior = "never";
+ return {
+ result: advertsSearchResult,
+ displayStyle: firstAdComputedStyle === null || firstAdComputedStyle === void 0 ? void 0 : firstAdComputedStyle.style,
+ };
+}
+/**
+ * Determines whether or not the ad is considered a dupe of the first organic result.
+ * @param adID The adaimID of the ad result
+ * @param firstResultID The adamID of the first organic result
+ * @param installedState A mapping of adamIDs to device app install state to determine if the ad/first result is on the user's device
+ * @returns whether the ad and result are dupes of the same app and that the first result will not condensed as a condensed first result
+ * will have no bearing on dupe status or display.
+ */
+function adIsDupe(adID, firstResultID, installedState) {
+ const isResultInstalled = installedState && installedState[firstResultID];
+ const isDupeResult = adID && firstResultID && adID === firstResultID;
+ return isDupeResult && !isResultInstalled;
+}
+// endregion
+// region Ad Display Styles
+/**
+ * Resolves a SearchAdDisplayStyle for the given ad media and the search results context (the first organic search result).
+ * If the ad is a dupe of the first organic search result, we attempt to create two lockups with "full creative" to ensure there is enough
+ * media to show both.
+ * @param media the media from which to calculate the style.
+ * @param adId the id of the ad, for logging purposes.
+ * @param adDataType the ad type, based on the first search result.
+ * @param firstAdComputedStyle a previously computed `SearchAdDisplayStyleContainer`, if any, to maintain compatibility.
+ * @param firstSearchResult The first organic search result which we informs how to handle DUP logic
+ * @param installStates A mapping of adamIDs to their respective install states that is used to determine if the app is currently installed by the user
+ * @param appStates A mapping of adamIDs to their respective app state to determine if the ad/first result has been installed by the user in the past
+ * @param metricsOptions Metrics options for built models.
+ * @param personalizationDataContainer The data container to use for personalizing the data.
+ * @returns a `SearchAdDisplayStyleContainer` with a style compatible with the first search result, if one can be found.
+ */
+function resolvedAdDisplayStyleForMedia(objectGraph, media, adId, adDataType, iAdAllowsMedia, firstAdComputedStyle, screenshotsDisplayStyle, firstSearchResult, installStates, appStates, metricsOptions, personalizationDataContainer) {
+ const isFirstAd = serverData.isNullOrEmpty(firstAdComputedStyle);
+ const adDisplayStyle = getAdDisplayStyleForMedia(objectGraph, media, adId, iAdAllowsMedia, firstAdComputedStyle, screenshotsDisplayStyle);
+ if (serverData.isNull(adDisplayStyle)) {
+ return null;
+ }
+ const searchAdDisplayContainer = {
+ platform: media.mediaPlatformUsedForDisplayStyle,
+ style: adDisplayStyle,
+ };
+ adLogger(objectGraph, `[${adId}] tentatively resolved to: ${debugDescriptionForStyle(searchAdDisplayContainer)}`);
+ if (adDataType === "DUP" /* iAdDataType.DUPE_AD */) {
+ removeUsedMediaForAdDisplayStyle(adDisplayStyle, media);
+ const organicSearchResultDisplayStyle = getAdDisplayStyleForMedia(objectGraph, media, adId, iAdAllowsMedia, null, screenshotsDisplayStyle);
+ const organicHasFullCreative = isDisplayStyleFullCreativeForOrganic(objectGraph, organicSearchResultDisplayStyle, screenshotsDisplayStyle);
+ const organicWillUseAppEvent = searchResultWillUseAppEventDisplay(objectGraph, firstSearchResult, installStates, appStates, metricsOptions, personalizationDataContainer);
+ if ((organicHasFullCreative || organicWillUseAppEvent) && isFirstAd) {
+ adLogger(objectGraph, `[${adId}] Organic Dupe would be full creative as ${organicSearchResultDisplayStyle} so choosing tentative style for ad`);
+ return searchAdDisplayContainer;
+ }
+ else if (organicHasFullCreative &&
+ !isFirstAd &&
+ isProposedAdStyleCompatible(adDisplayStyle, firstAdComputedStyle)) {
+ adLogger(objectGraph, `[${adId}] Organic Dupe would be a full creative, but ad is not the first so returning compatible style with first ${adDisplayStyle}`);
+ return searchAdDisplayContainer;
+ }
+ else if (isProposedAdStyleCompatible("UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */, firstAdComputedStyle)) {
+ adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result so returning UNRESTRICTED_IMAGE_ONE_ASSET`);
+ return {
+ style: "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */,
+ };
+ }
+ else if (isProposedAdStyleCompatible("UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */, firstAdComputedStyle)) {
+ adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result so returning UNRESTRICTED_VIDEO_ONE_ASSET`);
+ return {
+ style: "UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */,
+ };
+ }
+ else if (isProposedAdStyleCompatible("TEXT" /* SearchAdDisplayStyle.TEXT */, firstAdComputedStyle)) {
+ adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result so returning TEXT`);
+ return {
+ style: "TEXT" /* SearchAdDisplayStyle.TEXT */,
+ };
+ }
+ else {
+ adLogger(objectGraph, `[${adId}] tentative style would not yield full creative for organic result and first style is not compatible with TEXT so skipping ad`);
+ return null;
+ }
+ }
+ else if (serverData.isDefinedNonNull(firstAdComputedStyle) &&
+ firstAdComputedStyle.style === "TEXT" /* SearchAdDisplayStyle.TEXT */) {
+ adLogger(objectGraph, `[${adId}] tentative style would be filtered since the first ad has style: ${debugDescriptionForStyle(firstAdComputedStyle)}, so returning TEXT`);
+ return {
+ style: "TEXT" /* SearchAdDisplayStyle.TEXT */,
+ };
+ }
+ return searchAdDisplayContainer;
+}
+function allowsFourScreenshots(screenshotsDisplayStyle) {
+ if (!serverData.isDefinedNonNull(screenshotsDisplayStyle)) {
+ return false;
+ }
+ return screenshotsDisplayStyle === "four-screenshots";
+}
+/**
+ * Finds the position of a specific lockup within the organic search results
+ * @param searchResults List of search results
+ * @param adResult The adamId of the app to search for
+ * @returns The 0-indexed position in the list that contains the lockup. If the organic results doesn't contain the lockup, this instead returns null.
+ */
+function findOrganicLockupPosition(searchResults, adamId) {
+ const index = searchResults.findIndex((datum) => datum.id === adamId);
+ return index === -1 ? null : index;
+}
+/**
+ * Returns a suitable style for a given set of lockup media.
+ * If a firstComputedAdStyle is provided, we check to confirm the proposed style is compatible before selecting that new style.
+ * @param media a set of media from which to calculate a style.
+ * @param adId the id of the ad, for logging purposes.
+ * @param iAdAllowsMedia whether the iAd Data has enabled media for the given ad.
+ * @param firstComputedAdStyle whether the iAd Data has enabled media for the given ad.
+ * @returns the preferred display style from the provided set of media.
+ */
+function getAdDisplayStyleForMedia(objectGraph, media, adId, iAdAllowsMedia, firstComputedAdStyle, screenshotsDisplayStyle) {
+ if (!iAdAllowsMedia) {
+ adLogger(objectGraph, `[${adId}] is not allowed to display media because of iAd configuration.`);
+ return "TEXT" /* SearchAdDisplayStyle.TEXT */;
+ }
+ if (media.mediaPlatformUsedForDisplayStyle &&
+ firstComputedAdStyle &&
+ firstComputedAdStyle.mediaPlatform &&
+ !media.mediaPlatformUsedForDisplayStyle.isEqualTo(firstComputedAdStyle.mediaPlatform)) {
+ adLogger(objectGraph, `[${adId}] filtered because media is derived from: ${media.mediaPlatformUsedForDisplayStyle.mediaType}, but first ad media is derived from: ${firstComputedAdStyle.mediaPlatform.mediaType}`);
+ return null;
+ }
+ let displayStyle;
+ let firstVideoPreview = null;
+ if (serverData.isDefinedNonNullNonEmpty(media.videos)) {
+ const firstVideo = media.videos[0];
+ firstVideoPreview = firstVideo.preview;
+ }
+ if (isSome(media.alignedRegionArtwork) &&
+ isProposedAdStyleCompatible("UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */, firstComputedAdStyle)) {
+ displayStyle = "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */;
+ }
+ else if (isSome(media.alignedRegionVideo) &&
+ isProposedAdStyleCompatible("UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */, firstComputedAdStyle)) {
+ displayStyle = "UV1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_VIDEO_ONE_ASSET */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(firstVideoPreview) &&
+ firstVideoPreview.isLandscape() &&
+ isProposedAdStyleCompatible("LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */, firstComputedAdStyle)) {
+ // If first trailer's preview is landscape the lockup will render as landscape video
+ displayStyle = "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(firstVideoPreview) &&
+ firstVideoPreview.isPortrait() &&
+ allowsFourScreenshots(screenshotsDisplayStyle) &&
+ isProposedAdStyleCompatible("PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */, firstComputedAdStyle)) {
+ // The assumption here is that all combinations of portrait video are compatible with each other.
+ // If this changes in future, this logic will need to be revisited.
+ // If first trailer's preview is portrait the lockup will render as portrait with portrait images filled in
+ if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) && media.portraitScreenshots.length >= 3) {
+ displayStyle = "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) &&
+ media.portraitScreenshots.length >= 2) {
+ displayStyle = "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots)) {
+ displayStyle = "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */;
+ }
+ else {
+ displayStyle = "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */;
+ }
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(firstVideoPreview) &&
+ firstVideoPreview.isPortrait() &&
+ isProposedAdStyleCompatible("PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */, firstComputedAdStyle)) {
+ // The assumption here is that all combinations of portrait video are compatible with each other.
+ // If this changes in future, this logic will need to be revisited.
+ // If first trailer's preview is portrait the lockup will render as portrait with portrait images filled in
+ if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) && media.portraitScreenshots.length >= 2) {
+ displayStyle = "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots)) {
+ displayStyle = "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */;
+ }
+ else {
+ displayStyle = "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */;
+ }
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.landscapeScreenshots) &&
+ isProposedAdStyleCompatible("LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */, firstComputedAdStyle)) {
+ displayStyle = "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */;
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) &&
+ allowsFourScreenshots(screenshotsDisplayStyle) &&
+ isProposedAdStyleCompatible("PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */, firstComputedAdStyle)) {
+ // The assumption here is that all combinations of portrait video are compatible with each other.
+ // If this changes in future, this logic will need to be revisited.
+ if (media.portraitScreenshots.length >= 4) {
+ displayStyle = "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */;
+ }
+ else if (media.portraitScreenshots.length >= 3) {
+ displayStyle = "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */;
+ }
+ else if (media.portraitScreenshots.length >= 2) {
+ displayStyle = "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */;
+ }
+ else {
+ displayStyle = "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */;
+ }
+ }
+ else if (serverData.isDefinedNonNullNonEmpty(media.portraitScreenshots) &&
+ isProposedAdStyleCompatible("PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */, firstComputedAdStyle)) {
+ // The assumption here is that all combinations of portrait video are compatible with each other.
+ // If this changes in future, this logic will need to be revisited.
+ if (media.portraitScreenshots.length >= 3) {
+ displayStyle = "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */;
+ }
+ else if (media.portraitScreenshots.length >= 2) {
+ displayStyle = "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */;
+ }
+ else {
+ displayStyle = "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */;
+ }
+ }
+ else if (isProposedAdStyleCompatible("TEXT" /* SearchAdDisplayStyle.TEXT */, firstComputedAdStyle)) {
+ displayStyle = "TEXT" /* SearchAdDisplayStyle.TEXT */;
+ }
+ else {
+ adLogger(objectGraph, `[${adId}] filtered because we could not create a compatible style for the first style of: ${debugDescriptionForStyle(firstComputedAdStyle)}`);
+ return null;
+ }
+ if (completePortraitMediaCount(objectGraph, screenshotsDisplayStyle) === 2) {
+ if (displayStyle === "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */) {
+ displayStyle = "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */;
+ }
+ else if (displayStyle === "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */) {
+ displayStyle = "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */;
+ }
+ }
+ return displayStyle;
+}
+/**
+ * Removes media used by the nominated displayStyle.
+ * Used in the case of a dupe where we need to confirm we have "full creative" for a subsequent presentation of a lockup.
+ * @param lockup the lockup to modify
+ * @param displayStyle the display style previously calculated as suitable for the lockup
+ */
+function removeUsedMediaForAdDisplayStyle(displayStyle, media) {
+ // In the case of any video assets being used, as per the implementation in `getAdDisplayStyleForMedia`,
+ // we always pick the first video, so whether it's portrait or landscape just remove the first.
+ switch (displayStyle) {
+ case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */:
+ // We don't want to splice here as we might need these for reuse on dupe ads
+ if (media.portraitScreenshots.length <= 5) {
+ media.portraitScreenshots.splice(0, 4);
+ }
+ break;
+ case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */:
+ media.portraitScreenshots.splice(0, 3);
+ break;
+ case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */:
+ media.portraitScreenshots.splice(0, 2);
+ break;
+ case "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */:
+ media.portraitScreenshots.splice(0, 1);
+ break;
+ case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */:
+ // We will keep the video splice as the video is always first, so it will never be reused between the ad and organic
+ media.videos.splice(0, 1);
+ // We don't want to splice here as we might need these for reuse on dupe ads
+ if (media.portraitScreenshots.length <= 4) {
+ media.portraitScreenshots.splice(0, 3);
+ }
+ break;
+ case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */:
+ media.videos.splice(0, 1);
+ media.portraitScreenshots.splice(0, 2);
+ break;
+ case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */:
+ media.videos.splice(0, 1);
+ media.portraitScreenshots.splice(0, 1);
+ break;
+ case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */:
+ media.landscapeScreenshots.splice(0, 1);
+ break;
+ case "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */:
+ case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */:
+ media.videos.splice(0, 1);
+ break;
+ default:
+ break;
+ }
+}
+/**
+ * A function to determine whether a calculated organic search display style is considered "full creative".
+ * @param displayStyle the display style calculated for an organic dupe result to validate.
+ * @returns whether the given display style is considered "full creative" for an organic result.
+ */
+function isDisplayStyleFullCreativeForOrganic(objectGraph, displayStyle, screenshotsDisplayStyle) {
+ switch (displayStyle) {
+ case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */:
+ case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */:
+ return true;
+ case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */:
+ case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */:
+ return completePortraitMediaCount(objectGraph, screenshotsDisplayStyle) === 3;
+ case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */:
+ case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */:
+ return true;
+ case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */:
+ case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */:
+ return completePortraitMediaCount(objectGraph) === 2;
+ default:
+ return false;
+ }
+}
+/**
+ * Indicates whether the proposed ad style is compatible with a previously computed ad style.
+ * If there is no previously computed style, we return true as it's assumed the new style is compatible.
+ * @param proposedAdStyle
+ * @param firstAdComputedStyle
+ * @returns a boolean indicating whether the styles are compatible.
+ */
+function isProposedAdStyleCompatible(proposedAdStyle, firstComputedAdStyle) {
+ if (serverData.isNull(firstComputedAdStyle)) {
+ return true;
+ }
+ let areStylesCompatible = true;
+ switch (proposedAdStyle) {
+ case "TEXT" /* SearchAdDisplayStyle.TEXT */:
+ areStylesCompatible = firstComputedAdStyle.style === "TEXT" /* SearchAdDisplayStyle.TEXT */;
+ break;
+ case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */:
+ case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */:
+ areStylesCompatible =
+ firstComputedAdStyle.style === "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */ ||
+ firstComputedAdStyle.style === "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */;
+ break;
+ case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */:
+ case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */:
+ case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */:
+ case "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */:
+ case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */:
+ case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */:
+ case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */:
+ case "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */:
+ areStylesCompatible =
+ firstComputedAdStyle.style === "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */ ||
+ firstComputedAdStyle.style === "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */ ||
+ firstComputedAdStyle.style === "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */ ||
+ firstComputedAdStyle.style === "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */ ||
+ firstComputedAdStyle.style === "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */ ||
+ firstComputedAdStyle.style === "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */ ||
+ firstComputedAdStyle.style === "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */ ||
+ firstComputedAdStyle.style === "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */;
+ break;
+ case "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */:
+ areStylesCompatible = firstComputedAdStyle.style === "UI1_3x2" /* SearchAdDisplayStyle.UNRESTRICTED_IMAGE_ONE_ASSET */;
+ break;
+ default:
+ areStylesCompatible = false;
+ }
+ return areStylesCompatible;
+}
+/**
+ * Extract the best media for the platform from the lockup.
+ * @param lockup a lockup created from which we should extract the media.
+ * @returns the best media for the platform.
+ */
+function platformMediaForLockup(lockup) {
+ let mediaPlatformUsedForDisplayStyle = null;
+ // This works on the assumption that when a lockup is created screenshots and trailers are sorted
+ // and the first in the array of each is what we attempt to display.
+ const firstTrailer = lockup.trailers[0];
+ let videos = null;
+ if (serverData.isDefinedNonNullNonEmpty(firstTrailer)) {
+ firstTrailer.videos.sort((a, b) => {
+ return artworkSortingFunction(a.preview, b.preview);
+ });
+ videos = firstTrailer.videos;
+ mediaPlatformUsedForDisplayStyle = firstTrailer.mediaPlatform;
+ }
+ // Grab the first array of platform screenshots, the one the search ad will display.
+ // Additional elements in this array might be for other platforms we wouldn't
+ // display (e.g. if current device is an iPhone then [1] might be iPad screenshots)
+ const platformScreenshots = lockup.screenshots[0];
+ // Split the screenshots into portrait and landscape arrays.
+ // Landscape is preferred, so we use those first before portrait images, but we also need to know
+ // of any portrait screenshots so we can fill in any space left after a portrait video, which is
+ // preferred over a landscape screenshot.
+ const portraitScreenshots = [];
+ const landscapeScreenshots = [];
+ if (serverData.isDefinedNonNullNonEmpty(platformScreenshots)) {
+ platformScreenshots.artwork.forEach((artwork) => {
+ if (artwork.isPortrait()) {
+ portraitScreenshots.push(artwork);
+ }
+ else {
+ landscapeScreenshots.push(artwork);
+ }
+ });
+ mediaPlatformUsedForDisplayStyle = platformScreenshots.mediaPlatform;
+ }
+ return {
+ portraitScreenshots: portraitScreenshots,
+ landscapeScreenshots: landscapeScreenshots,
+ alignedRegionArtwork: lockup.alignedRegionArtwork,
+ alignedRegionVideo: lockup.alignedRegionVideo,
+ videos: videos,
+ mediaPlatformUsedForDisplayStyle: mediaPlatformUsedForDisplayStyle,
+ };
+}
+// endregion
+function iadAttributesForType(data, type) {
+ let iAdDictionaryToUse = null;
+ const iAdOptions = mediaAttributes.attributeAsDictionary(data, "iads");
+ const iAdJSONStringToUse = serverData.asString(iAdOptions, type);
+ if (iAdJSONStringToUse && iAdJSONStringToUse.length) {
+ iAdDictionaryToUse = JSON.parse(iAdJSONStringToUse);
+ }
+ return iAdDictionaryToUse;
+}
+function iAdDataTypeForAdvert(firstSearchResult, isDupe) {
+ if (serverData.isNullOrEmpty(firstSearchResult)) {
+ return "NOORGANIC" /* iAdDataType.NO_ORGANIC_RESULTS */;
+ }
+ if (isDupe) {
+ return "DUP" /* iAdDataType.DUPE_AD */;
+ }
+ return "NORMAL" /* iAdDataType.NORMAL */;
+}
+/**
+ * Removes unused media from the provided ad lockup, matching the selected ad display style.
+ * @param ad the ad lockup from which media should be removed.
+ * @param searchAdDisplayStyle the selected `SearchAdDisplayStyle` to match.
+ * @param isDupe if the given ad is a dupe of the first organic search result.
+ * @param isFirstAd if the given ad is the first search ad.
+ * @returns
+ */
+function modifyLockupToMatchAdDisplayStyle(ad, searchAdDisplayStyle, isDupe, isFirstAd) {
+ var _a, _b;
+ // Derive whether the ad lockup has a CPP (custom product page/ppid) that's being used for it's assets.
+ const hasCPP = serverData.isDefinedNonNullNonEmpty((_b = (_a = ad.impressionMetrics) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b.pageCustomId);
+ // If this ad lockup is a duplicate of the first organic search result and is not the first received ad,
+ // the organic search result gets the "first choice" of media. This flag indicates whether we should keep
+ // the "first set" or "second set" of media for this ad lockup.
+ // This logic does *not* apply if the ad is using a CPP. This is because the CPP from an ad only gets applied
+ // to an organic dupe result where the matching ad is in the first position. If not, the ad and organic are
+ // using a different set of assets, so there's no need to prefer one with the "first" or "second" set of media.
+ const wantsSecondSetOfMedia = isDupe && !isFirstAd && !hasCPP;
+ if (serverData.isDefinedNonNullNonEmpty(ad.trailers)) {
+ const firstTrailers = ad.trailers.shift();
+ firstTrailers.videos.sort((a, b) => {
+ return artworkSortingFunction(a.preview, b.preview);
+ });
+ ad.trailers.unshift(firstTrailers);
+ }
+ // Split the screenshots into portrait and landscape arrays.
+ // When removing screenshots from the array below, we can't just assume the first ones match the orientation
+ // we're using. For example, we only use portrait images with a portait video and some landscape images could
+ // be mixed in.
+ let mediaPlatformUsedForDisplayStyle;
+ let portraitScreenshots = [];
+ let landscapeScreenshots = [];
+ if (serverData.isDefinedNonNullNonEmpty(ad.screenshots)) {
+ const firstScreenshots = ad.screenshots.shift();
+ firstScreenshots.artwork.forEach((artwork) => {
+ if (artwork.isPortrait()) {
+ portraitScreenshots.push(artwork);
+ }
+ else {
+ landscapeScreenshots.push(artwork);
+ }
+ });
+ mediaPlatformUsedForDisplayStyle = firstScreenshots.mediaPlatform;
+ }
+ switch (searchAdDisplayStyle.style) {
+ case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */:
+ // We've previously determined advert can support this type. Remove videos and landscape images so
+ // advert only displays portrait images.
+ // With 4 portrait assets, we need to make sure that the ads and organic can be full by borrowing some assets from the organic.
+ // However, per product, if the app has 5 or less portrait assets, then it will go to the text ad as today.
+ // So we need to check for 5 assets as the lower bound (exclusive) and 8 (exclusive) as the upper for
+ // the extra work to reuse assets.
+ ad.trailers = null;
+ landscapeScreenshots = null;
+ ad.screenshotsDisplayStyle = "four-screenshots";
+ if (wantsSecondSetOfMedia) {
+ if (portraitScreenshots.length > 5 && portraitScreenshots.length < 8) {
+ const usedAssets = portraitScreenshots.splice(0, 4);
+ const reuseCount = 4 - portraitScreenshots.length;
+ const reuseAssets = usedAssets.splice(usedAssets.length - reuseCount);
+ portraitScreenshots.unshift(...reuseAssets);
+ }
+ else {
+ portraitScreenshots.splice(0, 4);
+ }
+ }
+ else {
+ portraitScreenshots.splice(4);
+ }
+ break;
+ case "PI3" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_THREE_ASSETS */:
+ // We've previously determined advert can support this type. Remove videos and landscape images so
+ // advert only displays portrait images.
+ ad.trailers = null;
+ landscapeScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ portraitScreenshots.splice(0, 3);
+ }
+ else {
+ portraitScreenshots.splice(3);
+ }
+ break;
+ case "PI2" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_TWO_ASSETS */:
+ // We've previously determined advert can support this type. Remove videos and landscape images so
+ // advert only displays portrait images.
+ ad.trailers = null;
+ landscapeScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ portraitScreenshots.splice(0, 2);
+ }
+ else {
+ portraitScreenshots.splice(2);
+ }
+ break;
+ case "PI1" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_ONE_ASSET */:
+ // We've previously determined advert can support this type. Remove videos and landscape images so
+ // advert only displays portrait images.
+ ad.trailers = null;
+ landscapeScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ portraitScreenshots.splice(0, 1);
+ }
+ else {
+ portraitScreenshots.splice(1);
+ }
+ break;
+ case "LI1" /* SearchAdDisplayStyle.LANDSCAPE_IMAGE_ONE_ASSET */:
+ // We've previously determined advert can support this type. Remove videos and portrait images so
+ // advert only displays landscape images.
+ ad.trailers = null;
+ portraitScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ landscapeScreenshots.splice(0, 1);
+ }
+ else {
+ landscapeScreenshots.splice(1);
+ }
+ break;
+ case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */:
+ // We've previously determined advert can support this type. Remove landscape images so
+ // advert only displays portrait assets.
+ // With 4 portrait assets, we need to make sure that the ads and organic can be full by borrowing some assets from the organic.
+ // However, per product, if the app has 5 or less portrait assets, then it will go to the text ad as today.
+ // Since this style includes a portrait video, we only need to check for 4 assets (exclusive) as the lower bound and 7 (exclusive) as the upper for
+ // the extra work to reuse assets
+ landscapeScreenshots = null;
+ ad.screenshotsDisplayStyle = "four-screenshots";
+ if (wantsSecondSetOfMedia) {
+ ad.trailers[0].videos.splice(0, 1);
+ if (portraitScreenshots.length > 4 && portraitScreenshots.length < 7) {
+ const usedAssets = portraitScreenshots.splice(0, 3);
+ const reuseCount = 3 - portraitScreenshots.length;
+ const reuseAssets = usedAssets.splice(usedAssets.length - reuseCount);
+ portraitScreenshots.unshift(...reuseAssets);
+ }
+ else {
+ portraitScreenshots.splice(0, 3);
+ }
+ }
+ else {
+ ad.trailers[0].videos.splice(1);
+ portraitScreenshots.splice(3);
+ }
+ break;
+ case "PV3" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_THREE_ASSETS */:
+ // We've previously determined advert can support this type. Remove landscape images so
+ // advert only displays portrait assets.
+ landscapeScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ ad.trailers[0].videos.splice(0, 1);
+ portraitScreenshots.splice(0, 2);
+ }
+ else {
+ ad.trailers[0].videos.splice(1);
+ portraitScreenshots.splice(2);
+ }
+ break;
+ case "PV2" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_TWO_ASSETS */:
+ // We've previously determined advert can support this type. Remove landscape images so
+ // advert only displays portrait assets.
+ landscapeScreenshots = null;
+ if (wantsSecondSetOfMedia) {
+ ad.trailers[0].videos.splice(0, 1);
+ portraitScreenshots.splice(0, 1);
+ }
+ else {
+ ad.trailers[0].videos.splice(1);
+ portraitScreenshots.splice(1);
+ }
+ break;
+ case "LV1" /* SearchAdDisplayStyle.LANDSCAPE_VIDEO_ONE_ASSET */:
+ case "PV1" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_ONE_ASSET */:
+ // We've determined advert can support this type. Remove images so only the single video displays
+ if (wantsSecondSetOfMedia) {
+ ad.trailers[0].videos.splice(0, 1);
+ }
+ else {
+ ad.trailers[0].videos.splice(1);
+ }
+ landscapeScreenshots = null;
+ portraitScreenshots = null;
+ break;
+ case "TEXT" /* SearchAdDisplayStyle.TEXT */:
+ // All adverts can support text only, remove all screenshots and trailers so they display in this style
+ ad.trailers = null;
+ landscapeScreenshots = null;
+ portraitScreenshots = null;
+ break;
+ default:
+ break;
+ }
+ if (serverData.isDefinedNonNullNonEmpty(ad.trailers)) {
+ const firstTrailers = ad.trailers.shift();
+ ad.trailers = [firstTrailers];
+ }
+ // Combine the remaining landscape and portrait screenshots, and set them back on the ad as the only values.
+ const allScreenshots = [].concat(...[landscapeScreenshots, portraitScreenshots].filter(serverData.isDefinedNonNull));
+ if (serverData.isDefinedNonNullNonEmpty(allScreenshots)) {
+ const screenshots = new models.Screenshots(allScreenshots, mediaPlatformUsedForDisplayStyle);
+ ad.screenshots = [screenshots];
+ }
+ else {
+ ad.screenshots = null;
+ }
+ return ad;
+}
+function completePortraitMediaCount(objectGraph, searchExperimentsData = null) {
+ if (objectGraph.client.isPhone) {
+ return allowsFourScreenshots(searchExperimentsData) ? 4 : 3;
+ }
+ else {
+ return 2;
+ }
+}
+function debugDescriptionForStyle(styleContainer) {
+ if (serverData.isNullOrEmpty(styleContainer)) {
+ return "";
+ }
+ let mediaTypeString = "";
+ if (styleContainer && styleContainer.mediaPlatform) {
+ mediaTypeString = ` derived from ${styleContainer.mediaPlatform.mediaType} media`;
+ }
+ return `${styleContainer.style}${mediaTypeString}`;
+}
+/**
+ * Stores a value representing the current setting of the native ad debug logging.
+ * This starts as `null`, and the first time we log we ask native to provide the current value.
+ * Requires a re-bootstrap to update, so we avoid sending a message back to native on every call
+ * if logging is disabled.
+ */
+let isNativeAdLoggingEnabled = null;
+export function adLogger(objectGraph, message) {
+ objectGraph.console.log(`[Ads] ${message}`);
+ if (objectGraph.client.buildType === "internal" &&
+ objectGraph.isAvailable(ads) &&
+ serverData.isDefinedNonNull(objectGraph.ads.debugLog)) {
+ // If we haven't asked native for whether the debug setting for ad logging is enabled, do it now.
+ if (serverData.isNull(isNativeAdLoggingEnabled) &&
+ serverData.isDefinedNonNull(objectGraph.ads.isNativeAdLoggingEnabled)) {
+ isNativeAdLoggingEnabled = objectGraph.ads.isNativeAdLoggingEnabled();
+ }
+ if (isNativeAdLoggingEnabled) {
+ objectGraph.ads.debugLog(message);
+ }
+ }
+}
+const artworkSortingFunction = (a, b) => {
+ const aLandscape = a.isLandscape();
+ const bLandscape = b.isLandscape();
+ if (aLandscape === bLandscape) {
+ return 0;
+ }
+ if (aLandscape) {
+ return -1;
+ }
+ return 1;
+};
+/**
+ * A wrapper around the ad search result to include the ad's displayStyle it will present with
+ */
+export class SearchAdsDisplayStyleResultContainer {
+}
+/**
+ * Removes any media used in a provided ad search result from the organic search result.
+ * @param objectGraph the Object Graph
+ * @param adResult the ad search result
+ * @param searchResult the organic search result
+ * @param searchExperimentsData the metadata for search result experiemnts
+ * @param searchAdDisplayStyle the display style for the ad that is matching with the organic
+ */
+export function dedupeAdMediaFromMatchingResult(objectGraph, adResult, searchResult, searchExperimentsData, searchAdDisplayStyle) {
+ var _a;
+ // Run de-duping on `AppSearchResult`s and `AppEventSearchResult`s.
+ // We specifically run this for `AppEventSearchResult`s because they can be presented in the search results
+ // as either a regular search result (ie. MixedMediaLockup) (if the app isn't installed), or as an IAE search
+ // result (if it is installed). This means we need to run de-duping in case it appears as a regular result,
+ // matching behaviour of the `AppSearchResult`.
+ if (!(searchResult instanceof models.AppSearchResult || searchResult instanceof models.AppEventSearchResult)) {
+ return;
+ }
+ const searchLockup = searchResult.lockup;
+ const adLockup = adResult.lockups[0];
+ if (adLockup.adamId !== searchLockup.adamId) {
+ return;
+ }
+ const usedTemplateUrls = new Set();
+ if (serverData.isDefinedNonNullNonEmpty(adLockup.screenshots)) {
+ for (const screenshot of adLockup.screenshots[0].artwork) {
+ usedTemplateUrls.add(screenshot.template);
+ }
+ }
+ if (serverData.isDefinedNonNullNonEmpty(adLockup.trailers)) {
+ for (const video of adLockup.trailers[0].videos) {
+ usedTemplateUrls.add(video.preview.template);
+ }
+ }
+ if (serverData.isDefinedNonNullNonEmpty(searchLockup.screenshots)) {
+ const filteredArtwork = searchLockup.screenshots[0].artwork.filter((artwork) => {
+ return !usedTemplateUrls.has(artwork.template);
+ });
+ searchLockup.screenshots[0] = new models.Screenshots(filteredArtwork, searchLockup.screenshots[0].mediaPlatform);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(searchLockup.trailers)) {
+ const filteredVideos = searchLockup.trailers[0].videos.filter((video) => {
+ return !usedTemplateUrls.has(video.preview.template);
+ });
+ searchLockup.trailers[0] = new models.Trailers(filteredVideos, searchLockup.trailers[0].mediaPlatform);
+ }
+ /// Exit early if we don't need to do anything special for the 4 screenshots case
+ if (((_a = searchExperimentsData === null || searchExperimentsData === void 0 ? void 0 : searchExperimentsData.displayStyle) === null || _a === void 0 ? void 0 : _a.screenshots) !== "four-screenshots") {
+ return;
+ }
+ // We need to possibly reuse some of the screenshots from the ad to make sure the organic has enough screenshots to show;
+ // This is only if we will show 4 portrait assets
+ const padSearchLockupScreenshotsToPreferredSize = (totalRequiredArtwork) => {
+ const currentScreenshots = searchLockup.screenshots[0].artwork;
+ if (currentScreenshots.length >= totalRequiredArtwork) {
+ return;
+ }
+ let screenshotsStillNeededCount = totalRequiredArtwork - currentScreenshots.length;
+ const adArtworksToReuse = adLockup.screenshots[0].artwork.slice().reverse();
+ for (const artwork of adArtworksToReuse) {
+ if (screenshotsStillNeededCount <= 0) {
+ return;
+ }
+ searchLockup.screenshots[0].artwork.unshift(artwork);
+ screenshotsStillNeededCount -= 1;
+ }
+ };
+ switch (searchAdDisplayStyle) {
+ case "PV4" /* SearchAdDisplayStyle.PORTRAIT_VIDEO_FOUR_ASSETS */:
+ case "PI4" /* SearchAdDisplayStyle.PORTRAIT_IMAGE_FOUR_ASSETS */:
+ padSearchLockupScreenshotsToPreferredSize(4);
+ break;
+ default:
+ break;
+ }
+}
+/**
+ * As part of the CPP implementation for Search Results ads, if the first ad result has a CPP applied, and there is a dupe scenario
+ * (ie. the first ad matches the first organic result) the CPP must also be applied to the first organic result.
+ * Here we update the CPP data on the organic result to match the first ad result, if applicable.
+ * @param objectGraph The object graph
+ * @param adData The raw ad data used to build the ad results.
+ * @param adResult The built ad result.
+ * @param searchResultData The data for the first organic search result.
+ * @param installStates A mapping of adamIDs to their respective install states that is used to determine if the app is currently installed by the user
+ * @param appStates A mapping of adamIDs to their respective app state to determine if the ad/first result has been installed by the user in the past
+ * @param metricsOptions Metrics options for built models.
+ * @param personalizationDataContainer The data container to use for personalizing the data.
+ */
+export function updateDupeOrganicResultCPPData(objectGraph, adData, adResult, searchResultData, installStates, appStates, metricsOptions, personalizationDataContainer) {
+ var _a, _b;
+ const adLockup = adResult.lockups[0];
+ if (adLockup.adamId !== searchResultData.id) {
+ return;
+ }
+ // Get the data for the first ad lockup. This *should* just be the first, but
+ // it's possible the first data got dropped when building the ad lockups.
+ const dataForAdLockup = adData.find((data) => data.id === adLockup.adamId);
+ const shouldSkipUpdatingOrganicCPP = searchResultWillUseAppEventDisplay(objectGraph, searchResultData, installStates, appStates, metricsOptions, personalizationDataContainer);
+ if (shouldSkipUpdatingOrganicCPP) {
+ return;
+ }
+ // The first ad lockup matches the first organic result. Apply the Ad cppId to matching organic result.
+ updatePPIDInData((_b = (_a = dataForAdLockup === null || dataForAdLockup === void 0 ? void 0 : dataForAdLockup.meta) === null || _a === void 0 ? void 0 : _a.cppData) === null || _b === void 0 ? void 0 : _b["ppid"], searchResultData);
+}
+/**
+ * Update the ppid for the given data.
+ * If provided `ppid` is `null`, the field will be deleted.
+ * If `cppData` does not already exist on `meta`, it will be created.
+ * @param ppid The ppid to update in the data. Can be null, which will delete the field.
+ * @param data The data object to update.
+ */
+function updatePPIDInData(ppid, data) {
+ var _a;
+ let meta = shallowCopyOf(data.meta);
+ if (serverData.isNull(ppid)) {
+ (_a = meta === null || meta === void 0 ? void 0 : meta.cppData) === null || _a === void 0 ? true : delete _a["ppid"];
+ }
+ else {
+ if (serverData.isNull(meta)) {
+ meta = {};
+ }
+ if (serverData.isNull(meta.cppData)) {
+ meta.cppData = {};
+ }
+ meta.cppData["ppid"] = ppid;
+ }
+ data.meta = meta;
+}
+/**
+ * Whether or not platform supports adverts.
+ */
+export function platformSupportsAdverts(objectGraph) {
+ return ((clientIdentifierSupportsAdverts(objectGraph) && objectGraph.host.isiOS) ||
+ objectGraph.host.platform === "unknown");
+}
+function clientIdentifierSupportsAdverts(objectGraph) {
+ return (objectGraph.host.clientIdentifier === client.appStoreIdentifier ||
+ objectGraph.host.clientIdentifier === client.productPageExtensionIdentifier);
+}
+//# sourceMappingURL=search-ads.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-common.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-common.js
new file mode 100644
index 0000000..94e7438
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-common.js
@@ -0,0 +1,59 @@
+/**
+ * Common operations for builders in search tab.
+ */
+// region Search Term State
+/**
+ * Build the `SearchTermContext` for a given response triggered by a search for `term`, possibly originating from `originatingTerm`
+ * @param requestDescriptor The options that describe fetch request.
+ * @param searchResponse The sequential response that was returned.
+ */
+export function createTermContextForSpellcheckedSequentialResponse(objectGraph, requestDescriptor, searchResponse) {
+ var _a, _b, _c, _d, _e, _f, _g;
+ return {
+ term: requestDescriptor.term,
+ suggestedTerm: (_b = (_a = searchResponse.results) === null || _a === void 0 ? void 0 : _a.spellCheck) === null || _b === void 0 ? void 0 : _b.suggestedTerm,
+ correctedTerm: (_d = (_c = searchResponse.results) === null || _c === void 0 ? void 0 : _c.spellCheck) === null || _d === void 0 ? void 0 : _d.correctedTerm,
+ resultsTerm: (_g = (_f = (_e = searchResponse.results) === null || _e === void 0 ? void 0 : _e.spellCheck) === null || _f === void 0 ? void 0 : _f.correctedTerm) !== null && _g !== void 0 ? _g : requestDescriptor.term,
+ originatingTerm: requestDescriptor.originatingTerm,
+ };
+}
+/**
+ * Create a search term context for the segmented search results completed fetch
+ * @param objectGraph The app store object graph
+ * @param requestDescriptor The search request descriptor
+ * @param searchResponse The response for the segmented search results page
+ * @returns The search term context for the segmented search results fetch
+ */
+export function createTermContextForSpellcheckedGroupedResponse(objectGraph, requestDescriptor, searchResponse) {
+ var _a, _b, _c, _d, _e, _f, _g;
+ return {
+ term: requestDescriptor.term,
+ suggestedTerm: (_b = (_a = searchResponse.results) === null || _a === void 0 ? void 0 : _a.spellCheck) === null || _b === void 0 ? void 0 : _b.suggestedTerm,
+ correctedTerm: (_d = (_c = searchResponse.results) === null || _c === void 0 ? void 0 : _c.spellCheck) === null || _d === void 0 ? void 0 : _d.correctedTerm,
+ resultsTerm: (_g = (_f = (_e = searchResponse.results) === null || _e === void 0 ? void 0 : _e.spellCheck) === null || _f === void 0 ? void 0 : _f.correctedTerm) !== null && _g !== void 0 ? _g : requestDescriptor.term,
+ originatingTerm: requestDescriptor.originatingTerm,
+ };
+}
+/**
+ * Build the `SearchTermContext` purely from `requestDescriptor` for requests that don't have spellchecking.
+ * @param requestDescriptor The options that describe fetch request.
+ */
+export function createTermContextForNonspellcheckRequest(objectGraph, requestDescriptor) {
+ return {
+ term: requestDescriptor.term,
+ resultsTerm: requestDescriptor.term,
+ originatingTerm: requestDescriptor.originatingTerm,
+ };
+}
+// endregion
+// region Constants
+/**
+ * The field name desired within the `meta.metrics` in search response.
+ */
+export const searchMetricsDataSetID = "data.search.dataSetId";
+/**
+ * The actual field name within the `meta.metrics` in search response.
+ */
+export const legacySearchMetricsDataSetID = "dataSetId";
+// endregion
+//# sourceMappingURL=search-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-facets.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-facets.js
new file mode 100644
index 0000000..dd0ccf4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-facets.js
@@ -0,0 +1,146 @@
+/**
+ * Build methods for Search Facets.
+ */
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { categoryListFromApiResponse } from "../categories";
+import * as metricsClickHelpers from "../metrics/helpers/clicks";
+/**
+ * Create Search Facets. Theres platform specific variations here.
+ * @param requestFacets Facets in current request.
+ * @param categoryFacetsData Additional
+ */
+export function createSearchFacets(objectGraph, requestFacets, categoryFacetsData) {
+ const selectedFacets = requestFacets || {};
+ const facets = [];
+ // Device Type
+ if (objectGraph.client.deviceType !== "mac") {
+ facets.push(new models.SearchFacetSet("targetPlatform", [
+ new models.SearchFacetValue(objectGraph.loc.string("Search.Facets.iPadAndIPhone"), null, selectedFacets["targetPlatform"]),
+ new models.SearchFacetValue(objectGraph.loc.string("Search.Facets.iPhoneOnly"), "iphone", selectedFacets["targetPlatform"]),
+ ]));
+ }
+ // Price
+ facets.push(new models.SearchFacetSet("price", [
+ new models.SearchFacetValue(objectGraph.loc.string("SEARCH_FACET_ANY_PRICE", "Any"), null, selectedFacets["price"]),
+ new models.SearchFacetValue(objectGraph.loc.string("SEARCH_FACET_FREE", "Any"), "free", selectedFacets["price"]),
+ ]));
+ // Categories
+ const categoryList = categoryListFromApiResponse(objectGraph, categoryFacetsData, false);
+ if (categoryList) {
+ const serverCategories = categoryList.categories;
+ if (serverCategories.length) {
+ const genreFacetValues = serverCategories
+ .filter((category) => {
+ return serverData.isDefinedNonNull(category.genreId);
+ })
+ .map((category) => {
+ return new models.SearchFacetValue(category.name, category.genreId, selectedFacets["genre"]);
+ });
+ genreFacetValues.unshift(new models.SearchFacetValue(objectGraph.loc.string("SEARCH_FACET_ANY_CATEGORY", "Any"), null, selectedFacets["genre"]));
+ facets.push(new models.SearchFacetSet("genre", genreFacetValues));
+ }
+ }
+ const searchSortOptions = objectGraph.bag.searchSortOptions;
+ // Sorts
+ const sortFacetValues = [];
+ sortFacetValues.push(new models.SearchFacetValue(objectGraph.loc.string("SEARCH_FACET_RELEVANCE"), null, selectedFacets["sort"]));
+ for (const sortValue of searchSortOptions) {
+ sortFacetValues.push(new models.SearchFacetValue(objectGraph.loc.string("SEARCH_FACET_" + sortValue), sortValue, selectedFacets["sort"]));
+ }
+ if (sortFacetValues.length > 1) {
+ facets.push(new models.SearchFacetSet("sort", sortFacetValues));
+ }
+ const serverAgeBands = objectGraph.bag.ageBands;
+ const ageBandFacetValues = serverAgeBands.map((ageBand) => {
+ return new models.SearchFacetValue(serverData.asString(ageBand, "name"), serverData.asString(ageBand, "ageBandId"), selectedFacets["ages"]);
+ });
+ if (ageBandFacetValues.length > 0 && objectGraph.client.deviceType !== "mac") {
+ facets.push(new models.SearchFacetSet("ages", ageBandFacetValues));
+ }
+ return facets;
+}
+/**
+ * Create Search Facets. Theres platform specific variations here.
+ * @param categoryFacetsData Additional
+ */
+export function createSearchPageFacets(objectGraph, categoryFacetsData) {
+ let categoryFacet = null;
+ let sortsFacet = null;
+ let ageBandsFacet = null;
+ // Platform
+ const deviceTypeFacet = new models.PageFacetsFacet("targetPlatform", "targetPlatform", objectGraph.loc.string("SEARCH_FACET_TYPE_TITLE_DEVICE_TYPE"), "singleSelection", [
+ new models.PageFacetOption(objectGraph.loc.string("Search.Facets.iPadAndIPhone"), null),
+ new models.PageFacetOption(objectGraph.loc.string("Search.Facets.iPhoneOnly"), "iphone"),
+ ], null, null, pageFacetChangeAction(objectGraph, "targetPlatform"));
+ // Price
+ const priceFacet = new models.PageFacetsFacet("filter[price]", "filter[price]", objectGraph.loc.string("SEARCH_FACET_TYPE_TITLE_PRICE"), "singleSelection", [
+ new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_ANY_PRICE", "Any"), null),
+ new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_FREE", "Any"), "free"),
+ ], null, null, pageFacetChangeAction(objectGraph, "price"));
+ // Categories
+ const categoryList = categoryListFromApiResponse(objectGraph, categoryFacetsData, false);
+ if (categoryList) {
+ const serverCategories = categoryList.categories;
+ if (serverCategories.length) {
+ const categories = serverCategories.filter((category) => {
+ return serverData.isDefinedNonNull(category.genreId);
+ });
+ categoryFacet = new models.PageFacetsFacet("filter[genre]", "filter[genre]", objectGraph.loc.string("SEARCH_FACET_TYPE_TITLE_CATEGORY"), "singleSelection", [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_ANY_CATEGORY", "Any"), null)], null, null, pageFacetChangeAction(objectGraph, "genre"));
+ for (const category of categories) {
+ categoryFacet.options.push(new models.PageFacetOption(category.name, category.genreId));
+ }
+ }
+ }
+ // Sorts
+ const searchSortOptions = objectGraph.bag.searchSortOptions;
+ sortsFacet = new models.PageFacetsFacet("sort", "sort", objectGraph.loc.string("SEARCH_FACET_TYPE_TITLE_SORT"), "singleSelection", [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_RELEVANCE"), null)], null, null, pageFacetChangeAction(objectGraph, "sort"));
+ for (const sortValue of searchSortOptions) {
+ sortsFacet.options.push(new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_" + sortValue), sortValue));
+ }
+ // Age Bands
+ const serverAgeBands = objectGraph.bag.ageBands;
+ const ageBandFacetOptions = serverAgeBands.map((ageBand) => {
+ return new models.PageFacetOption(serverData.asString(ageBand, "name"), serverData.asString(ageBand, "ageBandId"));
+ });
+ if (ageBandFacetOptions.length > 0 && objectGraph.client.deviceType !== "mac") {
+ ageBandsFacet = new models.PageFacetsFacet("filter[ages]", "filter[ages]", objectGraph.loc.string("SEARCH_FACET_TYPE_TITLE_AGE_BAND"), "singleSelection", ageBandFacetOptions, null, null, pageFacetChangeAction(objectGraph, "ages"));
+ }
+ const pageFacets = new models.PageFacets([], false, null);
+ if (objectGraph.client.isMac) {
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([priceFacet]));
+ if (serverData.isDefinedNonNull(categoryFacet)) {
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([categoryFacet]));
+ }
+ pageFacets.facetGroups.push(new models.PageFacetsGroup([sortsFacet]));
+ }
+ else {
+ const facets = [deviceTypeFacet, priceFacet];
+ if (serverData.isDefinedNonNull(categoryFacet)) {
+ facets.push(categoryFacet);
+ }
+ facets.push(sortsFacet);
+ if (serverData.isDefinedNonNull(ageBandsFacet)) {
+ facets.push(ageBandsFacet);
+ }
+ for (const facet of facets) {
+ facet.showsSelectedOptions = true;
+ }
+ pageFacets.facetGroups.push(new models.PageFacetsGroup(facets));
+ }
+ return pageFacets;
+}
+export function createDefaultSelectedFacetOptions(objectGraph) {
+ return {
+ "targetPlatform": [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_IPAD_ONLY"), null)],
+ "filter[price]": [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_ANY_PRICE", "Any"), null)],
+ "sort": [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_RELEVANCE"), null)],
+ "filter[genre]": [new models.PageFacetOption(objectGraph.loc.string("SEARCH_FACET_ANY_CATEGORY", "Any"), null)],
+ };
+}
+function pageFacetChangeAction(objectGraph, facetParameter) {
+ const action = new models.BlankAction();
+ metricsClickHelpers.addClickEventToPageFacetsChangeAction(objectGraph, action, facetParameter);
+ return action;
+}
+//# sourceMappingURL=search-facets.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-landing-page-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-landing-page-utils.js
new file mode 100644
index 0000000..f26b78b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-landing-page-utils.js
@@ -0,0 +1,386 @@
+import * as validation from "@jet/environment/json/validation";
+import { FetchTimingMetricsBuilder } from "@jet/environment/metrics/fetch-timing-metrics-builder";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import { PageRefreshPolicy, SearchAction, SearchFocusPage, SearchLandingPage, Shelf } from "../../api/models";
+import * as mediaRequestUtils from "../../common/builders/url-mapping-utils";
+import * as appStoreExperiments from "../../foundation/experimentation/app-store-experiments";
+import { ExperimentAreaId } from "../../foundation/experimentation/experiment-area-id";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaDataFetching from "../../foundation/media/data-fetching";
+import * as mediaNetwork from "../../foundation/media/network";
+import * as mediaRelationship from "../../foundation/media/relationships";
+import { Parameters, Path, Protocol } from "../../foundation/network/url-constants";
+import * as impressionDemotion from "../../common/personalization/on-device-impression-demotion";
+import { iadInfoFromOnDeviceAdResponse, isAdPlacementEnabled } from "../ads/ad-common";
+import * as adIncidents from "../ads/ad-incident-recorder";
+import { fetchAds as landingAdFetchFetchAds } from "../ads/on-device-ad-fetch";
+import * as landingAdStitch from "../ads/on-device-ad-stitch";
+import * as contentArtwork from "../content/artwork/artwork";
+import * as groupingCommon from "../grouping/grouping-common";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import * as metricsHelpersUtil from "../metrics/helpers/util";
+import * as onDevicePersonalization from "../personalization/on-device-personalization";
+import * as productPageVariants from "../product-page/product-page-variants";
+import { areAppTagsEnabled } from "../util/app-tags-util";
+import { isFeatureEnabledForCurrentUser } from "../util/lottery";
+import { SearchPageType } from "./content/search-shelves";
+import * as searchLandingCohort from "./landing/search-landing-cohort";
+import * as searchLandingShelfController from "./landing/search-landing-shelf-controller";
+/**
+ * Determines whether or not the user will use the legacy Search Landing Page protocol
+ * @param objectGraph The App Store Object Graph
+ * @returns Whether or not the user will use the legacy Search Landing Page protocol
+ */
+function shouldUseProtocolV1(objectGraph) {
+ if (objectGraph.client.isVision) {
+ return false; // visionOS always uses the modern V2 protocol.
+ }
+ if (objectGraph.client.isWeb) {
+ return false; // the "web" expects to use the V2 protocol as well
+ }
+ if (!objectGraph.bag.supportsSearchLandingPageV2) {
+ return true; // Use V1 protocol when V2 is unsupported
+ }
+ // Use V1 protocol based on the bags rollout rate for V2 protocol.
+ return !isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.searchLandingPageV2RolloutRate);
+}
+async function fetchSearchLandingPage(objectGraph, fetchAds) {
+ if (shouldUseProtocolV1(objectGraph)) {
+ return await fetchSearchLandingPageV1(objectGraph, fetchAds);
+ }
+ if (objectGraph.bag.mediaAPISearchFocusEnabled) {
+ return await fetchSearchLandingPageV2WithFocusPage(objectGraph, fetchAds);
+ }
+ return await fetchSearchLandingPageV2(objectGraph, fetchAds);
+}
+async function fetchSearchLandingPageV1(objectGraph, fetchAds) {
+ const searchLandingRequest = new mediaDataFetching.Request(objectGraph)
+ .forType("landing")
+ .includingAgeRestrictions()
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph)); // for `extend=customArtwork`
+ searchLandingRequest.targetResourceType = "groupings";
+ const cohortIdOrNil = searchLandingCohort.cohortIdForUser(objectGraph, objectGraph.user.dsid);
+ if ((cohortIdOrNil === null || cohortIdOrNil === void 0 ? void 0 : cohortIdOrNil.length) > 0) {
+ searchLandingRequest.addingQuery("clusterId", cohortIdOrNil);
+ }
+ const fetchTimingMetricsBuilder = new FetchTimingMetricsBuilder();
+ const modifiedObjectGraph = objectGraph.addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder);
+ const fetchSearchLanding = mediaNetwork.fetchData(modifiedObjectGraph, searchLandingRequest);
+ return await Promise.all([fetchSearchLanding, fetchAds]).then(([responseData, adResponse]) => {
+ return fetchTimingMetricsBuilder.measureModelConstruction(() => {
+ return landingPageFromResponseV1(modifiedObjectGraph, responseData, adResponse);
+ });
+ });
+}
+/**
+ * Creates `SearchLandingPage` model from the V1 Search Landing Page protocol
+ * @param objectGraph The App Store Object Graph
+ * @param landingPageResponse The response from the fetch
+ * @param adResponse The response from the ad fetch
+ * @returns A `SearchLandingPage` model from the V1 Search Landing Page protocol
+ */
+function landingPageFromResponseV1(objectGraph, landingPageResponse, adResponse) {
+ const mediaApiGroupingDataArray = serverData.asArrayOrEmpty(landingPageResponse, "results.contents");
+ const mediaApiGroupingData = mediaApiGroupingDataArray[0];
+ if (serverData.isNullOrEmpty(mediaApiGroupingData)) {
+ return null;
+ }
+ if (!mediaRelationship.hasRelationship(mediaApiGroupingData, "tabs")) {
+ return null;
+ }
+ const groupingGenreAdamId = mediaAttributes.attributeAsString(mediaApiGroupingData, "id");
+ const pageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, "Genre", mediaApiGroupingData.id, landingPageResponse);
+ const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph);
+ pageInformation.recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(pageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData);
+ pageInformation.iAdInfo = iadInfoFromOnDeviceAdResponse(objectGraph, "searchLanding", adResponse);
+ const adIncidentRecorder = adIncidents.newRecorder(objectGraph, pageInformation.iAdInfo);
+ adIncidents.recordAdResponseEventsIfNeeded(objectGraph, adIncidentRecorder, adResponse);
+ const groupingParseContext = {
+ shelves: [],
+ metricsPageInformation: pageInformation,
+ metricsLocationTracker: metricsHelpersLocation.newLocationTracker(),
+ pageGenreAdamId: groupingGenreAdamId,
+ pageGenreId: mediaAttributes.attributeAsNumber(mediaApiGroupingData, "genre"),
+ hasAuthenticatedUser: serverData.isDefinedNonNull(objectGraph.user.dsid),
+ isSearchLandingPage: true,
+ adStitcher: landingAdStitch.adStitcherForOnDeviceSLPAdvertData(objectGraph, adResponse),
+ adIncidentRecorder: adIncidentRecorder,
+ };
+ const flattenedGrouping = groupingCommon.flattenMediaApiGroupingData(objectGraph, mediaApiGroupingData);
+ groupingCommon.insertInitialShelvesIntoGroupingParseContext(objectGraph, flattenedGrouping, groupingParseContext);
+ const page = new SearchLandingPage(groupingParseContext.shelves);
+ // Page refresh
+ const refreshPolicy = new PageRefreshPolicy("timeSinceOnScreen", objectGraph.bag.searchLandingPageRefreshUpdateDelayInterval, objectGraph.bag.searchLandingPageOffscreenRefreshInterval, null);
+ page.pageRefreshPolicy = refreshPolicy;
+ // Ad Incidents
+ page.adIncidents = adIncidents.recordedIncidents(objectGraph, groupingParseContext.adIncidentRecorder);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, groupingParseContext.metricsPageInformation);
+ return page;
+}
+function makeSearchLandingRequestV2(objectGraph, fetchAds) {
+ const searchLandingRequest = new mediaDataFetching.Request(objectGraph)
+ .forType("landing:new-protocol")
+ .includingAgeRestrictions()
+ .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph)) // for `extend=customArtwork`
+ .includingScopedRelationships("search-recommendations", ["contents"])
+ .addingQuery("name", "search-landing");
+ if (areAppTagsEnabled(objectGraph, "slp")) {
+ mediaRequestUtils.configureTagsForMediaRequest(searchLandingRequest);
+ }
+ if (objectGraph.client.isVision || objectGraph.client.isWeb) {
+ searchLandingRequest.includingScopedAttributes("editorial-items", ["editorialClientParams"]);
+ }
+ const cohortIdOrNil = searchLandingCohort.cohortIdForUser(objectGraph, objectGraph.user.dsid);
+ if ((cohortIdOrNil === null || cohortIdOrNil === void 0 ? void 0 : cohortIdOrNil.length) > 0) {
+ searchLandingRequest.addingQuery("clusterId", cohortIdOrNil);
+ }
+ if (objectGraph.client.isiOS) {
+ searchLandingRequest.addingQuery("meta", "adDisplayStyle");
+ }
+ return searchLandingRequest;
+}
+async function fetchSearchLandingPageV2(objectGraph, fetchAds) {
+ const searchLandingRequest = makeSearchLandingRequestV2(objectGraph, fetchAds);
+ const fetchTimingMetricsBuilder = new FetchTimingMetricsBuilder();
+ const modifiedObjectGraph = objectGraph.addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder);
+ const fetchSearchLanding = mediaNetwork.fetchData(modifiedObjectGraph, searchLandingRequest);
+ const amsEngagement = objectGraph.amsEngagement;
+ let amdPromise;
+ if (amsEngagement && objectGraph.bag.enableRecoOnDeviceReordering) {
+ const request = {
+ timeout: 500,
+ eventType: impressionDemotion.AMSEngagementAppStoreEventKey,
+ tab: "search",
+ };
+ amdPromise = amsEngagement.performRequest(request);
+ }
+ return await Promise.all([fetchSearchLanding, fetchAds, amdPromise]).then(([responseData, adResponse, amdResponse]) => {
+ return fetchTimingMetricsBuilder.measureModelConstruction(() => {
+ return landingPageFromResponseV2(modifiedObjectGraph, responseData, adResponse, amdResponse);
+ });
+ });
+}
+/**
+ * Creates `SearchLandingPage` model from the V2 Search Landing Page protocol
+ * @param objectGraph The App Store Object Graph
+ * @param landingPageResponse The response from the fetch
+ * @param adResponse The response from the ad fetch
+ * @returns A `SearchLandingPage` model from the V2 Search Landing Page protocol
+ */
+function landingPageFromResponseV2(objectGraph, landingPageResponse, adResponse, impressionData) {
+ if (serverData.isNullOrEmpty(landingPageResponse.data)) {
+ return null;
+ }
+ // Creates the page info
+ const pageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, "SearchLanding", "SearchLanding", landingPageResponse);
+ // Decorate page info with personalization metrics
+ const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph);
+ pageInformation.recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(pageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData);
+ pageInformation.iAdInfo = iadInfoFromOnDeviceAdResponse(objectGraph, "searchLanding", adResponse);
+ const adIncidentRecorder = adIncidents.newRecorder(objectGraph, pageInformation.iAdInfo);
+ adIncidents.recordAdResponseEventsIfNeeded(objectGraph, adIncidentRecorder, adResponse);
+ // Creates Search Landing Page Context
+ const landingPageContext = {
+ shelves: [],
+ metricsLocationTracker: metricsHelpersLocation.newLocationTracker(),
+ metricsPageInformation: pageInformation,
+ adStitcher: landingAdStitch.adStitcherForOnDeviceSLPAdvertData(objectGraph, adResponse, landingPageResponse),
+ adIncidentRecorder: adIncidentRecorder,
+ pageType: SearchPageType.Landing,
+ recoImpressionData: impressionDemotion.impressionEventsFromData(objectGraph, impressionData),
+ };
+ // Create the shelves for the page
+ searchLandingShelfController.insertShelvesIntoSearchPageContext(objectGraph, landingPageResponse, landingPageContext);
+ // Add Unified Messaging placement to top of page for NLS BT.
+ const bubbleTipShelf = createNaturalLanguageSearchBubbleTipShelf(objectGraph);
+ if (bubbleTipShelf) {
+ landingPageContext.shelves.unshift(bubbleTipShelf);
+ }
+ const landingPage = new SearchLandingPage(landingPageContext.shelves);
+ // Page refresh
+ landingPage.pageRefreshPolicy = new PageRefreshPolicy("timeSinceOnScreen", objectGraph.bag.searchLandingPageRefreshUpdateDelayInterval, objectGraph.bag.searchLandingPageOffscreenRefreshInterval, null);
+ // Ad Incidents
+ landingPage.adIncidents = adIncidents.recordedIncidents(objectGraph, landingPageContext.adIncidentRecorder);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, landingPage, landingPageContext.metricsPageInformation);
+ return landingPage;
+}
+/**
+ * Creates the NLS BT shelf for SLP if enabled.
+ * @param objectGraph The app store object graph.
+ * @returns The shelf for the NLS BT shown on SLP, or undefined if bag has feature disabled.
+ */
+export function createNaturalLanguageSearchBubbleTipShelf(objectGraph) {
+ var _a;
+ if (!objectGraph.bag.isNaturalLanguageSearchEnabled && !objectGraph.bag.isNaturalLanguageSearchResultsEnabled) {
+ return undefined; // feature not enabled in the bag
+ }
+ const context = {
+ signal: {
+ lastNLSQueryDate: objectGraph.storage.retrieveString("lastNLSQueryDate"),
+ treatmentId: (_a = appStoreExperiments.currentTreatmentIdForArea(objectGraph, ExperimentAreaId.SearchLandingPage)) !== null && _a !== void 0 ? _a : null,
+ },
+ };
+ const shelf = groupingCommon.shelfForUnifiedMessage(objectGraph, "searchFocusHeader", context, "pullOnly");
+ shelf.refreshUrl = `${Protocol.internal}:/${Path.searchLandingPage}/${Path.shelf}/?${Parameters.isSearchFocusHeaderShelf}=true`;
+ return shelf;
+}
+async function fetchSearchLandingPageV2WithFocusPage(objectGraph, fetchAds) {
+ const searchLandingRequest = makeSearchLandingRequestV2(objectGraph, fetchAds).enablingFeature("search-focus-suggestions");
+ const fetchTimingMetricsBuilder = new FetchTimingMetricsBuilder();
+ const modifiedObjectGraph = objectGraph.addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder);
+ const fetchSearchLanding = mediaNetwork.fetchData(modifiedObjectGraph, searchLandingRequest);
+ const amsEngagement = objectGraph.amsEngagement;
+ let amdPromise = null;
+ if (amsEngagement && objectGraph.bag.enableRecoOnDeviceReordering) {
+ const request = {
+ timeout: 500,
+ eventType: impressionDemotion.AMSEngagementAppStoreEventKey,
+ tab: "search",
+ };
+ amdPromise = amsEngagement.performRequest(request);
+ }
+ return await Promise.all([fetchSearchLanding, fetchAds, amdPromise]).then(async ([responseData, adResponse, amdResponse]) => {
+ return await fetchTimingMetricsBuilder.measureModelConstructionAsync(async () => await landingPageFromResponseV2WithFocusPage(modifiedObjectGraph, responseData, adResponse, amdResponse));
+ });
+}
+/**
+ * Creates `SearchLandingPage` model from the V2 Search Landing Page protocol, but with search-focus feature enabled.
+ * @param objectGraph The App Store Object Graph
+ * @param landingPageResponse The response from the landing fetch
+ * @param landingAdResponse The response from the landingAd fetch
+ * @returns A `SearchLandingPage` model created from server response.
+ */
+async function landingPageFromResponseV2WithFocusPage(objectGraph, landingPageResponse, landingAdResponse, impressionData) {
+ // MAINTAINER'S NOTE: V3 protocol does not change any existing SLP v2 fields and is purely additives for focus page support
+ const landingPage = landingPageFromResponseV2(objectGraph, landingPageResponse, landingAdResponse, impressionData);
+ // Create Search Focus Page
+ return await fetchFocusPageUsingLandingPageResponse(objectGraph, landingPageResponse).then((focusPage) => {
+ landingPage.searchFocusPage = focusPage;
+ return landingPage;
+ });
+}
+/**
+ * Creates `SearchFocusPage` model from the V3 Search Landing Page protocol, fetching search history if needed.
+ * @param objectGraph The App Store Object Graph
+ * @param focusPageResponse The response from the fetch
+ * @returns A `SearchFocusPage` model from the V3 Search Landing Page protocol
+ */
+async function fetchFocusPageUsingLandingPageResponse(objectGraph, landingPageResponse) {
+ var _a;
+ if (serverData.isNullOrEmpty(landingPageResponse.data)) {
+ return null;
+ }
+ // Creates the page info
+ const pageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, "SearchFocus", "Focus", landingPageResponse, " ");
+ // Decorate page info with personalization metrics
+ const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph);
+ pageInformation.recoMetricsData = metricsHelpersUtil.combinedRecoMetricsDataFromMetricsData(pageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData);
+ // Creates Search Focus Page Context
+ const focusPageContext = {
+ shelves: [],
+ metricsLocationTracker: metricsHelpersLocation.newLocationTracker(),
+ metricsPageInformation: pageInformation,
+ pageType: SearchPageType.Focus,
+ };
+ const searchHistoryShelfMarker = searchLandingShelfController.firstShelfMarkerMatchingUseCase(landingPageResponse, focusPageContext, "recentSearches");
+ // Skip fetching search history if there isn't a marker for it in SLP response.
+ if (isNothing(searchHistoryShelfMarker)) {
+ return createFocusPageFromResponse(objectGraph, landingPageResponse, focusPageContext);
+ }
+ const searchHistoryDisplayCount = (_a = mediaAttributes.attributeAsNumber(searchHistoryShelfMarker, "displayCount")) !== null && _a !== void 0 ? _a : 0;
+ const fetchSearchHistory = objectGraph.onDeviceSearchHistoryManager.fetchRecentsWithLimit(searchHistoryDisplayCount);
+ return await fetchSearchHistory.then((searchHistory) => {
+ focusPageContext.searchHistory = searchHistory;
+ return createFocusPageFromResponse(objectGraph, landingPageResponse, focusPageContext);
+ });
+}
+function createFocusPageFromResponse(objectGraph, landingPageResponse, focusPageContext) {
+ // Create the shelves for the focus page using same logic as landing page, but
+ // includes search history in page context to support `search-recommendations-marker`.
+ searchLandingShelfController.insertShelvesIntoSearchPageContext(objectGraph, landingPageResponse, focusPageContext);
+ const focusPage = new SearchFocusPage(focusPageContext.shelves);
+ if (serverData.isNullOrEmpty(focusPage.shelves)) {
+ return null;
+ }
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, focusPage, focusPageContext.metricsPageInformation);
+ return focusPage;
+}
+async function fetchTrendingSearchesFallbackPage(objectGraph, fetchAds) {
+ const fetchRequest = {
+ url: objectGraph.bag.trendingSearchesURL,
+ };
+ const trendingSearchesPromise = objectGraph.network.fetch(fetchRequest).then((response) => {
+ if (!response.ok) {
+ throw Error(`Bad Status code ${response.status} for ${fetchRequest.url}`);
+ }
+ return JSON.parse(response.body);
+ });
+ return await Promise.all([trendingSearchesPromise, fetchAds]).then(([trendingSearchesData, adResponse]) => {
+ var _a;
+ const page = new SearchLandingPage(trendingSearchesShelvesForResponse(objectGraph, trendingSearchesData));
+ const pageInformation = metricsHelpersPage.fakeMetricsPageInformation(objectGraph, "SearchLanding", "trending", ""); // old trending endpoint doesn't have metrics meta
+ pageInformation.iAdInfo = iadInfoFromOnDeviceAdResponse(objectGraph, "searchLanding", adResponse);
+ (_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.setMissedOpportunity(objectGraph, "SLPLOAD", "searchLanding"); // trending fallback never displays ad, so is always missed opportunity.
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, pageInformation);
+ return page;
+ });
+}
+/**
+ * Creates a trending searches shelves from the given JSON response.
+ * @param objectGraph The App Store Object Graph.
+ * @param response The API response JSON data.
+ * @return {Shelf[]} Trending searches shelves created from response.
+ */
+function trendingSearchesShelvesForResponse(objectGraph, response) {
+ return validation.context("trendingSearchesShelfForResponse", () => {
+ const locationTracker = metricsHelpersLocation.newLocationTracker();
+ const searches = serverData.asArrayOrEmpty(response, "trendingSearches").map((rawSearch) => {
+ const term = serverData.asString(rawSearch, "label");
+ const searchAction = new SearchAction(term, term, serverData.asString(rawSearch, "url"), "trending");
+ if (objectGraph.client.isPhone) {
+ searchAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://magnifyingglass");
+ }
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", locationTracker);
+ metricsHelpersLocation.nextPosition(locationTracker);
+ return searchAction;
+ });
+ let maxNumberOfSearches = 0;
+ switch (objectGraph.client.deviceType) {
+ case "pad":
+ maxNumberOfSearches = 10;
+ break;
+ case "phone":
+ maxNumberOfSearches = 7;
+ break;
+ default:
+ break;
+ }
+ const shelf = new Shelf("action");
+ shelf.title = searches.length > 0 ? serverData.asString(response, "header.label") : null;
+ shelf.isHorizontal = false;
+ shelf.items = searches.slice(0, maxNumberOfSearches);
+ return [shelf];
+ });
+}
+export async function fetchPage(objectGraph) {
+ const fetchAds = isAdPlacementEnabled(objectGraph, "searchLanding")
+ ? landingAdFetchFetchAds(objectGraph, "searchLanding").catch(() => null)
+ : null;
+ return await fetchSearchLandingPage(objectGraph, fetchAds).catch(async (e) => {
+ // If the client has provided a `trendingSearchesURL`, we can fallback to that search
+ // mechanism if the search landing request fails. If `trendingSearchesURL` is not
+ // provided, we re-throw the original landing page error for the client to handle.
+ if (isSome(objectGraph.bag.trendingSearchesURL)) {
+ return await fetchTrendingSearchesFallbackPage(objectGraph, fetchAds);
+ }
+ else {
+ throw e;
+ }
+ });
+}
+//# sourceMappingURL=search-landing-page-utils.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-page-url.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-page-url.js
new file mode 100644
index 0000000..9462f94
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-page-url.js
@@ -0,0 +1,5 @@
+import { generateRoutes } from "../util/generate-routes";
+import { makeSearchResultsPageIntentFromURLParams } from "../../api/intents/search-results-page-intent";
+const { routes: searchResultsPageRoutes, makeCanonicalUrl: makeCanonicalSearchResultsPageUrl } = generateRoutes(makeSearchResultsPageIntentFromURLParams, "/{platform}/search", ["term"]);
+export { searchResultsPageRoutes, makeCanonicalSearchResultsPageUrl };
+//# sourceMappingURL=search-page-url.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-fetching.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-fetching.js
new file mode 100644
index 0000000..f04a8e1
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-fetching.js
@@ -0,0 +1,496 @@
+/**
+ * Data Fetching for Search Results for:
+ * - Initial search requests
+ * - Content pagination requests
+ */
+import { isNothing } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import { asBooleanOrFalse, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNullOrEmpty, } from "../../foundation/json-parsing/server-data";
+import { defaultAdditionalPlatformsForClient, Request } from "../../foundation/media/data-fetching";
+import { metricsFromMediaApiObject, } from "../../foundation/media/data-structure";
+import { fetchData } from "../../foundation/media/network";
+import { buildURLFromRequest } from "../../foundation/media/url-builder";
+import * as constants from "../../foundation/util/constants";
+import * as client from "../../foundation/wrappers/client";
+import * as appPromotionsCommon from "../app-promotions/app-promotions-common";
+import * as categories from "../categories";
+import { addVariantParametersToRequestForItems, shouldFetchCustomAttributes, } from "../product-page/product-page-variants";
+import { setPreviewPlatform } from "../preview-platform";
+import { SponsoredSearchRequestData } from "./search-ads";
+import { searchMetricsDataSetID } from "./search-common";
+import { fetchSponsoredSearchNativeAdvertData } from "./sponsored-search-fetching";
+import { dateFlooredToHour } from "../../foundation/util/date-util";
+import { shouldUsePrerenderedIconArtwork } from "../content/content";
+import { AppEventsAttributes } from "../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types";
+export async function fetchSegmentedSearchResults(objectGraph, options) {
+ var _a;
+ // Primary search request
+ const searchRequest = (_a = createSearchRequest(objectGraph, options)) === null || _a === void 0 ? void 0 : _a.addingQuery("groupBy[search]", "platform");
+ if (isNothing(searchRequest)) {
+ return null;
+ }
+ const fetchSearchResults = fetchData(objectGraph, searchRequest, undefined);
+ return await fetchSearchResults.then((catalogResponse) => {
+ renameDataSetIdKey(catalogResponse);
+ const data = {
+ catalogResponse: catalogResponse,
+ requestMetadata: {
+ requestDescriptor: options,
+ searchRequestUrl: buildURLFromRequest(objectGraph, searchRequest).toString(), // Searches are attributed to request url
+ },
+ categoriesFilterData: null,
+ sponsoredSearchRequestData: null,
+ sponsoredSearchAdvertData: null,
+ };
+ return data;
+ });
+}
+/**
+ * Fetch a collection of data for displaying a single, sequential set of search results.
+ * @param options Parameters for the search being performed.
+ * @returns `Promise` for aggregate search result data, or `null` if `options` was invalid.
+ */
+export async function fetchSequentialSearchResultsData(objectGraph, options) {
+ // Advert targeting data from client
+ const sponsoredSearchRequestData = new SponsoredSearchRequestData(options.targetingData, objectGraph.random.nextUUID());
+ // Primary search request
+ const searchRequest = createSearchRequest(objectGraph, options);
+ if (searchRequest === null) {
+ return null;
+ }
+ const fetchSearchResults = fetchData(objectGraph, searchRequest, createSearchFetchOptions(objectGraph, sponsoredSearchRequestData));
+ // Search History
+ if (objectGraph.bag.mediaAPISearchFocusEnabled) {
+ const historyItem = {
+ term: options.term.trim(),
+ entity: options.searchEntity,
+ };
+ // ** MAINTAINER'S NOTE **
+ // Fire and forget this request to reduce delay showing search results on persisting recent searches that are not visible at this time.
+ objectGraph.onDeviceSearchHistoryManager.saveRecentSearchWithLimit(historyItem, 30);
+ }
+ // Optional requests
+ const fetchSponsoredSearchAds = fetchSponsoredSearchNativeAdvertData(objectGraph, sponsoredSearchRequestData, options.term, fetchSearchResults);
+ const fetchCategoriesOrNull = fetchCategoryFiltersDataIfNeeded(objectGraph);
+ return await Promise.all([fetchSearchResults, fetchSponsoredSearchAds, fetchCategoriesOrNull]).then(([catalogResponse, sponsoredSearchAdvertData, categoriesFilterData]) => {
+ var _a, _b, _c, _d;
+ renameDataSetIdKey(catalogResponse);
+ // Remember the last time a natural language search was performed.
+ if ((_c = (_b = (_a = catalogResponse.meta) === null || _a === void 0 ? void 0 : _a.results) === null || _b === void 0 ? void 0 : _b.search) === null || _c === void 0 ? void 0 : _c.naturalLanguage) {
+ const previousNLSQueryDate = objectGraph.storage.retrieveString("lastNLSQueryDate");
+ const lastNLSQueryDate = dateFlooredToHour(new Date()).getTime().toString();
+ objectGraph.storage.storeString("lastNLSQueryDate", lastNLSQueryDate);
+ // Notify ODJ on change using AMSEngagement.
+ (_d = objectGraph.amsEngagement) === null || _d === void 0 ? void 0 : _d.enqueueData({
+ eventType: "lastNLSQueryDateChange",
+ app: "com.apple.AppStore",
+ oldState: previousNLSQueryDate,
+ newState: lastNLSQueryDate,
+ });
+ }
+ const data = {
+ catalogResponse: catalogResponse,
+ categoriesFilterData: categoriesFilterData,
+ sponsoredSearchRequestData: sponsoredSearchRequestData,
+ sponsoredSearchAdvertData: sponsoredSearchAdvertData,
+ requestMetadata: {
+ requestDescriptor: options,
+ searchRequestUrl: buildURLFromRequest(objectGraph, searchRequest).toString(), // Searches are attributed to request url
+ },
+ };
+ return data;
+ });
+}
+/**
+ * Fetch a collection of data for displaying several, grouped set of search results.
+ * @param options Parameters for the search being performed.
+ * @returns `Promise` for aggregate search result data, or `Promise` of `null` if `options` was invalid.
+ */
+export async function fetchPlatformGroupedSearchResultsData(objectGraph, options) {
+ // Primary search request
+ const searchRequest = createPlatformGroupedSearchRequest(objectGraph, options);
+ if (isNothing(searchRequest)) {
+ return null;
+ }
+ const fetchSearchResults = fetchData(objectGraph, searchRequest, createSearchFetchOptions(objectGraph));
+ // Optional requests
+ const fetchCategoriesOrNull = fetchCategoryFiltersDataIfNeeded(objectGraph);
+ return await Promise.all([fetchSearchResults, fetchCategoriesOrNull]).then(([catalogResponse, categoriesFilterData]) => {
+ renameDataSetIdKey(catalogResponse);
+ const data = {
+ catalogResponse: catalogResponse,
+ categoriesFilterData: categoriesFilterData,
+ sponsoredSearchRequestData: null,
+ sponsoredSearchAdvertData: null,
+ requestMetadata: {
+ requestDescriptor: options,
+ searchRequestUrl: buildURLFromRequest(objectGraph, searchRequest).toString(), // Searches are attributed to request url
+ },
+ };
+ return data;
+ });
+}
+/**
+ * Fetch a set of items for appearing in search results
+ * @param items Items to fetch.
+ */
+export async function fetchSearchResultItems(objectGraph, items) {
+ const request = createCatalogRequestForItems(objectGraph, items);
+ return await fetchData(objectGraph, request);
+}
+// endregion
+// region Request Header
+/**
+ * Create fetch options, annotating with `SponsoredSearchRequestData` header fields.
+ * @param adData Advert data to include in header.
+ */
+function createSearchFetchOptions(objectGraph, sponsoredSearchRequestData) {
+ const searchRequestHeader = {};
+ if (sponsoredSearchRequestData && sponsoredSearchRequestData.validAdRequest()) {
+ searchRequestHeader[constants.appStoreClientRequestIdName] = sponsoredSearchRequestData.appStoreClientRequestId;
+ searchRequestHeader[constants.iAdRequestDataHeaderName] = sponsoredSearchRequestData.sponsoredSearchRequestData;
+ searchRequestHeader[constants.iAdRoutingInfoHeaderName] = sponsoredSearchRequestData.routingInfo;
+ }
+ return {
+ headers: searchRequestHeader,
+ };
+}
+// endregion
+// region Search Results Request
+/**
+ * Set of relationships fetched as relation for search request / catalog requests
+ */
+const searchIncludeRelationships = ["apps", "top-apps"];
+/**
+ * Create the `Request` object to execute a search described by `options`
+ * @param options Options for search being executed.
+ * @returns `Request` configured for `options`, or `null` if `options` was invalid.
+ */
+export function createSearchRequest(objectGraph, options) {
+ var _a;
+ const term = (_a = options.term) === null || _a === void 0 ? void 0 : _a.trim();
+ if (isNullOrEmpty(term)) {
+ return null;
+ }
+ const origin = options.origin;
+ const source = options.source;
+ const searchEntityOrNull = options.searchEntity;
+ const facetsOrNull = options.facets;
+ const selectedFacetOptionsOrNull = options.selectedFacetOptions;
+ const spellCheckEnabled = options.spellCheckEnabled;
+ const excludedTermsOrNull = options.excludedTerms;
+ const clientIdentifier = objectGraph.host.clientIdentifier;
+ const searchRequest = new Request(objectGraph)
+ .withSearchTerm(term)
+ .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph))
+ .includingAttributes(includeAttributesForSearchResults(objectGraph))
+ .includingScopedAttributes("editorial-items", ["showLabelInSearch"])
+ .includingRelationshipsForUpsell(true)
+ .includingMacOSCompatibleIOSAppsWhenSupported()
+ .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ setPreviewPlatform(objectGraph, searchRequest);
+ if (!objectGraph.client.isWatch && !objectGraph.client.isVision) {
+ searchRequest.includingRelationships(searchIncludeRelationships);
+ }
+ if (objectGraph.client.isVision) {
+ // Temporarily increase the sparseLimit on Vision to workaround lack of filtering for bincompat.
+ searchRequest.addingQuery("sparseLimit[developers:top-apps]", "12");
+ }
+ if (appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
+ searchRequest.includingAssociateKeys("apps", ["app-events"]);
+ searchRequest.includingScopedAttributes("app-events", AppEventsAttributes);
+ searchRequest.includingScopedRelationships("editorial-items", ["primary-content"]);
+ }
+ /**
+ * Tinker Filter
+ */
+ if (asBooleanOrFalse(objectGraph.client.isTinkerWatch)) {
+ searchRequest.withFilter("contexts", "tinker");
+ }
+ /**
+ * Guided Search
+ */
+ if (objectGraph.host.isiOS) {
+ // Feature enablers
+ searchRequest.enablingFeature("guidedSearch");
+ searchRequest.enablingFeature("midScrollGuidedSearch");
+ // Selected facets, if any.
+ if (isDefinedNonNullNonEmpty(options.guidedSearchTokens)) {
+ searchRequest.addingQuery("selectedFacets", options.guidedSearchTokens.join(","));
+ }
+ // Optimization term, if any were on request.
+ if (isDefinedNonNullNonEmpty(options.guidedSearchOptimizationTerm)) {
+ searchRequest.addingQuery("finalTerm", options.guidedSearchOptimizationTerm);
+ }
+ if (objectGraph.bag.isLLMSearchTagsEnabled) {
+ searchRequest.includingAssociateKeys("results:apps", ["tags"]);
+ }
+ }
+ if (objectGraph.featureFlags.isEnabled("voyager_bundles_2025A")) {
+ searchRequest.includingScopedAttributes("apps", ["screenshotsByType"]);
+ }
+ /**
+ * Entities in results
+ */
+ if (searchEntityOrNull === "story") {
+ searchRequest.searchingOverTypes(["editorial-items"]);
+ }
+ else if (searchEntityOrNull === "developer") {
+ searchRequest.searchingOverTypes(["developers"]);
+ }
+ else if (searchEntityOrNull === "watch" || searchEntityOrNull === "arcade") {
+ searchRequest.searchingOverTypes(["apps"]).withFilter("contexts", searchEntityOrNull);
+ }
+ else if (objectGraph.client.isTV) {
+ searchRequest.searchingOverTypes(["apps", "developers", "groupings", "editorial-items"]);
+ }
+ else if (objectGraph.client.isVision) {
+ searchRequest.searchingOverTypes([
+ "apps",
+ "developers",
+ "editorial-items",
+ "app-bundles",
+ "in-apps",
+ "editorial-pages",
+ ]);
+ }
+ else {
+ searchRequest.searchingOverTypes([
+ "apps",
+ "developers",
+ "groupings",
+ "editorial-items",
+ "app-bundles",
+ "in-apps",
+ ]);
+ }
+ /**
+ * Signal Rosetta unavailability.
+ */
+ if (objectGraph.appleSilicon.isSupportEnabled && !objectGraph.appleSilicon.isRosettaAvailable) {
+ searchRequest.addingQuery("restrict", "!requiresRosetta");
+ }
+ /**
+ * Facets / filters
+ */
+ if (facetsOrNull) {
+ for (const key of Object.keys(facetsOrNull)) {
+ searchRequest.addingQuery(key, facetsOrNull[key]);
+ }
+ }
+ if (selectedFacetOptionsOrNull) {
+ for (const key of Object.keys(selectedFacetOptionsOrNull)) {
+ const requestValues = models.PageFacets.requestValuesForSelectedFacetOptions(selectedFacetOptionsOrNull[key]);
+ if (isDefinedNonNullNonEmpty(requestValues)) {
+ searchRequest.addingQuery(key, requestValues.value);
+ for (const additionalKey of Object.keys(requestValues.additionalKeyValuePairs)) {
+ searchRequest.addingQuery(additionalKey, requestValues.additionalKeyValuePairs[additionalKey]);
+ }
+ }
+ }
+ }
+ /**
+ * natural language search availability
+ */
+ const isNaturalLanguageSearchResultsEnabled = objectGraph.bag.isNaturalLanguageSearchEnabled || objectGraph.bag.isNaturalLanguageSearchResultsEnabled;
+ /**
+ * source attribution
+ */
+ const sourceKey = isNaturalLanguageSearchResultsEnabled ? "source" : "src";
+ if (origin === "hints") {
+ const hintSource = isNaturalLanguageSearchResultsEnabled && (source === null || source === void 0 ? void 0 : source.length) ? "hint:".concat(source) : "hint";
+ searchRequest.addingQuery(sourceKey, hintSource);
+ }
+ else if (origin === "recents") {
+ searchRequest.addingQuery(sourceKey, "recent");
+ }
+ else if (origin === "trending") {
+ searchRequest.addingQuery(sourceKey, "trending");
+ }
+ else if (origin === "undoSpellCorrection") {
+ searchRequest.addingQuery(sourceKey, "searchInstead");
+ }
+ else if (origin === "applySpellCorrection") {
+ searchRequest.addingQuery(sourceKey, "didYouMean");
+ }
+ else if (origin === "guidedToken") {
+ searchRequest.addingQuery(sourceKey, "facet");
+ }
+ /**
+ * contexts
+ */
+ switch (clientIdentifier) {
+ case client.watchIdentifier:
+ searchRequest.addingContext("watch");
+ break;
+ case client.messagesIdentifier:
+ searchRequest.addingContext("messages");
+ break;
+ case client.arcadeIdentifier:
+ searchRequest.addingContext("arcade");
+ break;
+ default:
+ break;
+ }
+ /**
+ * Advert limit
+ */
+ searchRequest.addingQuery("limit[ads-result]", objectGraph.bag.mediaAdvertRequestLimit.toString());
+ /**
+ * Ads locale metadata.
+ */
+ if (isDefinedNonNullNonEmpty(objectGraph.bag.adsOverrideLanguage)) {
+ searchRequest.enablingFeature("adsLocaleMetadata");
+ }
+ /**
+ * Feature: Spellcheck
+ */
+ if (spellCheckEnabled) {
+ searchRequest.enablingFeature("spellCheck");
+ }
+ /**
+ * Feature: Natural Language
+ */
+ if (isNaturalLanguageSearchResultsEnabled) {
+ searchRequest.enablingFeature("naturalLanguage");
+ }
+ /**
+ * Feature: Organic CPPs
+ */
+ if (objectGraph.host.isiOS) {
+ searchRequest.enablingFeature("searchResultCpps");
+ }
+ /**
+ * Excluded terms
+ */
+ if (excludedTermsOrNull) {
+ searchRequest.addingQuery("excludeTerms", excludedTermsOrNull.join(","));
+ }
+ return searchRequest;
+}
+/**
+ * Create the `Request` object to execute a search described by `options`, with results grouped by platform
+ * @param options Options for search being executed.
+ * @returns `Request` configured for `options`, or `null` if `options` was invalid.
+ */
+export function createPlatformGroupedSearchRequest(objectGraph, options) {
+ var _a;
+ return (_a = createSearchRequest(objectGraph, options)) === null || _a === void 0 ? void 0 : _a.addingQuery("groupBy[search]", "platform").includingMacOSCompatibleIOSAppsWhenSupported();
+}
+/**
+ * Create a request for fetching set of `items` for appearing in search tab. Used for pagination.
+ * @param items Items to fetch.
+ */
+function createCatalogRequestForItems(objectGraph, items) {
+ const shouldUseMixedCatalogRequest = objectGraph.bag.isLLMSearchTagsEnabled ||
+ asBooleanOrFalse(objectGraph.bag.supportedMixedMediaRequestUsecases["search"]);
+ const searchRequest = new Request(objectGraph, items, shouldUseMixedCatalogRequest, ["tags"])
+ .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph))
+ .includingScopedAttributes("editorial-items", ["showLabelInSearch"])
+ .includingAttributes(includeAttributesForSearchResults(objectGraph))
+ .includingRelationshipsForUpsell(true)
+ .includingMacOSCompatibleIOSAppsWhenSupported()
+ .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ addVariantParametersToRequestForItems(objectGraph, searchRequest, items);
+ if (!objectGraph.client.isWatch) {
+ searchRequest.includingRelationships(searchIncludeRelationships);
+ }
+ if (appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
+ searchRequest.includingAssociateKeys("apps", ["app-events"]);
+ searchRequest.includingScopedAttributes("app-events", AppEventsAttributes);
+ searchRequest.includingScopedRelationships("editorial-items", ["primary-content"]);
+ }
+ return searchRequest;
+}
+/**
+ * Returns the include attributes used for:
+ * 1. Initial search request
+ * 2. Subsequent catalog request for pagination
+ */
+function includeAttributesForSearchResults(objectGraph) {
+ const attributes = [
+ "screenshotsByType",
+ "messagesScreenshots",
+ "videoPreviewsByType",
+ "requiredCapabilities",
+ "editorialBadgeInfo",
+ "supportsFunCamera",
+ "minimumOSVersion",
+ "customScreenshotsByTypeForAd",
+ "customVideoPreviewsByTypeForAd",
+ "secondaryGenreShortDisplayNames",
+ "genreShortDisplayName",
+ "editorialVideo",
+ ];
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.client.isVision) {
+ attributes.push("compatibilityControllerRequirement");
+ }
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ /// We don't want to unnecessarily ask for clients that don't have metadata ribbon capability
+ if (objectGraph.host.isOSAtLeast(15, 5, 0)) {
+ attributes.push("remoteControllerRequirement");
+ }
+ // Keeping custom creatives out of prod.
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ if (objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) {
+ attributes.push("adCreativeArtwork");
+ attributes.push("adCreativeVideo");
+ }
+ }
+ return attributes;
+}
+// endregion
+// region Categories Filter Request
+/**
+ * Fetches data to for category filters on devices that need them.
+ */
+async function fetchCategoryFiltersDataIfNeeded(objectGraph) {
+ const deviceType = objectGraph.client.deviceType;
+ if (deviceTypeSupportsCategoryFilters(deviceType)) {
+ const categoriesRequest = categories.createRequest(objectGraph, null, null, defaultAdditionalPlatformsForClient(objectGraph));
+ if (categoriesRequest) {
+ // Failable request.
+ return await fetchData(objectGraph, categoriesRequest).catch(() => null);
+ }
+ }
+ return null;
+}
+function deviceTypeSupportsCategoryFilters(deviceType) {
+ switch (deviceType) {
+ case "pad":
+ case "mac":
+ return true;
+ default:
+ return false;
+ }
+}
+// endregion
+// region Search Result Modification
+/**
+ * The search dataset id is set to the key `dataSetId` on `meta.metrics`, but needs to have `data.search.dataSetId` key.
+ * <rdar://problem/39920993> Metrics: metrics blob in search response has incorrect field name
+ */
+function renameDataSetIdKey(searchResponse) {
+ var _a;
+ const searchMetrics = metricsFromMediaApiObject(searchResponse);
+ const oldDataSetId = "dataSetId";
+ if (isDefinedNonNull(searchMetrics) &&
+ isDefinedNonNull(searchResponse.meta) &&
+ isDefinedNonNull((_a = searchResponse.meta) === null || _a === void 0 ? void 0 : _a.metrics)) {
+ searchResponse.meta.metrics[searchMetricsDataSetID] = asString(searchMetrics, oldDataSetId);
+ delete searchResponse.meta.metrics[oldDataSetId];
+ }
+}
+// endregion
+//# sourceMappingURL=search-results-fetching.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-learn-more-notice.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-learn-more-notice.js
new file mode 100644
index 0000000..8c2e741
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-learn-more-notice.js
@@ -0,0 +1,46 @@
+/**
+ * Build methods for Search Transparency.
+ * Search transparency provides additional disclosure message about search ranking required in certain storefronts.
+ */
+import { isNothing } from "@jet/environment";
+import { FlowAction, LinkableText, StyledText } from "../../api/models";
+import { addClickEventToAction } from "../metrics/helpers/clicks";
+import { makeRoutableArticlePageCanonicalUrl } from "../today/routable-article-page-url-utils";
+import { makeRoutableArticlePageIntent } from "../../api/intents/routable-article-page-intent";
+import { getPlatform } from "../preview-platform";
+import { getLocale } from "../locale";
+export function createSearchResultsLearnMoreNoticeLinkableText(objectGraph, metricsOptions) {
+ const editorialItemId = objectGraph.bag.searchResultsLearnMoreEditorialId;
+ if (isNothing(editorialItemId) || (editorialItemId === null || editorialItemId === void 0 ? void 0 : editorialItemId.length) === 0) {
+ return undefined;
+ }
+ const clickOptions = {
+ kind: "editorialItem",
+ softwareType: null,
+ title: objectGraph.loc.string("SEARCH_TRANSPARENCY_LINK"),
+ id: editorialItemId,
+ targetType: "link",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ };
+ const text = objectGraph.loc.string("SEARCH_TRANSPARENCY_TEXT");
+ const learnMoreAction = new FlowAction("article");
+ learnMoreAction.title = text;
+ learnMoreAction.pageUrl = `https://apps.apple.com/story/id${editorialItemId}`;
+ if (objectGraph.client.isWeb) {
+ const destination = makeRoutableArticlePageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ id: editorialItemId,
+ });
+ const pageUrlString = makeRoutableArticlePageCanonicalUrl(objectGraph, destination);
+ learnMoreAction.pageUrl = pageUrlString;
+ learnMoreAction.destination = destination;
+ }
+ addClickEventToAction(objectGraph, learnMoreAction, clickOptions);
+ const linkSubstrings = {};
+ linkSubstrings[`${objectGraph.loc.string("SEARCH_TRANSPARENCY_LINK")}`] = learnMoreAction;
+ const styledText = new StyledText(text, "text/plain");
+ return new LinkableText(styledText, linkSubstrings);
+}
+//# sourceMappingURL=search-results-learn-more-notice.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js
new file mode 100644
index 0000000..f6b64c1
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-results-pipeline.js
@@ -0,0 +1,248 @@
+/**
+ * Builder methods for building a collection of search results models for a series of search result data.
+ */
+import * as validation from "@jet/environment/json/validation";
+import { isNothing } from "@jet/environment/types/optional";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { isDataHydrated } from "../../foundation/media/data-structure";
+import { shouldFilter } from "../filtering";
+import { nextPosition, pushBasicLocation, popLocation } from "../metrics/helpers/location";
+import * as onDevicePersonalization from "../personalization/on-device-personalization";
+import { searchResultFromData } from "./content/search-results";
+import * as searchAds from "./search-ads";
+import * as guidedSearch from "./guided-search/guided-search";
+import { addImpressionFields } from "../metrics/helpers/impressions";
+import { searchAdMissedOpportunityFromId } from "../lockups/ad-lockups";
+/**
+ * Determines where to display guided search, e.g. as mid-scroll module or pinned to top.
+ * @param meta The metadata from the MAPI response for search catalog request.
+ * @param objectGraph The App Store object graph.
+ * @returns The position of the mid-scroll guided search module in search results, or `undefined` for the pinned to top experience.
+ */
+export function guidedSearchPositionFromSearchResponseMeta(meta, objectGraph) {
+ var _a, _b, _c;
+ // Client debug override
+ const guidedSearchPositionOverride = (_a = objectGraph.userDefaults) === null || _a === void 0 ? void 0 : _a.integer("GuidedSearchOverrides.position");
+ if (serverData.isNumber(guidedSearchPositionOverride) && guidedSearchPositionOverride > 1) {
+ return guidedSearchPositionOverride;
+ }
+ // Server controlled by default
+ return (_c = (_b = meta === null || meta === void 0 ? void 0 : meta.displayStyle) === null || _b === void 0 ? void 0 : _b.guidedSearch) === null || _c === void 0 ? void 0 : _c.position;
+}
+/**
+ * Create a `SearchResultsBuildResult` containing set of built and deferred results from the top-level search data container.
+ * This method supports prepending adverts.
+ * @param objectGraph
+ * @param requestMetadata Request metadata for search being performed.
+ * @param searchResponseMetadata Response metadata for search that was performed.
+ * @param metricsOptions Metrics options for built models.
+ * @param resultsData Array of search results data.
+ * @param advertData Data container with advert results.
+ * @param facetData Array of guided search token response data.
+ * @param installStates A mapping of adamIDs to their respective install states that is used to determine if the app is currently installed by the user
+ * @param appStates A mapping of adamIDs to their respective app state to determine if the ad/first result has been installed by the user in the past
+ */
+export async function createSearchResults(objectGraph, requestMetadata, searchResponseMetadata, metricsOptions, resultsData, advertData = undefined, facetData = undefined, installedStates = undefined, appStates = undefined) {
+ var _a, _b, _c, _d, _e, _f;
+ /// Built models
+ const builtResults = [];
+ // Unhydrated items that will be fetched in pagination.
+ const deferredResults = [];
+ // Search Experiments Data
+ const searchExperimentsData = searchResponseMetadata || null;
+ // Generate the personalization data
+ const appIds = resultsData
+ .filter((resultData) => {
+ return resultData.type === "apps";
+ })
+ .map((resultData) => {
+ return resultData.id;
+ });
+ const personalizationDataContainer = onDevicePersonalization.personalizationDataContainerForAppIds(objectGraph, new Set(appIds));
+ // Build Adverts
+ let advertsSearchResult;
+ let advertsDisplayStyle;
+ if (searchAds.platformSupportsAdverts(objectGraph) && serverData.isDefinedNonNullNonEmpty(advertData)) {
+ const adsResultAndDisplayStyle = searchAds.adsResultFromSearchResults(objectGraph, advertData, resultsData, requestMetadata, metricsOptions, installedStates !== null && installedStates !== void 0 ? installedStates : null, appStates !== null && appStates !== void 0 ? appStates : null, searchExperimentsData, personalizationDataContainer);
+ advertsSearchResult = adsResultAndDisplayStyle.result;
+ advertsDisplayStyle = adsResultAndDisplayStyle.displayStyle;
+ if (serverData.isDefinedNonNullNonEmpty(advertsSearchResult === null || advertsSearchResult === void 0 ? void 0 : advertsSearchResult.lockups)) {
+ advertsSearchResult.searchAdOpportunity = advertsSearchResult.lockups[0].searchAdOpportunity;
+ builtResults.push(advertsSearchResult);
+ }
+ }
+ // Flag for Ad Media Deduping
+ let isFirstResult = true;
+ const guidedSearchPosition = guidedSearchPositionFromSearchResponseMeta(searchResponseMetadata, objectGraph);
+ for (const [index, resultData] of resultsData.entries()) {
+ // Inject the mid-scroll guided search module if we've reached the desired position.
+ if (index === guidedSearchPosition) {
+ const tokens = createGuidedSearchTokens(objectGraph, requestMetadata.requestDescriptor, facetData, metricsOptions);
+ if (tokens.length > 0) {
+ const title = (_c = (_b = (_a = searchResponseMetadata === null || searchResponseMetadata === void 0 ? void 0 : searchResponseMetadata.displayStyle) === null || _a === void 0 ? void 0 : _a.guidedSearch) === null || _b === void 0 ? void 0 : _b.title) !== null && _c !== void 0 ? _c : objectGraph.loc.string("Search.Guided.Title.ExploreMore"); // static fallback query context
+ const guidedSearchResult = new models.GuidedSearchResult(title, tokens);
+ const impressionOptions = {
+ ...metricsOptions,
+ id: "midScrollGuidedSearch",
+ kind: "grouping",
+ targetType: "module",
+ title: title,
+ softwareType: null,
+ };
+ addImpressionFields(objectGraph, guidedSearchResult, impressionOptions);
+ builtResults.push(guidedSearchResult);
+ nextPosition(metricsOptions.locationTracker);
+ }
+ }
+ // Deferred items for subsequent pagination.
+ if (!isDataHydrated(resultData)) {
+ // On the first unhydrated item, attach the rest of the queue to `deferredResults` to preserve ordering.
+ deferredResults.push(...resultsData.slice(index));
+ break;
+ }
+ // Filter
+ if (shouldFilter(objectGraph, resultData, 10750 /* Filter.Search */)) {
+ continue;
+ }
+ // Advert: Update CPP data on first organic result.
+ // We must do this *after* the ad results are built, because we need to ensure we're picking the first lockup that will appear,
+ // not just the first data (that may be filtered somehow).
+ if (isFirstResult && serverData.isDefinedNonNullNonEmpty(advertsSearchResult === null || advertsSearchResult === void 0 ? void 0 : advertsSearchResult.lockups)) {
+ searchAds.updateDupeOrganicResultCPPData(objectGraph, advertData !== null && advertData !== void 0 ? advertData : [], advertsSearchResult, resultData, installedStates !== null && installedStates !== void 0 ? installedStates : null, appStates !== null && appStates !== void 0 ? appStates : null, metricsOptions, personalizationDataContainer);
+ }
+ // Build model
+ const searchResult = searchResultFromData(objectGraph, resultData, searchResponseMetadata, personalizationDataContainer, metricsOptions, requestMetadata.requestDescriptor.isNetworkConstrained, requestMetadata.requestDescriptor.searchEntity, searchExperimentsData);
+ if (!searchResult || !platformSupportsResultType(objectGraph, searchResult)) {
+ continue;
+ }
+ /**
+ * Advert: When first advert and result matches, modify media.
+ */
+ if (isFirstResult &&
+ serverData.isDefinedNonNullNonEmpty(advertsSearchResult) &&
+ serverData.isDefinedNonNullNonEmpty(advertsSearchResult.lockups)) {
+ searchAds.dedupeAdMediaFromMatchingResult(objectGraph, advertsSearchResult, searchResult, searchExperimentsData, advertsDisplayStyle);
+ }
+ /**
+ * Advert: When advert isn't available, mark the organic as a missed opportunity slot
+ */
+ if (isFirstResult &&
+ searchAds.platformSupportsAdverts(objectGraph) &&
+ serverData.isDefinedNonNull((_d = metricsOptions.pageInformation) === null || _d === void 0 ? void 0 : _d.iAdInfo) &&
+ (serverData.isNull(advertsSearchResult) || serverData.isNullOrEmpty(advertsSearchResult === null || advertsSearchResult === void 0 ? void 0 : advertsSearchResult.lockups))) {
+ searchResult.searchAdOpportunity = searchAdMissedOpportunityFromId(objectGraph, metricsOptions.pageInformation);
+ (_e = searchResult.searchAdOpportunity) === null || _e === void 0 ? void 0 : _e.setMissedOpportunityReason("NOAD");
+ (_f = searchResult.searchAdOpportunity) === null || _f === void 0 ? void 0 : _f.setTemplateType("APPLOCKUP");
+ }
+ builtResults.push(searchResult);
+ isFirstResult = false;
+ nextPosition(metricsOptions.locationTracker);
+ }
+ return await applyClientFilteringToIAPs(objectGraph, builtResults).then((builtResultsFiltered) => {
+ return {
+ builtSearchResults: builtResultsFiltered,
+ deferredSearchResults: deferredResults,
+ };
+ });
+}
+// endregion
+// region Internals
+/**
+ * Create guided search tokens for the given search request descriptor and facet MAPI response data.
+ * @param objectGraph The App Store object graph.
+ * @param requestDescriptor The search request descriptor.
+ * @param facetData The media API response for guided search facets.
+ * @param metricsOptions The metrics options.
+ * @returns The guided search tokens to display.
+ */
+function createGuidedSearchTokens(objectGraph, requestDescriptor, facetData, metricsOptions) {
+ if (!objectGraph.host.isiOS || isNothing(facetData) || facetData.length === 0) {
+ return [];
+ }
+ pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "SearchRevisions",
+ }, "");
+ // Tokens from facet data
+ const tokens = [];
+ for (const data of facetData) {
+ const token = guidedSearch.createGuidedSearchToken(objectGraph, "rewrite", requestDescriptor, data, metricsOptions);
+ if (token) {
+ tokens.push(token);
+ nextPosition(metricsOptions.locationTracker);
+ }
+ }
+ popLocation(metricsOptions.locationTracker);
+ return tokens;
+}
+/**
+ * Apply client-side iAP filtering to set of results.
+ * @param objectGraph
+ * @param resultsToFilter Results to apply iAP filtering on.
+ */
+async function applyClientFilteringToIAPs(objectGraph, resultsToFilter) {
+ return await validation.context("applyClientFilteringToIAPs", async () => {
+ const iAPProductIDToParentBundleID = {};
+ for (const result of resultsToFilter) {
+ if (result.resultType === "inAppPurchase") {
+ const inAppPurchaseResult = result;
+ const inAppPurchaseLockup = inAppPurchaseResult.lockup;
+ if (inAppPurchaseLockup.parent &&
+ inAppPurchaseLockup.productIdentifier &&
+ inAppPurchaseLockup.parent.bundleId) {
+ iAPProductIDToParentBundleID[inAppPurchaseLockup.productIdentifier] =
+ inAppPurchaseLockup.parent.bundleId;
+ }
+ else {
+ validation.unexpectedNull("ignoredValue", "string", `required fields for ${inAppPurchaseLockup.adamId}`);
+ }
+ }
+ }
+ if (Object.keys(iAPProductIDToParentBundleID).length === 0) {
+ return await Promise.resolve(resultsToFilter);
+ }
+ return await objectGraph.clientOrdering.visibilityForIAPs(iAPProductIDToParentBundleID).then((visibilities) => {
+ const filteredResults = resultsToFilter.filter((result) => {
+ if (result.resultType !== "inAppPurchase") {
+ return true;
+ }
+ const inAppPurchaseResult = result;
+ const inAppPurchaseLockup = inAppPurchaseResult.lockup;
+ if (inAppPurchaseLockup.productIdentifier && visibilities[inAppPurchaseLockup.productIdentifier]) {
+ return true;
+ }
+ else {
+ return inAppPurchaseLockup.isVisibleByDefault;
+ }
+ });
+ return filteredResults;
+ });
+ });
+}
+/**
+ * Whether or not current platform supports displaying given `searchResult`.
+ */
+function platformSupportsResultType(objectGraph, searchResult) {
+ if (objectGraph.host.isTV) {
+ switch (searchResult.resultType) {
+ case "content":
+ case "editorial":
+ return true;
+ default:
+ return false;
+ }
+ }
+ if (!objectGraph.host.isiOS && !objectGraph.client.isWeb) {
+ switch (searchResult.resultType) {
+ case "appEvent":
+ return false;
+ default:
+ break;
+ }
+ }
+ return true;
+}
+// endregion
+//# sourceMappingURL=search-results-pipeline.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-spell-correction.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-spell-correction.js
new file mode 100644
index 0000000..60466dd
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-spell-correction.js
@@ -0,0 +1,98 @@
+/**
+ * Build methods for Search Spell Correction.
+ * Spell correction allows users to change an previous search term to higher or lower confidence searches.
+ */
+import * as models from "../../api/models";
+import { isDefinedNonNull } from "../../foundation/json-parsing/server-data";
+import { addEventsToSearchAction } from "../metrics/helpers/clicks";
+import { popLocation, pushBasicLocation } from "../metrics/helpers/location";
+/**
+ * Build a `SearchResultsMessage` for a search result with given `SearchTermContext`
+ * @param termContext Context for the state of search terms.
+ */
+export function spellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions) {
+ const showUndoSpellCorrection = isDefinedNonNull(termContext.correctedTerm);
+ const showSuggestion = isDefinedNonNull(termContext.suggestedTerm);
+ pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "link",
+ }, "spellCorrection");
+ if (showUndoSpellCorrection) {
+ return undoSpellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions);
+ }
+ if (showSuggestion) {
+ return correctionSuggestionMessageFromTermContext(objectGraph, termContext, metricsOptions);
+ }
+ popLocation(metricsOptions.locationTracker);
+ return null;
+}
+/**
+ * Build a message that allows user to undo an auto-applied spell correction on user-initiated term.
+ * This is the "Showing Results for ABC. Search Instead For XYZ" variation for high-confidence misspellings
+ * @param termContext Context for the state of search terms.
+ * @param locationTracker Location tracker for page it is appearing in.
+ */
+function undoSpellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions) {
+ // SearchAction for `term` again, disabling spell correction to `correctedTerm`.
+ const uncorrectedTerm = termContext.term;
+ const searchInsteadAction = searchActionForSpellCorrection(objectGraph, uncorrectedTerm, termContext.resultsTerm, "undoSpellCorrection");
+ addEventsToSearchAction(objectGraph, searchInsteadAction, "button", metricsOptions.locationTracker);
+ // "Showing Results for <i>correctedTerm</i>" with no links.
+ const correctedToTerm = `<i>${termContext.correctedTerm}</i>`;
+ const showingResultsForTemplate = objectGraph.loc.string("SEARCH_SHOWING_RESULTS_FOR_TEMPLATE");
+ const showingResultsForMessage = showingResultsForTemplate.replace("{searchTerm}", correctedToTerm);
+ const primaryText = new models.LinkableText(new models.StyledText(showingResultsForMessage, "text/x-apple-as3-nqml"), {});
+ // "Search Instead for term" with link.
+ const searchInsteadForTemplate = objectGraph.loc.string("SEARCH_SEARCH_INSTEAD_FOR_TEMPLATE");
+ const searchInsteadForMessage = searchInsteadForTemplate.replace("{searchTerm}", uncorrectedTerm);
+ const searchInsteadForLinks = {};
+ searchInsteadForLinks[`${searchInsteadForMessage}`] = searchInsteadAction;
+ const secondaryText = new models.LinkableText(new models.StyledText(searchInsteadForMessage), searchInsteadForLinks);
+ return new models.SearchResultsMessage(primaryText, secondaryText, searchInsteadAction);
+}
+/**
+ * Build a message that allows user to accept a suggestion to modify a user-initiated term.
+ * This is the "Did you mean ABC?" variation for low-confidence misspellings
+ * @param termContext Context for the state of search terms.
+ * @param locationTracker Location tracker for page it is appearing in.
+ */
+function correctionSuggestionMessageFromTermContext(objectGraph, termContext, metricsOptions) {
+ // Search action for `suggestedTerm`
+ const suggestedTerm = termContext.suggestedTerm;
+ const suggestedSearchAction = searchActionForSpellCorrection(objectGraph, suggestedTerm, termContext.resultsTerm, "applySpellCorrection");
+ addEventsToSearchAction(objectGraph, suggestedSearchAction, "button", metricsOptions.locationTracker);
+ // "Did you mean <i>suggestedTerm</i>?" where suggested term is linked.
+ const styledSuggestedTerm = `<i>${suggestedTerm}</i>`;
+ const didYouMeanTemplate = objectGraph.loc.string("SEARCH_DID_YOU_MEAN_TEMPLATE");
+ const didYouMeanMessage = didYouMeanTemplate.replace("{searchTerm}", styledSuggestedTerm);
+ // Link both the raw suggested term, and suggested term followed by ?
+ const didYouMeanLinks = {};
+ didYouMeanLinks[`${suggestedTerm}`] = suggestedSearchAction;
+ didYouMeanLinks[`${suggestedTerm}?`] = suggestedSearchAction;
+ const primaryText = new models.LinkableText(new models.StyledText(didYouMeanMessage, "text/x-apple-as3-nqml"), didYouMeanLinks);
+ return new models.SearchResultsMessage(primaryText, null, suggestedSearchAction);
+}
+// endregion
+// region Search Action Builders
+/**
+ * Build a SearchAction that is for:
+ * - Applying a suggested spell correction.
+ * - Undoing an automatically applied spell correction.
+ *
+ * Exported for testing
+ *
+ * @param suggestedOrUncorrectedTerm The suggestion or uncorrected term to search for.
+ * @param resultTerm The original result term.
+ * @param spellCorrectionActionType The type of spell correction search.
+ */
+export function searchActionForSpellCorrection(objectGraph, suggestedOrUncorrectedTerm, resultsTerm, spellCorrectionActionType) {
+ // SearchAction for `term` again, disabling spell correction to `correctedTerm`.
+ const suggestedSearchAction = new models.SearchAction(suggestedOrUncorrectedTerm, suggestedOrUncorrectedTerm, null, spellCorrectionActionType);
+ suggestedSearchAction.spellCheckEnabled = false; // Don't trigger corrections / suggestions again.
+ suggestedSearchAction.excludedTerms = [resultsTerm];
+ suggestedSearchAction.originatingTerm = resultsTerm;
+ return suggestedSearchAction;
+}
+// endregion
+//# sourceMappingURL=search-spell-correction.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search-token.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search-token.js
new file mode 100644
index 0000000..f49b680
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search-token.js
@@ -0,0 +1,57 @@
+import { isNullOrEmpty } from "../../foundation/json-parsing/server-data";
+import { currentPosition } from "../metrics/helpers/location";
+/**
+ * Opaque token to use for paginating a list of search results.
+ * This is used for both standard and segmented search results.
+ */
+export class SearchToken {
+}
+/// The number of results to load per page.
+const suggestedMaxResutsPerPage = 30;
+/**
+ * Create a search token for loading more search results.
+ * @param results Remaining set of data that is yet to be paginated
+ * @param requestMetadata The nature of request that was fired.
+ * @param responseMetadata The meta blob returned as part of initial search. This is preserved, as subsequent pagination requests don't hit search endpoints.
+ * @param metricsOptions Metrics options to preserve during pagination
+ */
+export function createSearchToken(objectGraph, results, requestMetadata, responseMetadata, metricsOptions) {
+ if (isNullOrEmpty(results)) {
+ return null;
+ }
+ return {
+ results: results,
+ maxPerPage: suggestedMaxResutsPerPage,
+ requestMetadata: requestMetadata,
+ metricsOptions: metricsOptions,
+ responseMetadata: responseMetadata !== null && responseMetadata !== void 0 ? responseMetadata : {},
+ contentOffsetWithinResultsShelf: currentPosition(metricsOptions.locationTracker),
+ };
+}
+/**
+ * Returns the next set of items to load.
+ * @param searchToken Search token used for paginating.
+ */
+export function getNextItemsToFetch(objectGraph, searchToken) {
+ if (!searchToken || !searchToken.results) {
+ return [];
+ }
+ return searchToken.results.slice(0, searchToken.maxPerPage);
+}
+/**
+ * Advance the search token by the items that were loaded, consistent with `getNextItemsToFetch`
+ * @param searchToken Search token to create new token with.
+ */
+export function advanceSearchTokenResults(objectGraph, searchToken) {
+ let nextPageResults = [];
+ if (searchToken && searchToken.results) {
+ nextPageResults = searchToken.results.slice(searchToken.maxPerPage, searchToken.results.length);
+ }
+ if (isNullOrEmpty(nextPageResults)) {
+ return null;
+ }
+ const nextToken = { ...searchToken };
+ nextToken.results = nextPageResults;
+ return nextToken;
+}
+//# sourceMappingURL=search-token.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/search.js b/node_modules/@jet-app/app-store/tmp/src/common/search/search.js
new file mode 100644
index 0000000..aec35e0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/search.js
@@ -0,0 +1,1024 @@
+//
+// search.ts
+// AppStoreKit
+//
+// Created by Kevin MacWhinnie on 8/15/16.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import { FetchTimingMetricsBuilder } from "@jet/environment/metrics/fetch-timing-metrics-builder";
+import { PageInvocationPoint } from "@jet/environment/types/metrics";
+import * as models from "../../api/models";
+import { SearchResultsLearnMoreNotice } from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as contentArtwork from "../content/artwork/artwork";
+import * as metricsBuilder from "../metrics/builder";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersMisc from "../metrics/helpers/misc";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import * as guidedSearch from "./guided-search/guided-search";
+import { addGuidedSearchParentImpressionMetrics, addSearchResultParentImpressionMetrics, } from "./guided-search/guided-search-metrics";
+import * as searchAdsODML from "./search-ads-odml";
+import * as searchCommon from "./search-common";
+import { createDefaultSelectedFacetOptions, createSearchFacets, createSearchPageFacets } from "./search-facets";
+import * as searchResultsFetching from "./search-results-fetching";
+import { createSearchResultsLearnMoreNoticeLinkableText } from "./search-results-learn-more-notice";
+import * as searchResultsPipeline from "./search-results-pipeline";
+import * as searchSpellCorrection from "./search-spell-correction";
+import * as searchToken from "./search-token";
+// MARK: - Search Hints
+/**
+ * Convert the response from the search hints endpoint into a model object.
+ * @param {String} prefixTerm The term the search hints are for (i.e. searchPrefix).
+ * @param {String} searchUrl The Url if we were to search for this term right away
+ * @param {*} response The API response.
+ * @return {SearchHintSet} The search hint set containing the search hint array
+ */
+export function searchHintsFromApiResponse(objectGraph, prefixTerm, hintsContainer) {
+ return validation.context("searchHintsFromApiResponse", () => {
+ var _a, _b, _c, _d;
+ const metricsOptions = {
+ targetType: "listItem",
+ pageInformation: metricsHelpersPage.pageInformationForSearchHintsPage(objectGraph, prefixTerm, hintsContainer.hintsRequestUrl, hintsContainer.dataSetId),
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ };
+ // Build user hint that matches what user typed. Appears in first position.
+ let userTypedHintAction = null;
+ if ((_a = hintsContainer.userTypedTerm) === null || _a === void 0 ? void 0 : _a.length) {
+ userTypedHintAction = new models.SearchAction(hintsContainer.userTypedTerm, hintsContainer.userTypedTerm, null, "userTypedHint");
+ userTypedHintAction.spellCheckEnabled = true;
+ userTypedHintAction.prefixTerm = prefixTerm;
+ metricsHelpersImpressions.addImpressionMetricsToHintsSearchAction(objectGraph, userTypedHintAction, metricsOptions);
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, userTypedHintAction, metricsOptions.targetType, metricsOptions.locationTracker, metricsOptions.pageInformation);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ }
+ // Build standard hints.
+ const searchHintActions = (_c = (_b = hintsContainer.rawHints) === null || _b === void 0 ? void 0 : _b.map((rawHint) => {
+ return searchHintAction(objectGraph, rawHint, prefixTerm, metricsOptions);
+ })) !== null && _c !== void 0 ? _c : [];
+ // Prepend user hint if any.
+ if (userTypedHintAction != null) {
+ searchHintActions.unshift(userTypedHintAction);
+ }
+ const hintSet = new models.SearchHintSet(searchHintActions, (_d = hintsContainer.ghostHintsTerm) !== null && _d !== void 0 ? _d : null);
+ /**
+ * Send `input` Search Events when search hints returns.
+ * For SSS, this is the granularity we agreed on, instead of sending it in native per keystroke.
+ * Unlike standard page metrics, we only:
+ * - Fire a 'input' search event
+ * - Setup pageFields for page fields generator.
+ */
+ const searchEvent = metricsBuilder.createMetricsSearchData(objectGraph, prefixTerm, "key", "input", hintsContainer.hintsRequestUrl, { ...metricsHelpersMisc.fieldsFromPageInformation(metricsOptions.pageInformation) });
+ hintSet.pageMetrics.pageFields = metricsHelpersMisc.fieldsFromPageInformation(metricsOptions.pageInformation);
+ hintSet.pageMetrics.addData(searchEvent, [PageInvocationPoint.pageEnter]);
+ hintSet.searchClearAction = createSearchCancelledOrClearedAction(objectGraph, "clear", metricsOptions.pageInformation, metricsOptions.locationTracker, prefixTerm);
+ hintSet.searchCancelAction = createSearchCancelledOrClearedAction(objectGraph, "cancel", metricsOptions.pageInformation, metricsOptions.locationTracker, prefixTerm);
+ return hintSet;
+ });
+}
+/**
+ * Creates a search hint action from the search hint data
+ * @param objectGraph The App Store object graph
+ * @param hintData The hint object data
+ * @param prefixTerm The hint prefix term
+ * @param metricsOptions The metrics options to use for click and impressions metrics for this hint
+ * @returns A search hint action for the hint data
+ */
+function searchHintAction(objectGraph, hintData, prefixTerm, metricsOptions) {
+ var _a, _b, _c, _d, _e;
+ const searchEntity = (_a = searchEntityFromHintData(hintData)) !== null && _a !== void 0 ? _a : undefined;
+ const searchAction = new models.SearchAction((_b = hintData.displayTerm) !== null && _b !== void 0 ? _b : "", (_c = hintData.searchTerm) !== null && _c !== void 0 ? _c : "", null, "hints", searchEntity, hintData.source);
+ searchAction.artwork = contentArtwork.createArtworkForSystemImage(objectGraph, (_d = models.searchEntitySystemImage(searchEntity)) !== null && _d !== void 0 ? _d : "magnifyingglass");
+ searchAction.spellCheckEnabled = true;
+ searchAction.prefixTerm = prefixTerm;
+ metricsHelpersImpressions.addImpressionMetricsToHintsSearchAction(objectGraph, searchAction, metricsOptions);
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, metricsOptions.targetType, metricsOptions.locationTracker, (_e = metricsOptions.pageInformation) !== null && _e !== void 0 ? _e : undefined);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ return searchAction;
+}
+/**
+ *
+ * @param objectGraph The App Store object graph
+ * @param entity The entity value string from the hint object
+ * @param context The context value string from the hint object
+ * @returns The correct entity for the hint based on legacy entity types
+ *
+ * Entities:
+ * "arcade" ==> "apps" with context "arcade"
+ "developer" ==> "developers"
+ "story" ==> "editorial-items"
+ "watch" ==> "apps" with context "watch"
+ */
+function searchEntityFromHintData(rawHint) {
+ const hintEntity = rawHint.entity;
+ switch (hintEntity) {
+ case "apps":
+ return rawHint.context;
+ case "developers":
+ return "developer";
+ case "editorial-items":
+ return "story";
+ default:
+ return null;
+ }
+}
+// MARK: - Trending Searches
+/**
+ * Convert the response from the trending searches endpoint into a model object.
+ * @param {*} response The API response.
+ * @return {TrendingSearch} A trending searches model object.
+ */
+export function trendingSearchesFromApiResponse(objectGraph, response) {
+ return validation.context("trendingSearchesFromApiResponse", () => {
+ const locationTracker = metricsHelpersLocation.newLocationTracker();
+ const searches = serverData.asArrayOrEmpty(response, "trendingSearches").map((rawSearch) => {
+ const term = serverData.asString(rawSearch, "label");
+ const searchAction = new models.SearchAction(term, term, serverData.asString(rawSearch, "url"), "trending");
+ metricsHelpersClicks.addEventsToSearchAction(objectGraph, searchAction, "button", locationTracker);
+ metricsHelpersLocation.nextPosition(locationTracker);
+ return searchAction;
+ });
+ const title = searches.length > 0 ? serverData.asString(response, "header.label") : null;
+ const trendingSearches = new models.TrendingSearches(title, searches);
+ switch (objectGraph.client.deviceType) {
+ case "pad":
+ trendingSearches.maxNumberOfSearches = 10;
+ break;
+ case "phone":
+ trendingSearches.maxNumberOfSearches = 7;
+ break;
+ default:
+ break;
+ }
+ return trendingSearches;
+ });
+}
+// MARK: - Search Results
+/**
+ * Gets the shelf ID for search results, on a specfic segment if applicable
+ * @param segmentTitle The title of the segment this shelf is on, if using segmented search
+ * @returns The id for the search results shelf
+ * @note This needs to include the segment title for segment search results since the contents on
+ * each segment would otherwise have the same shelf id and therefore would lead to the content never changing
+ */
+function getSearchResultsShelfId(segmentTitle) {
+ if (isSome(segmentTitle) && segmentTitle.length !== 0) {
+ return `SearchResults.${segmentTitle}.shelfId`;
+ }
+ else {
+ return "SearchResults.shelfId";
+ }
+}
+/**
+ * Gets metrics pageid to use for the search results page.
+ * @param segmentType The segment this page is displaying if any
+ * @returns The id for the search results page
+ */
+function getSearchResultsPageId(segmentType) {
+ switch (segmentType) {
+ case models.SegmentedSearchResultsPageSegmentType.iOS:
+ return "ios";
+ case models.SegmentedSearchResultsPageSegmentType.visionOS:
+ return "visionos";
+ default:
+ return "SearchTopResults";
+ }
+}
+/**
+ * Creates a new empty `SearchResults` object.
+ * @return {SearchResults} An empty search results model object.
+ */
+export function emptyResults(objectGraph, requestFacets) {
+ const searchResults = new models.SearchResults();
+ if (serverData.isDefinedNonNull(requestFacets)) {
+ searchResults.facets = createSearchFacets(objectGraph, requestFacets);
+ searchResults.pageFacets = createSearchPageFacets(objectGraph);
+ searchResults.selectedFacetOptions = createDefaultSelectedFacetOptions(objectGraph);
+ }
+ searchResults.results = [];
+ return searchResults;
+}
+/**
+ * Creates a new empty `SearchResultsPage` object.
+ * @return {SearchResults} An empty search results model object.
+ */
+export function emptyResultsPage(objectGraph, requestFacets) {
+ const searchResultsPage = new models.SearchResultsPage([]);
+ if (serverData.isDefinedNonNull(requestFacets)) {
+ searchResultsPage.facets = createSearchFacets(objectGraph, requestFacets);
+ searchResultsPage.pageFacets = createSearchPageFacets(objectGraph);
+ searchResultsPage.selectedFacetOptions = createDefaultSelectedFacetOptions(objectGraph);
+ }
+ return searchResultsPage;
+}
+/**
+ * A container like class to manage a search results page segment and surrounding context
+ */
+class SegmentedSearchResultsPageSegmentContext {
+}
+/**
+ * Builds `BaseSearchPage` from data.
+ */
+async function baseSearchPageFromResponse(objectGraph, combinedSearchData) {
+ return await validation.context("searchResultsFromResponse", async () => {
+ var _a;
+ const fetchTimingMetricsBuilder = (_a = objectGraph.fetchTimingMetricsBuilder) !== null && _a !== void 0 ? _a : new FetchTimingMetricsBuilder();
+ const page = await fetchTimingMetricsBuilder.measureModelConstructionAsync(async () => {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
+ const response = combinedSearchData.catalogResponse;
+ const requestMetadata = combinedSearchData.requestMetadata;
+ const searchRequestUrl = requestMetadata.searchRequestUrl;
+ const sponsoredSearchRequestData = combinedSearchData.sponsoredSearchRequestData;
+ const guidedSearchData = response.results.guidedSearch;
+ // Term Context
+ const termContext = searchCommon.createTermContextForSpellcheckedSequentialResponse(objectGraph, requestMetadata.requestDescriptor, response);
+ // Metrics
+ const wasOdmlSuccessful = searchAdsODML.wasODMLSuccessful(objectGraph, combinedSearchData.sponsoredSearchAdvertData);
+ const metricsOptions = {
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ pageInformation: metricsHelpersPage.pageInformationForSearchPage(objectGraph, requestMetadata.requestDescriptor, response, termContext, searchRequestUrl, getSearchResultsPageId(), sponsoredSearchRequestData, wasOdmlSuccessful, guidedSearchData),
+ createUniqueImpressionId: true,
+ };
+ // Root container
+ const isShelfBasedSearch = objectGraph.featureFlags.isEnabled("shelves_2_0_search") ||
+ objectGraph.client.isiOS ||
+ objectGraph.client.isTV ||
+ objectGraph.client.isVision ||
+ objectGraph.client.isWeb;
+ const baseSearchPage = isShelfBasedSearch
+ ? new models.SearchResultsPage()
+ : new models.SearchResults();
+ // Guided Search Experimentation (pinned vs mid-scroll)
+ const guidedSearchPosition = searchResultsPipeline.guidedSearchPositionFromSearchResponseMeta(response.meta, objectGraph);
+ if (isNothing(guidedSearchPosition)) {
+ /**
+ * Guided Search
+ * - note: This is important to occur before `createSearchResults` since its impression index should be lower than the container for search results.
+ */
+ addModelsForGuidedSearch(objectGraph, baseSearchPage, requestMetadata, guidedSearchData === null || guidedSearchData === void 0 ? void 0 : guidedSearchData.facets, metricsOptions);
+ }
+ const shelfMetricsOptions = {
+ id: "search-results",
+ kind: null,
+ softwareType: null,
+ targetType: "SearchResults",
+ title: "Search Results",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ idType: "relationship",
+ };
+ let resultsShelf;
+ if (isShelfBasedSearch) {
+ resultsShelf = new models.Shelf("searchResult");
+ resultsShelf.id = getSearchResultsShelfId();
+ resultsShelf.isHorizontal = false;
+ if (objectGraph.client.isWeb) {
+ resultsShelf.title = objectGraph.loc
+ .string("Search.ResultsTitle")
+ .replace("@@search_term@@", termContext.term);
+ }
+ // We need to create and apply impressions fields to the shelf before creating the search results,
+ // so that the shelf is sitting in the right location relative to its children from an impressions perspective.
+ metricsHelpersImpressions.addImpressionFields(objectGraph, resultsShelf, shelfMetricsOptions);
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, "Search Results");
+ }
+ let advertData = dataForAdvertsFromCombinedData(objectGraph, combinedSearchData);
+ const appliedPolicy = (_a = combinedSearchData.sponsoredSearchAdvertData) === null || _a === void 0 ? void 0 : _a.appliedPolicy;
+ if (isSome(appliedPolicy)) {
+ // If there was an applied policy, suppress the ad.
+ advertData = [];
+ if (appliedPolicy === "ageRestricted") {
+ (_b = metricsOptions.pageInformation) === null || _b === void 0 ? void 0 : _b.iAdInfo.setMissedOpportunity(objectGraph, "ODP_NOAD", "searchResults");
+ }
+ }
+ const unsafeSearch = (_f = (_e = (_d = (_c = response.meta) === null || _c === void 0 ? void 0 : _c.results) === null || _d === void 0 ? void 0 : _d.search) === null || _e === void 0 ? void 0 : _e.searchSafety) !== null && _f !== void 0 ? _f : false;
+ const missedOpportunityReason = ((_k = (_j = (_h = (_g = response.meta) === null || _g === void 0 ? void 0 : _g.results) === null || _h === void 0 ? void 0 : _h.search) === null || _j === void 0 ? void 0 : _j.reason) === null || _k === void 0 ? void 0 : _k.kind) === "no-results" ? "NLS_NORESULTS" : "NLS_NOAD";
+ if (unsafeSearch) {
+ // If the search was deemed unsafe, suppress Ad and record the missed opportunity.
+ advertData = [];
+ (_l = metricsOptions.pageInformation) === null || _l === void 0 ? void 0 : _l.iAdInfo.setMissedOpportunity(objectGraph, missedOpportunityReason, "searchResults");
+ }
+ /**
+ * Advert + Search Results
+ */
+ const builderResults = await searchResultsPipeline.createSearchResults(objectGraph, requestMetadata, response.meta, metricsOptions, dataForSearchResultsFromCombinedData(objectGraph, combinedSearchData), advertData, guidedSearchData === null || guidedSearchData === void 0 ? void 0 : guidedSearchData.facets, installedStatesForAdvertsData(combinedSearchData), appStatesForAdvertsData(combinedSearchData));
+ if (unsafeSearch && builderResults.builtSearchResults.length !== 0) {
+ const searchAdOpportunity = builderResults.builtSearchResults[0].lockup
+ .searchAdOpportunity;
+ searchAdOpportunity === null || searchAdOpportunity === void 0 ? void 0 : searchAdOpportunity.setMissedOpportunityReason(missedOpportunityReason);
+ searchAdOpportunity === null || searchAdOpportunity === void 0 ? void 0 : searchAdOpportunity.setTemplateType("APPLOCKUP");
+ }
+ // Add result shelves to page.
+ if (isShelfBasedSearch && resultsShelf) {
+ const searchResultsPage = baseSearchPage;
+ resultsShelf.items = builderResults.builtSearchResults;
+ searchResultsPage.resultsParentImpressionMetrics = resultsShelf.impressionMetrics;
+ searchResultsPage.shelves.push(resultsShelf);
+ // Display the context message for the results (or lack thereof).
+ const contextCard = createSearchResultsContextCard(response.results.queryContext, objectGraph);
+ const resultsReason = (_p = (_o = (_m = response.meta) === null || _m === void 0 ? void 0 : _m.results) === null || _o === void 0 ? void 0 : _o.search) === null || _p === void 0 ? void 0 : _p.reason;
+ if ((resultsReason === null || resultsReason === void 0 ? void 0 : resultsReason.kind) === "no-results") {
+ searchResultsPage.unavailableReason = {
+ title: objectGraph.loc.stringWithFallback("Search.Results.Empty.Title", "No results"),
+ message: resultsReason.text,
+ action: actionFromSearchResultsLinks(resultsReason.links),
+ contextCard: contextCard,
+ };
+ }
+ else if (contextCard) {
+ const paragraphShelf = new models.Shelf("searchResultsContextCard");
+ paragraphShelf.items = [contextCard];
+ const contextCardPosition = (_t = (_s = (_r = (_q = response.meta) === null || _q === void 0 ? void 0 : _q.displayStyle) === null || _r === void 0 ? void 0 : _r.queryContext) === null || _s === void 0 ? void 0 : _s.position) !== null && _t !== void 0 ? _t : 0;
+ if (contextCardPosition > 0) {
+ const postCardShelfContents = resultsShelf.items.splice(contextCardPosition);
+ const postCardResulsShelf = {
+ ...resultsShelf,
+ id: "searchResults2",
+ items: postCardShelfContents,
+ isValid: resultsShelf.isValid,
+ };
+ searchResultsPage.shelves.push(paragraphShelf);
+ searchResultsPage.shelves.push(postCardResulsShelf);
+ }
+ else {
+ searchResultsPage.shelves.unshift(paragraphShelf);
+ }
+ }
+ // Remove unsafe searches from recents if no results were provided.
+ if (unsafeSearch && builderResults.builtSearchResults.length === 0) {
+ (_v = (_u = objectGraph.onDeviceSearchHistoryManager).removeRecentSearchTerm) === null || _v === void 0 ? void 0 : _v.call(_u, termContext.term);
+ }
+ }
+ else {
+ const searchResults = baseSearchPage;
+ searchResults.results = builderResults.builtSearchResults;
+ addSearchResultParentImpressionMetrics(objectGraph, searchResults, metricsOptions);
+ }
+ /**
+ * Next Page
+ */
+ if (builderResults.deferredSearchResults.length > 0) {
+ baseSearchPage.nextPage = searchToken.createSearchToken(objectGraph, builderResults.deferredSearchResults, requestMetadata, response.meta, metricsOptions);
+ }
+ if (isShelfBasedSearch) {
+ metricsHelpersLocation.popLocation(shelfMetricsOptions.locationTracker);
+ }
+ /**
+ * Spell Correction Message
+ */
+ baseSearchPage.message = searchSpellCorrection.spellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions);
+ /**
+ * Search Entity
+ */
+ const searchEntity = requestMetadata.requestDescriptor.searchEntity;
+ const searchEntitySpecified = !serverData.isNullOrEmpty(searchEntity);
+ if (!searchEntitySpecified) {
+ baseSearchPage.facets = createSearchFacets(objectGraph, requestMetadata.requestDescriptor.facets, combinedSearchData.categoriesFilterData);
+ baseSearchPage.pageFacets = createSearchPageFacets(objectGraph, combinedSearchData.categoriesFilterData);
+ baseSearchPage.selectedFacetOptions = serverData.isDefinedNonNullNonEmpty(combinedSearchData.requestMetadata.requestDescriptor.selectedFacetOptions)
+ ? combinedSearchData.requestMetadata.requestDescriptor.selectedFacetOptions
+ : createDefaultSelectedFacetOptions(objectGraph);
+ }
+ // Attach search term context
+ baseSearchPage.searchTermContext = termContext;
+ // Enable autoplay search results on all clients except tv.
+ baseSearchPage.isAutoPlayEnabled = objectGraph.client.deviceType !== "tv";
+ baseSearchPage.isCondensedSearchLockupsEnabled = objectGraph.client.isPhone;
+ // Search Transparency
+ baseSearchPage.transparencyLink = createSearchResultsLearnMoreNoticeLinkableText(objectGraph, metricsOptions);
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, baseSearchPage, metricsOptions.pageInformation);
+ baseSearchPage.searchClearAction = createSearchCancelledOrClearedAction(objectGraph, "clear", metricsOptions.pageInformation, metricsOptions.locationTracker, termContext.term);
+ baseSearchPage.searchCancelAction = createSearchCancelledOrClearedAction(objectGraph, "cancel", metricsOptions.pageInformation, metricsOptions.locationTracker, termContext.term);
+ return baseSearchPage;
+ });
+ return page;
+ });
+}
+// Creates context card model for search results from media API data.
+export function createSearchResultsContextCard(queryContext, objectGraph) {
+ return validation.context("createSearchResultsContextCard", () => {
+ var _a, _b;
+ if (isNothing(queryContext) || !["iOS", "macOS"].includes(objectGraph.host.platform)) {
+ return undefined; // no server data or platform unsupported
+ }
+ const linkActions = (_a = queryContext.links) === null || _a === void 0 ? void 0 : _a.map(actionFromSearchResultsLink);
+ const linkedSubstrings = linkActions === null || linkActions === void 0 ? void 0 : linkActions.reduce((map, linkAction) => {
+ var _a;
+ if ((_a = linkAction.title) === null || _a === void 0 ? void 0 : _a.length) {
+ if (objectGraph.host.isMac) {
+ linkAction.title += "\u00a0\u2197";
+ }
+ map[linkAction.title] = linkAction;
+ }
+ return map;
+ }, {});
+ let rawText = queryContext.text;
+ if ((linkActions === null || linkActions === void 0 ? void 0 : linkActions.length) === 1 && ((_b = linkActions[0].title) === null || _b === void 0 ? void 0 : _b.length)) {
+ // Add single link to end of text, separated by a space.
+ rawText += " " + linkActions[0].title;
+ }
+ else if (linkActions && linkActions.length > 1) {
+ // Add multiple lines below end of text, separated by a new line.
+ if (rawText.length > 0) {
+ rawText += "\n";
+ }
+ rawText += linkActions
+ .map((action) => action.title)
+ .filter((title) => title === null || title === void 0 ? void 0 : title.length)
+ .join("\n");
+ }
+ const styledText = new models.StyledText(rawText);
+ const linkableText = new models.LinkableText(styledText, linkedSubstrings);
+ const contextCard = new models.SearchResultsContextCard(linkableText);
+ return contextCard;
+ });
+}
+/// Creates and returns the first url action from the provided link data included in the search results response.
+function actionFromSearchResultsLinks(linksData) {
+ return validation.context("actionFromSearchResultsLinks", () => {
+ const bestLinkData = linksData === null || linksData === void 0 ? void 0 : linksData.find((linkData) => linkData.url.length > 0);
+ return bestLinkData ? actionFromSearchResultsLink(bestLinkData) : undefined;
+ });
+}
+/// Creates and returns an action from the provided link data included in the search results response.
+function actionFromSearchResultsLink(linkData) {
+ return validation.context("actionFromSearchResultsLink", () => {
+ var _a;
+ const linkAction = new models.ExternalUrlAction(linkData.url, false);
+ linkAction.title = (_a = linkData.label) === null || _a === void 0 ? void 0 : _a.replace(" ", "\u00a0"); // Use non-breaking spaces for link labels.
+ linkAction.artwork = new models.Artwork("systemimage://arrow.up.forward", 0, 0, []);
+ return linkAction;
+ });
+}
+function installedStatesForAdvertsData(combinedSearchData) {
+ var _a, _b;
+ return (_b = (_a = combinedSearchData.sponsoredSearchAdvertData) === null || _a === void 0 ? void 0 : _a.installedStates) !== null && _b !== void 0 ? _b : {};
+}
+function appStatesForAdvertsData(combinedSearchData) {
+ var _a, _b;
+ return (_b = (_a = combinedSearchData.sponsoredSearchAdvertData) === null || _a === void 0 ? void 0 : _a.appStates) !== null && _b !== void 0 ? _b : {};
+}
+export async function searchResultsFromResponse(objectGraph, combinedSearchData) {
+ return await baseSearchPageFromResponse(objectGraph, combinedSearchData);
+}
+export async function searchResultsPageFromResponse(objectGraph, combinedSearchData) {
+ return await baseSearchPageFromResponse(objectGraph, combinedSearchData);
+}
+/**
+ * Creates a new empty `SegmentedSearchResultsPage` object.
+ * @return An empty segmented search results model object.
+ */
+export function emptySegmentedResultsPage(objectGraph) {
+ return new models.SegmentedSearchResultsPage();
+}
+/**
+ * Creates the segmented search results page form the response
+ * @param objectGraph The app store object graph
+ * @param combinedSearchData The combined data for the segmented search results response
+ * @returns A promise of the segmented search results page
+ */
+export async function segmentedSearchResultsPageFromResponse(objectGraph, combinedSearchData) {
+ return await validation.context("segmentedSearchResultsPageFromResponse", async () => {
+ const response = combinedSearchData.catalogResponse;
+ const requestMetadata = combinedSearchData.requestMetadata;
+ if (isSome(response.results.search.groups)) {
+ const fetchPages = response.results.search.groups.map(async (group) => {
+ const segmentResponse = await baseSegmentFromGroupResponse(objectGraph, combinedSearchData, response, group, requestMetadata);
+ return segmentResponse;
+ });
+ return await Promise.all(fetchPages).then((pageContexts) => {
+ const page = new models.SegmentedSearchResultsPage();
+ if (serverData.isNullOrEmpty(pageContexts)) {
+ return page;
+ }
+ const segments = pageContexts.map((context) => {
+ return context.segment;
+ });
+ page.segments = segments;
+ const initialSegmentId = response.results.search.autoSelectedGroupId || segments[0].groupId;
+ page.selectedSegmentId = initialSegmentId;
+ addSegmentChangeActionsToPages(objectGraph, pageContexts, initialSegmentId);
+ return page;
+ });
+ }
+ else {
+ return new models.SegmentedSearchResultsPage();
+ }
+ });
+}
+/**
+ * Adds all segment change actions to each search result page segment
+ * @param objectGraph The App Store object graph
+ * @param contexts The segment contexts for the page
+ * @param initialSegmentId The initial segment Id.
+ */
+function addSegmentChangeActionsToPages(objectGraph, contexts, initialSegmentId) {
+ const firstNonEmptySegmentContext = contexts.find((context) => {
+ const hasNonEmptyPage = context.segment.page.shelves.some((shelf) => {
+ const isNotEmpty = shelf.items.length > 0;
+ return isNotEmpty;
+ });
+ return hasNonEmptyPage;
+ });
+ const firstNonEmptySegmentId = firstNonEmptySegmentContext === null || firstNonEmptySegmentContext === void 0 ? void 0 : firstNonEmptySegmentContext.segment.groupId;
+ const segments = contexts.map((context) => {
+ return context.segment;
+ });
+ const nativeSegmentGroupId = segmentGroupIdForPlatform(objectGraph, segments);
+ // Check if the native segment has content.
+ const nativeSegmentIsNonEmpty = segments.some((segment) => {
+ if (segment.groupId !== nativeSegmentGroupId) {
+ return false;
+ }
+ return segment.page.shelves.some((shelf) => {
+ return shelf.items.length > 0;
+ });
+ });
+ contexts.forEach((context) => {
+ context.segment.emptySegmentChangeAction = segmentChangeAction(objectGraph, SegmentChangeReason.EmptyResults, context.segment.page.id, context.metricsOptions, firstNonEmptySegmentId, context.segment.groupId);
+ // Only add the non-native segment change action if the native segment has content, and
+ // the initial segment wasn't the native segment.
+ if (nativeSegmentIsNonEmpty && initialSegmentId !== nativeSegmentGroupId) {
+ context.segment.nonNativeSegmentChangeAction = segmentChangeAction(objectGraph, SegmentChangeReason.NonNative, context.segment.page.id, context.metricsOptions, nativeSegmentGroupId, context.segment.groupId);
+ }
+ context.segment.segmentPickerSegmentChangeAction = segmentChangeAction(objectGraph, SegmentChangeReason.Picker, context.segment.page.id, context.metricsOptions, context.segment.groupId);
+ });
+}
+/**
+ *
+ * @param objectGraph The app store object graph
+ * @param combinedSearchData The combined search results which contains the response for segmented results
+ * @param groupResponse The response for the segmented results
+ * @param searchData The data for a single search results segment
+ * @param requestMetadata The metadata from the search request
+ * @returns A promise of a segmented search results page segment
+ */
+async function baseSegmentFromGroupResponse(objectGraph, combinedSearchData, groupResponse, searchData, requestMetadata) {
+ return await validation.context("baseSearchPageFromGroupResponse", async () => {
+ const searchRequestUrl = requestMetadata.searchRequestUrl;
+ const segmentType = segmentTypeFromGroupId(searchData.groupId);
+ // Term Context
+ const termContext = searchCommon.createTermContextForSpellcheckedGroupedResponse(objectGraph, requestMetadata.requestDescriptor, groupResponse);
+ // Metrics
+ const metricsOptions = {
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ pageInformation: metricsHelpersPage.pageInformationForSearchPage(objectGraph, requestMetadata.requestDescriptor, groupResponse, termContext, searchRequestUrl, getSearchResultsPageId(segmentType), null, false, null),
+ createUniqueImpressionId: true,
+ };
+ const shelfMetricsOptions = {
+ id: "search-results",
+ kind: null,
+ softwareType: null,
+ targetType: "SearchResults",
+ title: "Search Results",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ idType: "relationship",
+ };
+ // Root container
+ const searchResultsPage = new models.SearchResultsPage();
+ const searchSegment = new models.SegmentedSearchResultsPageSegment();
+ const resultsShelf = new models.Shelf("searchResult");
+ resultsShelf.isHorizontal = false;
+ // We need to create and apply impressions fields to the shelf before creating the search results,
+ // so that the shelf is sitting in the right location relative to its children from an impressions perspective.
+ metricsHelpersImpressions.addImpressionFields(objectGraph, resultsShelf, shelfMetricsOptions);
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, "Search Results");
+ const resultsData = mediaDataStructure.dataCollectionFromDataContainer(searchData);
+ const builderResults = await searchResultsPipeline.createSearchResults(objectGraph, requestMetadata, groupResponse.meta, metricsOptions, resultsData);
+ const learnMoreNoticeLinkableText = createSearchResultsLearnMoreNoticeLinkableText(objectGraph, metricsOptions);
+ const shelves = searchResultsShelvesFromBuilderResults(objectGraph, learnMoreNoticeLinkableText, builderResults, resultsShelf);
+ searchResultsPage.shelves.push(...shelves);
+ searchResultsPage.resultsParentImpressionMetrics = resultsShelf.impressionMetrics;
+ if (builderResults.deferredSearchResults.length > 0) {
+ searchResultsPage.nextPage = searchToken.createSearchToken(objectGraph, builderResults.deferredSearchResults, requestMetadata, groupResponse.meta, metricsOptions);
+ }
+ metricsHelpersLocation.popLocation(shelfMetricsOptions.locationTracker);
+ /**
+ * Spell Correction Message
+ */
+ searchResultsPage.message = searchSpellCorrection.spellCorrectionMessageFromTermContext(objectGraph, termContext, metricsOptions);
+ // Enable autoplay search results on all clients except tv.
+ searchResultsPage.isAutoPlayEnabled = objectGraph.client.deviceType !== "tv";
+ searchResultsPage.isCondensedSearchLockupsEnabled = objectGraph.client.isPhone;
+ // Search Transparency
+ searchResultsPage.transparencyLink = learnMoreNoticeLinkableText;
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, searchResultsPage, metricsOptions.pageInformation);
+ searchSegment.groupId = searchData.groupId;
+ resultsShelf.id = getSearchResultsShelfId(searchSegment.groupId);
+ searchSegment.page = searchResultsPage;
+ searchSegment.title = segmentTitleForSegmentType(objectGraph, false, segmentType);
+ return {
+ segment: searchSegment,
+ metricsOptions: metricsOptions,
+ };
+ });
+}
+/**
+ * Get an array of shelves from the search builder results.
+ * This is used to decide where to insert the search results learn more notice across
+ * initial and paginated page builds.
+ *
+ * This is currently only used on visionOS search paths, but has the appropriate checks to ensure
+ * it doesn't run on unsupported platforms, so could be used in all search code paths.
+ * @param objectGraph The Object Graph.
+ * @param learnMoreNoticeLinkableText The linkable text to display as part of the learn more notice.
+ * @param builderResults The results from `createSearchResults`.
+ * @param resultsShelf The initial shelf created to hold the results. This is passed in
+ * because in many cases a reference to this is required later when building out the page.
+ * @param startingContentOffsetWithinResults The starting content offset before the newly built
+ * content is added. For an initial page build, this will be zero, and for paginated pages this
+ * is stored on the token.
+ * @returns An array of shelves for a search results page, with the learn more notice inserted
+ * if required.
+ */
+function searchResultsShelvesFromBuilderResults(objectGraph, learnMoreNoticeLinkableText, builderResults, resultsShelf, startingContentOffsetWithinResults = 0) {
+ let learnMoreNoticeIndex = searchResultsLearnMoreNoticeIndex(objectGraph);
+ const builtResultsCount = builderResults.builtSearchResults.length;
+ const hasDeferredResults = builderResults.deferredSearchResults.length > 0;
+ // The total built results for the page so far. This is the initial page, plus any paginated content.
+ const totalBuiltResults = startingContentOffsetWithinResults + builtResultsCount;
+ // There are a number of conditions we don't want to insert the learn more notice:
+ // 1. No index at which to insert it - this generally means the platform doesn't support this method of insertion.
+ // 2. No linkable text object - the bag key wasn't present.
+ // 3. The starting offset is greater than or equal to the insertion index - we can assume we've already shown the
+ // notice in a previous page build.
+ // 4. We have deferred results and the total built results count is less than the threshold - we'll insert the
+ // notice in a subsequent fetch.
+ // 5. We have no built results and no deferred results, ie. the page will be empty.
+ // If we fail any of these conditions, we will just attach all the items into the single shelf and return that.
+ if (learnMoreNoticeIndex === undefined ||
+ learnMoreNoticeLinkableText === undefined ||
+ startingContentOffsetWithinResults >= learnMoreNoticeIndex ||
+ (hasDeferredResults && totalBuiltResults < learnMoreNoticeIndex) ||
+ (totalBuiltResults === 0 && !hasDeferredResults)) {
+ resultsShelf.items = builderResults.builtSearchResults;
+ return [resultsShelf];
+ }
+ // Adjust the index by the starting offset, to ensure we take any items built in the initial fetch into account.
+ learnMoreNoticeIndex = learnMoreNoticeIndex - startingContentOffsetWithinResults;
+ // We have satisfied the insertion conditions for the learn more notice - we need to split the shelf in two to incorporate
+ // the learn more notice as its own shelf in between. This is due to layout restrictions on visionOS where we cannot
+ // accommodate a full width item in a grid of two items per row.
+ resultsShelf.items = builderResults.builtSearchResults.slice(0, learnMoreNoticeIndex);
+ const learnMoreNoticeShelf = new models.Shelf("searchResultsLearnMoreNotice");
+ learnMoreNoticeShelf.isHorizontal = false;
+ const searchResult = new SearchResultsLearnMoreNotice(learnMoreNoticeLinkableText);
+ learnMoreNoticeShelf.items = [searchResult];
+ const shelves = [resultsShelf, learnMoreNoticeShelf];
+ const secondResultsShelfItems = builderResults.builtSearchResults.slice(learnMoreNoticeIndex);
+ if (secondResultsShelfItems.length > 0) {
+ const secondResultsShelf = new models.Shelf("searchResult");
+ // Give the second results shelf the same impression metrics. This will result in each shelf contributing to the same impression metrics,
+ // the only difference is we will see two "viewedInfo" entries for each distinct shelf within the top level impressions item.
+ secondResultsShelf.impressionMetrics = resultsShelf.impressionMetrics;
+ secondResultsShelf.isHorizontal = false;
+ secondResultsShelf.items = secondResultsShelfItems;
+ shelves.push(secondResultsShelf);
+ }
+ return shelves;
+}
+/**
+ * Get the index for where the Search Results Learn More Notice should be inserted.
+ * Returns undefined if either:
+ * - the editorial ID is not available in the bag, or
+ * - the platform does not support this style of presentation.
+ * @param objectGraph The Object Graph
+ */
+function searchResultsLearnMoreNoticeIndex(objectGraph) {
+ const editorialItemId = objectGraph.bag.searchResultsLearnMoreEditorialId;
+ if (isNothing(editorialItemId) || (editorialItemId === null || editorialItemId === void 0 ? void 0 : editorialItemId.length) === 0) {
+ return undefined;
+ }
+ if (objectGraph.client.isVision) {
+ return 6;
+ }
+ return undefined;
+}
+export async function paginatedSearchResultsWithToken(objectGraph, token) {
+ return await validation.context("paginatedSearchResultsWithToken", async () => {
+ const nextItemsToFetch = searchToken.getNextItemsToFetch(objectGraph, token);
+ const advancedToken = searchToken.advanceSearchTokenResults(objectGraph, token);
+ if (nextItemsToFetch.length === 0) {
+ return await Promise.resolve(emptyResults(objectGraph));
+ }
+ return await searchResultsFetching
+ .fetchSearchResultItems(objectGraph, nextItemsToFetch)
+ .then(async (dataContainer) => {
+ const resultsDatum = mediaDataStructure.dataCollectionFromDataContainer(dataContainer);
+ return await searchResultsPipeline
+ .createSearchResults(objectGraph, token.requestMetadata, token.responseMetadata, token.metricsOptions, resultsDatum)
+ .then((builderResults) => {
+ const searchResults = new models.SearchResults();
+ searchResults.results = builderResults.builtSearchResults;
+ searchResults.nextPage = advancedToken;
+ return searchResults;
+ });
+ });
+ });
+}
+export async function paginatedSearchResultsPageWithToken(objectGraph, token) {
+ return await validation.context("paginatedSearchResultsPageWithToken", async () => {
+ const nextItemsToFetch = searchToken.getNextItemsToFetch(objectGraph, token);
+ const advancedToken = searchToken.advanceSearchTokenResults(objectGraph, token);
+ if (nextItemsToFetch.length === 0) {
+ return await Promise.resolve(emptyResultsPage(objectGraph));
+ }
+ return await searchResultsFetching
+ .fetchSearchResultItems(objectGraph, nextItemsToFetch)
+ .then(async (dataContainer) => {
+ const resultsDatum = mediaDataStructure.dataCollectionFromDataContainer(dataContainer);
+ const shelfMetricsOptions = {
+ id: "search-results",
+ kind: null,
+ softwareType: null,
+ targetType: "SearchResults",
+ title: "Search Results",
+ pageInformation: token.metricsOptions.pageInformation,
+ locationTracker: token.metricsOptions.locationTracker,
+ idType: "relationship",
+ };
+ // Set up the "new" shelf
+ const resultsShelf = new models.Shelf("searchResult");
+ resultsShelf.id = getSearchResultsShelfId();
+ resultsShelf.isHorizontal = false;
+ // Shelf Impressions: Add impression fields.
+ // This shelf, and the associated metrics data isn't really used - it's basically just a vehicle for the new items to be
+ // merged into the old page/shelf. Even so, we create and apply impressions metrics correctly before setting the content
+ // location and current position so it looks correct.
+ metricsHelpersImpressions.addImpressionFields(objectGraph, resultsShelf, shelfMetricsOptions);
+ // Set the content location to the "search-results" shelf.
+ metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, "Search Results");
+ // Update position to content offset within the same search shelf prior to building the new results.
+ metricsHelpersLocation.setCurrentPosition(shelfMetricsOptions.locationTracker, token.contentOffsetWithinResultsShelf);
+ return await searchResultsPipeline
+ .createSearchResults(objectGraph, token.requestMetadata, token.responseMetadata, token.metricsOptions, resultsDatum)
+ .then((builderResults) => {
+ const learnMoreNoticeLinkableText = createSearchResultsLearnMoreNoticeLinkableText(objectGraph, shelfMetricsOptions);
+ const shelves = searchResultsShelvesFromBuilderResults(objectGraph, learnMoreNoticeLinkableText, builderResults, resultsShelf, token.contentOffsetWithinResultsShelf);
+ const searchResultsPage = new models.SearchResultsPage(shelves);
+ // Ensure we increment the offset for any future paginated results.
+ if (serverData.isDefinedNonNull(advancedToken)) {
+ advancedToken.contentOffsetWithinResultsShelf = metricsHelpersLocation.currentPosition(shelfMetricsOptions.locationTracker);
+ searchResultsPage.nextPage = advancedToken;
+ }
+ searchResultsPage.isCondensedSearchLockupsEnabled = objectGraph.client.isPhone;
+ searchResultsPage.resultsParentImpressionMetrics = resultsShelf.impressionMetrics;
+ metricsHelpersLocation.popLocation(token.metricsOptions.locationTracker);
+ searchResultsPage.searchClearAction = createSearchCancelledOrClearedAction(objectGraph, "clear", token.metricsOptions.pageInformation, token.metricsOptions.locationTracker, token.requestMetadata.requestDescriptor.term);
+ searchResultsPage.searchCancelAction = createSearchCancelledOrClearedAction(objectGraph, "cancel", token.metricsOptions.pageInformation, token.metricsOptions.locationTracker, token.requestMetadata.requestDescriptor.term);
+ return searchResultsPage;
+ });
+ });
+ });
+}
+// region Extracting Data
+/**
+ * Returns the array of data objects to build search results with.
+ */
+function dataForSearchResultsFromCombinedData(objectGraph, combinedSearchData) {
+ return mediaDataStructure.dataCollectionFromDataContainer(combinedSearchData.catalogResponse.results.search);
+}
+/**
+ * Returns the array of data objects to build search adverts with.
+ */
+function dataForAdvertsFromCombinedData(objectGraph, combinedSearchData) {
+ const rawAdvertsData = mediaDataStructure.dataCollectionFromDataContainer(combinedSearchData.catalogResponse.results["ads-result"]);
+ return searchAdsODML.applyNativeAdvertData(objectGraph, rawAdvertsData, combinedSearchData.sponsoredSearchAdvertData);
+}
+// endregion
+// region Guided Search
+/**
+ * Add models for Guided Search into `SearchResults` model, specifically:
+ * - `GuidedSearchToken`s
+ * - `GuidedSearchQuery`s
+ * - Metrics Container for tokens.
+ */
+function addModelsForGuidedSearch(objectGraph, searchResultsPage, requestMetadata, facetData, metricsOptions) {
+ if (!objectGraph.host.isiOS) {
+ return;
+ }
+ const request = requestMetadata.requestDescriptor;
+ metricsHelpersLocation.pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "SearchRevisions",
+ }, "");
+ const tokens = [];
+ // Tokens from facet data, if any
+ if (serverData.isDefinedNonNullNonEmpty(facetData)) {
+ for (const data of facetData) {
+ const token = guidedSearch.createGuidedSearchToken(objectGraph, "toggle", request, data, metricsOptions);
+ if (token) {
+ tokens.push(token);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ }
+ }
+ }
+ // Token from selected entity hint, iff there aren't tokens already
+ if (serverData.isNullOrEmpty(tokens) && requestMetadata.requestDescriptor.searchEntity) {
+ const entityClearingToken = guidedSearch.createGuidedSearchTokenClearingEntityFilter(objectGraph, requestMetadata.requestDescriptor, metricsOptions);
+ tokens.push(entityClearingToken);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
+ }
+ const queries = guidedSearch.createGuidedSearchQueries(objectGraph, requestMetadata.requestDescriptor, facetData);
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ if (serverData.isDefinedNonNullNonEmpty(tokens)) {
+ searchResultsPage.guidedSearchTokens = tokens;
+ searchResultsPage.guidedSearchQueries = queries;
+ addGuidedSearchParentImpressionMetrics(objectGraph, searchResultsPage, metricsOptions);
+ metricsHelpersLocation.nextPosition(metricsOptions.locationTracker); // increment **after** assigning guided token parent impression.
+ }
+}
+/// The contextual reason for the segment change
+var SegmentChangeReason;
+(function (SegmentChangeReason) {
+ SegmentChangeReason[SegmentChangeReason["EmptyResults"] = 0] = "EmptyResults";
+ SegmentChangeReason[SegmentChangeReason["Picker"] = 1] = "Picker";
+ SegmentChangeReason[SegmentChangeReason["NonNative"] = 2] = "NonNative";
+})(SegmentChangeReason || (SegmentChangeReason = {}));
+/**
+ * Creates a segment change action for moving from one segment to another for a specific reason
+ * @param objectGraph The App Store object graph
+ * @param reason The reason why the segment is changing
+ * @param pageId The search results page id
+ * @param metricsOptions The metrics options for the segment this action will attach to
+ * @param switchingToGroupId The groupId of the segment we are switching to
+ * @param switchingFromGroupId The groupId of the segment we are switching from (the current segment)
+ * @returns A segment change action for the specific context and locations
+ */
+function segmentChangeAction(objectGraph, reason, pageId, metricsOptions, switchingToGroupId, switchingFromGroupId) {
+ const includeAppsSuffix = reason !== SegmentChangeReason.Picker;
+ const switchingToSegmentTitle = segmentTitleForSegmentType(objectGraph, includeAppsSuffix, segmentTypeFromGroupId(switchingToGroupId));
+ let action;
+ switch (reason) {
+ case SegmentChangeReason.EmptyResults:
+ if (isNothing(switchingToSegmentTitle) || switchingFromGroupId === switchingToGroupId) {
+ return undefined;
+ }
+ metricsHelpersLocation.pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "SearchResults",
+ }, "emptyResultsSegmentSwitch");
+ const emptyResultsTitle = objectGraph.loc
+ .string("SEARCH_RESULTS_SWITCH_TO_OTHER_RESULTS")
+ .replace("{platformApps}", switchingToSegmentTitle);
+ action = new models.SearchPageSegmentChangeAction(switchingToGroupId, switchingToSegmentTitle, new models.StyledText(emptyResultsTitle));
+ metricsHelpersClicks.addClickEventToSearchPageSegmentChangeAction(objectGraph, action, "button", metricsOptions.locationTracker);
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ break;
+ case SegmentChangeReason.Picker:
+ if (isNothing(switchingToSegmentTitle)) {
+ return undefined;
+ }
+ metricsHelpersLocation.pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "SearchResults",
+ }, "searchResultsSegmentSwitch");
+ action = new models.SearchPageSegmentChangeAction(switchingToGroupId, switchingToSegmentTitle);
+ metricsHelpersClicks.addClickEventToSearchPageSegmentChangeAction(objectGraph, action, "button", metricsOptions.locationTracker);
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ break;
+ case SegmentChangeReason.NonNative:
+ const currentSegmentType = segmentTypeFromGroupId(switchingFromGroupId);
+ if (currentSegmentType === segmentTypeForPlatform(objectGraph)) {
+ return undefined;
+ }
+ const switchingFromSegmentTitle = segmentTitleForSegmentType(objectGraph, true, segmentTypeFromGroupId(switchingFromGroupId));
+ const nonNativeTitle = objectGraph.loc
+ .string("Search.Results.ShowingNonNativeResults")
+ .replace("@@current_platform_apps@@", switchingFromSegmentTitle)
+ .replace("@@native_platform_apps@@", switchingToSegmentTitle);
+ metricsHelpersLocation.pushBasicLocation(objectGraph, {
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ targetType: "SearchResults",
+ }, "nonNativeResultsSegmentSwitch");
+ action = new models.SearchPageSegmentChangeAction(switchingToGroupId, switchingToSegmentTitle, new models.StyledText(nonNativeTitle, "text/x-apple-as3-nqml"));
+ metricsHelpersLocation.popLocation(metricsOptions.locationTracker);
+ break;
+ default:
+ break;
+ }
+ const clickActionOptions = {
+ actionType: "navigate",
+ id: pageId,
+ targetType: "button",
+ pageInformation: metricsOptions.pageInformation,
+ locationTracker: metricsOptions.locationTracker,
+ };
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, clickActionOptions);
+ return action;
+}
+/**
+ * Gets the segment type for the given group id
+ * @param groupId The group id of the segment
+ * @returns The segment type for the group id
+ */
+function segmentTypeFromGroupId(groupId) {
+ if (isSome(groupId)) {
+ switch (groupId) {
+ case "iOS":
+ case "ios":
+ return models.SegmentedSearchResultsPageSegmentType.iOS;
+ case "xrOS":
+ case "xros":
+ return models.SegmentedSearchResultsPageSegmentType.visionOS;
+ default:
+ return undefined;
+ }
+ }
+ return undefined;
+}
+/**
+ * Gets the segment type for the current platform
+ * @param objectGraph The App Store object graph
+ * @returns The segment type for the current platform
+ */
+function segmentTypeForPlatform(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "vision":
+ return models.SegmentedSearchResultsPageSegmentType.visionOS;
+ case "pad":
+ case "phone":
+ return models.SegmentedSearchResultsPageSegmentType.iOS;
+ default:
+ return undefined;
+ }
+}
+/**
+ * Gets the segment title for the given segment type
+ * @param objectGraph The App Store object graph
+ * @param includeAppsSuffix Whether to include the word "Apps" at the end of the title
+ * @param segmentType The segment type we want the title of
+ * @returns The segment title for the segment type
+ */
+function segmentTitleForSegmentType(objectGraph, includeAppsSuffix, segmentType) {
+ switch (segmentType) {
+ case models.SegmentedSearchResultsPageSegmentType.visionOS:
+ return includeAppsSuffix
+ ? objectGraph.loc.string("SEARCH_RESULTS_VISION_APPS_TITLE")
+ : objectGraph.loc.string("SEARCH_RESULTS_VISION_TITLE");
+ case models.SegmentedSearchResultsPageSegmentType.iOS:
+ return includeAppsSuffix
+ ? objectGraph.loc.string("SEARCH_RESULTS_IPHONE_IPAD_APPS_TITLE")
+ : objectGraph.loc.string("SEARCH_RESULTS_IPHONE_IPAD_TITLE");
+ default:
+ return undefined;
+ }
+}
+/**
+ * Gets the group id for the current platform
+ * @param objectGraph The App Store object graph
+ * @param segments The segments on the search results page
+ * @returns The group id for the current platform
+ */
+function segmentGroupIdForPlatform(objectGraph, segments) {
+ const nativeSegmentType = segmentTypeForPlatform(objectGraph);
+ const nativeSegment = segments.find((segment) => {
+ return segmentTypeFromGroupId(segment.groupId) === nativeSegmentType;
+ });
+ return nativeSegment === null || nativeSegment === void 0 ? void 0 : nativeSegment.groupId;
+}
+/**
+ *
+ * @param objectGraph The App Store Object Graph
+ * @param searchClearActionType The way the user cleared the search (x in search field or cancel button in toolbar)
+ * @param pageInformation The metrics page information
+ * @param locationTracker The metrics location tracker
+ * @param searchTerm The current search term
+ * @returns An action to trigger when the search is cancelled or cleared
+ */
+export function createSearchCancelledOrClearedAction(objectGraph, searchClearActionType, pageInformation, locationTracker, searchTerm) {
+ const action = new models.BlankAction();
+ let type;
+ let targetId;
+ switch (searchClearActionType) {
+ case "cancel":
+ type = "dismiss";
+ targetId = "cancel";
+ break;
+ case "clear":
+ type = "delete";
+ targetId = "clear";
+ break;
+ default:
+ break;
+ }
+ metricsHelpersClicks.addClickEventToSearchCancelOrDismissAction(objectGraph, action, {
+ targetType: "button",
+ id: targetId,
+ idType: undefined,
+ actionType: type,
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ }, "button", searchTerm);
+ return action;
+}
+//# sourceMappingURL=search.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/shelves/search-history-shelf.js b/node_modules/@jet-app/app-store/tmp/src/common/search/shelves/search-history-shelf.js
new file mode 100644
index 0000000..c24e8c3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/shelves/search-history-shelf.js
@@ -0,0 +1,148 @@
+import { isNothing } from "@jet/environment/types/optional";
+import * as models from "../../../api/models";
+import * as serverData from "../../../foundation/json-parsing/server-data";
+import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
+import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../../metrics/helpers/location";
+import * as searchLandingShelfController from "../landing/search-landing-shelf-controller";
+/**
+ * The `SearchHistoryShelfToken` type is responsible for plumbing through all
+ * the data needed to render an incomplete shelf (a shelf that requires a
+ * lookup) on the focus page.
+ */
+export class SearchHistoryShelfToken {
+ constructor(title, maxItems, shelfDisplayStyle, itemDisplayStyle) {
+ this.title = title;
+ this.maxItems = maxItems;
+ this.shelfDisplayStyle = shelfDisplayStyle;
+ this.itemDisplayStyle = itemDisplayStyle;
+ }
+}
+function encodedShelfToken(token) {
+ return encodeURIComponent(JSON.stringify(token));
+}
+export function decodeShelfToken(json) {
+ if (isNothing(json)) {
+ return undefined;
+ }
+ return JSON.parse(decodeURIComponent(json));
+}
+export function createShelfWithContext(objectGraph, pageContext, shelfAttributes) {
+ const token = new SearchHistoryShelfToken(shelfAttributes.title, shelfAttributes.displayCount, shelfAttributes.displayStyle, shelfAttributes.searchLandingItemDisplayStyle);
+ return createShelfWithToken(objectGraph, token, pageContext.metricsPageInformation, pageContext.metricsLocationTracker, pageContext.searchHistory);
+}
+function createShelfItems(objectGraph, metricsPageInformation, metricsLocationTracker, itemDisplayStyle, searchHistory) {
+ if (serverData.isNullOrEmpty(searchHistory)) {
+ return [];
+ }
+ const items = [];
+ for (const [historyIndex, historyItem] of searchHistory.entries()) {
+ const item = createShelfItem(objectGraph, historyItem, historyIndex, metricsPageInformation, metricsLocationTracker, itemDisplayStyle);
+ if (serverData.isNullOrEmpty(item)) {
+ continue;
+ }
+ items.push(item);
+ metricsHelpersLocation.nextPosition(metricsLocationTracker);
+ }
+ return items;
+}
+function createShelfURL(token) {
+ return `${Protocol.internal}:/${Path.searchLandingPage}/${Path.shelf}/?${Parameters.isOnDeviceSearchHistoryShelf}=true&${Parameters.token}=${encodedShelfToken(token)}`;
+}
+export function createShelfWithToken(objectGraph, token, metricsPageInformation, metricsLocationTracker, searchHistory) {
+ // Create Items
+ const items = createShelfItems(objectGraph, metricsPageInformation, metricsLocationTracker, token.itemDisplayStyle, searchHistory);
+ // Clear History Action
+ const clearHistoryAction = new models.ClearSearchHistoryAction();
+ clearHistoryAction.title = objectGraph.loc.string("Action.ClearSearches");
+ metricsHelpersClicks.addClickEventToClearSearchHistoryAction(objectGraph, clearHistoryAction);
+ // Clear History Sheet Action
+ const clearHistorySheetAction = new models.SheetAction([clearHistoryAction]);
+ clearHistorySheetAction.title = objectGraph.loc.string("Sheet.ClearSearches.Title");
+ clearHistorySheetAction.message = objectGraph.loc.string("Sheet.ClearSearches.Message");
+ clearHistorySheetAction.destructiveActionIndex = 0;
+ clearHistorySheetAction.isCancelable = true;
+ // Clear History Compound Action
+ const clearHistoryShelfAction = new models.CompoundAction([clearHistorySheetAction]);
+ clearHistoryShelfAction.title = objectGraph.loc.string("Action.Clear");
+ // Shelf
+ const contentType = shelfContentTypeForDisplayStyle(token.shelfDisplayStyle);
+ const shelf = new models.Shelf(contentType);
+ shelf.id = "onDeviceSearchHistory";
+ shelf.presentationHints = { isWidthConstrained: true };
+ // Header
+ shelf.header = {
+ title: token.title,
+ accessoryAction: clearHistoryShelfAction,
+ };
+ // Grid
+ if (shelf.contentType === "scrollablePill") {
+ shelf.isHorizontal = true;
+ shelf.rowsPerColumn = token.shelfDisplayStyle.layoutSize;
+ }
+ shelf.contentsMetadata = {
+ type: "searchFocusTwoColumnList",
+ numberOfColumns: items.length > 1 ? token.shelfDisplayStyle.layoutSize : 1,
+ };
+ // Content
+ shelf.items = items;
+ shelf.isHidden = serverData.isNullOrEmpty(items);
+ shelf.refreshUrl = createShelfURL(token);
+ return shelf;
+}
+function shelfContentTypeForDisplayStyle(displayStyle) {
+ if (displayStyle.layout === "word_cloud" /* models.GenericSearchPageShelfDisplayStyleLayout.WordCloud */) {
+ return "scrollablePill";
+ }
+ if (displayStyle.layoutSize === 2) {
+ // MAINTAINER'S NOTE: Automatically renders as single column in AX text sizes.
+ return "twoColumnList";
+ }
+ return "singleColumnList";
+}
+function createItemTitle(objectGraph, searchTerm, searchEntity) {
+ if (isNothing(searchEntity)) {
+ return searchTerm;
+ }
+ let formatLocKey;
+ if (searchEntity === "developer") {
+ formatLocKey = "Search.ResultsTitle.InDevelopers";
+ }
+ else if (searchEntity === "story") {
+ formatLocKey = "Search.ResultsTitle.InStories";
+ }
+ else if (searchEntity === "watch") {
+ formatLocKey = "Search.ResultsTitle.InWatch";
+ }
+ else if (searchEntity === "arcade") {
+ formatLocKey = "Search.ResultsTitle.InArcade";
+ }
+ if (isNothing(formatLocKey)) {
+ return searchTerm;
+ }
+ return objectGraph.loc.string(formatLocKey).replace("@@search_term@@", searchTerm);
+}
+function createShelfItem(objectGraph, historyItem, itemIndex, metricsPageInformation, metricsLocationTracker, displayStyle) {
+ const searchTerm = historyItem.term;
+ const searchEntity = historyItem.entity;
+ const searchAction = searchLandingShelfController.createFocusPageSearchAction(objectGraph, createItemTitle(objectGraph, searchTerm, searchEntity), searchTerm, searchEntity, metricsLocationTracker, "recents", undefined, /// MAINTAINER'S NOTE: In the future, we could keep track of the original search source and attribute this recent search to that.
+ metricsPageInformation, displayStyle);
+ if (isNothing(searchAction)) {
+ return null;
+ }
+ searchAction.id = historyItem.id;
+ metricsHelpersImpressions.addImpressionFields(objectGraph, searchAction, {
+ targetType: "link",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ kind: "link",
+ softwareType: null,
+ title: historyItem.term,
+ hintsEntity: historyItem.entity,
+ id: `${itemIndex}`,
+ idType: "sequential",
+ });
+ return searchAction;
+}
+//# sourceMappingURL=search-history-shelf.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/sponsored-search-fetching.js b/node_modules/@jet-app/app-store/tmp/src/common/search/sponsored-search-fetching.js
new file mode 100644
index 0000000..c9ee59f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/sponsored-search-fetching.js
@@ -0,0 +1,122 @@
+/**
+ Data Fetching for Sponsored Search.
+ */
+import { ads } from "../../api/typings/constants";
+import { asString, isNullOrEmpty } from "../../foundation/json-parsing/server-data";
+import { attributeAsDictionary } from "../../foundation/media/attributes";
+import { dataCollectionFromDataContainer, } from "../../foundation/media/data-structure";
+import { allProductVariantIdsForData, productVariantDataForData, productVariantIDForVariantData, } from "../product-page/product-page-variants";
+import { adLogger } from "./search-ads";
+import * as content from "../content/content";
+// region exports
+/**
+ * Fetch the set of processed search ads with the raw adverts in the sequential response.
+ * @param sponsoredSearchRequestData The request data to fetch processed ads for.
+ * @param searchTerm The search term to fetch processed adverts for.
+ * @param fetchResponse The promise that will resolve to the response.
+ * @returns A promise that will provide `SponsoredSearchNativeAdvertData`. Note that even error conditions are represented in the result for instrumentation purposes.
+ */
+export async function fetchSponsoredSearchNativeAdvertData(objectGraph, sponsoredSearchRequestData, searchTerm, fetchResponse) {
+ var _a;
+ if (!sponsoredSearchRequestData.validAdRequest()) {
+ adLogger(objectGraph, `ODML fetch skipped - Malformed request`);
+ return {
+ adverts: [],
+ odmlSuccess: false,
+ };
+ }
+ /**
+ * This is a weird path where we:
+ * 1. Fetch response from search
+ * 2. Pass parts of the response back to native to be modified by SearchAds.
+ */
+ const response = await fetchResponse;
+ const adverts = sponsoredSearchAdvertsFromResponse(objectGraph, response);
+ const organics = sponsoredSearchOrganicsFromResponse(objectGraph, response, 1); // per POR, only 1 organic for now.
+ try {
+ if (!objectGraph.isAvailable(ads)) {
+ adLogger(objectGraph, `ODML fetch skipped - Unsupported client`);
+ return {
+ adverts: adverts,
+ odmlSuccess: false,
+ };
+ }
+ else {
+ const processedAdverts = await objectGraph.ads.processAdvertsForSponsoredSearch(adverts, organics, searchTerm, objectGraph.bag.sponsoredSearchODMLTimeout, objectGraph.client.isPhone || objectGraph.client.isPad);
+ if (!processedAdverts.odmlSuccess) {
+ adLogger(objectGraph, `ODML processing failed`);
+ }
+ else {
+ adLogger(objectGraph, `ODML processing completed`);
+ }
+ return {
+ adverts: (_a = processedAdverts.adverts) !== null && _a !== void 0 ? _a : adverts,
+ odmlSuccess: processedAdverts.odmlSuccess,
+ installedStates: processedAdverts.installedStates,
+ appliedPolicy: processedAdverts.appliedPolicy,
+ appStates: processedAdverts.appStates,
+ };
+ }
+ }
+ catch (e) {
+ adLogger(objectGraph, `ODML fetch failed - ${e}`);
+ return {
+ adverts: adverts,
+ odmlSuccess: false,
+ };
+ }
+}
+// endregion
+// region internals
+/**
+ * Build the search advert models from the response.
+ */
+function sponsoredSearchAdvertsFromResponse(objectGraph, response) {
+ const adverts = dataCollectionFromDataContainer(response.results["ads-result"]);
+ const bridgedAdverts = [];
+ for (const ad of adverts) {
+ const id = asString(ad, "id");
+ const adData = attributeAsDictionary(ad, "iads");
+ if (isNullOrEmpty(id) || isNullOrEmpty(adData)) {
+ continue;
+ }
+ let productVariantId = null;
+ let allProductVariantIds = null;
+ if (objectGraph.bag.enableCPPInSearchAds) {
+ const productVariantData = productVariantDataForData(objectGraph, ad);
+ productVariantId = productVariantIDForVariantData(productVariantData);
+ allProductVariantIds = allProductVariantIdsForData(objectGraph, ad);
+ }
+ bridgedAdverts.push({
+ instanceId: objectGraph.random.nextUUID(),
+ adamId: id,
+ assetInformation: {},
+ adData: adData,
+ cppIds: allProductVariantIds,
+ serverCppId: productVariantId,
+ selectedCppId: productVariantId,
+ appBinaryTraits: content.appBinaryTraitsFromData(objectGraph, ad),
+ });
+ }
+ return bridgedAdverts;
+}
+/**
+ * Build the search organic models from the response, up to `limit`.
+ */
+function sponsoredSearchOrganicsFromResponse(objectGraph, response, limit) {
+ const organics = dataCollectionFromDataContainer(response.results.search);
+ const bridgedOrganics = [];
+ for (const result of organics) {
+ const id = asString(result, "id");
+ if (isNullOrEmpty(id)) {
+ continue;
+ }
+ bridgedOrganics.push({
+ adamId: id,
+ assetInformation: {},
+ });
+ }
+ return bridgedOrganics;
+}
+// endregion
+//# sourceMappingURL=sponsored-search-fetching.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/search/web-search-action.js b/node_modules/@jet-app/app-store/tmp/src/common/search/web-search-action.js
new file mode 100644
index 0000000..28c0491
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/search/web-search-action.js
@@ -0,0 +1,23 @@
+import { FlowAction } from "../../api/models";
+import { makeSearchResultsPageIntent, } from "../../api/intents/search-results-page-intent";
+import { getLocale } from "../locale";
+import { makeCanonicalSearchResultsPageUrl } from "./search-page-url";
+/**
+ * Creates a `FlowAction` destined for the search results page
+ *
+ * This will be used by the "web" client to perform search from the search landing page,
+ * or to change the platform on search results page
+ */
+export function makeWebSearchAction(objectGraph, platform, term = "") {
+ const searchAction = new FlowAction("search");
+ const destination = makeSearchResultsPageIntent({
+ ...getLocale(objectGraph),
+ platform,
+ term,
+ origin: "externalUrl",
+ });
+ searchAction.destination = destination;
+ searchAction.pageUrl = makeCanonicalSearchResultsPageUrl(objectGraph, destination);
+ return searchAction;
+}
+//# sourceMappingURL=web-search-action.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/sharing.js b/node_modules/@jet-app/app-store/tmp/src/common/sharing.js
new file mode 100644
index 0000000..65c73b5
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/sharing.js
@@ -0,0 +1,428 @@
+//
+// sharing.ts
+// AppStoreKit
+//
+// Created by Sam Vafaee on 6/16/17.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../api/models";
+import * as serverData from "../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../foundation/media/attributes";
+import { Parameters, ShareURLParameters } from "../foundation/network/url-constants";
+import * as urls from "../foundation/network/urls";
+import * as dateUtil from "../foundation/util/date-util";
+import * as client from "../foundation/wrappers/client";
+import * as contentArtwork from "./content/artwork/artwork";
+import * as contentAttributes from "./content/attributes";
+import * as content from "./content/content";
+import * as metricsHelpersClicks from "./metrics/helpers/clicks";
+import * as offers from "./offers/offers";
+import { productVariantDataForData, productVariantIDForVariantData } from "./product-page/product-page-variants";
+import { cardDisplayStyleFromData, editorialArtKeyPathForCardDisplayStyle, todayCardArtworkFromArtworkData, todayCardHeroArtForData, } from "./today/today-card-util";
+import { HeroMediaDisplayContext } from "./today/today-types";
+import { dataHasDeviceFamily, dataOnlyHasDeviceFamily } from "./content/device-family";
+// region Data helpers
+export function adamIdForShareSheetGiftActivityFromProductData(objectGraph, data) {
+ if (!objectGraph.bag.isContentGiftingEnabled) {
+ return null;
+ }
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ const offer = offers.offerDataFromData(objectGraph, data);
+ if (serverData.isNull(offer)) {
+ return null;
+ }
+ // Disable gifting for pre-orders
+ const isPreorder = mediaAttributes.attributeAsBoolean(data, "isPreorder");
+ if (isPreorder) {
+ return null;
+ }
+ const price = serverData.asNumber(offer, "price");
+ if (price > 0) {
+ return data.id;
+ }
+ return null;
+}
+/**
+ * Return the url for sharing for given product
+ * @param objectGraph Dependency soup
+ * @param data Apps Resource data for product
+ */
+function shareUrlForProductData(objectGraph, data, urlKey) {
+ const rawUrl = mediaAttributes.attributeAsString(data, urlKey);
+ if (serverData.isNullOrEmpty(rawUrl)) {
+ return null;
+ }
+ const url = new urls.URL(rawUrl);
+ // Add client specifier to url so apps are shown in the same client they were shared from
+ let clientSpecifier = null;
+ const clientIdentifier = objectGraph.host.clientIdentifier;
+ switch (clientIdentifier) {
+ case client.messagesIdentifier:
+ clientSpecifier = "messages";
+ break;
+ case client.watchIdentifier:
+ clientSpecifier = "watch";
+ break;
+ default:
+ break;
+ }
+ if (clientSpecifier) {
+ url.param(ShareURLParameters.clientSpecifier, clientSpecifier);
+ }
+ // Custom product page variant id must be added to `url` for MAPI resource caching constraints.
+ const productVariantData = productVariantDataForData(objectGraph, data);
+ const productVariantID = productVariantIDForVariantData(productVariantData);
+ if (serverData.isDefinedNonNull(productVariantID)) {
+ url.param(Parameters.productVariantID, productVariantID);
+ }
+ return url.toString();
+}
+function notesMetadataFromProductData(objectGraph, data) {
+ return validation.context("notesMetadataFromProductData", () => {
+ var _a;
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ const itemName = mediaAttributes.attributeAsString(data, "name");
+ // Require name
+ if (isNothing(itemName) || itemName.length === 0) {
+ return null;
+ }
+ const url = shareUrlForProductData(objectGraph, data, "url");
+ const developer = mediaAttributes.attributeAsString(data, "artistName");
+ const category = mediaAttributes.attributeAsString(data, "genreNames.0");
+ const fileSize = (_a = content.combinedFileSizeFromData(objectGraph, data)) === null || _a === void 0 ? void 0 : _a.fileSizeByDevice;
+ let mediaType;
+ switch (data.type) {
+ case "apps": {
+ mediaType = "app";
+ break;
+ }
+ case "app-bundles": {
+ mediaType = "bundle";
+ break;
+ }
+ case "in-apps": {
+ mediaType = "iap";
+ break;
+ }
+ default: {
+ mediaType = null;
+ }
+ }
+ return new models.ShareSheetNotesMetadata(itemName, url, developer, category, fileSize, mediaType);
+ });
+}
+function copyLinkShareSheetActivityForURL(objectGraph, url, title) {
+ if (serverData.isNullOrEmpty(url)) {
+ return null;
+ }
+ const copyTextAction = new models.CopyTextAction(url);
+ copyTextAction.title = title !== null && title !== void 0 ? title : objectGraph.loc.string("ShareSheet.CopyLink.Title");
+ copyTextAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://link");
+ const copyLinkActionActivity = new models.ShareSheetActivity("com.apple.AppStore.copyLinkActivity", copyTextAction);
+ return copyLinkActionActivity;
+}
+function openInGameCenterShareSheetActivityForURL(url, title) {
+ if (serverData.isNullOrEmpty(url)) {
+ return undefined;
+ }
+ const openURLAction = new models.ExternalUrlAction(url);
+ openURLAction.title = title;
+ const activity = new models.ShareSheetActivity("com.apple.AppStore.openInGameCenterActivity", openURLAction);
+ return activity;
+}
+// endregion
+// region Article
+export function shareSheetDataForArticle(objectGraph, text, url, shortUrl, articleArtwork, data) {
+ // Sharing is currently not supported on watchOS.
+ if (serverData.isNull(data) || objectGraph.client.isWatch) {
+ return null;
+ }
+ return validation.context("shareSheetDataForArticle", () => {
+ let artwork = articleArtwork;
+ if (isNothing(artwork) && isSome(data)) {
+ artwork = shareSheetArtForData(objectGraph, data);
+ }
+ const subtitle = objectGraph.loc.string("ShareSheet.Story.Subtitle");
+ const articleMetadata = new models.ShareSheetArticleMetadata(data.id, text, subtitle, artwork);
+ return new models.ShareSheetData(articleMetadata, url, shortUrl);
+ });
+}
+/**
+ * Determines the artwork to use in share sheets.
+ * @param {Data} data The data from which to retrieve the artwork.
+ * @returns {Artwork} The artwork suitable for display in the share sheet.
+ */
+export function shareSheetArtForData(objectGraph, data) {
+ const cardDisplayStyle = cardDisplayStyleFromData(data);
+ if (!objectGraph.client.isVision && !preprocessor.GAMES_TARGET) {
+ const heroArt = todayCardHeroArtForData(objectGraph, data, HeroMediaDisplayContext.Article, cardDisplayStyle);
+ if (serverData.isDefinedNonNull(heroArt)) {
+ return heroArt;
+ }
+ }
+ let cropCode;
+ if (objectGraph.client.isVision) {
+ cropCode = "SCS.ApDPCS01";
+ }
+ const artKeyPath = editorialArtKeyPathForCardDisplayStyle(objectGraph, cardDisplayStyle);
+ return todayCardArtworkFromArtworkData(objectGraph, mediaAttributes.attributeAsDictionary(data, artKeyPath), cropCode);
+}
+export function shareSheetActivitiesForArticle(objectGraph, shareUrl, todayCardPreviewUrl, storyID) {
+ const shareSheetActivities = [];
+ if ((shareUrl === null || shareUrl === void 0 ? void 0 : shareUrl.length) > 0) {
+ const copyLinkActivity = copyLinkShareSheetActivityForURL(objectGraph, shareUrl);
+ if (serverData.isDefinedNonNull(copyLinkActivity)) {
+ shareSheetActivities.push(copyLinkActivity);
+ }
+ }
+ if ((todayCardPreviewUrl === null || todayCardPreviewUrl === void 0 ? void 0 : todayCardPreviewUrl.length) > 0) {
+ const copyCardPreviewLinkActivity = copyLinkShareSheetActivityForURL(objectGraph, todayCardPreviewUrl, "Copy Card Preview Link");
+ if (serverData.isDefinedNonNull(copyCardPreviewLinkActivity)) {
+ shareSheetActivities.push(copyCardPreviewLinkActivity);
+ }
+ }
+ if (isSome(storyID)) {
+ let activity;
+ if (objectGraph.featureFlags.isGSEUIEnabled("de7bbd8e") &&
+ objectGraph.featureFlags.isEnabled("open_in_story_share_sheet")) {
+ activity = openInGameCenterShareSheetActivityForURL(`games:///story/id${storyID}`, objectGraph.loc.string("ShareSheet.OpenInGameCenter.Title"));
+ }
+ if (serverData.isDefinedNonNull(activity)) {
+ shareSheetActivities.push(activity);
+ }
+ }
+ return shareSheetActivities;
+}
+// endregion
+// region App Events
+export function shareSheetDataForAppEvent(objectGraph, text, subtitle, url, shortUrl, appEventArtwork) {
+ return validation.context("shareSheetDataForAppEvent", () => {
+ const artwork = appEventArtwork;
+ const appEventMetadata = new models.ShareSheetAppEventMetadata(text, subtitle, artwork);
+ return new models.ShareSheetData(appEventMetadata, url, shortUrl);
+ });
+}
+export function shareSheetActivitiesForAppEvent(objectGraph, appEvent, shareUrl) {
+ var _a;
+ const shareSheetActivities = [];
+ // Copy link action
+ if ((shareUrl === null || shareUrl === void 0 ? void 0 : shareUrl.length) > 0) {
+ const copyLinkActivity = copyLinkShareSheetActivityForURL(objectGraph, shareUrl);
+ if (serverData.isDefinedNonNull) {
+ shareSheetActivities.push(copyLinkActivity);
+ }
+ }
+ // If the event has already started, remove the option to create a calendar event
+ if (appEvent.startDate.getTime() <= Date.now()) {
+ return shareSheetActivities;
+ }
+ // Creating events is not supported in the product page extension
+ if (objectGraph.host.clientIdentifier === client.productPageExtensionIdentifier) {
+ return shareSheetActivities;
+ }
+ // Not authorized action
+ const notAuthorizedAction = new models.AlertAction("default");
+ notAuthorizedAction.title = objectGraph.loc.string("APP_EVENTS_CALENDAR_NOT_AUTHORIZED_TITLE");
+ notAuthorizedAction.message = objectGraph.loc.string("APP_EVENTS_CALENDAR_NOT_AUTHORIZED_DETAIL");
+ notAuthorizedAction.isCancelable = true;
+ notAuthorizedAction.buttonTitles = [objectGraph.loc.string("ACTION_SETTINGS")];
+ // NOTE: This URL only works on iOS. If this feature is expanded beyond iOS, this code will need to be split per-platform.
+ notAuthorizedAction.buttonActions = [new models.ExternalUrlAction("prefs:root=Privacy&path=CALENDARS", true)];
+ let isAllDay = false;
+ if (serverData.isDefinedNonNull(appEvent.endDate)) {
+ // If the start and end date are > 6 hours apart, and spans over multiple days, then mark as all-day
+ const startMidnight = dateUtil.convertLocalDateToLocalMidnight(appEvent.startDate);
+ const endMidnight = dateUtil.convertLocalDateToLocalMidnight(appEvent.endDate);
+ const difference = appEvent.endDate.getTime() - appEvent.startDate.getTime();
+ const sixHoursDifference = 1000 * 60 * 60 * 6;
+ if (endMidnight.getTime() > startMidnight.getTime() && difference > sixHoursDifference) {
+ isAllDay = true;
+ }
+ // If the start and end date are on the same day, and the event runs for 23 hours & 59 mins
+ // then mark as all-day. This effectively ignores the seconds portion as calendar ignores
+ // this anyway.
+ const fullDayDifference = 1000 * 60 * 60 * 23 + 1000 * 60 * 59;
+ if (startMidnight.getTime() === endMidnight.getTime() && difference >= fullDayDifference) {
+ isAllDay = true;
+ }
+ }
+ // Create calendar event activity
+ const createCalendarEventAction = new models.CreateCalendarEventAction(appEvent.startDate, appEvent.endDate, isAllDay, appEvent.title, (_a = appEvent.lockup) === null || _a === void 0 ? void 0 : _a.title, appEvent.detail, shareUrl, notAuthorizedAction, "free");
+ createCalendarEventAction.title = objectGraph.loc.string("SHARE_SHEET_ADD_TO_CALENDAR");
+ createCalendarEventAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://calendar.circle");
+ const createCalendarEventActivity = new models.ShareSheetActivity("com.apple.AppStore.createCalendarEventActivity", createCalendarEventAction);
+ // Create Calendar Event should be at the beginning.
+ shareSheetActivities.unshift(createCalendarEventActivity);
+ return shareSheetActivities;
+}
+// endregion
+// region Products
+export function shareSheetDataForProductFromProductData(objectGraph, data, clientIdentifierOverride) {
+ return validation.context("shareSheetDataForProductFromProductData", () => {
+ // Sharing is currently not supported on watchOS or the web client.
+ if (serverData.isNull(data) || objectGraph.client.isWatch || objectGraph.client.isWeb) {
+ return null;
+ }
+ // required attributes
+ const url = shareUrlForProductData(objectGraph, data, "url");
+ const title = mediaAttributes.attributeAsString(data, "name");
+ const developerName = mediaAttributes.attributeAsString(data, "artistName");
+ const adamId = data.id;
+ const storeFrontIdentifier = objectGraph.client.storefrontIdentifier;
+ // Sanity check
+ if (!url || !title || !developerName || !adamId) {
+ return null;
+ }
+ // optional attributes
+ const shortUrl = shareUrlForProductData(objectGraph, data, "shortUrl");
+ let artwork = null;
+ let notesMetadata = null;
+ const screenshots = content.screenshotsFromData(objectGraph, data, 4 /* content.ArtworkUseCase.LockupScreenshots */);
+ const videos = content.videoPreviewsFromData(objectGraph, data);
+ const subtitle = contentAttributes.contentAttributeAsString(objectGraph, data, "subtitle") || developerName;
+ const genreName = null;
+ const isMessagesOnlyApp = false;
+ const messagesAppIcon = null;
+ // Platform
+ let platform;
+ const isMacOnlyApp = dataOnlyHasDeviceFamily(objectGraph, data, "mac");
+ const isMacApp = dataHasDeviceFamily(objectGraph, data, "mac");
+ if (isMacOnlyApp || (objectGraph.client.isMac && isMacApp)) {
+ platform = "Mac";
+ }
+ else {
+ platform = "iOS";
+ }
+ // Add product info
+ if (serverData.isDefinedNonNull(data) && mediaAttributes.attributeAsString(data, "url")) {
+ artwork = content.iconFromData(objectGraph, data, {
+ useCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ }, clientIdentifierOverride);
+ notesMetadata = notesMetadataFromProductData(objectGraph, data);
+ }
+ const productMetadata = new models.ShareSheetProductMetadata(adamId, storeFrontIdentifier, title, platform, artwork, screenshots, videos, isMessagesOnlyApp, subtitle, genreName, messagesAppIcon, notesMetadata);
+ return new models.ShareSheetData(productMetadata, url, shortUrl);
+ });
+}
+export function shareProductActionFromData(objectGraph, data, metricsPageInformation, metricsLocationTracker, clientIdentifierOverride) {
+ return validation.context(`shareActionFromData: ${data.type}`, () => {
+ var _a;
+ const id = data.id;
+ switch (objectGraph.client.deviceType) {
+ case "mac": {
+ const shareSheetData = shareSheetDataForProductFromProductData(objectGraph, data);
+ // Share action
+ if (shareSheetData) {
+ const shareAction = new models.ShareSheetAction(shareSheetData, []);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, shareAction, {
+ targetType: "button",
+ id: id,
+ actionType: "share",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ });
+ return shareAction;
+ }
+ break;
+ }
+ case "phone":
+ case "pad":
+ case "vision": {
+ const shareSheetData = shareSheetDataForProductFromProductData(objectGraph, data, clientIdentifierOverride);
+ const shareSheetActivities = [];
+ // Copy link action
+ if (((_a = shareSheetData === null || shareSheetData === void 0 ? void 0 : shareSheetData.url) === null || _a === void 0 ? void 0 : _a.length) > 0) {
+ const copyLinkActivity = copyLinkShareSheetActivityForURL(objectGraph, shareSheetData.url);
+ if (serverData.isDefinedNonNull) {
+ shareSheetActivities.push(copyLinkActivity);
+ }
+ }
+ // Gift action
+ const giftAdamId = adamIdForShareSheetGiftActivityFromProductData(objectGraph, data);
+ if (giftAdamId) {
+ const giftAction = new models.FlowAction("finance");
+ giftAction.presentationContext = "presentModal";
+ giftAction.title = objectGraph.loc.string("SHARE_GIFT_APP");
+ giftAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://app.gift");
+ giftAction.pageUrl = `gift/${giftAdamId}`;
+ metricsHelpersClicks.addClickEventToAction(objectGraph, giftAction, {
+ targetType: "button",
+ id: id,
+ actionType: "gift",
+ actionContext: "shareSheet",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ });
+ const giftActionActivity = new models.ShareSheetActivity("com.apple.AppStore.giftActivity", giftAction);
+ shareSheetActivities.push(giftActionActivity);
+ }
+ if (shareSheetData) {
+ // Share action
+ const shareSheetStyle = "expanded";
+ const shareAction = new models.ShareSheetAction(shareSheetData, shareSheetActivities, shareSheetStyle);
+ shareAction.title = objectGraph.loc.string("SHARE_APP");
+ shareAction.artwork = contentArtwork.createArtworkForResource(objectGraph, "systemimage://square.and.arrow.up");
+ metricsHelpersClicks.addClickEventToAction(objectGraph, shareAction, {
+ targetType: "button",
+ id: id,
+ actionType: "share",
+ actionContext: "shareSheet",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ });
+ return shareAction;
+ }
+ else if (shareSheetActivities.length > 0) {
+ // Map ActionActivity[] to Action[] as we're not dealing with a share sheet
+ const sheetActions = shareSheetActivities.map((activity) => activity.action);
+ // Create action sheet instead of share sheet
+ const sheetAction = new models.SheetAction(sheetActions);
+ sheetAction.isCancelable = true;
+ sheetAction.isCustom = true;
+ metricsHelpersClicks.addClickEventToAction(objectGraph, sheetAction, {
+ targetType: "button",
+ id: id,
+ actionType: "actionSheet",
+ pageInformation: metricsPageInformation,
+ locationTracker: metricsLocationTracker,
+ });
+ return sheetAction;
+ }
+ break;
+ }
+ default: {
+ // These device types do not support sharing, so we don't create any share actions.
+ break;
+ }
+ }
+ return null;
+ });
+}
+// region Generic Pages
+export function shareSheetDataForGenericPage(objectGraph, text, url, subtitle, shortUrl, artwork) {
+ return validation.context("shareSheetDataForGenericPage", () => {
+ if (serverData.isNullOrEmpty(url)) {
+ return null;
+ }
+ const metadata = new models.ShareSheetGenericMetadata(text, subtitle, artwork !== null && artwork !== void 0 ? artwork : undefined);
+ return new models.ShareSheetData(metadata, url, shortUrl);
+ });
+}
+export function shareSheetActivitiesForGenericPage(objectGraph, shareUrl) {
+ const shareSheetActivities = [];
+ if ((shareUrl === null || shareUrl === void 0 ? void 0 : shareUrl.length) > 0) {
+ const copyLinkActivity = copyLinkShareSheetActivityForURL(objectGraph, shareUrl);
+ if (serverData.isDefinedNonNull) {
+ shareSheetActivities.push(copyLinkActivity);
+ }
+ }
+ return shareSheetActivities;
+}
+// endregion
+//# sourceMappingURL=sharing.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/article-request.js b/node_modules/@jet-app/app-store/tmp/src/common/today/article-request.js
new file mode 100644
index 0000000..4371605
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/article-request.js
@@ -0,0 +1,86 @@
+import { defaultAdditionalPlatformsForClient, Request } from "../../foundation/media/data-fetching";
+import { shouldFetchCustomAttributes } from "../product-page/product-page-variants";
+import { appEventsAreEnabled, appOfferItemsAreEnabled } from "../app-promotions/app-promotions-common";
+import { shouldUsePrerenderedIconArtwork } from "../content/content";
+import { AppEventsAttributes } from "../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types";
+function buildAttributesForArticlePageRequest(objectGraph) {
+ const attributes = [
+ "screenshotsByType",
+ "videoPreviewsByType",
+ "requiredCapabilities",
+ "minimumOSVersion",
+ "editorialArtwork",
+ "editorialVideo",
+ "editorialClientParams",
+ "shortEditorialNotes",
+ "enrichedEditorialNotes",
+ ];
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (objectGraph.appleSilicon.isSupportEnabled) {
+ attributes.push("macRequiredCapabilities");
+ }
+ if (objectGraph.client.isMac) {
+ attributes.push("hasMacIPAPackage");
+ }
+ if (objectGraph.client.isVision) {
+ attributes.push("compatibilityControllerRequirement");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ return attributes;
+}
+/**
+ * Create a Media API request for an `editorial-item`
+ *
+ * This corresponsd to an "Article" or "Story" page
+ */
+export function buildArticlePageRequest(objectGraph, intent, isIncomingURL) {
+ const request = new Request(objectGraph)
+ .withIdOfType(intent.id, "editorial-items")
+ .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph))
+ .includingAttributes(buildAttributesForArticlePageRequest(objectGraph))
+ .includingRelationships(["canvas"])
+ .includingRelationshipsForUpsell(true)
+ .includingMacOSCompatibleIOSAppsWhenSupported(true)
+ .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ if (!isIncomingURL) {
+ request.includingAgeRestrictions();
+ }
+ if (appEventsAreEnabled(objectGraph)) {
+ request.enablingFeature("appEvents");
+ request.includingScopedAttributes("app-events", AppEventsAttributes);
+ request.includingScopedRelationships("app-events", ["app"]);
+ request.includingScopedRelationships("editorial-item-shelves", ["app-events"]);
+ request.includingScopedAvailableIn("app-events", ["past"]);
+ }
+ if (appOfferItemsAreEnabled(objectGraph)) {
+ request.enablingFeature("offerItems");
+ request.includingScopedRelationships("offer-items", ["salables"]);
+ request.includingAssociateKeys("editorial-items", ["editorial-cards"]);
+ request.includingMetaKeys("offer-items:salables", ["discountOffer"]);
+ request.includingScopedAttributes("offer-items", [
+ "title",
+ "subtitle",
+ "additionalTerms",
+ "redemptionExpirationDate",
+ ]);
+ }
+ if (objectGraph.client.isVision) {
+ request.enablingFeature("supportsCustomTextColor");
+ request.includingScopedAttributes("editorial-items", ["enrichedEditorialNotes"]);
+ }
+ if (objectGraph.client.isWeb) {
+ request.includingAttributes([
+ // Publication date is used as part of SEO meta-data
+ "lastPublishedDate",
+ ]);
+ }
+ if (preprocessor.GAMES_TARGET) {
+ request.includingScopedAttributes("apps", ["isEligibleForGamesApp"]);
+ }
+ return request;
+}
+//# sourceMappingURL=article-request.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/article.js b/node_modules/@jet-app/app-store/tmp/src/common/today/article.js
new file mode 100644
index 0000000..a67fde4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/article.js
@@ -0,0 +1,1572 @@
+/**
+ * Created by keithpk on 3/21/17.
+ */
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import * as mediaAugment from "../../foundation/media/augment";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as mediaNetwork from "../../foundation/media/network";
+import * as mediaRelationships from "../../foundation/media/relationships";
+import * as urls from "../../foundation/network/urls";
+import * as color from "../../foundation/util/color-util";
+import { PageID } from "../../gameservicesui/src/common/id-builder";
+import * as gamesComponentBuilder from "../../gameservicesui/src/editorial-page/editorial-component-builder";
+import * as appPromotionsShelf from "../app-promotions/app-promotions-shelf";
+import * as arcadeCommon from "../arcade/arcade-common";
+import * as arcadeUpsell from "../arcade/arcade-upsell";
+import * as breakoutsCommon from "../arcade/breakouts-common";
+import * as videoDefaults from "../constants/video-constants";
+import * as artworkBuilder from "../content/artwork/artwork";
+import * as content from "../content/content";
+import { EditorialMediaPlacement } from "../editorial-pages/editorial-media-util";
+import { buildSmallStoryCardShelf } from "../editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-collection-shelf-builder";
+import { buildStoryCard } from "../editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-utils";
+import { createBaseShelfToken } from "../editorial-pages/editorial-page-shelf-token";
+import { CollectionShelfDisplayStyle } from "../editorial-pages/editorial-page-types";
+import * as externalDeepLink from "../linking/external-deep-link";
+import * as links from "../linking/os-update-links";
+import * as lockups from "../lockups/lockups";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersMedia from "../metrics/helpers/media";
+import * as metricsHelpersPage from "../metrics/helpers/page";
+import * as metricsHelpersUtil from "../metrics/helpers/util";
+import * as sharing from "../sharing";
+import { crossLinkSubtitleFromData, defaultTodayCardConfiguration, fallbackWatchTodayCardFromData, todayCardFromData, } from "./today-card-util";
+import * as todayHorizontalCardUtil from "./today-horizontal-card-util";
+import { todayCardPreviewUrlForTodayCard } from "./today-parse-util";
+import { HeroMediaDisplayContext, TodayCardDisplayStyle, TodayParseContext, } from "./today-types";
+export const iAPBackgroundColor = color.named("componentBackgroundStandout");
+const appShowcaseBackgroundColor = color.named("componentBackgroundStandout");
+const arcadeShowcaseShelfBackgroundColor = color.named("componentBackgroundStandout");
+/**
+ * Resolves the article module's app media platform to an `AppPlatform` to use for screenshots.
+ * @param {AppMediaPlatform} appMediaPlatform The server-dictated media platform to use for the module.
+ * @returns {AppPlatform} The app platform that is appropriate for this media platform, taking into account our device.
+ */
+function appPlatformFromAppMediaPlatform(objectGraph, appMediaPlatform) {
+ switch (appMediaPlatform) {
+ case "Watch":
+ return "watch";
+ case "iOS":
+ if (objectGraph.client.isPad) {
+ return "pad";
+ }
+ else {
+ return "phone";
+ }
+ case "tvOS":
+ return "tv";
+ case "Messages":
+ return "messages";
+ case "visionOS":
+ return "vision";
+ default:
+ return null;
+ }
+}
+export class ArticleParseContext {
+ constructor() {
+ // The index of the current module
+ this.index = 0;
+ // The reco metrics from the shelf on the today page
+ this.todayShelfRecoMetricsData = {};
+ /// Whether there are any focusable elements (for touch mode)
+ this.hasFocusableElements = false;
+ /// Whether there are any non-focusable elements (for touch mode)
+ this.hasNonFocusableElements = false;
+ /// Whether there is a resilient deep link.
+ this.isResilientDeepLink = false;
+ /// Whether or not to allow app event previews, used by editorial to preview app event stories before they are published
+ this.allowUnpublishedAppEventPreviews = false;
+ }
+}
+function todayCardConfigFromArticleContext(objectGraph, articleContext) {
+ if (!serverData.isDefinedNonNull(articleContext)) {
+ return null;
+ }
+ if (isSome(articleContext.todayCardConfig)) {
+ return articleContext.todayCardConfig;
+ }
+ const config = defaultTodayCardConfiguration(objectGraph);
+ config.enableListCardToMultiAppFallback = false;
+ config.clientIdentifierOverride = articleContext.clientIdentifierOverride;
+ config.useOTDTextStyle = false;
+ config.allowUnpublishedAppEventPreviews = articleContext.allowUnpublishedAppEventPreviews;
+ config.currentRowIndex = undefined;
+ switch (objectGraph.client.deviceType) {
+ case "mac":
+ config.prevailingCropCodes = { defaultCrop: "en" };
+ config.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.Grid;
+ config.heroDisplayContext = HeroMediaDisplayContext.Article;
+ break;
+ case "tv":
+ config.prevailingCropCodes = {
+ "defaultCrop": "ek",
+ "editorialArtwork.storyCenteredStatic16x9": "SCS.ApDHXL01",
+ };
+ config.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.Grid;
+ config.heroDisplayContext = HeroMediaDisplayContext.Article;
+ break;
+ case "web":
+ config.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.River;
+ config.prevailingCropCodes = {
+ "defaultCrop": "sr",
+ "editorialArtwork.dayCard": "grav.west",
+ };
+ break;
+ default:
+ break;
+ }
+ return config;
+}
+export function articlePageFromResponse(objectGraph, articleResponse, context) {
+ return validation.context("articlePageWithResponse", () => {
+ var _a;
+ const articleData = mediaDataStructure.dataFromDataContainer(objectGraph, articleResponse);
+ context.metricsPageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, "editorialItem", articleData.id, articleResponse);
+ context.metricsLocationTracker = metricsHelpersLocation.newLocationTracker();
+ context.pageId = articleData.id;
+ // Bridge over article contexts to today's metrics context and card config
+ const todayParseContext = new TodayParseContext(context.metricsPageInformation, context.metricsLocationTracker, context.refreshController);
+ const todayCardConfig = todayCardConfigFromArticleContext(objectGraph, context);
+ // Render the top card
+ let todayCard = todayCardFromData(objectGraph, articleData, todayCardConfig, todayParseContext);
+ let editorialStoryCard = null;
+ const todayCardMedia = todayCard === null || todayCard === void 0 ? void 0 : todayCard.media;
+ if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
+ editorialStoryCard = buildStoryCard(objectGraph, articleData, EditorialMediaPlacement.StoryDetail, {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ }, CollectionShelfDisplayStyle.StoryMedium, false);
+ todayCard = null;
+ }
+ if (isNothing(todayCard)) {
+ todayCard = fallbackWatchTodayCardFromData(objectGraph, articleData, todayCardConfig, todayParseContext);
+ }
+ // Get the title for metrics purposes.
+ const title = (_a = todayCard === null || todayCard === void 0 ? void 0 : todayCard.title) !== null && _a !== void 0 ? _a : editorialStoryCard === null || editorialStoryCard === void 0 ? void 0 : editorialStoryCard.title;
+ const editorialItemKind = mediaAttributes.attributeAsString(articleData, "kind");
+ // Configure subtitle for cross link
+ context.crossLinkSubtitle = crossLinkSubtitleFromData(objectGraph, articleData);
+ // Bridge today config back into articles, now that cards are created.
+ // Now we've created the card, reference the clientIdentifierOverride it used for the rest of the article.
+ context.clientIdentifierOverride = todayCardConfig.clientIdentifierOverride;
+ // Start a metrics location
+ metricsHelpersLocation.pushContentLocation(objectGraph, {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ targetType: "article",
+ id: context.pageId,
+ idType: "its_id",
+ }, title);
+ // Render the article itself
+ const shelves = renderArticle(objectGraph, articleData, todayCardMedia, context);
+ const lastShelf = shelves[shelves.length - 1];
+ // Sharing
+ const shareAction = objectGraph.client.isTV ||
+ objectGraph.client.isWeb ||
+ context.isResilientDeepLink ||
+ preprocessor.GAMES_TARGET ||
+ editorialItemKind === "OfferItem"
+ ? null
+ : shareSheetActionFromData(objectGraph, articleData, todayCardConfig);
+ if (serverData.isDefinedNonNull(shareAction)) {
+ // Add click event
+ metricsHelpersClicks.addClickEventToAction(objectGraph, shareAction, {
+ targetType: "button",
+ id: context.pageId,
+ actionType: "share",
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ });
+ const isLastModuleFullWidth = isArticleShelfFullWidth(objectGraph, lastShelf, context.module);
+ const shareButtonShelf = createShareShelf(objectGraph, shareAction, context, isLastModuleFullWidth);
+ if (shareButtonShelf) {
+ shelves.push(shareButtonShelf);
+ }
+ }
+ const page = new models.ArticlePage(todayCard, shelves, shareAction);
+ page.editorialStoryCard = editorialStoryCard;
+ page.title = todayCard === null || todayCard === void 0 ? void 0 : todayCard.title;
+ page.subtitle = todayCard === null || todayCard === void 0 ? void 0 : todayCard.inlineDescription;
+ addFooterLockupForPageIfNeeded(objectGraph, page, articleData, context);
+ if (objectGraph.client.isTV) {
+ if (context.hasFocusableElements && !context.hasNonFocusableElements) {
+ page.touchMode = "focus";
+ }
+ else if (!context.hasFocusableElements && context.hasNonFocusableElements) {
+ page.touchMode = "pan";
+ }
+ else {
+ page.touchMode = "auto";
+ }
+ }
+ // Map whether the article should terminate on close.
+ page.shouldTerminateOnClose = context.isResilientDeepLink;
+ metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, context.metricsPageInformation, (fields) => {
+ let additionalValue = title;
+ if ((todayCard === null || todayCard === void 0 ? void 0 : todayCard.media) instanceof models.TodayCardMediaBrandedSingleApp &&
+ (todayCard === null || todayCard === void 0 ? void 0 : todayCard.overlay) instanceof models.TodayCardLockupOverlay) {
+ const lockupOverlay = todayCard === null || todayCard === void 0 ? void 0 : todayCard.overlay;
+ additionalValue = lockupOverlay.lockup.title;
+ }
+ if (!additionalValue) {
+ return;
+ }
+ let pageDetails = serverData.asString(serverData.asJSONValue(fields["pageDetails"]), "coercible");
+ pageDetails = pageDetails || serverData.asString(serverData.asJSONValue(fields["pageId"]));
+ if (pageDetails) {
+ fields["pageDetails"] = `${pageDetails}_${additionalValue}`;
+ }
+ else {
+ fields["pageDetails"] = `unknown_${additionalValue}`;
+ }
+ });
+ page.canonicalURL = mediaAttributes.attributeAsString(articleData, "url");
+ if (isSome(articleData)) {
+ const articleUrl = mediaAttributes.attributeAsString(articleData, "url");
+ if (isSome(articleUrl)) {
+ page.viewArticleAction = new models.ExternalUrlAction(articleUrl, true);
+ }
+ }
+ return page;
+ });
+}
+function renderArticle(objectGraph, articleData, cardMedia, context) {
+ return validation.context("renderArticle", () => {
+ var _a;
+ const shelves = [];
+ const canvas = (_a = mediaRelationships.relationshipCollection(articleData, "canvas")) !== null && _a !== void 0 ? _a : [];
+ for (const storyModule of canvas) {
+ context.module = mediaAttributes.attributeAsString(storyModule, "displayType");
+ context.subStyle = null;
+ const shelfIndex = shelves.length;
+ const shelvesToRender = renderModule(objectGraph, storyModule, articleData, context, shelfIndex);
+ if (shelvesToRender.length > 0) {
+ for (const shelf of shelvesToRender) {
+ shelf.title = context.titleForNextShelf;
+ if (objectGraph.client.isTV) {
+ // Skip unsupported tvOS shelves
+ if (shelf.contentType === "editorialLink") {
+ continue;
+ }
+ }
+ else if (objectGraph.client.isWatch) {
+ // Skip unsupported watchOS shelves
+ if (shelf.contentType === "editorialLink") {
+ continue;
+ }
+ }
+ shelves.push(shelf);
+ context.titleForNextShelf = null;
+ }
+ }
+ context.index++;
+ metricsHelpersLocation.nextPosition(context.metricsLocationTracker);
+ }
+ // If we we're showing the fallback list card type on the today page, we're going to show
+ // the lockups as a list shelf underneath so we can still display the list contents.
+ // If we're on watchOS, we also want to hit this codepath so that lockup lists do not
+ // show as empty pages.
+ if ((context.showingFallbackMediaInline ||
+ objectGraph.client.isWatch ||
+ objectGraph.client.isVision ||
+ objectGraph.client.isWeb ||
+ preprocessor.GAMES_TARGET) &&
+ shelves.length === 0) {
+ const fallbackShelf = createFallbackListShelf(objectGraph, cardMedia);
+ if (serverData.isDefinedNonNull(fallbackShelf)) {
+ shelves.push(fallbackShelf);
+ }
+ }
+ return shelves;
+ });
+}
+// region Data Augmenting
+/**
+ * Article specific entrypoint for page response augmenting. See `augment.ts`.
+ * @param response Response to augment.
+ */
+export async function fetchAdditionalDataForInitialResponse(objectGraph, response) {
+ return await mediaAugment.fetchAugmentedData(objectGraph, response, findAdditionalDataKeysForArticleResponse, fetchDataForArticleDataKey);
+}
+/**
+ * Determine the set of data, expressed as an set of `ArticleAdditionalDataKey`s, that need to be fetched for given article response to be displayed.
+ * This is equivalent to `AbstractMediaApiPageBuilder.additionalDataKeysNeededForData`, but for article builder which doesn't adopt the `builder` API.
+ *
+ * @param articleResponse Initial response to determine additional data requirements for.
+ * @returns {Set<ArticleAdditionalDataKey>} Additional data needed expressed as set of `ArticleAdditionalDataKey`
+ */
+function findAdditionalDataKeysForArticleResponse(objectGraph, articleResponse) {
+ /**
+ * Keys for requested requirements determined by:
+ * - Modules in canvas only now :)
+ */
+ const allAdditionalDataKeySet = new Set();
+ // Requirements based on canvas items:
+ const articleData = mediaDataStructure.dataFromDataContainer(objectGraph, articleResponse);
+ const canvasModules = mediaRelationships.relationshipCollection(articleData, "canvas");
+ for (const storyModule of canvasModules) {
+ // Determine additional requests and add to `allRequirementsSet`
+ const dataKeysForModule = additionalDataKeysForArticleModule(objectGraph, storyModule, articleData);
+ if (serverData.isDefinedNonNullNonEmpty(dataKeysForModule)) {
+ for (const requirement of dataKeysForModule) {
+ allAdditionalDataKeySet.add(requirement);
+ }
+ }
+ }
+ return allAdditionalDataKeySet;
+}
+/**
+ * Builds a promise that will fetch data fulfilling given requirement. Note that these promises will return `null` when they fail,
+ * and their failure should not cause the entire page to fail.
+ * This is equivalent to `AbstractMediaApiPageBuilder.fetchAdditionalDataForKey`, but for article builder which doesn't adopt the `builder` API.
+ *
+ * @param dataKey Corresponding data key to fetch data for.
+ */
+// eslint-disable-next-line @typescript-eslint/promise-function-async
+function fetchDataForArticleDataKey(objectGraph, dataKey) {
+ let request;
+ if (dataKey === "upsellForNonacquisitionCanvas") {
+ // Use `editorialItem` matching context of that would've otherwise been joined if this story was an acquisition story.
+ request = arcadeCommon.arcadeUpsellRequest(objectGraph, models.marketingItemContextFromString("editorialItemCanvas"));
+ }
+ if (dataKey === "arcadeIcons") {
+ // Require 10 for now.
+ request = arcadeCommon.arcadeAppsRequestForIcons(objectGraph, 10);
+ }
+ if (serverData.isNull(request)) {
+ return null;
+ }
+ // Failable data fetch, either resolving to valid response or `null`.
+ return mediaNetwork.fetchData(objectGraph, request).catch(() => null);
+}
+/**
+ * Determine the requirements for single article module as determined by it's type.
+ * @param storyModule The module to fetch additional requirements for.
+ * @param articleData The article that contains `storyModule` in its canvas.
+ * @returns {ArticleAdditionalDataKey[] | undefined} Set of data keys if any are needed for rendering given module.
+ */
+export function additionalDataKeysForArticleModule(objectGraph, storyModule, articleData) {
+ // Only `AppMarker` has additional requirements.
+ const moduleType = mediaAttributes.attributeAsString(storyModule, "displayType");
+ if (moduleType !== "AppMarker") {
+ return null;
+ }
+ const markerType = mediaAttributes.attributeAsString(storyModule, "appMarkerType");
+ // <rdar://problem/55919205> In story Arcade acquisition module dropping from stories
+ // Editorial wants to use the acquisition module in non-acquisition stories, but the `upsell` relationship is only joined for EIs marked with the acquisition flag.
+ // When an article is missing the upsell relationship, we'll fetch it separately if we have modules that need it...
+ const articleDataIsMissingUpsell = serverData.isNull(arcadeCommon.upsellFromRelationshipOf(objectGraph, articleData));
+ /**
+ * Acquisition AppMarker, i.e. `ArcadeShowcase` needs:
+ * 1. Upsell data for text data, e.g. editorial notes and breakoutCallToAction label, provided this data isn't already provided as part of original page.
+ * 2. Assortment of Arcade App Icons (iOS Only).
+ */
+ const additionalDataKeysForModule = [];
+ if (markerType === "Acquisition") {
+ // iOS needs icon dependency
+ if (objectGraph.host.isiOS || objectGraph.client.isVision) {
+ additionalDataKeysForModule.push("arcadeIcons");
+ }
+ // All platform needs upsell to render acquisition modules, add it if missing.
+ if (articleDataIsMissingUpsell) {
+ additionalDataKeysForModule.push("upsellForNonacquisitionCanvas");
+ }
+ }
+ return additionalDataKeysForModule;
+}
+// endregion
+/**
+ * Create a shelf model representing a single module within article pages.
+ * @param storyModule Module server data to build shelf and contents from.
+ * @param articleData The data for article that contains `storyModule` above.
+ * @param context Global parse context updated while entire sets of modules are being parsed.
+ * @returns an array of `Shelf` or `null` if building fails for given module.
+ */
+function renderModule(objectGraph, storyModule, articleData, context, shelfIndex) {
+ return validation.catchingContext(`module: ${context.module}`, () => {
+ var _a;
+ const shelves = [];
+ switch (context.module) {
+ case "Header": {
+ context.titleForNextShelf = mediaAttributes.attributeAsString(storyModule, "editorialCopy");
+ break;
+ }
+ case "TextBlock": {
+ const textBlockShelf = createParagraph(objectGraph, storyModule, context);
+ if (isSome(textBlockShelf)) {
+ shelves.push(textBlockShelf);
+ context.hasNonFocusableElements = true;
+ }
+ break;
+ }
+ case "CollectionLockup": {
+ const appListShelf = createAppList(objectGraph, storyModule, context);
+ if (isSome(appListShelf)) {
+ shelves.push(appListShelf);
+ context.hasFocusableElements = true;
+ }
+ break;
+ }
+ case "InlineImage": {
+ const inlineImageShelf = createImage(objectGraph, storyModule, context);
+ if (isSome(inlineImageShelf)) {
+ shelves.push(inlineImageShelf);
+ context.hasNonFocusableElements = true;
+ }
+ break;
+ }
+ case "AppLockup": {
+ const appLockupShelf = createAppLockup(objectGraph, storyModule, context);
+ if (isSome(appLockupShelf)) {
+ shelves.push(appLockupShelf);
+ context.hasFocusableElements = true;
+ }
+ break;
+ }
+ case "TipBlock": {
+ const tipShelf = createTip(objectGraph, storyModule, context);
+ if (isSome(tipShelf)) {
+ shelves.push(tipShelf);
+ context.hasNonFocusableElements = true;
+ }
+ break;
+ }
+ case "PullQuote": {
+ const pullQuoteShelf = createPullQuote(objectGraph, storyModule, context);
+ if (isSome(pullQuoteShelf)) {
+ shelves.push(pullQuoteShelf);
+ context.hasNonFocusableElements = true;
+ }
+ break;
+ }
+ case "HorizontalRule": {
+ const horizontalRuleShelf = createHorizontalRule(objectGraph, storyModule, context);
+ if (isSome(horizontalRuleShelf)) {
+ shelves.push(horizontalRuleShelf);
+ context.hasNonFocusableElements = true;
+ }
+ break;
+ }
+ case "InlineVideo": {
+ const inlineVideoShelf = createVideo(objectGraph, storyModule, context);
+ if (isSome(inlineVideoShelf)) {
+ shelves.push(inlineVideoShelf);
+ context.hasFocusableElements = true;
+ }
+ break;
+ }
+ case "AppMedia": {
+ const appMediaShelf = createAppMedia(objectGraph, storyModule, context);
+ if (isSome(appMediaShelf)) {
+ shelves.push(appMediaShelf);
+ context.hasFocusableElements = true;
+ }
+ break;
+ }
+ case "LinkBlock": {
+ const linkBlockShelf = createLink(objectGraph, storyModule, context);
+ if (isSome(linkBlockShelf)) {
+ shelves.push(linkBlockShelf);
+ context.hasFocusableElements = true;
+ }
+ break;
+ }
+ case "TextList": {
+ const textListShelf = createTextList(objectGraph, storyModule, context);
+ if (isSome(textListShelf)) {
+ shelves.push(textListShelf);
+ context.hasNonFocusableElements = true;
+ }
+ break;
+ }
+ case "IAPLockup": {
+ const iapLockupShelf = createIAPLockup(objectGraph, storyModule, context);
+ if (isSome(iapLockupShelf)) {
+ shelves.push(iapLockupShelf);
+ context.hasFocusableElements = true;
+ }
+ break;
+ }
+ case "AppMarker": {
+ const appMarkerShelf = createAppMarker(objectGraph, storyModule, articleData, context);
+ if (isSome(appMarkerShelf)) {
+ shelves.push(appMarkerShelf);
+ context.hasFocusableElements = true;
+ }
+ break;
+ }
+ case "StoryList": {
+ const storyListShelf = createStoryCards(objectGraph, storyModule, context, shelfIndex);
+ if (isSome(storyListShelf)) {
+ shelves.push(storyListShelf);
+ context.hasFocusableElements = true;
+ }
+ break;
+ }
+ case "AppEventLockup": {
+ const appEventShelf = createAppEventLockup(objectGraph, storyModule, context);
+ if (isSome(appEventShelf)) {
+ shelves.push(appEventShelf);
+ context.hasFocusableElements = true;
+ }
+ break;
+ }
+ case "OfferItemLockup": {
+ const offerItemShelves = createOfferItemLockup(objectGraph, storyModule, context);
+ if (isSome(offerItemShelves)) {
+ shelves.push(...offerItemShelves);
+ context.hasFocusableElements = true;
+ }
+ break;
+ }
+ default: {
+ objectGraph.console.log(`Unknown module: ${context.module}`);
+ }
+ }
+ for (const shelf of shelves) {
+ const existingShelfPresentationHints = (_a = shelf.presentationHints) !== null && _a !== void 0 ? _a : {};
+ shelf.presentationHints = {
+ ...existingShelfPresentationHints,
+ isArticleContext: true,
+ };
+ }
+ return shelves;
+ });
+}
+const FULL_WIDTH_MODULES = ["AppLockup", "InlineImage", "InlineVideo", "AppMarker"];
+/**
+ * Determines whether the provided parameters signifies a full-width article
+ * module.
+ * @param shelf The shelf in question.
+ * @param type The type of article module.
+ * @returns Whether or not the given shelf for the article type is full width.
+ */
+function isArticleShelfFullWidth(objectGraph, shelf, type) {
+ if (shelf && type) {
+ const itemCount = shelf.items.length;
+ if (itemCount > 0 && FULL_WIDTH_MODULES.indexOf(type) !== -1) {
+ const lastItem = shelf.items[itemCount - 1];
+ switch (shelf.contentType) {
+ case "framedArtwork": {
+ const framedArt = lastItem;
+ return framedArt && framedArt.isFullWidth;
+ }
+ case "framedVideo": {
+ const framedVideo = lastItem;
+ return framedVideo && framedVideo.isFullWidth;
+ }
+ default: {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+// region Footer Lockup
+/**
+ * Adds either a `footerLockup` or `arcadeFooterLockup` property on `ArticlePage` model, based on type of article.
+ * @param page Page to add footer to if needed.
+ * @param articleData Original data of article being rendered.
+ * @param context Parse context for article builder.
+ */
+function addFooterLockupForPageIfNeeded(objectGraph, page, articleData, context) {
+ // App Lockup for Articles about single specific app.
+ const footerProductData = productDataFromArticle(objectGraph, articleData);
+ if (footerProductData) {
+ const externalDeepLinkUrl = externalDeepLink.deepLinkUrlFromData(objectGraph, articleData);
+ page.footerLockup = productFooterLockupFromData(objectGraph, footerProductData, context, externalDeepLinkUrl);
+ return;
+ }
+ // Arcade Lockup for Acquisition Story for supported platforms
+ const isArcadeAcquisitionEI = mediaAttributes.attributeAsBooleanOrFalse(articleData, "isAcquisition");
+ const platformSupportsArcadeFooterLockup = objectGraph.host.isiOS || objectGraph.host.isMac;
+ const additionalDataIsAvailable = serverData.isDefinedNonNull(context.additionalData);
+ if (additionalDataIsAvailable && isArcadeAcquisitionEI && platformSupportsArcadeFooterLockup) {
+ const upsellData = arcadeCommon.upsellFromRelationshipOf(objectGraph, articleData);
+ page.arcadeFooterLockup = arcadeFooterLockupFromData(objectGraph, upsellData, context);
+ }
+}
+/**
+ * Find platform data from editorial item to enhance sharing and display in footer lockup
+ * At the moment, only single app editorials get footer lockups and have enhanced sharing.
+ *
+ * @param editorialItem Item to find footer content for
+ * @returns content to display in footer lockup, or null if no content should be displayed
+ */
+export function productDataFromArticle(objectGraph, editorialItem) {
+ const relatedContent = mediaRelationships.relationshipCollection(editorialItem, "card-contents");
+ if (relatedContent.length !== 1) {
+ return null;
+ }
+ const contentData = relatedContent[0];
+ if (!contentData) {
+ return null;
+ }
+ switch (contentData.type) {
+ case "apps":
+ case "app-bundles":
+ return contentData;
+ default:
+ return null;
+ }
+}
+/**
+ * Creates a footer lockup with a data for a specific app.
+ * Cover method over `lockupFromData` to override `offerStyle`.
+ *
+ * @param data MAPI data to build footer with.
+ * @param context Parse context
+ * @param externalDeepLinkUrl promotional deep link url to use on the lockup's offer.
+ * @returns A new `Lockup` object for footer lockups.
+ */
+function productFooterLockupFromData(objectGraph, data, context, externalDeepLinkUrl) {
+ const lockupOptions = {
+ offerStyle: footerLockupOfferStyle(objectGraph),
+ metricsOptions: {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ },
+ clientIdentifierOverride: context.clientIdentifierOverride,
+ externalDeepLinkUrl: externalDeepLinkUrl,
+ crossLinkSubtitle: context.crossLinkSubtitle,
+ artworkUseCase: 0 /* content.ArtworkUseCase.Default */,
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "smallLockup"),
+ };
+ return lockups.lockupFromData(objectGraph, data, lockupOptions);
+}
+/**
+ * Creates a footer lockup representing the Arcade subscription service.
+ * @param upsellData Contains both editorial and iAP data for Arcade
+ * @param context Parse context.
+ */
+function arcadeFooterLockupFromData(objectGraph, upsellData, context) {
+ const metricsOptions = {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ };
+ return lockups.arcadeLockupFromData(objectGraph, upsellData, metricsOptions, models.marketingItemContextFromString("editorialItem"), "infer", null);
+}
+/**
+ * Determines the offer style to use for the footer lockup.
+ */
+function footerLockupOfferStyle(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "mac":
+ return "white";
+ default:
+ return "infer";
+ }
+}
+// endregion
+function createFallbackListShelf(objectGraph, cardMedia) {
+ if (cardMedia instanceof models.TodayCardMediaList || cardMedia instanceof models.TodayCardMediaRiver) {
+ const fallbackShelf = new models.Shelf("smallLockup");
+ fallbackShelf.items = cardMedia.lockups;
+ if (objectGraph.client.isWeb) {
+ fallbackShelf.presentationHints = {
+ ...fallbackShelf.presentationHints,
+ isArticleContext: true,
+ };
+ }
+ return fallbackShelf;
+ }
+ return null;
+}
+function shareSheetActionFromData(objectGraph, editorialItem, todayCardConfig) {
+ const productData = productDataFromArticle(objectGraph, editorialItem);
+ /*
+ * Determine title
+ */
+ let title = null;
+ const name = content.notesFromData(objectGraph, editorialItem, "name");
+ const short = content.notesFromData(objectGraph, editorialItem, "short");
+ // Prefer "name: short"
+ if (name && short) {
+ title = objectGraph.loc
+ .string("ShareSheet.TitleSubtitle.Format", "{title}: {subtitle}")
+ .replace("{title}", name)
+ .replace("{subtitle}", short);
+ }
+ // Followed by name
+ if (!title && name) {
+ title = name;
+ }
+ // Followed by short
+ if (!title && short) {
+ title = short;
+ }
+ // Followed by product name
+ if (!title && productData) {
+ const productTitle = mediaAttributes.attributeAsString(productData, "name");
+ const cardDisplayStyle = mediaAttributes.attributeAsString(editorialItem, "cardDisplayStyle");
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.GameOfTheDay: {
+ title = objectGraph.loc.string("SHARE_SHEET_GAME_OF_DAY_TITLE_FORMAT").replace("{title}", productTitle);
+ break;
+ }
+ case TodayCardDisplayStyle.AppOfTheDay: {
+ title = objectGraph.loc.string("SHARE_SHEET_APP_OF_DAY_TITLE_FORMAT").replace("{title}", productTitle);
+ break;
+ }
+ default: {
+ objectGraph.console.log(`No title for article with unknown style: ${cardDisplayStyle}`);
+ break;
+ }
+ }
+ }
+ const url = mediaAttributes.attributeAsString(editorialItem, "url");
+ let articleArtwork;
+ const cardDisplayStyle = mediaAttributes.attributeAsString(editorialItem, "cardDisplayStyle");
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.Grid:
+ case TodayCardDisplayStyle.List:
+ case TodayCardDisplayStyle.River:
+ articleArtwork = artworkBuilder.createArtworkForResource(objectGraph, "resource://ShareCollectionThumbnail", 40, 40);
+ break;
+ default:
+ articleArtwork = null;
+ break;
+ }
+ // Create share sheet model (bail out if unable to do so)
+ const shareData = sharing.shareSheetDataForArticle(objectGraph, title, url, null, articleArtwork, editorialItem);
+ if (!serverData.isDefinedNonNull(shareData)) {
+ return null;
+ }
+ const activities = sharing.shareSheetActivitiesForArticle(objectGraph, url, todayCardPreviewUrlForTodayCard(objectGraph, editorialItem.id, todayCardConfig), editorialItem.id);
+ return new models.ShareSheetAction(shareData, activities);
+}
+function createShareShelf(objectGraph, shareAction, context, isLastModuleFullWidth) {
+ if (!serverData.isDefinedNonNull(shareAction) ||
+ objectGraph.client.isVision ||
+ preprocessor.GAMES_TARGET ||
+ objectGraph.client.isCompanionVisionApp) {
+ return null;
+ }
+ // Create share button
+ const shareButton = new models.RoundedButton("share", objectGraph.loc.string("SHARE_STORY"), !isLastModuleFullWidth, shareAction);
+ // Add share shelf
+ const shareButtonShelf = new models.Shelf("roundedButton");
+ shareButtonShelf.items = [shareButton];
+ return shareButtonShelf;
+}
+function createParagraph(objectGraph, module, context) {
+ const text = mediaAttributes.attributeAsString(module, "editorialCopy");
+ if (!text) {
+ return null;
+ }
+ const paragraph = new models.Paragraph(text, "text/x-apple-as3-nqml", "article");
+ // Setup impressions
+ addImpressionsFieldsToModel(objectGraph, paragraph, context);
+ const shelf = new models.Shelf("paragraph");
+ shelf.items = [paragraph];
+ return shelf;
+}
+function createImage(objectGraph, module, context) {
+ const displayStyle = mediaAttributes.attributeAsString(module, "inlineImageDisplayType");
+ const artworkData = mediaAttributes.attributeAsDictionary(module, "artwork");
+ // If the displayStyle is FullWidth want to 'allowTransparency' so that images blend into the page in both
+ // light and dark mode. Previously editorial would bake white backgrounds into images they wanted to 'blend'
+ // with the page
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 13 /* content.ArtworkUseCase.ArticleImage */,
+ allowingTransparency: displayStyle === "FullWidth" && !objectGraph.client.isVision,
+ withJoeColorPlaceholder: objectGraph.client.isVision,
+ });
+ if (!artwork) {
+ return null;
+ }
+ const frame = new models.FramedArtwork(artwork, false, "text/x-apple-as3-nqml");
+ // Get the optional caption
+ frame.caption = mediaAttributes.attributeAsString(module, "editorialCopy");
+ context.subStyle = displayStyle;
+ if (displayStyle) {
+ switch (displayStyle) {
+ case "BoundingBox": {
+ frame.isFullWidth = false;
+ frame.hasRoundedCorners = true;
+ break;
+ }
+ case "FullWidth":
+ default: {
+ frame.isFullWidth = true;
+ frame.hasRoundedCorners = false;
+ break;
+ }
+ }
+ }
+ // Setup impressions
+ addImpressionsFieldsToModel(objectGraph, frame, context);
+ const shelf = new models.Shelf("framedArtwork");
+ shelf.items = [frame];
+ return shelf;
+}
+function createTip(objectGraph, module, context) {
+ const artworkData = mediaAttributes.attributeAsDictionary(module, "artwork");
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 13 /* content.ArtworkUseCase.ArticleImage */,
+ });
+ if (!artwork) {
+ return null;
+ }
+ const caption = mediaAttributes.attributeAsString(module, "editorialCopy");
+ const ordinal = mediaAttributes.attributeAsString(module, "tipNumber");
+ // Create the tip image
+ const frame = new models.FramedArtwork(artwork, false, "text/x-apple-as3-nqml");
+ frame.isFullWidth = false;
+ frame.hasRoundedCorners = true;
+ frame.caption = caption;
+ frame.ordinal = ordinal;
+ // Setup impressions
+ addImpressionsFieldsToModel(objectGraph, frame, context);
+ // Create the shelf
+ const shelf = new models.Shelf("framedArtwork");
+ shelf.items = [frame];
+ return shelf;
+}
+function createPullQuote(objectGraph, module, context) {
+ const text = mediaAttributes.attributeAsString(module, "quote");
+ const attribution = mediaAttributes.attributeAsString(module, "quoteAttribution");
+ // Get the optional artwork
+ const artworkData = mediaAttributes.attributeAsDictionary(module, "artwork");
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 13 /* content.ArtworkUseCase.ArticleImage */,
+ });
+ const fullWidth = mediaAttributes.attributeAsString(module, "pullQuoteDisplayType") === "FullWidth";
+ // Create the quote
+ const quote = new models.Quote(text, attribution, artwork, fullWidth);
+ // Setup impressions
+ addImpressionsFieldsToModel(objectGraph, quote, context);
+ // Create the shelf
+ const shelf = new models.Shelf("quote");
+ shelf.items = [quote];
+ return shelf;
+}
+function createHorizontalRule(objectGraph, module, context) {
+ const lineStyle = mediaAttributes.attributeAsString(module, "lineStyle");
+ const fullWidth = mediaAttributes.attributeAsString(module, "displayStyle") === "FullWidth";
+ let ruleColor = color.named("defaultLine");
+ if (objectGraph.client.isVision && (lineStyle === "Dotted" || lineStyle === "Dashed")) {
+ ruleColor = color.white;
+ }
+ // Parse the customColor from Media API. This can only be a dynamic color.
+ const apiColor = mediaAttributes.attributeAsDictionary(module, "customColor");
+ const lightColor = color.fromHex(serverData.asString(apiColor, "lightMode"));
+ const darkColor = color.fromHex(serverData.asString(apiColor, "darkMode"));
+ if (!serverData.isNullOrEmpty(lightColor) && !serverData.isNullOrEmpty(darkColor)) {
+ ruleColor = color.dynamicWith(lightColor, darkColor);
+ }
+ const horizontalRule = new models.HorizontalRule(lineStyle, ruleColor, fullWidth);
+ // Create the Shelf
+ const shelf = new models.Shelf("horizontalRule");
+ shelf.items = [horizontalRule];
+ return shelf;
+}
+function createVideo(objectGraph, module, context) {
+ // Get the preview artwork
+ const artworkData = mediaAttributes.attributeAsDictionary(module, "video.previewFrame");
+ const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 13 /* content.ArtworkUseCase.ArticleImage */,
+ });
+ if (!artwork) {
+ return null;
+ }
+ // Get the video URL
+ const videoUrl = mediaAttributes.attributeAsString(module, "video.video");
+ if (!videoUrl) {
+ return null;
+ }
+ const videoDisplayType = mediaAttributes.attributeAsString(module, "inlineVideoDisplayType");
+ const isFullWidth = videoDisplayType === "FullWidth";
+ // Create the video
+ const video = new models.Video(videoUrl, artwork, videoDefaults.defaultVideoConfiguration(objectGraph));
+ metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, video, {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ id: context.pageId,
+ });
+ const videoModule = new models.FramedVideo(video, isFullWidth, "text/x-apple-as3-nqml");
+ // Get the optional caption
+ videoModule.caption = mediaAttributes.attributeAsString(module, "editorialCopy");
+ // Setup impressions
+ addImpressionsFieldsToModel(objectGraph, videoModule, context);
+ // Create the shelf
+ const shelf = new models.Shelf("framedVideo");
+ shelf.items = [videoModule];
+ return shelf;
+}
+function createAppLockup(objectGraph, module, context) {
+ const contentData = contentFromModule(objectGraph, module, context);
+ if (!contentData) {
+ return null;
+ }
+ // Shelf to generate. Either lockup, app showcase, or app event shelf
+ let shelf = null;
+ // If we have an app-events relationship, we want to use this as the priority. This sometimes exists on the
+ // AppLockup type, rather than as the AppEventLockup type, so that older clients can still render this
+ // item by falling back to the AppLockup type.
+ const appEventsDataItems = mediaRelationships.relationshipCollection(module, "app-events");
+ if (serverData.isDefinedNonNullNonEmpty(appEventsDataItems)) {
+ shelf = appPromotionsShelf.appEventsShelfForArticle(objectGraph, appEventsDataItems, context.metricsPageInformation, context.metricsLocationTracker, context);
+ if (serverData.isDefinedNonNull(shelf)) {
+ return shelf;
+ }
+ }
+ // Set the display style
+ const displayStyle = mediaAttributes.attributeAsString(module, "appLockupSize");
+ context.subStyle = displayStyle;
+ let shelfStyle;
+ let isLockup = false;
+ if (displayStyle) {
+ switch (displayStyle) {
+ case "Small": {
+ shelfStyle = "smallLockup";
+ isLockup = true;
+ break;
+ }
+ case "Medium": {
+ shelfStyle = "mediumLockup";
+ isLockup = true;
+ break;
+ }
+ case "Large":
+ default: {
+ if (objectGraph.client.isWatch ||
+ objectGraph.client.isTV ||
+ objectGraph.client.isVision ||
+ preprocessor.GAMES_TARGET) {
+ // Per design, on watchOS we always show a lockup for app showcases.
+ // Watch App Store treats all lockup sizes the same -- let's pick small.
+ shelfStyle = "smallLockup";
+ isLockup = true;
+ }
+ else {
+ shelfStyle = "appShowcase";
+ }
+ break;
+ }
+ }
+ }
+ // Determine the deep link URL, if there is one.
+ const externalDeepLinkUrl = externalDeepLink.deepLinkUrlFromData(objectGraph, module);
+ // Create the appropriate shelf item
+ if (isLockup) {
+ const lockupShelf = new models.Shelf(shelfStyle);
+ const metricsOptions = {
+ metricsOptions: {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ },
+ clientIdentifierOverride: context.clientIdentifierOverride,
+ externalDeepLinkUrl: externalDeepLinkUrl,
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfStyle),
+ };
+ let lockup;
+ if (preprocessor.GAMES_TARGET) {
+ const shelfID = new PageID(context.pageId).shelfID(module.id);
+ lockup = gamesComponentBuilder.makeArticleGameLockup(objectGraph, contentData, shelfID);
+ }
+ else {
+ lockup = lockups.lockupFromData(objectGraph, contentData, metricsOptions);
+ }
+ if (isNothing(lockup)) {
+ return null;
+ }
+ lockupShelf.items = [lockup];
+ shelf = lockupShelf;
+ }
+ else {
+ // On all platforms, the AppLockup platform generates a AppShowcase when display style is large.
+ shelf = createAppShowcase(objectGraph, module, context);
+ }
+ return shelf;
+}
+function createAppShowcase(objectGraph, module, context) {
+ // Create the shelf
+ const shelf = new models.Shelf("appShowcase");
+ // Parameterize by platform:
+ // tvOS populates the `screenshots` field to display alongside video.
+ const showcaseHasScreenshots = objectGraph.client.isTV;
+ // Only non-tvOS has shelf background color
+ const shelfHasBackgroundColor = objectGraph.client.deviceType !== "tv";
+ const contentData = contentFromModule(objectGraph, module, context);
+ const externalDeepLinkUrl = externalDeepLink.deepLinkUrlFromData(objectGraph, module);
+ const lockup = lockups.lockupFromData(objectGraph, contentData, {
+ offerStyle: "colored",
+ metricsOptions: {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ },
+ clientIdentifierOverride: context.clientIdentifierOverride,
+ externalDeepLinkUrl: externalDeepLinkUrl,
+ crossLinkSubtitle: context.crossLinkSubtitle,
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ });
+ const showcase = new models.AppShowcase("large", lockup);
+ showcase.description = lockups.subtitleFromData(objectGraph, contentData);
+ // Add Video
+ // Configure the video for the showcase, if the module demands it.
+ let showcaseVideo = null;
+ const videoType = mediaAttributes.attributeAsString(module, "appLockupVideo");
+ switch (videoType) {
+ case "AppTrailer": {
+ const allAppVideos = content.videoPreviewsFromData(objectGraph, contentData);
+ if (allAppVideos && allAppVideos.length > 0) {
+ showcaseVideo = allAppVideos[0];
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ if (showcaseVideo) {
+ metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, showcaseVideo, {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ id: context.pageId,
+ });
+ showcase.video = showcaseVideo;
+ }
+ // Add Screenshots for AppShowcase if necessary
+ if (showcaseHasScreenshots) {
+ showcase.screenshots = content.screenshotsFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */, [content.currentAppPlatform(objectGraph)]);
+ }
+ // Configure background if necessary.
+ if (shelfHasBackgroundColor) {
+ shelf.background = {
+ type: "color",
+ color: appShowcaseBackgroundColor,
+ };
+ }
+ shelf.items = [showcase];
+ return shelf;
+}
+function createIAPLockup(objectGraph, module, context) {
+ const contentData = contentFromModule(objectGraph, module, context);
+ if (!contentData) {
+ return null;
+ }
+ // Create the lockup
+ const lockup = lockups.inAppPurchaseLockupFromData(objectGraph, contentData, {
+ metricsOptions: {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ },
+ clientIdentifierOverride: context.clientIdentifierOverride,
+ artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
+ });
+ if (!lockup) {
+ return null;
+ }
+ const showcase = new models.InAppPurchaseShowcase(lockup);
+ // Create the shelf
+ const shelf = new models.Shelf("inAppPurchaseShowcase");
+ shelf.background = {
+ type: "color",
+ color: iAPBackgroundColor,
+ };
+ shelf.items = [showcase];
+ return shelf;
+}
+function createAppList(objectGraph, module, context) {
+ const showOrdinals = mediaAttributes.attributeAsBooleanOrFalse(module, "showOrdinals");
+ const ordinalDirection = mediaAttributes.attributeAsString(module, "collectionLockupDisplayType") === "OrdinalDesc"
+ ? "descending"
+ : "ascending";
+ // Set the display style
+ const displayStyle = mediaAttributes.attributeAsString(module, "collectionLockupSize");
+ context.subStyle = displayStyle;
+ let style;
+ if (displayStyle) {
+ switch (displayStyle) {
+ case "Large": {
+ style = "largeLockup";
+ break;
+ }
+ case "Medium": {
+ style = "mediumLockup";
+ break;
+ }
+ case "Small":
+ default: {
+ style = "smallLockup";
+ break;
+ }
+ }
+ }
+ // Construct the lockup options
+ const lockupOptions = {
+ metricsOptions: {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ },
+ clientIdentifierOverride: context.clientIdentifierOverride,
+ artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, style),
+ canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, style),
+ };
+ // Check if we have content
+ const contents = mediaRelationships.relationshipCollection(module, "contents");
+ if (isNothing(contents)) {
+ return null;
+ }
+ let childLockups = [];
+ if (preprocessor.GAMES_TARGET) {
+ const shelfID = new PageID(context.pageId).shelfID(module.id);
+ childLockups = gamesComponentBuilder.makeArticleGameLockups(objectGraph, contents, shelfID);
+ }
+ else {
+ childLockups = lockups.lockupsFromData(objectGraph, contents, {
+ includeOrdinals: showOrdinals,
+ ordinalDirection: ordinalDirection,
+ lockupOptions: lockupOptions,
+ });
+ }
+ if (!childLockups || childLockups.length === 0) {
+ return null;
+ }
+ // Create the shelf
+ const shelf = new models.Shelf(style);
+ shelf.items = childLockups;
+ return shelf;
+}
+function createAppMedia(objectGraph, module, context) {
+ const contentData = contentFromModule(objectGraph, module, context);
+ if (!contentData) {
+ return null;
+ }
+ // Set the display style
+ const mediaOption = mediaAttributes.attributeAsString(module, "appMediaOption");
+ const appMediaPlatform = mediaAttributes.attributeAsString(module, "appMediaPlatform");
+ context.subStyle = mediaOption;
+ switch (mediaOption) {
+ case "Screenshots": {
+ let shelf = null;
+ // I'm so sorry, but making this split makes the macOS client code infinitely better because we are able
+ // to reuse the same product media view and component contract that is used on product page screenshots/trailers.
+ // Really, iOS should be reworked such that its module & product page implementation has a single source,
+ // but this has serious design obstacles that need to be worked through.
+ if (objectGraph.client.isMac) {
+ shelf = new models.Shelf("productMedia");
+ const productMedia = content.productMediaFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */);
+ if (serverData.isDefinedNonNull(productMedia) && productMedia.length) {
+ shelf.items = productMedia;
+ }
+ }
+ else {
+ shelf = new models.Shelf("screenshots");
+ if (serverData.isNull(appMediaPlatform)) {
+ /**
+ * The server did not tell us which app platform to use, so we need to infer based on various keys in
+ * the response. These parameters are only fully baked into product-dv responses, so we we need to do
+ * the more expensive product-dv lookup in order to correctly infer the default screenshots to use for
+ * the shelf.
+ */
+ const screenshots = content.screenshotsFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */);
+ if (screenshots && screenshots.length > 0) {
+ shelf.items = [screenshots[0]];
+ }
+ }
+ else {
+ /**
+ * Server tells us which platform to use -- dictated by `appMediaPlatform`. Selectively do a lookup for
+ * just those screenshots.
+ */
+ const desiredAppPlatform = appPlatformFromAppMediaPlatform(objectGraph, appMediaPlatform);
+ if (desiredAppPlatform) {
+ const screenshots = content.screenshotsFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */, [desiredAppPlatform]);
+ if (screenshots && screenshots.length) {
+ shelf.items = [screenshots[0]];
+ }
+ }
+ }
+ }
+ if (serverData.isDefinedNonNull(shelf) && shelf.items.length === 0) {
+ return null;
+ }
+ return shelf;
+ }
+ case "AppTrailers":
+ const trailersShelf = new models.Shelf("framedVideo");
+ const videoPreviews = content.videoPreviewsFromData(objectGraph, contentData);
+ if (videoPreviews && videoPreviews.length > 0) {
+ const video = videoPreviews[0];
+ metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, video, {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ id: context.pageId,
+ });
+ const firstTrailer = new models.FramedVideo(video, false, "text/plain", null, null, true);
+ trailersShelf.items = [firstTrailer];
+ return trailersShelf;
+ }
+ else {
+ return null;
+ }
+ default: {
+ return null;
+ }
+ }
+}
+function createLink(objectGraph, module, context) {
+ if (objectGraph.client.isTV || objectGraph.client.isWatch) {
+ return null;
+ }
+ const urlString = mediaAttributes.attributeAsString(module, "url");
+ if (!urlString) {
+ return null;
+ }
+ const url = new urls.URL(urlString);
+ const linkTitle = mediaAttributes.attributeAsString(module, "urlTitle");
+ let text = mediaAttributes.attributeAsString(module, "editorialCopy");
+ if (!text) {
+ text = url.host;
+ }
+ const mediaHosts = [
+ "itunes.apple.com",
+ "apps.apple.com",
+ "music.apple.com",
+ "books.apple.com",
+ "podcasts.apple.com",
+ "watch-app.cdn-apple.com",
+ "tv.apple.com",
+ ];
+ let linkPresentationEnabled = false;
+ for (const mediaHost of mediaHosts) {
+ if (url.host.endsWith(mediaHost)) {
+ linkPresentationEnabled = true;
+ }
+ }
+ const action = new models.ExternalUrlAction(urlString);
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, {
+ targetType: "link",
+ pageInformation: context.metricsPageInformation,
+ id: `${context.index}`,
+ locationTracker: context.metricsLocationTracker,
+ });
+ const link = new models.EditorialLink(linkTitle, text, action, linkPresentationEnabled);
+ // Setup impressions
+ addImpressionsFieldsToModel(objectGraph, link, context);
+ const shelf = new models.Shelf("editorialLink");
+ shelf.items = [link];
+ return shelf;
+}
+function createTextList(objectGraph, module, context) {
+ const listEntries = mediaAttributes.attributeAsArrayOrEmpty(module, "editorialCopy");
+ if (!listEntries.length) {
+ return null;
+ }
+ const type = mediaAttributes.attributeAsString(module, "textListDisplayType");
+ context.subStyle = type;
+ let isBulleted = false;
+ switch (type) {
+ case "Bulleted": {
+ isBulleted = true;
+ break;
+ }
+ default: {
+ isBulleted = false;
+ break;
+ }
+ }
+ let text;
+ if (isBulleted) {
+ text = "<ul>";
+ }
+ else {
+ text = "<ol>";
+ }
+ for (const textEntry of listEntries) {
+ const listItemJSONString = JSON.stringify(textEntry);
+ // rdar://104446319 - We must use `parse` on our JSON string to convert back to
+ // a raw string object as this ensures leading/trailing quotation marks are *not* escaped
+ const listItem = JSON.parse(listItemJSONString);
+ text = `${text}<li>${listItem}</li>`;
+ }
+ if (isBulleted) {
+ text = `${text}</ul>`;
+ }
+ else {
+ text = `${text}</ol>`;
+ }
+ const paragraph = new models.Paragraph(text, "text/x-apple-as3-nqml", "article");
+ // Setup impressions
+ addImpressionsFieldsToModel(objectGraph, paragraph, context);
+ const shelf = new models.Shelf("paragraph");
+ shelf.items = [paragraph];
+ return shelf;
+}
+function createStoryCards(objectGraph, module, context, shelfIndex) {
+ if (objectGraph.client.isVision) {
+ const shelfToken = createBaseShelfToken(objectGraph, undefined, module, false, shelfIndex, context.metricsPageInformation, context.metricsLocationTracker);
+ const shelf = buildSmallStoryCardShelf(objectGraph, shelfToken);
+ shelf.isHorizontal = true;
+ return shelf;
+ }
+ const cards = mediaRelationships.relationshipCollection(module, "contents");
+ if (!cards) {
+ return null;
+ }
+ const title = mediaAttributes.attributeAsString(module, "name");
+ const subtitle = mediaAttributes.attributeAsString(module, "tagline");
+ let shelf = null;
+ if (objectGraph.client.isiOS && objectGraph.featureFlags.isEnabled("mini_today_cards_article")) {
+ const todayParseContext = new TodayParseContext(context.metricsPageInformation, context.metricsLocationTracker);
+ shelf = todayHorizontalCardUtil.shelfForMiniTodayCards(objectGraph, cards, title, subtitle, todayParseContext);
+ }
+ else {
+ const isSmallStoryCardsSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isWeb;
+ const resolvedContentType = isSmallStoryCardsSupported ? "smallStoryCard" : "todayBrick";
+ shelf = todayHorizontalCardUtil.shelfForHorizontalCardItems(objectGraph, cards, resolvedContentType, title, subtitle, context, null);
+ if (isSmallStoryCardsSupported) {
+ // Only specific small story cards are supported and will crash otherwise, filter those here preemptively.
+ // rdar://91965501 (MAS Crashing - Earth Day Landing Page - 4/19)
+ if (Array.isArray(shelf.items)) {
+ shelf.items = shelf.items.filter((item) => {
+ if (!(item instanceof models.TodayCard)) {
+ return true;
+ }
+ return todayHorizontalCardUtil.isHorizontalCardSupportedForKind(objectGraph, item.media.kind, resolvedContentType);
+ });
+ }
+ }
+ }
+ return shelf;
+}
+function createAppEventLockup(objectGraph, module, context) {
+ const contentData = contentFromModule(objectGraph, module, context);
+ if (!contentData) {
+ return null;
+ }
+ return appPromotionsShelf.appEventsShelfForArticle(objectGraph, [contentData], context.metricsPageInformation, context.metricsLocationTracker, context);
+}
+function createOfferItemLockup(objectGraph, module, context) {
+ if (!objectGraph.client.isiOS) {
+ return [];
+ }
+ const offerItem = mediaRelationships.relationshipData(objectGraph, module, "contents");
+ if (serverData.isNullOrEmpty(offerItem)) {
+ return null;
+ }
+ // Offer detail Paragraph
+ const offerParagraph = mediaAttributes.attributeAsString(module, "editorialCopy");
+ const paragraph = new models.Paragraph(offerParagraph, "text/x-apple-as3-nqml", "article");
+ const paragraphShelf = new models.Shelf("paragraph");
+ paragraphShelf.items = [paragraph];
+ // Winback Offer Card
+ const offerItemShelf = appPromotionsShelf.appEventsShelfForArticle(objectGraph, [offerItem], context.metricsPageInformation, context.metricsLocationTracker, context);
+ return [paragraphShelf, offerItemShelf];
+}
+/**
+ * Ingests EI canvas modules of form:
+ * {
+ * id: <editorial-id>,
+ * type: "editorial-item-shelves",
+ * attributes: {
+ * displayType: "AppMarker",
+ * appMarkerType: <AppMarkerType>
+ * }
+ * }
+ * to generate an shelf for AppMarker model.
+ */
+function createAppMarker(objectGraph, appMarkerModule, articleData, context) {
+ const markerType = mediaAttributes.attributeAsString(appMarkerModule, "appMarkerType");
+ context.subStyle = markerType;
+ let shelf = null;
+ switch (markerType) {
+ case "OSUpgrade":
+ shelf = createOSUpgradeClientControlButton(objectGraph, appMarkerModule, context);
+ break;
+ case "Acquisition":
+ shelf = createArcadeShowcase(objectGraph, appMarkerModule, articleData, context);
+ break;
+ default:
+ break;
+ }
+ return shelf;
+}
+/**
+ * Ingests EI canvas modules of form:
+ * {
+ * id: <editorial-id>,
+ * type: "editorial-item-shelves",
+ * attributes: {
+ * displayType: "AppMarker",
+ * appMarkerType: "OSUpgrade"
+ * }
+ * }
+ * to generate an shelf with an button that links to preferences updates.
+ */
+function createOSUpgradeClientControlButton(objectGraph, osUpgradeModule, context) {
+ const deviceType = objectGraph.client.deviceType;
+ if (deviceType !== "mac") {
+ return null; // Early exit - Only MAS utilizes OS Upgrade Client Control Button currently.
+ }
+ const installUpdateUrl = links.osUpdateUrl(deviceType);
+ if (installUpdateUrl === null) {
+ return null;
+ }
+ // Action to Preferences
+ const openUpdatesAction = new models.ExternalUrlAction(installUpdateUrl);
+ // Action to open preferences is configured as `link`
+ metricsHelpersClicks.addClickEventToAction(objectGraph, openUpdatesAction, {
+ targetType: "link",
+ pageInformation: context.metricsPageInformation,
+ id: `${context.index}`,
+ locationTracker: context.metricsLocationTracker,
+ });
+ // Shelf model
+ const upgradeControlText = objectGraph.loc.string("CLIENT_CONTROL_OS_UPGRADE_TITLE", "CHECK FOR UPDATE");
+ const upgradeControl = new models.ClientControlButton(upgradeControlText, openUpdatesAction);
+ // Add impressions
+ addImpressionsFieldsToModel(objectGraph, upgradeControl, context);
+ const shelf = new models.Shelf("clientControlButton");
+ shelf.items = [upgradeControl];
+ return shelf;
+}
+/**
+ * Ingests EI canvas modules of form:
+ * {
+ * id: <editorial-id>,
+ * type: "editorial-item-shelves",
+ * }
+ * with additional data:
+ * - Upsell data on `context.additionalData`
+ * - Icon Artwork data (iOS only) on `context.additionalData`
+ *
+ * to generate an shelf that promotes Arcade service.
+ *
+ * @param arcadeShowcaseModule Arcade showcase module
+ * @param articleData The data backing the article containing the module. Used for top-level relationship.
+ * @param context Parse context for this page parsing. This context contains the additional requirements data.
+ */
+function createArcadeShowcase(objectGraph, arcadeShowcaseModule, articleData, context) {
+ const supportedOnPlatform = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.client.isVision;
+ if (!supportedOnPlatform) {
+ return null;
+ }
+ // Default to upsell on relation, falling back to upsell that may have been fetched separately for orphaned acquisition modules.
+ let upsellData = arcadeCommon.upsellFromRelationshipOf(objectGraph, articleData);
+ if (!upsellData && context.additionalData) {
+ const upsellResponse = context.additionalData.get("upsellForNonacquisitionCanvas");
+ upsellData = arcadeCommon.upsellFromContentsOfUpsellResponse(objectGraph, upsellResponse);
+ }
+ if (!serverData.isDefinedNonNull(upsellData)) {
+ return null;
+ }
+ const baseMetricsOptions = {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ };
+ // Flow to See All games if subscribed
+ const subscribedAction = arcadeCommon.openArcadeMainAction(objectGraph, context.metricsPageInformation, context.metricsLocationTracker, objectGraph.client.isVision);
+ if (preprocessor.GAMES_TARGET) {
+ subscribedAction.title = objectGraph.loc.string("OfferButton.Arcade.Title.Explore");
+ }
+ else {
+ subscribedAction.title = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE");
+ }
+ // Flow to Arcade Subscribe page if unsubscribed.
+ let unsubscribedAction;
+ const unsubscribedActionTitle = breakoutsCommon.callToActionLabelFromData(objectGraph, upsellData.marketingItemData);
+ if (serverData.isDefinedNonNullNonEmpty(unsubscribedActionTitle)) {
+ // We support an inline offer here instead, when the pricing token is there.
+ unsubscribedAction = arcadeUpsell.arcadeOfferButtonActionFromData(objectGraph, upsellData.marketingItemData, models.marketingItemContextFromString("editorialItemCanvas"), baseMetricsOptions);
+ if (serverData.isDefinedNonNull(unsubscribedAction)) {
+ unsubscribedAction.title = unsubscribedActionTitle;
+ }
+ }
+ else {
+ // If Upsell EI is misconfigured and missing `breakoutCallToActionLabel`, default to opening Arcade app for unsubscribed state.
+ unsubscribedAction = arcadeCommon.openArcadeMainAction(objectGraph, context.metricsPageInformation, context.metricsLocationTracker, objectGraph.client.isVision);
+ if (preprocessor.GAMES_TARGET) {
+ unsubscribedAction.title = objectGraph.loc.string("OfferButton.Arcade.Title.Explore");
+ }
+ else {
+ unsubscribedAction.title = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE");
+ }
+ }
+ const arcadeShowcase = new models.ArcadeShowcase(unsubscribedAction, subscribedAction);
+ const unsubscribedDescription = arcadeUpsell.descriptionFromData(objectGraph, upsellData.marketingItemData);
+ arcadeShowcase.unsubscribedDescription = unsubscribedDescription;
+ const offerDisplayProperties = new models.OfferDisplayProperties("arcade", objectGraph.bag.arcadeAppAdamId, null, "colored", null, "dark", null, null, null, null, null, null, null, null, null, null, null, null, objectGraph.bag.arcadeProductFamilyId);
+ if (preprocessor.GAMES_TARGET) {
+ offerDisplayProperties.titles["subscribed"] = objectGraph.loc.string("OfferButton.Arcade.Title.Explore");
+ }
+ else {
+ offerDisplayProperties.titles["subscribed"] = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE");
+ }
+ arcadeShowcase.offerDisplayProperties = offerDisplayProperties;
+ const showcaseMetricsOptions = {
+ ...baseMetricsOptions,
+ targetType: "arcadeShowcase",
+ title: unsubscribedActionTitle,
+ id: arcadeShowcaseModule.id,
+ kind: "arcadeShowcase",
+ softwareType: null,
+ displaysArcadeUpsell: true,
+ };
+ metricsHelpersImpressions.addImpressionFields(objectGraph, arcadeShowcase, showcaseMetricsOptions);
+ // Build Artwork for iOS only
+ if (objectGraph.host.isiOS || objectGraph.client.isVision) {
+ // Context should have additional data to source icons.
+ if (serverData.isNull(context.additionalData)) {
+ return null;
+ }
+ const iconResponse = context.additionalData.get("arcadeIcons");
+ if (serverData.isDefinedNonNullNonEmpty(iconResponse)) {
+ const iconMetricsOptions = {
+ pageInformation: context.metricsPageInformation,
+ locationTracker: context.metricsLocationTracker,
+ };
+ const iconsDataCollection = mediaDataStructure.dataCollectionFromResultsListContainer(iconResponse);
+ arcadeShowcase.iconArtworks = content.impressionableAppIconsFromDataCollection(objectGraph, iconsDataCollection, iconMetricsOptions, {
+ useCase: 2 /* content.ArtworkUseCase.LockupIconMedium */,
+ });
+ }
+ }
+ const shelf = new models.Shelf("arcadeShowcase");
+ shelf.items = [arcadeShowcase];
+ const shelfHasBackgroundColor = objectGraph.host.isiOS || objectGraph.client.isVision;
+ if (shelfHasBackgroundColor) {
+ shelf.background = {
+ type: "color",
+ color: arcadeShowcaseShelfBackgroundColor,
+ };
+ }
+ return shelf;
+}
+// endregion
+function contentFromModule(objectGraph, module, context) {
+ const contents = mediaRelationships.relationshipData(objectGraph, module, "contents");
+ if (!contents) {
+ return null;
+ }
+ return contents;
+}
+function addImpressionsFieldsToModel(objectGraph, model, context, impressionData) {
+ if (!model) {
+ return;
+ }
+ let impressionType = context.module;
+ if (context.subStyle) {
+ impressionType = impressionType + "_" + context.subStyle;
+ }
+ if (serverData.isNull(impressionData)) {
+ impressionData = {
+ id: `${context.index}`,
+ impressionIndex: context.index,
+ idType: "sequential",
+ impressionType: impressionType,
+ kind: "iosModule",
+ };
+ }
+ model.impressionMetrics = new models.ImpressionMetrics(metricsHelpersUtil.sanitizedMetricsDictionary(impressionData));
+}
+//# sourceMappingURL=article.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-motion-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-motion-16x9.js
new file mode 100644
index 0000000..b209cba
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-motion-16x9.js
@@ -0,0 +1,34 @@
+import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const categoryDetailMotion16x9Layout_mini = {
+ collapsedContentMode: ArtworkContentMode.bottom,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const categoryDetailMotion16x9Layout_base = {
+ collapsedContentMode: ArtworkContentMode.top,
+ expandedContentMode: ArtworkContentMode.top,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const categoryDetailMotion16x9Metrics_mini = {
+ maxWidth: 250.0,
+ ltr: categoryDetailMotion16x9Layout_mini,
+ rtl: categoryDetailMotion16x9Layout_mini,
+ collapsedSize: { type: "absolute", width: 455, height: 256 },
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const categoryDetailMotion16x9Metrics_base = {
+ ltr: categoryDetailMotion16x9Layout_base,
+ rtl: categoryDetailMotion16x9Layout_base,
+ collapsedSize: { type: "fractionalHeight", height: 1.0 },
+};
+export const categoryDetailMotion16x9 = {
+ objectPath: "editorialVideo.categoryDetailMotion16x9",
+ cardArtLayoutMetrics: [categoryDetailMotion16x9Metrics_mini, categoryDetailMotion16x9Metrics_base],
+ sourceWidth: 800,
+ sourceHeight: 450,
+ type: "video",
+ crops: ["sr"],
+};
+//# sourceMappingURL=category-detail-motion-16x9.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-static-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-static-16x9.js
new file mode 100644
index 0000000..ab3c57f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/category-detail-static-16x9.js
@@ -0,0 +1,49 @@
+import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const categoryDetailStatic16x9Layout_mini = {
+ collapsedContentMode: ArtworkContentMode.bottom,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const categoryDetailStatic16x9Layout_base = {
+ collapsedContentMode: ArtworkContentMode.bottom,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const categoryDetailStatic16x9Layout_700w = {
+ collapsedContentMode: ArtworkContentMode.scaleAspectFill,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const categoryDetailStatic16x9Metrics_mini = {
+ maxWidth: 250.0,
+ ltr: categoryDetailStatic16x9Layout_mini,
+ rtl: categoryDetailStatic16x9Layout_mini,
+ collapsedSize: { type: "absolute", width: 455, height: 256 },
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const categoryDetailStatic16x9Metrics_base = {
+ maxWidth: 704,
+ ltr: categoryDetailStatic16x9Layout_base,
+ rtl: categoryDetailStatic16x9Layout_base,
+ collapsedSize: { type: "fractionalHeight", height: 1.08 },
+};
+const categoryDetailStatic16x9Metrics_700w = {
+ ltr: categoryDetailStatic16x9Layout_700w,
+ rtl: categoryDetailStatic16x9Layout_700w,
+};
+export const categoryDetailStatic16x9 = {
+ objectPath: "editorialArtwork.categoryDetailStatic16x9",
+ cardArtLayoutMetrics: [
+ categoryDetailStatic16x9Metrics_mini,
+ categoryDetailStatic16x9Metrics_base,
+ categoryDetailStatic16x9Metrics_700w,
+ ],
+ crops: ["sr"],
+ sourceWidth: 800,
+ sourceHeight: 450,
+ type: "image",
+};
+//# sourceMappingURL=category-detail-static-16x9.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/day-card.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/day-card.js
new file mode 100644
index 0000000..4688784
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/day-card.js
@@ -0,0 +1,51 @@
+import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const dayCardLayout_mini = {
+ collapsedContentMode: ArtworkContentMode.bottomLeft,
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: {
+ top: 0,
+ left: -8,
+ bottom: 0,
+ right: 0,
+ },
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const dayCardLayout_base = {
+ collapsedContentMode: ArtworkContentMode.bottomLeft,
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const dayCardLayout_700w = {
+ collapsedContentMode: ArtworkContentMode.topLeft,
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const dayCardMetrics_mini = {
+ maxWidth: 250,
+ ltr: dayCardLayout_mini,
+ rtl: dayCardLayout_mini,
+ collapsedSize: { type: "fractionalHeight", height: 1.0 },
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const dayCardMetrics_base = {
+ maxWidth: 704,
+ ltr: dayCardLayout_base,
+ rtl: dayCardLayout_base,
+ collapsedSize: { type: "fractionalHeight", height: 1.062 },
+};
+const dayCardMetrics_700w = {
+ ltr: dayCardLayout_700w,
+ rtl: dayCardLayout_700w,
+ collapsedSize: { type: "fractionalWidth", width: 1.0 },
+};
+export const dayCard = {
+ objectPath: "editorialArtwork.dayCard",
+ cardArtLayoutMetrics: [dayCardMetrics_mini, dayCardMetrics_base, dayCardMetrics_700w],
+ crops: ["sr"],
+ sourceWidth: 800,
+ sourceHeight: 490,
+ type: "image",
+};
+//# sourceMappingURL=day-card.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/event-card.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/event-card.js
new file mode 100644
index 0000000..3317d1b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/event-card.js
@@ -0,0 +1,51 @@
+import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const eventCardLayout_mini = {
+ collapsedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: {
+ top: 0,
+ left: -8,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const eventCardLayout_base = {
+ collapsedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const eventCardLayout_700w = {
+ collapsedContentMode: ArtworkContentMode.scaleAspectFill,
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const eventCardMetrics_mini = {
+ maxWidth: 250,
+ ltr: eventCardLayout_mini,
+ rtl: eventCardLayout_mini,
+ collapsedSize: { type: "fractionalHeight", height: 1.0 },
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const eventCardMetrics_base = {
+ maxWidth: 704,
+ ltr: eventCardLayout_base,
+ rtl: eventCardLayout_base,
+ collapsedSize: { type: "fractionalHeight", height: 1.062 },
+};
+const eventCardMetrics_700w = {
+ ltr: eventCardLayout_700w,
+ rtl: eventCardLayout_700w,
+ collapsedSize: { type: "fractionalWidth", width: 1.0 },
+};
+export const eventCard = {
+ objectPath: "editorialArtwork.eventCard",
+ cardArtLayoutMetrics: [eventCardMetrics_mini, eventCardMetrics_base, eventCardMetrics_700w],
+ crops: ["sr"],
+ sourceWidth: 800,
+ sourceHeight: 490,
+ type: "image",
+};
+//# sourceMappingURL=event-card.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/general-card.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/general-card.js
new file mode 100644
index 0000000..f50840f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/general-card.js
@@ -0,0 +1,51 @@
+import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const generalCardLayout_mini = {
+ collapsedContentMode: ArtworkContentMode.bottomLeft,
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: {
+ top: 0,
+ left: -8,
+ bottom: 0,
+ right: 0,
+ },
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const generalCardLayout_base = {
+ collapsedContentMode: ArtworkContentMode.bottomLeft,
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const generalCardLayout_700w = {
+ collapsedContentMode: ArtworkContentMode.scaleAspectFill,
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const generalCardMetrics_mini = {
+ maxWidth: 250,
+ ltr: generalCardLayout_mini,
+ rtl: generalCardLayout_mini,
+ collapsedSize: { type: "fractionalHeight", height: 1.0 },
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const generalCardMetrics_base = {
+ maxWidth: 704,
+ ltr: generalCardLayout_base,
+ rtl: generalCardLayout_base,
+ collapsedSize: { type: "fractionalHeight", height: 1.062 },
+};
+const generalCardMetrics_700w = {
+ ltr: generalCardLayout_700w,
+ rtl: generalCardLayout_700w,
+ collapsedSize: { type: "fractionalWidth", width: 1.0 },
+};
+export const generalCard = {
+ objectPath: "editorialArtwork.generalCard",
+ cardArtLayoutMetrics: [generalCardMetrics_mini, generalCardMetrics_base, generalCardMetrics_700w],
+ crops: ["MC.ApSCFB01"],
+ sourceWidth: 800,
+ sourceHeight: 490,
+ type: "image",
+};
+//# sourceMappingURL=general-card.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/index.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/index.js
new file mode 100644
index 0000000..5efa2cb
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/index.js
@@ -0,0 +1,85 @@
+import { isSome } from "@jet/environment";
+import { dayCard } from "./day-card";
+import { eventCard } from "./event-card";
+import { generalCard } from "./general-card";
+import { listCardMotion21x9 } from "./list-card-motion-21x9";
+import { listCardStatic21x9 } from "./list-card-static-21x9";
+import { mediaCard } from "./media-card";
+import { storeFrontVideo } from "./store-front-video";
+import { storeFrontVideo4x3 } from "./store-front-video-4x3";
+import { storyCardMotion16x9 } from "./story-card-motion-16x9";
+import { storyCardStatic16x9 } from "./story-card-static-16x9";
+import { storyCenteredMotion16x9 } from "./story-centered-motion-16x9";
+import { storyCenteredStatic16x9 } from "./story-centered-static-16x9";
+import { storySearchStatic16x9 } from "./story-search-static-16x9";
+import { universalAMotion16x9 } from "./universal-a-motion-16x9";
+import { universalAStatic16x9 } from "./universal-a-static-16x9";
+import { categoryDetailMotion16x9 } from "./category-detail-motion-16x9";
+import { categoryDetailStatic16x9 } from "./category-detail-static-16x9";
+/**
+ * Hero position on iPad requires the new artwork flavor to be considered valid
+ */
+export function universalAConfigurations(objectGraph, isHeroPosition) {
+ return isHeroPosition
+ ? [universalAMotion16x9Configuration(objectGraph), universalAStatic16x9Configuration(objectGraph)]
+ : [];
+}
+export function dayCardConfiguration(objectGraph) {
+ return dayCard;
+}
+export function eventCardConfiguration(objectGraph) {
+ return eventCard;
+}
+export function generalCardConfiguration(objectGraph, cropCodeOverrides) {
+ return applyOverrides(generalCard, cropCodeOverrides);
+}
+export function listCardMotion21x9Configuration(objectGraph) {
+ return listCardMotion21x9;
+}
+export function listCardStatic21x9Configuration(objectGraph) {
+ return listCardStatic21x9;
+}
+export function mediaCardConfiguration(objectGraph, cropCodeOverrides) {
+ return applyOverrides(mediaCard, cropCodeOverrides);
+}
+export function storeFrontVideoConfiguration(objectGraph) {
+ return storeFrontVideo;
+}
+export function storeFrontVideo4x3Configuration(objectGraph) {
+ return storeFrontVideo4x3;
+}
+export function storyCardMotion16x9Configuration(objectGraph) {
+ return storyCardMotion16x9;
+}
+export function storyCardStatic16x9Configuration(objectGraph) {
+ return storyCardStatic16x9;
+}
+export function storyCenteredMotion16x9Configuration(objectGraph) {
+ return storyCenteredMotion16x9;
+}
+export function storyCenteredStatic16x9Configuration(objectGraph, cropCodeOverrides) {
+ return applyOverrides(storyCenteredStatic16x9, cropCodeOverrides);
+}
+export function categoryDetailMotion16x9Configuration(objectGraph) {
+ return categoryDetailMotion16x9;
+}
+export function categoryDetailStatic16x9Configuration(objectGraph, cropCodeOverrides) {
+ return applyOverrides(categoryDetailStatic16x9, cropCodeOverrides);
+}
+export function storySearchStatic16x9Configuration(objectGraph) {
+ return storySearchStatic16x9;
+}
+function universalAMotion16x9Configuration(objectGraph) {
+ return universalAMotion16x9;
+}
+function universalAStatic16x9Configuration(objectGraph) {
+ return universalAStatic16x9;
+}
+function applyOverrides(mediaConfiguration, cropCodeOverrides) {
+ const updatedMediaConfiguration = { ...mediaConfiguration };
+ if (isSome(cropCodeOverrides)) {
+ updatedMediaConfiguration.crops = cropCodeOverrides;
+ }
+ return updatedMediaConfiguration;
+}
+//# sourceMappingURL=index.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-motion-21x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-motion-21x9.js
new file mode 100644
index 0000000..8570df2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-motion-21x9.js
@@ -0,0 +1,116 @@
+import { ArtworkContentMode, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const expandedContentMode = ArtworkContentMode.topRight;
+const expandedLayoutInsets = {
+ top: -108,
+ left: 495,
+ bottom: 0,
+ right: 0,
+};
+const expandedContentMode_rtl = ArtworkContentMode.topLeft;
+const expandedLayoutInsets_rtl = {
+ top: -108,
+ left: -495,
+ bottom: 0,
+ right: 0,
+};
+const expandedSize = { type: "absolute", width: 1124, height: 482 };
+const listCardMotion16x9Layout_mini = {
+ collapsedContentMode: ArtworkContentMode.top,
+ collapsedLayoutInsets: {
+ top: -117,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode,
+ expandedLayoutInsets,
+};
+const listCardMotion16x9Layout_mini_rtl = {
+ collapsedContentMode: ArtworkContentMode.top,
+ collapsedLayoutInsets: {
+ top: -117,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: expandedContentMode_rtl,
+ expandedLayoutInsets: expandedLayoutInsets_rtl,
+};
+const listCardMotion16x9Layout_base = {
+ collapsedContentMode: ArtworkContentMode.topRight,
+ collapsedLayoutInsets: {
+ top: -165,
+ left: 524,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode,
+ expandedLayoutInsets,
+};
+const listCardMotion16x9Layout_base_rtl = {
+ collapsedContentMode: ArtworkContentMode.topLeft,
+ collapsedLayoutInsets: {
+ top: -165,
+ left: -524,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: expandedContentMode_rtl,
+ expandedLayoutInsets: expandedLayoutInsets_rtl,
+};
+const listCardMotion16x9Layout_extraWide = {
+ collapsedContentMode: ArtworkContentMode.top,
+ collapsedLayoutInsets: {
+ top: -55,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode,
+ expandedLayoutInsets,
+};
+const listCardMotion16x9Layout_extraWide_rtl = {
+ collapsedContentMode: ArtworkContentMode.top,
+ collapsedLayoutInsets: {
+ top: -55,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: expandedContentMode_rtl,
+ expandedLayoutInsets: expandedLayoutInsets_rtl,
+};
+const listCardMotion16x9Metrics_mini = {
+ maxWidth: 250.0,
+ ltr: listCardMotion16x9Layout_mini,
+ rtl: listCardMotion16x9Layout_mini_rtl,
+ collapsedSize: { type: "absolute", width: 914, height: 392 },
+ expandedSize,
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const listCardMotion16x9Metrics_base = {
+ ltr: listCardMotion16x9Layout_base,
+ rtl: listCardMotion16x9Layout_base_rtl,
+ collapsedSize: { type: "absolute", width: 1188, height: 509 },
+ expandedSize,
+};
+const listCardMotion16x9Metrics_extraWide = {
+ ltr: listCardMotion16x9Layout_extraWide,
+ rtl: listCardMotion16x9Layout_extraWide_rtl,
+ collapsedSize: { type: "absolute", width: 1456, height: 624 },
+ expandedSize,
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.ExtraWide,
+};
+export const listCardMotion21x9 = {
+ objectPath: "editorialVideo.listCardMotion21x9",
+ cardArtLayoutMetrics: [
+ listCardMotion16x9Metrics_mini,
+ listCardMotion16x9Metrics_base,
+ listCardMotion16x9Metrics_extraWide,
+ ],
+ crops: [],
+ sourceWidth: 1208,
+ sourceHeight: 518,
+ type: "video",
+};
+//# sourceMappingURL=list-card-motion-21x9.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-static-21x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-static-21x9.js
new file mode 100644
index 0000000..8adecbe
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/list-card-static-21x9.js
@@ -0,0 +1,92 @@
+import { ArtworkContentMode, EdgeInsetsZero, Size, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const listCardStatic21x9Layout_mini = {
+ collapsedContentMode: ArtworkContentMode.bottomRight,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.topRight,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const listCardStatic21x9Layout_mini_rtl = {
+ collapsedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.topLeft,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const listCardStatic21x9Layout_base = {
+ collapsedContentMode: ArtworkContentMode.topRight,
+ collapsedLayoutInsets: {
+ top: -49,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.right,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const listCardStatic21x9Layout_base_rtl = {
+ collapsedContentMode: ArtworkContentMode.topLeft,
+ collapsedLayoutInsets: {
+ top: -49,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.left,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const listCardStatic16x9Layout_extraWide = {
+ collapsedContentMode: ArtworkContentMode.scaleAspectFill,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.bottomRight,
+ expandedLayoutInsets: {
+ top: 50,
+ left: 495,
+ bottom: 0,
+ right: 0,
+ },
+};
+const listCardStatic16x9Layout_extraWide_rtl = {
+ collapsedContentMode: ArtworkContentMode.scaleAspectFill,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.bottomRight,
+ expandedLayoutInsets: {
+ top: 50,
+ left: -495,
+ bottom: 0,
+ right: 0,
+ },
+};
+const listCardStatic21x9Metrics_mini = {
+ maxWidth: 250.0,
+ ltr: listCardStatic21x9Layout_mini,
+ rtl: listCardStatic21x9Layout_mini_rtl,
+ sourceCropOverrideLTR: "LCS.ApLCS01",
+ sourceCropOverrideRTL: "LCS.ApLCS02",
+ sourceSizeOverride: new Size(550, 264),
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const listCardStatic21x9Metrics_base = {
+ ltr: listCardStatic21x9Layout_base,
+ rtl: listCardStatic21x9Layout_base_rtl,
+ collapsedSize: { type: "absolute", width: 672, height: 279 },
+};
+const listCardMotion16x9Metrics_extraWide = {
+ ltr: listCardStatic16x9Layout_extraWide,
+ rtl: listCardStatic16x9Layout_extraWide_rtl,
+ expandedSize: { type: "absolute", width: 1124, height: 482 },
+ sourceCropOverrideLTR: "LCS.ApLCXW01",
+ sourceCropOverrideRTL: "LCS.ApLCXW01",
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.ExtraWide,
+};
+export const listCardStatic21x9 = {
+ objectPath: "editorialArtwork.listCardStatic21x9",
+ cardArtLayoutMetrics: [
+ listCardStatic21x9Metrics_mini,
+ listCardStatic21x9Metrics_base,
+ listCardMotion16x9Metrics_extraWide,
+ ],
+ crops: ["LCS.ApLCL01", "LCS.ApLCL02"],
+ sourceWidth: 688,
+ sourceHeight: 286,
+ type: "image",
+};
+//# sourceMappingURL=list-card-static-21x9.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/media-card.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/media-card.js
new file mode 100644
index 0000000..3b8ff21
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/media-card.js
@@ -0,0 +1,51 @@
+import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const mediaCardLayout_mini = {
+ collapsedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: {
+ top: 0,
+ left: -8,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const mediaCardLayout_base = {
+ collapsedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const mediaCardLayout_700w = {
+ collapsedContentMode: ArtworkContentMode.scaleAspectFill,
+ expandedContentMode: ArtworkContentMode.bottomLeft,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const mediaCardMetrics_mini = {
+ maxWidth: 250,
+ ltr: mediaCardLayout_mini,
+ rtl: mediaCardLayout_mini,
+ collapsedSize: { type: "fractionalHeight", height: 1.0 },
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const mediaCardMetrics_base = {
+ maxWidth: 704,
+ ltr: mediaCardLayout_base,
+ rtl: mediaCardLayout_base,
+ collapsedSize: { type: "fractionalHeight", height: 1.062 },
+};
+const mediaCardMetrics_700w = {
+ ltr: mediaCardLayout_700w,
+ rtl: mediaCardLayout_700w,
+ collapsedSize: { type: "fractionalWidth", width: 1.0 },
+};
+export const mediaCard = {
+ objectPath: "editorialArtwork.mediaCard",
+ cardArtLayoutMetrics: [mediaCardMetrics_mini, mediaCardMetrics_base, mediaCardMetrics_700w],
+ crops: ["MC.ApSCFB01"],
+ sourceWidth: 800,
+ sourceHeight: 490,
+ type: "image",
+};
+//# sourceMappingURL=media-card.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video-4x3.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video-4x3.js
new file mode 100644
index 0000000..653df0e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video-4x3.js
@@ -0,0 +1,22 @@
+import { ArtworkContentMode, EdgeInsetsZero } from "../../../../api/models";
+const storeFrontVideo4x3Layout = {
+ collapsedContentMode: ArtworkContentMode.center,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.center,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storeFrontVideo4x3Metrics = {
+ ltr: storeFrontVideo4x3Layout,
+ rtl: storeFrontVideo4x3Layout,
+ collapsedSize: { type: "fractionalHeight", height: 1.0 },
+ expandedSize: { type: "fractionalHeight", height: 1.0 },
+};
+export const storeFrontVideo4x3 = {
+ objectPath: "editorialVideo.storeFrontVideo4x3",
+ cardArtLayoutMetrics: [storeFrontVideo4x3Metrics],
+ crops: [],
+ sourceWidth: 656,
+ sourceHeight: 492,
+ type: "video",
+};
+//# sourceMappingURL=store-front-video-4x3.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video.js
new file mode 100644
index 0000000..472e52c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/store-front-video.js
@@ -0,0 +1,25 @@
+import { ArtworkContentMode, EdgeInsetsZero, } from "../../../../api/models";
+const storeFrontVideoLayout = {
+ collapsedContentMode: ArtworkContentMode.center,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.center,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storeFrontVideoMetrics = {
+ ltr: storeFrontVideoLayout,
+ rtl: storeFrontVideoLayout,
+ collapsedSize: { type: "fractionalHeight", height: 1.0 },
+ expandedSize: { type: "fractionalHeight", height: 1.0 },
+};
+/**
+ * This configuration is in 16x9 aspect ratio.
+ */
+export const storeFrontVideo = {
+ objectPath: "editorialVideo.storeFrontVideo",
+ cardArtLayoutMetrics: [storeFrontVideoMetrics],
+ crops: [],
+ sourceWidth: 875,
+ sourceHeight: 492,
+ type: "video",
+};
+//# sourceMappingURL=store-front-video.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-motion-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-motion-16x9.js
new file mode 100644
index 0000000..b0e4530
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-motion-16x9.js
@@ -0,0 +1,34 @@
+import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const storyCardMotion16x9Layout_mini = {
+ collapsedContentMode: ArtworkContentMode.bottom,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storyCardMotion16x9Layout_base = {
+ collapsedContentMode: ArtworkContentMode.top,
+ expandedContentMode: ArtworkContentMode.top,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storyCardMotion16x9Metrics_mini = {
+ maxWidth: 250.0,
+ ltr: storyCardMotion16x9Layout_mini,
+ rtl: storyCardMotion16x9Layout_mini,
+ collapsedSize: { type: "absolute", width: 455, height: 256 },
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const storyCardMotion16x9Metrics_base = {
+ ltr: storyCardMotion16x9Layout_base,
+ rtl: storyCardMotion16x9Layout_base,
+ collapsedSize: { type: "fractionalHeight", height: 1.0 },
+};
+export const storyCardMotion16x9 = {
+ objectPath: "editorialVideo.storyCardMotion16x9",
+ cardArtLayoutMetrics: [storyCardMotion16x9Metrics_mini, storyCardMotion16x9Metrics_base],
+ sourceWidth: 800,
+ sourceHeight: 450,
+ type: "video",
+ crops: ["sr"],
+};
+//# sourceMappingURL=story-card-motion-16x9.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-static-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-static-16x9.js
new file mode 100644
index 0000000..c4e7220
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-card-static-16x9.js
@@ -0,0 +1,49 @@
+import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const storyCardStatic16x9Layout_mini = {
+ collapsedContentMode: ArtworkContentMode.bottom,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storyCardStatic16x9Layout_base = {
+ collapsedContentMode: ArtworkContentMode.bottom,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storyCardStatic16x9Layout_700w = {
+ collapsedContentMode: ArtworkContentMode.scaleAspectFill,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storyCardStatic16x9Metrics_mini = {
+ maxWidth: 250.0,
+ ltr: storyCardStatic16x9Layout_mini,
+ rtl: storyCardStatic16x9Layout_mini,
+ collapsedSize: { type: "absolute", width: 455, height: 256 },
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const storyCardStatic16x9Metrics_base = {
+ maxWidth: 704,
+ ltr: storyCardStatic16x9Layout_base,
+ rtl: storyCardStatic16x9Layout_base,
+ collapsedSize: { type: "fractionalHeight", height: 1.08 },
+};
+const storyCardStatic16x9Metrics_700w = {
+ ltr: storyCardStatic16x9Layout_700w,
+ rtl: storyCardStatic16x9Layout_700w,
+};
+export const storyCardStatic16x9 = {
+ objectPath: "editorialArtwork.storyCardStatic16x9",
+ cardArtLayoutMetrics: [
+ storyCardStatic16x9Metrics_mini,
+ storyCardStatic16x9Metrics_base,
+ storyCardStatic16x9Metrics_700w,
+ ],
+ crops: ["sr"],
+ sourceWidth: 800,
+ sourceHeight: 450,
+ type: "image",
+};
+//# sourceMappingURL=story-card-static-16x9.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-motion-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-motion-16x9.js
new file mode 100644
index 0000000..273503b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-motion-16x9.js
@@ -0,0 +1,34 @@
+import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const storyCenteredMotion16x9Layout_mini = {
+ collapsedContentMode: ArtworkContentMode.bottom,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storyCenteredMotion16x9Layout_base = {
+ collapsedContentMode: ArtworkContentMode.top,
+ expandedContentMode: ArtworkContentMode.top,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storyCenteredMotion16x9Metrics_mini = {
+ maxWidth: 250.0,
+ ltr: storyCenteredMotion16x9Layout_mini,
+ rtl: storyCenteredMotion16x9Layout_mini,
+ collapsedSize: { type: "absolute", width: 455, height: 256 },
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const storyCenteredMotion16x9Metrics_base = {
+ ltr: storyCenteredMotion16x9Layout_base,
+ rtl: storyCenteredMotion16x9Layout_base,
+ collapsedSize: { type: "fractionalHeight", height: 1.0 },
+};
+export const storyCenteredMotion16x9 = {
+ objectPath: "editorialVideo.storyCenteredMotion16x9",
+ cardArtLayoutMetrics: [storyCenteredMotion16x9Metrics_mini, storyCenteredMotion16x9Metrics_base],
+ sourceWidth: 800,
+ sourceHeight: 450,
+ type: "video",
+ crops: ["sr"],
+};
+//# sourceMappingURL=story-centered-motion-16x9.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-static-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-static-16x9.js
new file mode 100644
index 0000000..404d58c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/story-centered-static-16x9.js
@@ -0,0 +1,49 @@
+import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const storyCenteredStatic16x9Layout_mini = {
+ collapsedContentMode: ArtworkContentMode.bottom,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storyCenteredStatic16x9Layout_base = {
+ collapsedContentMode: ArtworkContentMode.bottom,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storyCenteredStatic16x9Layout_700w = {
+ collapsedContentMode: ArtworkContentMode.scaleAspectFill,
+ expandedContentMode: ArtworkContentMode.bottom,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const storyCenteredStatic16x9Metrics_mini = {
+ maxWidth: 250.0,
+ ltr: storyCenteredStatic16x9Layout_mini,
+ rtl: storyCenteredStatic16x9Layout_mini,
+ collapsedSize: { type: "absolute", width: 455, height: 256 },
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const storyCenteredStatic16x9Metrics_base = {
+ maxWidth: 704,
+ ltr: storyCenteredStatic16x9Layout_base,
+ rtl: storyCenteredStatic16x9Layout_base,
+ collapsedSize: { type: "fractionalHeight", height: 1.08 },
+};
+const storyCenteredStatic16x9Metrics_700w = {
+ ltr: storyCenteredStatic16x9Layout_700w,
+ rtl: storyCenteredStatic16x9Layout_700w,
+};
+export const storyCenteredStatic16x9 = {
+ objectPath: "editorialArtwork.storyCenteredStatic16x9",
+ cardArtLayoutMetrics: [
+ storyCenteredStatic16x9Metrics_mini,
+ storyCenteredStatic16x9Metrics_base,
+ storyCenteredStatic16x9Metrics_700w,
+ ],
+ crops: ["sr"],
+ sourceWidth: 800,
+ sourceHeight: 450,
+ type: "image",
+};
+//# sourceMappingURL=story-centered-static-16x9.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-motion-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-motion-16x9.js
new file mode 100644
index 0000000..f16d51f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-motion-16x9.js
@@ -0,0 +1,146 @@
+import { ArtworkContentMode, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const expandedSize = { type: "absolute", width: 1200, height: 675 };
+const expandedLayoutInsets = {
+ top: -130,
+ left: 0,
+ bottom: 0,
+ right: 0,
+};
+const universalAMotion16x9Layout_mini = {
+ collapsedContentMode: ArtworkContentMode.top,
+ collapsedLayoutInsets: {
+ top: -16,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.top,
+ expandedLayoutInsets,
+};
+const universalAMotion16x9Layout_base = {
+ collapsedContentMode: ArtworkContentMode.top,
+ collapsedLayoutInsets: {
+ top: -116,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.top,
+ expandedLayoutInsets,
+};
+const universalAMotion16x9Layout_700w = {
+ collapsedContentMode: ArtworkContentMode.topLeft,
+ collapsedLayoutInsets: {
+ top: -116,
+ left: -70,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.top,
+ expandedLayoutInsets,
+};
+const universalAMotion16x9Layout_700w_rtl = {
+ collapsedContentMode: ArtworkContentMode.topRight,
+ collapsedLayoutInsets: {
+ top: -116,
+ left: 70,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.top,
+ expandedLayoutInsets,
+};
+const universalAMotion16x9Layout_920w = {
+ collapsedContentMode: ArtworkContentMode.topLeft,
+ collapsedLayoutInsets: {
+ top: -148,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.top,
+ expandedLayoutInsets,
+};
+const universalAMotion16x9Layout_920w_rtl = {
+ collapsedContentMode: ArtworkContentMode.topRight,
+ collapsedLayoutInsets: {
+ top: -148,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.top,
+ expandedLayoutInsets,
+};
+const universalAMotion16x9Layout_max = {
+ collapsedContentMode: ArtworkContentMode.topLeft,
+ collapsedLayoutInsets: {
+ top: -170,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.top,
+ expandedLayoutInsets,
+};
+const universalAMotion16x9Layout_max_rtl = {
+ collapsedContentMode: ArtworkContentMode.topRight,
+ collapsedLayoutInsets: {
+ top: -170,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.top,
+ expandedLayoutInsets,
+};
+const universalAMotion16x9Metrics_mini = {
+ maxWidth: 250.0,
+ ltr: universalAMotion16x9Layout_mini,
+ rtl: universalAMotion16x9Layout_mini,
+ collapsedSize: { type: "absolute", width: 455, height: 256 },
+ expandedSize,
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const universalAMotion16x9Metrics_base = {
+ maxWidth: 699.0,
+ ltr: universalAMotion16x9Layout_base,
+ rtl: universalAMotion16x9Layout_base,
+ collapsedSize: { type: "absolute", width: 1094, height: 614 },
+ expandedSize,
+};
+const universalAMotion16x9Metrics_700w = {
+ maxWidth: 899.0,
+ ltr: universalAMotion16x9Layout_700w,
+ rtl: universalAMotion16x9Layout_700w_rtl,
+ collapsedSize: { type: "absolute", width: 1092, height: 614 },
+ expandedSize,
+};
+const universalAMotion16x9Metrics_920w = {
+ maxWidth: 1090.0,
+ ltr: universalAMotion16x9Layout_920w,
+ rtl: universalAMotion16x9Layout_920w_rtl,
+ collapsedSize: { type: "absolute", width: 1392, height: 783 },
+ expandedSize,
+};
+const universalAMotion16x9Metrics_max = {
+ ltr: universalAMotion16x9Layout_max,
+ rtl: universalAMotion16x9Layout_max_rtl,
+ collapsedSize: { type: "absolute", width: 1600, height: 900 },
+ expandedSize,
+};
+export const universalAMotion16x9 = {
+ objectPath: "editorialVideo.universalAMotion16x9",
+ cardArtLayoutMetrics: [
+ universalAMotion16x9Metrics_mini,
+ universalAMotion16x9Metrics_base,
+ universalAMotion16x9Metrics_700w,
+ universalAMotion16x9Metrics_920w,
+ universalAMotion16x9Metrics_max,
+ ],
+ crops: ["UAS.ApXWC01"],
+ sourceWidth: 1600,
+ sourceHeight: 900,
+ type: "video",
+};
+//# sourceMappingURL=universal-a-motion-16x9.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-static-16x9.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-static-16x9.js
new file mode 100644
index 0000000..f0d34ce
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/media-configurations/universal-a-static-16x9.js
@@ -0,0 +1,107 @@
+import { ArtworkContentMode, EdgeInsetsZero, TodayCardArtworkSizedLayoutMetricsPriority, } from "../../../../api/models";
+const universalAStatic16x9Layout_mini = {
+ collapsedContentMode: ArtworkContentMode.top,
+ collapsedLayoutInsets: {
+ top: 16,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.top,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const universalAStatic16x9Layout_base = {
+ collapsedContentMode: ArtworkContentMode.scaleAspectFill,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.scaleAspectFill,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const universalAStatic16x9Layout_700w = {
+ collapsedContentMode: ArtworkContentMode.topLeft,
+ collapsedLayoutInsets: {
+ top: 0,
+ left: -70,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.center,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const universalAStatic16x9Layout_700w_rtl = {
+ collapsedContentMode: ArtworkContentMode.topRight,
+ collapsedLayoutInsets: {
+ top: 0,
+ left: 70,
+ bottom: 0,
+ right: 0,
+ },
+ expandedContentMode: ArtworkContentMode.center,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const universalAStatic16x9Layout_920w = {
+ collapsedContentMode: ArtworkContentMode.topLeft,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.center,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const universalAStatic16x9Layout_920w_rtl = {
+ collapsedContentMode: ArtworkContentMode.topRight,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.center,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const universalAStatic16x9Layout_max = {
+ collapsedContentMode: ArtworkContentMode.topLeft,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.center,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const universalAStatic16x9Layout_max_rtl = {
+ collapsedContentMode: ArtworkContentMode.topRight,
+ collapsedLayoutInsets: EdgeInsetsZero,
+ expandedContentMode: ArtworkContentMode.center,
+ expandedLayoutInsets: EdgeInsetsZero,
+};
+const universalAStatic16x9Metrics_mini = {
+ maxWidth: 250.0,
+ ltr: universalAStatic16x9Layout_mini,
+ rtl: universalAStatic16x9Layout_mini,
+ collapsedSize: { type: "absolute", width: 455, height: 256 },
+ priority: TodayCardArtworkSizedLayoutMetricsPriority.Mini,
+};
+const universalAStatic16x9Metrics_base = {
+ maxWidth: 699.0,
+ ltr: universalAStatic16x9Layout_base,
+ rtl: universalAStatic16x9Layout_base,
+};
+const universalAStatic16x9Metrics_700w = {
+ maxWidth: 899.0,
+ ltr: universalAStatic16x9Layout_700w,
+ rtl: universalAStatic16x9Layout_700w_rtl,
+};
+const universalAStatic16x9Metrics_920w = {
+ maxWidth: 1090.0,
+ ltr: universalAStatic16x9Layout_920w,
+ rtl: universalAStatic16x9Layout_920w_rtl,
+ collapsedSize: { type: "absolute", width: 1400, height: 527 },
+};
+const universalAStatic16x9Metrics_max = {
+ ltr: universalAStatic16x9Layout_max,
+ rtl: universalAStatic16x9Layout_max_rtl,
+ collapsedSize: { type: "absolute", width: 1600, height: 604 },
+};
+export const universalAStatic16x9 = {
+ objectPath: "editorialArtwork.universalAStatic16x9",
+ cardArtLayoutMetrics: [
+ universalAStatic16x9Metrics_mini,
+ universalAStatic16x9Metrics_base,
+ universalAStatic16x9Metrics_700w,
+ universalAStatic16x9Metrics_920w,
+ universalAStatic16x9Metrics_max,
+ ],
+ crops: ["UAS.ApXWC01"],
+ sourceWidth: 1600,
+ sourceHeight: 604,
+ type: "image",
+};
+//# sourceMappingURL=universal-a-static-16x9.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/today-artwork-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/today-artwork-util.js
new file mode 100644
index 0000000..4705bef
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/artwork/today-artwork-util.js
@@ -0,0 +1,267 @@
+import { isNothing, isSome } from "@jet/environment";
+import { Video } from "../../../api/models";
+import { asDictionary, asNumber, asString } from "../../../foundation/json-parsing/server-data";
+import { editorialCardFromData } from "../../../foundation/media/associations";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import { autoPlayControls, standardControls } from "../../constants/video-constants";
+import { artworkFromApiArtwork, joeColorSetFromData } from "../../content/content";
+import { cardDisplayStyleFromData } from "../today-card-util";
+import { TodayCardDisplayStyle } from "../today-types";
+import { categoryDetailMotion16x9Configuration, categoryDetailStatic16x9Configuration, dayCardConfiguration, eventCardConfiguration, generalCardConfiguration, listCardMotion21x9Configuration, listCardStatic21x9Configuration, mediaCardConfiguration, storeFrontVideo4x3Configuration, storeFrontVideoConfiguration, storyCardMotion16x9Configuration, storyCardStatic16x9Configuration, storyCenteredMotion16x9Configuration, storyCenteredStatic16x9Configuration, storySearchStatic16x9Configuration, universalAConfigurations, } from "./media-configurations";
+/**
+ * Retrieves the artwork details for a today card. This function will determine the
+ * appropriate media configuration to use based on the card display style and the
+ * media data available in the card response.
+ *
+ * @param objectGraph - The object graph of the app store.
+ * @param data - The media data.
+ * @param cardConfig - The configuration for the today card.
+ * @returns The artwork details for the today card, or undefined if no artwork is available.
+ */
+export function todayCardArtworkDetails(objectGraph, data, cardConfig) {
+ var _a;
+ // Watch uses different artwork lookup logic
+ const isWebViewingWatch = objectGraph.client.isWeb && ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.previewPlatform) === "watch";
+ if (objectGraph.client.isWatch || isWebViewingWatch) {
+ return todayCardArtworkDetailsForWatch(objectGraph, data, cardConfig);
+ }
+ const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle);
+ // Determine our preferred ranking of media flavors for the display style,
+ // then get the highest ranked media available on the card response.
+ const mediaSearchOrder = cardConfig.isHorizontalShelfContext
+ ? horizontalShelfMediaSearchOrderForCardDisplayStyle(objectGraph, cardConfig)
+ : mediaSearchOrderForCardDisplayStyle(objectGraph, cardDisplayStyle, cardConfig);
+ let mediaConfigurationForCard;
+ let isUsingEditorialCardOverride = false;
+ const editorialCardData = editorialCardFromData(data);
+ if (mediaAttributes.hasAttributes(editorialCardData)) {
+ mediaConfigurationForCard = mediaSearchOrder.find((conf) => {
+ return isSome(mediaAttributes.attributeAsDictionary(editorialCardData, conf.objectPath));
+ });
+ isUsingEditorialCardOverride = isSome(mediaConfigurationForCard);
+ }
+ if (isNothing(mediaConfigurationForCard)) {
+ mediaConfigurationForCard = mediaSearchOrder.find((conf) => {
+ return isSome(mediaAttributes.attributeAsDictionary(data, conf.objectPath));
+ });
+ }
+ if (!mediaConfigurationForCard) {
+ return undefined;
+ }
+ const mediaData = isUsingEditorialCardOverride
+ ? mediaAttributes.attributeAsDictionary(editorialCardData, mediaConfigurationForCard.objectPath)
+ : mediaAttributes.attributeAsDictionary(data, mediaConfigurationForCard.objectPath);
+ if (mediaConfigurationForCard.type === "image") {
+ // Return an Artwork and cardArtLayout
+ const artworks = mediaConfigurationForCard.crops.map((crop) => {
+ let cropCodeToUse = crop;
+ if (cardConfig.isSearchContext && isSome(cardConfig.prevailingCropCodes)) {
+ cropCodeToUse = cardConfig.prevailingCropCodes.defaultCrop;
+ }
+ else if (isSome(cardConfig.prevailingCropCodes) &&
+ isSome(cardConfig.prevailingCropCodes[mediaConfigurationForCard.objectPath])) {
+ cropCodeToUse = cardConfig.prevailingCropCodes[mediaConfigurationForCard.objectPath];
+ }
+ const artwork = artworkFromApiArtwork(objectGraph, mediaData, {
+ withJoeColorPlaceholder: true,
+ cropCode: cropCodeToUse,
+ useCase: 15 /* ArtworkUseCase.TodayCardMedia */,
+ overrideHeight: mediaConfigurationForCard.sourceHeight,
+ overrideWidth: mediaConfigurationForCard.sourceWidth,
+ });
+ return artwork;
+ });
+ const joeColors = joeColorSetFromData(mediaData);
+ return {
+ artworks: artworks,
+ videos: [],
+ artworkLayoutsWithMetrics: mediaConfigurationForCard.cardArtLayoutMetrics,
+ joeColors: joeColors,
+ };
+ }
+ else {
+ // Return a Video and cardArtLayout
+ const previewArtworkData = asDictionary(mediaData, "previewFrame");
+ // All TodayCardArtworkLayouts are specified in landscape aspect ratio,
+ // but videos can come back in either landscape or portrait. If we
+ // receive a portrait video, we need to flip the TodayCardArtworkLayout
+ const previewArtworkWidth = asNumber(previewArtworkData, "width");
+ const previewArtworkHeight = asNumber(previewArtworkData, "height");
+ const isPortrait = previewArtworkHeight >= previewArtworkWidth;
+ let overrideHeight = mediaConfigurationForCard.sourceHeight;
+ let overrideWidth = mediaConfigurationForCard.sourceWidth;
+ if (isPortrait) {
+ [overrideHeight, overrideWidth] = [overrideWidth, overrideHeight];
+ }
+ const previewArtwork = artworkFromApiArtwork(objectGraph, previewArtworkData, {
+ withJoeColorPlaceholder: true,
+ cropCode: mediaConfigurationForCard.crops[0],
+ useCase: 15 /* ArtworkUseCase.TodayCardMedia */,
+ overrideHeight: overrideHeight,
+ overrideWidth: overrideWidth,
+ });
+ if (isNothing(previewArtwork)) {
+ return undefined;
+ }
+ let playbackControls = standardControls(objectGraph);
+ if (isSome(cardConfig.videoPlaybackControls)) {
+ playbackControls = cardConfig.videoPlaybackControls;
+ }
+ if (objectGraph.client.isMac) {
+ playbackControls.scrubber = true;
+ }
+ let autoplayPlaybackControls = autoPlayControls(objectGraph);
+ if (isSome(cardConfig.videoPlaybackControls)) {
+ autoplayPlaybackControls = cardConfig.videoAutoplayPlaybackControls;
+ }
+ const videoUrl = asString(mediaData, "video");
+ if (isNothing(videoUrl)) {
+ return undefined;
+ }
+ const video = new Video(videoUrl, previewArtwork, {
+ playbackControls: playbackControls,
+ autoPlayPlaybackControls: autoplayPlaybackControls,
+ canPlayFullScreen: cardConfig.enableFullScreenVideo,
+ });
+ const joeColors = joeColorSetFromData(previewArtworkData);
+ return {
+ artworks: [],
+ videos: [video],
+ artworkLayoutsWithMetrics: mediaConfigurationForCard.cardArtLayoutMetrics,
+ joeColors: joeColors,
+ };
+ }
+}
+function mediaSearchOrderForCardDisplayStyle(objectGraph, cardDisplayStyle, cardConfig) {
+ // Hero position, non-list card display styles, on iPad requires the UniversalA flavor artwork to be considered valid,
+ // so we'll only check those 2 configurations in this scenario.
+ const listCardDisplayStyles = new Set([TodayCardDisplayStyle.List, TodayCardDisplayStyle.NumberedList]);
+ if (objectGraph.client.isPad && cardConfig.isHeroCard && !listCardDisplayStyles.has(cardDisplayStyle)) {
+ return universalAConfigurations(objectGraph, cardConfig.isHeroCard);
+ }
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.AppEventCard:
+ return [
+ storyCardMotion16x9Configuration(objectGraph),
+ storyCardStatic16x9Configuration(objectGraph),
+ storyCenteredMotion16x9Configuration(objectGraph),
+ storyCenteredStatic16x9Configuration(objectGraph),
+ ...universalAConfigurations(objectGraph, cardConfig.isHeroCard),
+ eventCardConfiguration(objectGraph),
+ ];
+ case TodayCardDisplayStyle.AppOfTheDay:
+ case TodayCardDisplayStyle.GameOfTheDay:
+ return [
+ storyCardMotion16x9Configuration(objectGraph),
+ storyCardStatic16x9Configuration(objectGraph),
+ storyCenteredMotion16x9Configuration(objectGraph),
+ storyCenteredStatic16x9Configuration(objectGraph),
+ ...universalAConfigurations(objectGraph, cardConfig.isHeroCard),
+ dayCardConfiguration(objectGraph),
+ ];
+ case TodayCardDisplayStyle.List:
+ case TodayCardDisplayStyle.NumberedList:
+ return [listCardMotion21x9Configuration(objectGraph), listCardStatic21x9Configuration(objectGraph)];
+ case TodayCardDisplayStyle.ShortImage:
+ if (cardConfig.isSearchContext) {
+ return [
+ generalCardConfiguration(objectGraph),
+ storyCardMotion16x9Configuration(objectGraph),
+ storyCardStatic16x9Configuration(objectGraph),
+ storyCenteredMotion16x9Configuration(objectGraph),
+ storyCenteredStatic16x9Configuration(objectGraph),
+ ...universalAConfigurations(objectGraph, cardConfig.isHeroCard),
+ storeFrontVideoConfiguration(objectGraph),
+ storeFrontVideo4x3Configuration(objectGraph),
+ mediaCardConfiguration(objectGraph),
+ ];
+ }
+ else {
+ return [
+ storyCardMotion16x9Configuration(objectGraph),
+ storyCardStatic16x9Configuration(objectGraph),
+ storyCenteredMotion16x9Configuration(objectGraph),
+ storyCenteredStatic16x9Configuration(objectGraph),
+ ...universalAConfigurations(objectGraph, cardConfig.isHeroCard),
+ storeFrontVideoConfiguration(objectGraph),
+ storeFrontVideo4x3Configuration(objectGraph),
+ generalCardConfiguration(objectGraph),
+ mediaCardConfiguration(objectGraph),
+ ];
+ }
+ case TodayCardDisplayStyle.FullBleedImage:
+ const fullBleedConfigurations = [
+ storyCardMotion16x9Configuration(objectGraph),
+ storyCardStatic16x9Configuration(objectGraph),
+ categoryDetailMotion16x9Configuration(objectGraph),
+ categoryDetailStatic16x9Configuration(objectGraph),
+ storyCenteredMotion16x9Configuration(objectGraph),
+ storyCenteredStatic16x9Configuration(objectGraph),
+ ...universalAConfigurations(objectGraph, cardConfig.isHeroCard),
+ storeFrontVideoConfiguration(objectGraph),
+ storeFrontVideo4x3Configuration(objectGraph),
+ mediaCardConfiguration(objectGraph),
+ generalCardConfiguration(objectGraph),
+ ];
+ if (objectGraph.client.isVision && isSome(cardConfig.isSearchContext) && cardConfig.isSearchContext) {
+ fullBleedConfigurations.unshift(storySearchStatic16x9Configuration(objectGraph));
+ }
+ return fullBleedConfigurations;
+ case TodayCardDisplayStyle.SingleApp:
+ case TodayCardDisplayStyle.Video:
+ default:
+ const configurations = [
+ storyCardMotion16x9Configuration(objectGraph),
+ storyCardStatic16x9Configuration(objectGraph),
+ storyCenteredMotion16x9Configuration(objectGraph),
+ storyCenteredStatic16x9Configuration(objectGraph),
+ ...universalAConfigurations(objectGraph, cardConfig.isHeroCard),
+ storeFrontVideoConfiguration(objectGraph),
+ storeFrontVideo4x3Configuration(objectGraph),
+ mediaCardConfiguration(objectGraph),
+ generalCardConfiguration(objectGraph),
+ ];
+ if (objectGraph.client.isVision && isSome(cardConfig.isSearchContext) && cardConfig.isSearchContext) {
+ configurations.unshift(storySearchStatic16x9Configuration(objectGraph));
+ }
+ return configurations;
+ }
+}
+function horizontalShelfMediaSearchOrderForCardDisplayStyle(objectGraph, cardConfig) {
+ const searchOrder = [];
+ if (objectGraph.client.isiOS || objectGraph.client.isWeb) {
+ searchOrder.push(storyCenteredStatic16x9Configuration(objectGraph, ["SCS.ApDPCS01"]));
+ }
+ if (cardConfig.horizontalCardContentType === "largeStoryCard") {
+ searchOrder.push(storeFrontVideoConfiguration(objectGraph));
+ }
+ const shouldUseHorizontalCardCrop = objectGraph.client.isiOS || objectGraph.client.isMac;
+ searchOrder.push(mediaCardConfiguration(objectGraph, shouldUseHorizontalCardCrop ? ["sr"] : undefined), generalCardConfiguration(objectGraph, shouldUseHorizontalCardCrop ? ["sr"] : undefined));
+ return searchOrder;
+}
+/**
+ * Watch uses a more straightforward artwork lookup than other platforms.
+ * @param objectGraph The AppStoreObjectGraph
+ * @param data MAPI data for a Today Card
+ * @param cardConfig The TodayCardConfiguration
+ * @returns TodayCardArtworkDetails appropriate for display on Apple Watch
+ */
+function todayCardArtworkDetailsForWatch(objectGraph, data, cardConfig) {
+ var _a, _b;
+ const mediaData = mediaAttributes.attributeAsDictionary(data, "editorialArtwork.subscriptionHero");
+ const artwork = artworkFromApiArtwork(objectGraph, mediaData, {
+ withJoeColorPlaceholder: true,
+ cropCode: (_b = (_a = cardConfig.prevailingCropCodes) === null || _a === void 0 ? void 0 : _a.defaultCrop) !== null && _b !== void 0 ? _b : "SH.ApHXS01",
+ useCase: 15 /* ArtworkUseCase.TodayCardMedia */,
+ });
+ const joeColors = joeColorSetFromData(mediaData);
+ // Don't return an array containing `undefined`
+ const artworks = isSome(artwork) ? [artwork] : [];
+ return {
+ artworks: artworks,
+ videos: [],
+ artworkLayoutsWithMetrics: [],
+ joeColors: joeColors,
+ };
+}
+//# sourceMappingURL=today-artwork-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-ad-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-ad-card-builder.js
new file mode 100644
index 0000000..dfe9836
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-ad-card-builder.js
@@ -0,0 +1,232 @@
+import { isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import { MediumAdLockupWithScreenshotsBackground, CondensedAdLockupWithIconBackground, TodayCardMediaSingleLockup, TodayCardMediaMediumLockupWithScreenshots, TodayCardMediaMediumLockupWithAlignedRegion, MediumAdLockupWithAlignedRegionBackground, } from "../../../api/models";
+import { asString, isDefinedNonNull, isNull } from "../../../foundation/json-parsing/server-data";
+import { isColorEqualToColor } from "../../../foundation/util/color-util";
+import { todayAdStyle } from "../../ads/ad-common";
+import { recordLockupFromDataFailed } from "../../ads/ad-incident-recorder";
+import { iconFromData } from "../../content/content";
+import { getTemplateTypeForMediumAdFromLockupWithCustomCreative, getTemplateTypeForMediumAdFromLockupWithScreenshots, isCppDeeplinkEnabledForAdvert, performAdOverridesForCard, } from "../../lockups/ad-lockups";
+import * as lockups from "../../lockups/lockups";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import { productVariantDataForData } from "../../product-page/product-page-variants";
+import { popTodayCardLocation, pushTodayCardLocation } from "../today-card-util";
+import { createTodayBaseCard } from "./today-base-card-builder";
+import { getSelectedCustomCreativeId } from "../../search/custom-creative";
+/**
+ * Create TodayCard displaying an ad fetched from Ad platforms
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param adData The media api data to build the ad card from
+ * @param adIncidentRecorder The incident recorder for the ad, for when an issues arises
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @returns The newly created TodayCard, using the full bleed image media.
+ */
+export function createTodayAdCard(objectGraph, adData, adIncidentRecorder, cardConfig, context) {
+ return validation.context("createTodayAdCard", () => {
+ var _a, _b, _c, _d;
+ if (isNull(adData)) {
+ return null; // No task for the position
+ }
+ const adCard = createTodayBaseCard(objectGraph, adData, cardConfig, context);
+ pushTodayCardLocation(objectGraph, adData, cardConfig, context, asString(adData.attributes.name));
+ const metricsOptions = metricsHelpersImpressions.impressionOptions(objectGraph, adData, asString(adData.attributes.name), {
+ targetType: "todayCard",
+ pageInformation: context.pageInformation,
+ locationTracker: context.locationTracker,
+ isAdvert: true,
+ rowIndex: cardConfig.currentRowIndex,
+ displayStyle: cardConfig.metricsDisplayStyle,
+ });
+ const productVariantData = productVariantDataForData(objectGraph, adData);
+ metricsOptions.productVariantData = productVariantData;
+ metricsOptions.adSlotOverride = context.parsedCardCount;
+ metricsOptions.kind = "adItem";
+ const clickOptions = metricsOptions;
+ // Set up iAdInfo
+ metricsOptions.pageInformation.iAdInfo.apply(objectGraph, adData);
+ // Configure the action
+ let clientIdentifierOverride;
+ if (isDefinedNonNull(cardConfig)) {
+ clientIdentifierOverride = cardConfig.clientIdentifierOverride;
+ }
+ const customCreativeId = getSelectedCustomCreativeId(adData);
+ const isCustomCreative = isSome(customCreativeId);
+ adCard.style = "dark";
+ switch (todayAdStyle(objectGraph)) {
+ case "singleLockup":
+ // Hardcode the condensed ad template type. This is not dependent on media count.
+ (_a = metricsOptions.pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.setTemplateType("APPLOCKUP");
+ break;
+ case "mediumLockup":
+ if (isCustomCreative) {
+ (_b = metricsOptions.pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.setTemplateType(getTemplateTypeForMediumAdFromLockupWithCustomCreative());
+ }
+ break;
+ default:
+ break;
+ }
+ let lockup = createAdCardLockup(objectGraph, adData, cardConfig, context);
+ if (isNull(lockup)) {
+ recordLockupFromDataFailed(objectGraph, adIncidentRecorder, adData);
+ popTodayCardLocation(context);
+ // Configure impressions
+ metricsHelpersImpressions.addImpressionsFieldsToTodayCard(objectGraph, adCard, metricsOptions, null, null, false, false);
+ return null;
+ }
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_c = lockup.searchAdOpportunity) === null || _c === void 0 ? void 0 : _c.setTemplateType("APPLOCKUP");
+ }
+ else {
+ (_d = lockup.searchAd) === null || _d === void 0 ? void 0 : _d.setTemplateType("APPLOCKUP");
+ }
+ switch (todayAdStyle(objectGraph)) {
+ case "singleLockup":
+ const condensedAdLockupWithIconBackground = new CondensedAdLockupWithIconBackground(lockup, lockup.icon);
+ adCard.media = new TodayCardMediaSingleLockup(condensedAdLockupWithIconBackground);
+ adCard.media.impressionMetrics = lockup.impressionMetrics;
+ break;
+ case "mediumLockup":
+ if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
+ if (isCustomCreative && objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) {
+ adCard.media = createMediumLockupWithAlignedRegion(objectGraph, lockup, adData, metricsOptions.pageInformation);
+ // After the media is created, we need to recreate the lockup to ensure all instrumentation
+ // has the updated template type, which is derived from the final set of media in
+ // `createMediumLockupWithAlignedRegion`.
+ lockup = createAdCardLockup(objectGraph, adData, cardConfig, context);
+ // Preserve SearchAd metadata
+ lockup.searchAdOpportunity = adCard.media.mediumAdLockupWithAlignedRegionBackground.lockup.searchAdOpportunity;
+ lockup.searchAd = adCard.media.mediumAdLockupWithAlignedRegionBackground.lockup.searchAd;
+ // The new lockup with the correct metrics should be set on the media.
+ adCard.media.mediumAdLockupWithAlignedRegionBackground.lockup = lockup;
+ adCard.media.impressionMetrics = lockup.impressionMetrics;
+ break;
+ }
+ else {
+ adCard.media = createMediumScreenshotsCardMedia(adCard, objectGraph, lockup, adData, cardConfig, context, metricsOptions);
+ }
+ }
+ else {
+ adCard.media = createMediumScreenshotsCardMedia(adCard, objectGraph, lockup, adData, cardConfig, context, metricsOptions);
+ }
+ break;
+ default:
+ return null;
+ }
+ popTodayCardLocation(context);
+ // CPP deeplinks used for ads have a requirement to not be used when targeting criteria is too narrow.
+ const isCppDeeplinkingEnabled = isCppDeeplinkEnabledForAdvert(adData);
+ // It's important the click action is created after the template type is set, to ensure it's in the metrics.
+ adCard.clickAction = lockups.actionFromData(objectGraph, adData, clickOptions, clientIdentifierOverride, undefined, isCppDeeplinkingEnabled);
+ performAdOverridesForCard(objectGraph, adData, adCard, metricsOptions);
+ // Configure impressions
+ metricsHelpersImpressions.addImpressionsFieldsToTodayCard(objectGraph, adCard, metricsOptions, null, null, false, false);
+ return adCard;
+ });
+}
+function createMediumScreenshotsCardMedia(adCard, objectGraph, lockup, adData, cardConfig, context, metricsOptions) {
+ adCard.media = createMediumLockupWithScreenshots(objectGraph, lockup, objectGraph.bag.todayAdMediumLockupScreenshotAnimationEnabled, metricsOptions.pageInformation, adData);
+ // After the media is created, we need to recreate the lockup to ensure all instrumentation
+ // has the updated template type, which is derived from the final set of media in
+ // `createMediumLockupWithScreenshots`.
+ lockup = createAdCardLockup(objectGraph, adData, cardConfig, context);
+ if (isNull(lockup)) {
+ return null;
+ }
+ // Preserve SearchAd metadata
+ lockup.searchAdOpportunity = adCard.media.mediumAdLockupWithScreenshotsBackground.lockup.searchAdOpportunity;
+ lockup.searchAd = adCard.media.mediumAdLockupWithScreenshotsBackground.lockup.searchAd;
+ // The new lockup with the correct metrics should be set on the media.
+ adCard.media.mediumAdLockupWithScreenshotsBackground.lockup = lockup;
+ adCard.media.impressionMetrics = lockup.impressionMetrics;
+ return adCard.media;
+}
+function createAdCardLockup(objectGraph, data, cardConfig, context) {
+ const offerEnvironment = "ad";
+ const offerStyle = "transparent";
+ return lockups.mixedMediaAdLockupFromData(objectGraph, data, {
+ offerEnvironment: offerEnvironment,
+ offerStyle: offerStyle,
+ metricsOptions: {
+ pageInformation: context.pageInformation,
+ locationTracker: context.locationTracker,
+ isAdvert: true,
+ adSlotOverride: context.parsedCardCount,
+ disableFastImpressionsForAds: true,
+ },
+ clientIdentifierOverride: cardConfig.clientIdentifierOverride,
+ crossLinkSubtitle: cardConfig.crossLinkSubtitle,
+ artworkUseCase: 1 /* ArtworkUseCase.LockupIconSmall */,
+ canDisplayArcadeOfferButton: cardConfig.canDisplayArcadeOfferButton,
+ }, {
+ allowsAutoPlay: true,
+ looping: true,
+ canPlayFullScreen: false,
+ playbackControls: {},
+ }, null, false);
+}
+function createMediumLockupWithAlignedRegion(objectGraph, lockup, adData, pageInformation) {
+ var _a, _b, _c;
+ const alignedRegionArtwork = lockup.alignedRegionArtwork;
+ const templateString = getTemplateTypeForMediumAdFromLockupWithCustomCreative();
+ (_a = pageInformation === null || pageInformation === void 0 ? void 0 : pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.setTemplateType(templateString);
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_b = lockup.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.setTemplateType(templateString);
+ }
+ else {
+ (_c = lockup.searchAd) === null || _c === void 0 ? void 0 : _c.setTemplateType(templateString);
+ }
+ const mediumContainer = new MediumAdLockupWithAlignedRegionBackground(lockup, alignedRegionArtwork);
+ return new TodayCardMediaMediumLockupWithAlignedRegion(mediumContainer);
+}
+/**
+ * Creates the media for a medium Today ad, with the appropriate media.
+ * Also applies information about the created media template to a provided MetricsPageInformation.
+ * @param objectGraph The object graph.
+ * @param lockup The lockup for the app.
+ * @param isAnimated Whether the card background should be animated.
+ * @param pageInformation A MetricsPageInformation to apply the created template metrics information to.
+ * @param adData The data for the ad.
+ * @returns A `TodayCardMediumLockupWithScreenshots` with the correct media to be displayed.
+ */
+function createMediumLockupWithScreenshots(objectGraph, lockup, isAnimated, pageInformation, adData) {
+ var _a, _b, _c;
+ // Grab the first of the platform screenshots and trailers - the ones the ad will display.
+ const platformScreenshots = lockup.screenshots[0];
+ const templateString = getTemplateTypeForMediumAdFromLockupWithScreenshots(platformScreenshots);
+ (_a = pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.setTemplateType(templateString);
+ if (objectGraph.props.enabled("advertSlotReporting")) {
+ (_b = lockup.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.setTemplateType(templateString);
+ }
+ else {
+ (_c = lockup.searchAd) === null || _c === void 0 ? void 0 : _c.setTemplateType(templateString);
+ }
+ const iconData = iconFromData(objectGraph, adData, {
+ useCase: 0 /* ArtworkUseCase.Default */,
+ withJoeColorPlaceholder: true,
+ overrideTextColorKey: "textColor2",
+ });
+ let backgroundColor = iconData.backgroundColor;
+ let secondaryTextColor = iconData.textColor;
+ const rgbWhite = {
+ type: "rgb",
+ red: 1,
+ green: 1,
+ blue: 1,
+ };
+ // If `backgroundColor` is white, use `secondaryTextColor` as the "primary" color.
+ // Otherwise, if `secondaryTextColor` is white, remove it and use only `backgroundColor`.
+ // Native code has a fallback state where only `backgroundColor` is provided, so if
+ // either color is white, we prefer the single non-white color.
+ if (isColorEqualToColor(rgbWhite, backgroundColor)) {
+ backgroundColor = secondaryTextColor;
+ secondaryTextColor = undefined;
+ }
+ else if (isColorEqualToColor(rgbWhite, secondaryTextColor)) {
+ secondaryTextColor = undefined;
+ }
+ const mediumContainer = new MediumAdLockupWithScreenshotsBackground(lockup, [platformScreenshots], isAnimated, secondaryTextColor, backgroundColor, 8);
+ return new TodayCardMediaMediumLockupWithScreenshots(mediumContainer);
+}
+//# sourceMappingURL=today-ad-card-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-app-event-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-app-event-card-builder.js
new file mode 100644
index 0000000..c030a5a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-app-event-card-builder.js
@@ -0,0 +1,112 @@
+import { isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import { isNothing } from "@jet/environment/types/optional";
+import { TodayCardLockupOverlay, TodayCardMediaAppEvent, } from "../../../api/models";
+import { asBooleanOrFalse } from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import * as mediaRelationship from "../../../foundation/media/relationships";
+import * as color from "../../../foundation/util/color-util";
+import { appEventOrPromotionStartDateFromData } from "../../app-promotions/app-event";
+import { appEventsAreEnabled } from "../../app-promotions/app-promotions-common";
+import { addNextPreferredContentRefreshDate } from "../../refresh/page-refresh-controller";
+import { todayCardArtworkDetails } from "../artwork/today-artwork-util";
+import { applyTodayCardOverridesForAcquisitionStoryIfNecessary } from "../today-card-overlay-util";
+import { cardDisplayStyleFromData, cardStyleFromJoeColorsWithoutFallback, offerEnvironmentForTodayCard, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, } from "../today-card-util";
+import { createTodayBaseCard } from "./today-base-card-builder";
+/**
+ * Create TodayCard displaying an app event
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to build the card from
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data`
+ * @returns The newly created TodayCard, using a app event media.
+ */
+export function createTodayAppEventCard(objectGraph, data, cardConfig, context, augmentingData) {
+ return validation.context("createTodayAppEventCard", () => {
+ var _a, _b, _c, _d, _e;
+ if (!appEventsAreEnabled(objectGraph)) {
+ return null;
+ }
+ const appEventData = appEventDataFromData(objectGraph, data);
+ if (isNothing(appEventData)) {
+ return null;
+ }
+ const appEventCard = createTodayBaseCard(objectGraph, data, cardConfig, context, (clickOptions) => {
+ // Add app event ID to click options
+ clickOptions.inAppEventId = appEventData.id;
+ const parentAppData = mediaRelationship.relationshipData(objectGraph, appEventData, "app");
+ if (isSome(parentAppData)) {
+ clickOptions.relatedSubjectIds = [parentAppData.id];
+ }
+ });
+ if (isSome(appEventCard.editorialDisplayOptions)) {
+ appEventCard.editorialDisplayOptions.useMaterialBlur = true;
+ }
+ pushTodayCardLocation(objectGraph, data, cardConfig, context);
+ // Artwork
+ const mediaDetails = todayCardArtworkDetails(objectGraph, data, cardConfig);
+ if (isNothing(mediaDetails)) {
+ popTodayCardLocation(context);
+ return null;
+ }
+ // Tint and styling
+ const tintColor = (_b = (_a = mediaDetails.joeColors) === null || _a === void 0 ? void 0 : _a.textColor4) !== null && _b !== void 0 ? _b : color.black;
+ const artworkBackgroundColor = (_d = (_c = mediaDetails.joeColors) === null || _c === void 0 ? void 0 : _c.bgColor) !== null && _d !== void 0 ? _d : color.black;
+ const blurStyle = color.isDarkColor(artworkBackgroundColor) ? "dark" : "light";
+ // App event
+ const metricsOptions = {
+ pageInformation: context.pageInformation,
+ locationTracker: context.locationTracker,
+ targetType: "eventModule",
+ };
+ // Card style
+ appEventCard.style =
+ (_e = cardStyleFromJoeColorsWithoutFallback(mediaDetails.joeColors, "textColor4")) !== null && _e !== void 0 ? _e : (color.isDarkColor(tintColor) ? "dark" : "light");
+ // Offer style
+ const offerStyle = offerStyleForTodayCard(objectGraph, appEventCard.style);
+ const editorialKind = mediaAttributes.attributeAsString(data, "label");
+ const appEventOrDate = appEventOrPromotionStartDateFromData(objectGraph, appEventData, null, false, false, offerEnvironmentForTodayCard(appEventCard.style), offerStyle, true, metricsOptions, true, true, editorialKind, false, asBooleanOrFalse(cardConfig.allowUnpublishedAppEventPreviews));
+ // Return early if we received a Date, as this means the App Event shouldn't be shown yet.
+ if (isNothing(appEventOrDate) || appEventOrDate instanceof Date) {
+ addNextPreferredContentRefreshDate(appEventOrDate, context.refreshController);
+ popTodayCardLocation(context);
+ return null;
+ }
+ const appEvent = appEventOrDate;
+ // Card
+ appEventCard.media = new TodayCardMediaAppEvent(appEvent.formattedDates, appEvent.startDate, tintColor, mediaDetails.artworks, mediaDetails.videos, mediaDetails.artworkLayoutsWithMetrics, blurStyle);
+ if (isSome(appEvent.lockup)) {
+ appEventCard.overlay = new TodayCardLockupOverlay(appEvent.lockup);
+ }
+ // Special post-processing step for Acquisition story cards.
+ // This is needed to splice in data not included in initial response.
+ applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, appEventCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context);
+ popTodayCardLocation(context);
+ return appEventCard;
+ });
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to build the card from
+ * @returns The app event relationship data for this card
+ */
+function appEventDataFromData(objectGraph, data) {
+ let appEventData;
+ // Primary content
+ const primaryContent = mediaRelationship.relationshipCollection(data, "primary-content");
+ if (primaryContent.length > 0) {
+ appEventData = primaryContent[0];
+ }
+ else {
+ // Card contents
+ const cardContents = mediaRelationship.relationshipCollection(data, "card-contents");
+ if (cardContents.length === 0) {
+ return null;
+ }
+ appEventData = cardContents[0];
+ }
+ return appEventData;
+}
+//# sourceMappingURL=today-app-event-card-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-base-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-base-card-builder.js
new file mode 100644
index 0000000..77bd0de
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-base-card-builder.js
@@ -0,0 +1,244 @@
+import * as validation from "@jet/environment/json/validation";
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import { ExternalUrlAction, TodayCard, TodayCardActionOverlay, TodayCardMediaArtwork, } from "../../../api/models";
+import { isNull } from "../../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../../foundation/media/attributes";
+import { relationshipCollection } from "../../../foundation/media/relationships";
+import * as contentAttributes from "../../content/attributes";
+import { editorialNotesFromData, notesFromData } from "../../content/content";
+import { extractEditorialClientParams } from "../../editorial-pages/editorial-data-util";
+import { editorialItemActionFromData, subtitleFromData } from "../../lockups/lockups";
+import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
+import { addMetricsEventsToVideo } from "../../metrics/helpers/media";
+import { todayCardArtworkDetails } from "../artwork/today-artwork-util";
+import { isCardDataOnboardingCard } from "../onboarding-cards";
+import { brandedTitleArtworkForCard, cardDisplayStyleFromData, cardStyleFromJoeColors, isCardOTDIntention, relatedCardContentsContentsFromData, todayCardArtworkTitleBackingGradientForKey, todayCardEditorialDisplayOptionsFromData, todayCardMetricsOptions, } from "../today-card-util";
+import { recoMetricsFromTodayItem } from "../today-parse-util";
+import { TodayCardDisplayStyle } from "../today-types";
+/**
+ * If the title is not supplied for app of the day we use this
+ */
+const appOfTheDayFallbackTitle = "FEATURED APP";
+/**
+ * If the title is not supplied for game of the day we use this
+ */
+const gameOfTheDayFallbackTitle = "FEATURED GAME";
+/**
+ * Creates a TodayCard used as the base for all other TodayCards
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to build the card from
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @param clickOptionsModifier A function that can modify the click options for the card
+ * @returns The newly created TodayCard, which can then be modified by other TodayCard builders
+ */
+export function createTodayBaseCard(objectGraph, data, cardConfig, context, clickOptionsModifier) {
+ return validation.context("createTodayBaseCard", () => {
+ const baseCard = new TodayCard();
+ const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.coercedCollectionTodayCardDisplayStyle);
+ // Heading
+ const heading = cardHeadingFromData(objectGraph, data, cardDisplayStyle, cardConfig);
+ baseCard.heading = heading;
+ // Title, title artwork
+ const title = cardTitleFromData(objectGraph, data, cardConfig);
+ baseCard.title = title;
+ const shortTitle = contentAttributes.contentAttributeAsString(objectGraph, data, [
+ "shortEditorialNotes",
+ "name",
+ ]);
+ baseCard.shortTitle = shortTitle;
+ const cardTitleArtwork = brandedTitleArtworkForCard(objectGraph, data);
+ baseCard.titleArtwork = cardTitleArtwork;
+ // Description
+ const inlineDescription = cardDescriptionFromData(objectGraph, data);
+ baseCard.inlineDescription = inlineDescription;
+ const metricsOptions = todayCardMetricsOptions(objectGraph, data, cardConfig, context, title);
+ metricsOptions.adSlotOverride = context.parsedCardCount;
+ baseCard.clickAction = cardClickAction(objectGraph, data, cardDisplayStyle, cardConfig, context, metricsOptions, clickOptionsModifier);
+ // Configure impressions
+ metricsHelpersImpressions.addImpressionsFieldsToTodayCard(objectGraph, baseCard, metricsOptions, heading, cardDisplayStyle, isCardDataOnboardingCard(objectGraph, data));
+ baseCard.editorialDisplayOptions = todayCardEditorialDisplayOptionsFromData(objectGraph, data, cardConfig);
+ return baseCard;
+ });
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param cardDisplayStyle The display style of the card
+ * @param data The data to get the branded single app overlay heading from
+ * @param cardConfig The configuration for the card
+ * @returns The heading to use on the branded single app overlay
+ */
+export function cardHeadingFromData(objectGraph, data, cardDisplayStyle, cardConfig) {
+ var _a, _b;
+ let heading = null;
+ if (cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.useOTDTextStyle) {
+ if (cardConfig.isHorizontalShelfContext) {
+ // We don't support branded images or titles for a cards in a horizontal shelf
+ // so want to show the "APP OF THE DAY" / "GAME OF THE DAY" text as the heading instead.
+ heading = mediaAttributes.attributeAsString(data, "label");
+ }
+ else {
+ heading = null;
+ }
+ }
+ else if (isCardOTDIntention(data, cardConfig)) {
+ heading =
+ (_a = mediaAttributes.attributeAsString(data, "alternateLabel")) !== null && _a !== void 0 ? _a : mediaAttributes.attributeAsString(data, "label");
+ if (isNull(heading) && cardDisplayStyle === TodayCardDisplayStyle.AppOfTheDay) {
+ heading = appOfTheDayFallbackTitle;
+ }
+ else if (isNull(heading) && cardDisplayStyle === TodayCardDisplayStyle.GameOfTheDay) {
+ heading = gameOfTheDayFallbackTitle;
+ }
+ }
+ else {
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.AppEventCard:
+ const fallbackCardContents = fallbackCardContentFromData(objectGraph, data);
+ if (isSome(fallbackCardContents)) {
+ heading =
+ (_b = editorialNotesFromData(objectGraph, data, "badge", true)) !== null && _b !== void 0 ? _b : mediaAttributes.attributeAsString(fallbackCardContents, "kind");
+ }
+ break;
+ default:
+ heading = mediaAttributes.attributeAsString(data, "label");
+ break;
+ }
+ }
+ return heading;
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data used to determine the title of this card
+ * @param cardConfig The configuration for the card
+ * @returns The title to use for this card
+ */
+function cardTitleFromData(objectGraph, data, cardConfig) {
+ var _a;
+ let title = null;
+ if (cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.useOTDTextStyle) {
+ title =
+ (_a = mediaAttributes.attributeAsString(data, "ofTheDayLabel")) !== null && _a !== void 0 ? _a : mediaAttributes.attributeAsString(data, "label");
+ // We need to replace \n characters with <br> tags to ensure the
+ // newlines in APP OF THE DAY titles are maintained. The pattern must be a RegExp since string patterns
+ // only replace the first occurrence.
+ if (isSome(title)) {
+ title = title.replace(/\n/g, "<br>");
+ }
+ }
+ if (isNothing(title)) {
+ title = notesFromData(objectGraph, data, "name", true);
+ }
+ if (isNothing(title)) {
+ // Lastly fallback to the first related content title
+ const fallbackCardContents = fallbackCardContentFromData(objectGraph, data);
+ title = isSome(fallbackCardContents) ? mediaAttributes.attributeAsString(fallbackCardContents, "name") : null;
+ }
+ return title;
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data used to determine the description of this card
+ * @returns The title to use for this card
+ */
+function cardDescriptionFromData(objectGraph, data) {
+ var _a;
+ const editorialClientParams = extractEditorialClientParams(objectGraph, data);
+ const ignoreShortNotes = mediaAttributes.attributeAsBooleanOrFalse(data, "ignoreITunesShortNotes");
+ if (ignoreShortNotes || editorialClientParams.suppressNoteShort) {
+ return null;
+ }
+ let description = notesFromData(objectGraph, data, "short", true);
+ if (isNothing(description) && !editorialClientParams.suppressNoteTagline) {
+ // Lastly fallback to the first related content title
+ const fallbackCardContents = fallbackCardContentFromData(objectGraph, data);
+ if (isSome(fallbackCardContents)) {
+ description =
+ (_a = notesFromData(objectGraph, fallbackCardContents, "tagline")) !== null && _a !== void 0 ? _a : subtitleFromData(objectGraph, fallbackCardContents);
+ }
+ }
+ return description;
+}
+/**
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data used to build the card
+ * @returns
+ */
+function fallbackCardContentFromData(objectGraph, data) {
+ const primaryContent = relationshipCollection(data, "primary-content");
+ if ((primaryContent === null || primaryContent === void 0 ? void 0 : primaryContent.length) === 1) {
+ return primaryContent[0];
+ }
+ // Card contents
+ const cardContents = relatedCardContentsContentsFromData(objectGraph, data);
+ if (cardContents.length === 1) {
+ return cardContents[0];
+ }
+ return null;
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data used to build the card this action is for
+ * @param cardDisplayStyle The display style of the card
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @param baseMetricsOptions The base metrics options for the card
+ * @returns The click action used for tapping on the card
+ */
+function cardClickAction(objectGraph, data, cardDisplayStyle, cardConfig, context, baseMetricsOptions, clickOptionsModifier) {
+ var _a;
+ const clickOptions = baseMetricsOptions;
+ const franchise = mediaAttributes.attributeAsString(data, "label");
+ // <rdar://problem/33677354> Metrics: JS: Add franchise/label to todayCard click events
+ const actionDetails = {
+ cardType: cardDisplayStyle,
+ franchise: franchise,
+ };
+ const isOnboardingCard = isCardDataOnboardingCard(objectGraph, data);
+ if (isOnboardingCard) {
+ actionDetails["isOnboardingCard"] = isOnboardingCard;
+ }
+ clickOptions["actionDetails"] = actionDetails;
+ if (isSome(clickOptionsModifier)) {
+ clickOptionsModifier(clickOptions);
+ }
+ return editorialItemActionFromData(objectGraph, data, clickOptions, (_a = cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.clientIdentifierOverride) !== null && _a !== void 0 ? _a : objectGraph.host.clientIdentifier, recoMetricsFromTodayItem(context.currentTodayItem), cardConfig);
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data used to build the base card
+ * @param baseCard The base card to add the artwork media to
+ * @param cropCode The crop code to use for the artwork
+ * @param context The context for the page were creating the card for
+ * @returns Whether or not we successfully added artwork media to the base card
+ */
+export function addArtworkMediaToBaseCard(objectGraph, data, baseCard, cardConfig, context) {
+ return validation.context("addArtworkMediaToBaseCard", () => {
+ const ignoreEditorialArt = mediaAttributes.attributeAsBooleanOrFalse(data, "ignoreEditorialArt");
+ if (ignoreEditorialArt) {
+ return false;
+ }
+ const mediaDetails = todayCardArtworkDetails(objectGraph, data, cardConfig);
+ if (isNothing(mediaDetails)) {
+ return false;
+ }
+ if (isSome(context)) {
+ addMetricsEventsToVideo(objectGraph, mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.videos[0], {
+ pageInformation: context.pageInformation,
+ locationTracker: context.locationTracker,
+ id: data.id,
+ });
+ }
+ baseCard.media = new TodayCardMediaArtwork(mediaDetails.artworks, mediaDetails.videos, mediaDetails.artworkLayoutsWithMetrics, todayCardArtworkTitleBackingGradientForKey(objectGraph, data, cardConfig));
+ baseCard.style = cardStyleFromJoeColors(mediaDetails.joeColors, "bgColor");
+ // External URL
+ if (baseCard.clickAction instanceof ExternalUrlAction && objectGraph.client.isiOS) {
+ baseCard.overlay = new TodayCardActionOverlay(baseCard.clickAction);
+ baseCard.style = "white";
+ }
+ return true;
+ });
+}
+//# sourceMappingURL=today-base-card-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-branded-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-branded-card-builder.js
new file mode 100644
index 0000000..9088bfa
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-branded-card-builder.js
@@ -0,0 +1,58 @@
+import * as validation from "@jet/environment/json/validation";
+import { isSome } from "@jet/environment/types/optional";
+import { TodayCardMediaBrandedSingleApp } from "../../../api/models";
+import { isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { deepLinkUrlFromData } from "../../linking/external-deep-link";
+import { todayCardArtworkDetails } from "../artwork/today-artwork-util";
+import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util";
+import { cardDisplayStyleFromData, cardStyleFromJoeColors, lockupsForRelatedContent, offerEnvironmentForTodayCard, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, todayCardArtworkTitleBackingGradientForKey, } from "../today-card-util";
+import { createTodayBaseCard } from "./today-base-card-builder";
+import { createTodaySingleAppCard } from "./today-single-app-card-builder";
+/**
+ * Create TodayCard displaying the App of the Day and Game of the Day editorial items
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to build the card from
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data`
+ * @returns The newly created TodayCard, using an artwork media to display the App of the Day and Game of the Day
+ */
+export function createTodayBrandedCard(objectGraph, data, cardConfig, context, augmentingData) {
+ return validation.context("createTodayBrandedCard", () => {
+ const brandedCard = createTodayBaseCard(objectGraph, data, cardConfig, context);
+ pushTodayCardLocation(objectGraph, data, cardConfig, context);
+ // Configure the card style
+ const mediaDetails = todayCardArtworkDetails(objectGraph, data, cardConfig);
+ if (isSome(mediaDetails) &&
+ isSome(mediaDetails.joeColors.bgColor) &&
+ (mediaDetails.artworks.length > 0 || mediaDetails.videos.length > 0)) {
+ brandedCard.style = cardStyleFromJoeColors(mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.joeColors, "bgColor");
+ }
+ else {
+ // If we don't have artwork, try building a single app card instead.
+ popTodayCardLocation(context);
+ return createTodaySingleAppCard(objectGraph, data, cardConfig, context);
+ }
+ const offerStyle = offerStyleForTodayCard(objectGraph, brandedCard.style);
+ const relatedContent = relatedCardContentsContentsFromData(objectGraph, data);
+ const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, offerEnvironmentForTodayCard(brandedCard.style), offerStyle, deepLinkUrlFromData(objectGraph, data), true);
+ if (isNullOrEmpty(relatedContentLockups) || relatedContentLockups.length !== 1) {
+ popTodayCardLocation(context);
+ return null;
+ }
+ if (!cardConfig.isHorizontalShelfContext) {
+ brandedCard.overlay = relatedContentOverlayFromData(objectGraph, brandedCard, cardConfig, data, relatedContentLockups);
+ }
+ // Configure the card media
+ const brandedAppLockup = relatedContentLockups[0];
+ brandedCard.media = new TodayCardMediaBrandedSingleApp(brandedAppLockup.icon, mediaDetails.artworks, mediaDetails.videos, mediaDetails.artworkLayoutsWithMetrics, todayCardArtworkTitleBackingGradientForKey(objectGraph, data, cardConfig));
+ brandedCard.media.impressionMetrics = brandedAppLockup.impressionMetrics;
+ // Special post-processing step for Acquisition story cards.
+ // This is needed to splice in data not included in initial response.
+ applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, brandedCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context);
+ popTodayCardLocation(context);
+ return brandedCard;
+ });
+}
+//# sourceMappingURL=today-branded-card-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-full-bleed-image-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-full-bleed-image-card-builder.js
new file mode 100644
index 0000000..86cacec
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-full-bleed-image-card-builder.js
@@ -0,0 +1,37 @@
+import * as validation from "@jet/environment/json/validation";
+import { deepLinkUrlFromData } from "../../linking/external-deep-link";
+import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util";
+import { cardDisplayStyleFromData, lockupsForRelatedContent, offerEnvironmentForTodayCard, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util";
+import { addArtworkMediaToBaseCard, createTodayBaseCard } from "./today-base-card-builder";
+/**
+ * Create TodayCard displaying the full bleed image
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to build the card from
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data`
+ * @returns The newly created TodayCard, using the full bleed image media.
+ */
+export function createTodayFullBleedImageCard(objectGraph, data, cardConfig, context, augmentingData) {
+ return validation.context("createTodayFullBleedImageCard", () => {
+ const fullBleedImageCard = createTodayBaseCard(objectGraph, data, cardConfig, context);
+ pushTodayCardLocation(objectGraph, data, cardConfig, context);
+ const didAddArtworkToCard = addArtworkMediaToBaseCard(objectGraph, data, fullBleedImageCard, cardConfig, context);
+ if (!didAddArtworkToCard) {
+ popTodayCardLocation(context);
+ return null;
+ }
+ const relatedContent = relatedCardContentsContentsFromData(objectGraph, data);
+ const offerStyle = offerStyleForTodayCard(objectGraph, fullBleedImageCard.style);
+ const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, offerEnvironmentForTodayCard(fullBleedImageCard.style), offerStyle, deepLinkUrlFromData(objectGraph, data));
+ const overlay = relatedContentOverlayFromData(objectGraph, fullBleedImageCard, cardConfig, data, relatedContentLockups);
+ fullBleedImageCard.overlay = overlay;
+ // Special post-processing step for Acquisition story cards.
+ // This is needed to splice in data not included in initial response.
+ applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, fullBleedImageCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context);
+ popTodayCardLocation(context);
+ return fullBleedImageCard;
+ });
+}
+//# sourceMappingURL=today-full-bleed-image-card-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-in-app-purchase-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-in-app-purchase-card-builder.js
new file mode 100644
index 0000000..d326c5c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-in-app-purchase-card-builder.js
@@ -0,0 +1,62 @@
+import * as validation from "@jet/environment/json/validation";
+import { TodayCardInAppPurchase } from "../../../api/models";
+import { isNullOrEmpty } from "../../../foundation/json-parsing/server-data";
+import { inAppPurchaseLockupFromData } from "../../lockups/lockups";
+import { applyTodayCardOverridesForAcquisitionStoryIfNecessary } from "../today-card-overlay-util";
+import { cardDisplayStyleFromData, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util";
+import { createTodayBaseCard } from "./today-base-card-builder";
+/**
+ * Create TodayCard displaying an in app purchase
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to build the card from
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data`
+ * @returns The newly created TodayCard, using the in app purchase media.
+ */
+export function createTodayInAppPurchaseCard(objectGraph, data, cardConfig, context, augmentingData) {
+ return validation.context("createTodayInAppPurchaseCard", () => {
+ const inAppPurchaseCard = createTodayBaseCard(objectGraph, data, cardConfig, context);
+ pushTodayCardLocation(objectGraph, data, cardConfig, context);
+ const iAPData = inAppPurchaseDataFromRelatedContent(objectGraph, relatedCardContentsContentsFromData(objectGraph, data));
+ if (isNullOrEmpty(iAPData)) {
+ popTodayCardLocation(context);
+ return null;
+ }
+ const lockup = inAppPurchaseLockupFromData(objectGraph, iAPData, {
+ offerStyle: "colored",
+ metricsOptions: {
+ pageInformation: context.pageInformation,
+ locationTracker: context.locationTracker,
+ },
+ artworkUseCase: 1 /* ArtworkUseCase.LockupIconSmall */,
+ });
+ if (isNullOrEmpty(lockup)) {
+ popTodayCardLocation(context);
+ return null;
+ }
+ lockup.theme = "infer";
+ inAppPurchaseCard.media = new TodayCardInAppPurchase(lockup);
+ // we need to add impressions to the media here because
+ // there is no overlay on an IAP inAppPurchaseCard for some reason
+ inAppPurchaseCard.media.impressionMetrics = lockup.impressionMetrics;
+ inAppPurchaseCard.media.impressionMetrics.fields["parentId"] = inAppPurchaseCard.impressionMetrics.fields["id"];
+ inAppPurchaseCard.style = "white";
+ // Special post-processing step for Acquisition story cards.
+ // This is needed to splice in data not included in initial response.
+ applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, inAppPurchaseCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context);
+ popTodayCardLocation(context);
+ return inAppPurchaseCard;
+ });
+}
+function inAppPurchaseDataFromRelatedContent(objectGraph, relatedContent) {
+ if (relatedContent.length === 1) {
+ const contentData = relatedContent[0];
+ if (contentData.type === "in-apps") {
+ return contentData;
+ }
+ }
+ return null;
+}
+//# sourceMappingURL=today-in-app-purchase-card-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-list-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-list-card-builder.js
new file mode 100644
index 0000000..b803c9f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-list-card-builder.js
@@ -0,0 +1,71 @@
+import * as validation from "@jet/environment/json/validation";
+import { TodayCardMediaList } from "../../../api/models";
+import * as videoDefaults from "../../constants/video-constants";
+import { todayCardArtworkDetails } from "../artwork/today-artwork-util";
+import { applyTodayCardOverridesForAcquisitionStoryIfNecessary } from "../today-card-overlay-util";
+import { applyMultiAppFallbackToCollectionCard, cardDisplayStyleFromData, cardStyleFromJoeColorsWithoutFallback, listFallbackLimit, lockupsForRelatedContent, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util";
+import { TodayCardDisplayStyle, } from "../today-types";
+import { createTodayBaseCard } from "./today-base-card-builder";
+import { isEligibleForGamesApp } from "../../content/content";
+/**
+ * Create TodayCard displaying the list of lockups
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to build the card from
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data`
+ * @returns The newly created TodayCard, using a list or numbered list media.
+ */
+export function createTodayListCard(objectGraph, data, cardConfig, context, augmentingData) {
+ return validation.context("createTodayListCard", () => {
+ var _a, _b, _c;
+ const listCard = createTodayBaseCard(objectGraph, data, cardConfig, context);
+ pushTodayCardLocation(objectGraph, data, cardConfig, context);
+ cardConfig.useJoeColorIconPlaceholder = true;
+ let relatedContents = relatedCardContentsContentsFromData(objectGraph, data);
+ if (preprocessor.GAMES_TARGET) {
+ // Filtering out non-eligible games as they can be used as content for stories collection.
+ // see `createFallbackListShelf` in article.ts.
+ relatedContents = relatedContents.filter((item) => isEligibleForGamesApp(item));
+ }
+ const listLockups = lockupsForRelatedContent(objectGraph, relatedContents, cardConfig, context.pageInformation, context.locationTracker, undefined, undefined, undefined);
+ if (listLockups.length === 0) {
+ popTodayCardLocation(context);
+ return null;
+ }
+ cardConfig.canDisplayArcadeOfferButton = false;
+ // We require at least `listFallbackLimit` lockups for the list card
+ // If this is for use on top of an article its ok to have less than `listFallbackLimit`
+ if (listLockups.length < listFallbackLimit(objectGraph) && cardConfig.enableListCardToMultiAppFallback) {
+ applyMultiAppFallbackToCollectionCard(objectGraph, data, listLockups, listCard);
+ }
+ else {
+ const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle);
+ if (cardDisplayStyle === TodayCardDisplayStyle.NumberedList) {
+ let index = 1;
+ for (const lockup of listLockups) {
+ lockup.ordinal = `${index}`;
+ index++;
+ }
+ }
+ const mediaDetails = todayCardArtworkDetails(objectGraph, data, cardConfig);
+ const cardStyle = cardStyleFromJoeColorsWithoutFallback(mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.joeColors, "bgColor");
+ const isModernListCard = objectGraph.host.isiOS;
+ // Since list videos are very wide (21:9) and ~50% masked, we prevent them from going fullscreen
+ mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.videos.forEach((video) => {
+ video.canPlayFullScreen = false;
+ video.playbackControls = videoDefaults.noControls(objectGraph);
+ video.autoPlayPlaybackControls = videoDefaults.noControls(objectGraph);
+ });
+ listCard.style = isModernListCard ? undefined : "white";
+ listCard.media = new TodayCardMediaList(listLockups, (_a = mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.artworks) !== null && _a !== void 0 ? _a : [], (_b = mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.videos) !== null && _b !== void 0 ? _b : [], (_c = mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.artworkLayoutsWithMetrics) !== null && _c !== void 0 ? _c : [], undefined, isModernListCard ? cardStyle !== "white" : cardStyle === "dark");
+ }
+ // Special post-processing step for Acquisition story cards.
+ // This is needed to splice in data not included in initial response.
+ applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, listCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context);
+ popTodayCardLocation(context);
+ return listCard;
+ });
+}
+//# sourceMappingURL=today-list-card-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-river-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-river-card-builder.js
new file mode 100644
index 0000000..d93fa7e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-river-card-builder.js
@@ -0,0 +1,62 @@
+import * as validation from "@jet/environment/json/validation";
+import { TodayCardMediaRiver } from "../../../api/models";
+import { isDarkColor, rgbWith } from "../../../foundation/util/color-util";
+import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util";
+import { applyMultiAppFallbackToCollectionCard, cardDisplayStyleFromData, gridFallbackLimit, lockupsForCollectionCardFromData, popTodayCardLocation, pushTodayCardLocation, } from "../today-card-util";
+import { createTodayBaseCard } from "./today-base-card-builder";
+/**
+ * Create TodayCard displaying the river of lockups
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to build the card from
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data`
+ * @returns The newly created TodayCard, using a river media.
+ */
+export function createTodayRiverCard(objectGraph, data, cardConfig, context, augmentingData) {
+ return validation.context("createTodayRiverCard", () => {
+ const riverCard = createTodayBaseCard(objectGraph, data, cardConfig, context);
+ cardConfig.useJoeColorIconPlaceholder = true;
+ pushTodayCardLocation(objectGraph, data, cardConfig, context);
+ const shouldIncludeLockupClickActions = objectGraph.client.isWeb;
+ const riverLockups = lockupsForCollectionCardFromData(objectGraph, data, cardConfig, context, shouldIncludeLockupClickActions);
+ if (riverLockups.length === 0) {
+ popTodayCardLocation(context);
+ return null;
+ }
+ cardConfig.canDisplayArcadeOfferButton = false;
+ if (riverLockups.length < gridFallbackLimit(objectGraph)) {
+ // <rdar://problem/37261537> P2: Some Today cards not showing on Cinar
+ // less that `gridFallbackLimit` lockups gets a fallback card on Emet only
+ applyMultiAppFallbackToCollectionCard(objectGraph, data, riverLockups, riverCard);
+ }
+ else {
+ riverCard.style = "dark";
+ replaceHighLuminanceIconColorsInLockups(riverLockups);
+ riverCard.media = new TodayCardMediaRiver(riverLockups);
+ }
+ if (objectGraph.client.isWatch) {
+ riverCard.overlay = relatedContentOverlayFromData(objectGraph, riverCard, cardConfig, data, riverLockups);
+ }
+ // Special post-processing step for Acquisition story cards.
+ // This is needed to splice in data not included in initial response.
+ applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, riverCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context);
+ popTodayCardLocation(context);
+ return riverCard;
+ });
+}
+/**
+ * Replace any icon background colors with luminance > 0.9 with grey color.
+ * This ensure that river background colors don't compete with the overlayed white text.
+ *
+ * @param riverLockups The river lockups.
+ */
+function replaceHighLuminanceIconColorsInLockups(riverLockups) {
+ for (const lockup of riverLockups) {
+ if (!isDarkColor(lockup.icon.backgroundColor, 90)) {
+ lockup.icon.backgroundColor = rgbWith(0.8, 0.8, 0.8);
+ }
+ }
+}
+//# sourceMappingURL=today-river-card-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-short-image-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-short-image-card-builder.js
new file mode 100644
index 0000000..c50836a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-short-image-card-builder.js
@@ -0,0 +1,42 @@
+import * as validation from "@jet/environment/json/validation";
+import { deepLinkUrlFromData } from "../../linking/external-deep-link";
+import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util";
+import { cardDisplayStyleFromData, lockupsForRelatedContent, offerEnvironmentForTodayCard, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util";
+import { addArtworkMediaToBaseCard, createTodayBaseCard } from "./today-base-card-builder";
+import { addSingleAppFallbackToCard } from "./today-single-app-card-builder";
+/**
+ * Create TodayCard displaying the full bleed image
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to build the card from
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data`
+ * @returns The newly created TodayCard, using the full bleed image media.
+ */
+export function createTodayShortImageCard(objectGraph, data, cardConfig, context, augmentingData) {
+ return validation.context("createTodayShortImageCard", () => {
+ const shortImageCard = createTodayBaseCard(objectGraph, data, cardConfig, context);
+ pushTodayCardLocation(objectGraph, data, cardConfig, context);
+ const didAddArtworkToCard = addArtworkMediaToBaseCard(objectGraph, data, shortImageCard, cardConfig, context);
+ let didAddSingleAppFallback = false;
+ const relatedContent = relatedCardContentsContentsFromData(objectGraph, data);
+ const offerStyle = offerStyleForTodayCard(objectGraph, shortImageCard.style);
+ const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, offerEnvironmentForTodayCard(shortImageCard.style), offerStyle, deepLinkUrlFromData(objectGraph, data));
+ const overlay = relatedContentOverlayFromData(objectGraph, shortImageCard, cardConfig, data, relatedContentLockups);
+ shortImageCard.overlay = overlay;
+ if (!didAddArtworkToCard && relatedContent.length === 1) {
+ didAddSingleAppFallback = addSingleAppFallbackToCard(objectGraph, data, shortImageCard, relatedContent, cardConfig, context);
+ }
+ if (!didAddArtworkToCard && !didAddSingleAppFallback) {
+ popTodayCardLocation(context);
+ return null;
+ }
+ // Special post-processing step for Acquisition story cards.
+ // This is needed to splice in data not included in initial response.
+ applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, shortImageCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context);
+ popTodayCardLocation(context);
+ return shortImageCard;
+ });
+}
+//# sourceMappingURL=today-short-image-card-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-single-app-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-single-app-card-builder.js
new file mode 100644
index 0000000..18828c8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-single-app-card-builder.js
@@ -0,0 +1,81 @@
+import * as validation from "@jet/environment/json/validation";
+import { TodayCardMediaAppIcon, TodayCardMediaList, TodayCardParagraphOverlay } from "../../../api/models";
+import { isDefinedNonNull } from "../../../foundation/json-parsing/server-data";
+import { deepLinkUrlFromData } from "../../linking/external-deep-link";
+import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util";
+import { cardDisplayStyleFromData, lockupsForRelatedContent, offerEnvironmentForTodayCard, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util";
+import { addArtworkMediaToBaseCard, createTodayBaseCard } from "./today-base-card-builder";
+/**
+ * Create TodayCard displaying the SingleApp card display style
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to build the card from
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data`
+ * @returns The newly created TodayCard, either artwork or the single app icon.
+ */
+export function createTodaySingleAppCard(objectGraph, data, cardConfig, context, augmentingData) {
+ return validation.context("createTodaySingleAppCard", () => {
+ const singleAppCard = createTodayBaseCard(objectGraph, data, cardConfig, context);
+ pushTodayCardLocation(objectGraph, data, cardConfig, context);
+ // Next prefer artwork card, and then fallback to substyles
+ const didAddArtworkToCard = addArtworkMediaToBaseCard(objectGraph, data, singleAppCard, cardConfig, context);
+ const relatedContent = relatedCardContentsContentsFromData(objectGraph, data);
+ const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, "todayCard", null, deepLinkUrlFromData(objectGraph, data));
+ const overlay = relatedContentOverlayFromData(objectGraph, singleAppCard, cardConfig, data, relatedContentLockups);
+ if (isDefinedNonNull(overlay) && overlay instanceof TodayCardParagraphOverlay) {
+ overlay.style = "white";
+ }
+ singleAppCard.overlay = overlay;
+ if (!didAddArtworkToCard && relatedContent.length === 1) {
+ const didAddSingleAppFallback = addSingleAppFallbackToCard(objectGraph, data, singleAppCard, relatedContent, cardConfig, context);
+ if (!didAddSingleAppFallback) {
+ popTodayCardLocation(context);
+ return null;
+ }
+ }
+ // Special post-processing step for Acquisition story cards.
+ // This is needed to splice in data not included in initial response.
+ applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, singleAppCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context);
+ popTodayCardLocation(context);
+ return singleAppCard;
+ });
+}
+/**
+ * Adds a single app fallback to the card.
+ *
+ * @param objectGraph - The object graph of the app store.
+ * @param data - The media data.
+ * @param card - The today card.
+ * @param relatedContent - The related content data.
+ * @param cardConfig - The configuration for the today card.
+ * @param context - The parse context for today card.
+ * @returns Returns true or false based on whether we could create a single app fallback.
+ */
+export function addSingleAppFallbackToCard(objectGraph, data, card, relatedContent, cardConfig, context) {
+ if (relatedContent.length !== 1) {
+ return false;
+ }
+ cardConfig.useJoeColorIconPlaceholder = true;
+ card.style = "dark";
+ const offerStyle = offerStyleForTodayCard(objectGraph, card.style);
+ const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, offerEnvironmentForTodayCard(card.style), offerStyle, deepLinkUrlFromData(objectGraph, data));
+ if (relatedContentLockups.length !== 1) {
+ return false;
+ }
+ const lockup = relatedContentLockups[0];
+ // tvOS doesn't have layout to properly render appIcon media.
+ // However, `media` can't be simply removed from TodayCard, as it is a required field in native code.
+ // This `TodayCardMediaList` works as an empty media placeholder,
+ // that allows to render text from TodayCard but not the image.
+ // From StarlightB AppIcon is disabled in native code and this check can be removed in the next major release.
+ if (objectGraph.client.isTV && !objectGraph.props.enabled("disableAppIconMediaTodayHeader")) {
+ card.media = new TodayCardMediaList([], [], [], [], undefined, undefined);
+ }
+ else {
+ card.media = new TodayCardMediaAppIcon(lockup.icon);
+ }
+ return true;
+}
+//# sourceMappingURL=today-single-app-card-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-video-card-builder.js b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-video-card-builder.js
new file mode 100644
index 0000000..77516f6
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/cards/today-video-card-builder.js
@@ -0,0 +1,33 @@
+import * as validation from "@jet/environment/json/validation";
+import { deepLinkUrlFromData } from "../../linking/external-deep-link";
+import { applyTodayCardOverridesForAcquisitionStoryIfNecessary, relatedContentOverlayFromData, } from "../today-card-overlay-util";
+import { cardDisplayStyleFromData, lockupsForRelatedContent, offerStyleForTodayCard, popTodayCardLocation, pushTodayCardLocation, relatedCardContentsContentsFromData, } from "../today-card-util";
+import { addArtworkMediaToBaseCard, createTodayBaseCard } from "./today-base-card-builder";
+/**
+ * Create TodayCard displaying a video
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to build the card from
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ * @param augmentingData that stores some additional responses that may be used to enhance the contents of `data`
+ * @returns The newly created TodayCard, using the video media.
+ */
+export function createTodayVideoCard(objectGraph, data, cardConfig, context, augmentingData) {
+ return validation.context("createTodayVideoCard", () => {
+ const videoCard = createTodayBaseCard(objectGraph, data, cardConfig, context);
+ addArtworkMediaToBaseCard(objectGraph, data, videoCard, cardConfig, context);
+ pushTodayCardLocation(objectGraph, data, cardConfig, context);
+ const offerStyle = offerStyleForTodayCard(objectGraph, videoCard.style);
+ const relatedContent = relatedCardContentsContentsFromData(objectGraph, data);
+ const relatedContentLockups = lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, context.pageInformation, context.locationTracker, "todayCard", offerStyle, deepLinkUrlFromData(objectGraph, data));
+ const overlay = relatedContentOverlayFromData(objectGraph, videoCard, cardConfig, data, relatedContentLockups);
+ videoCard.overlay = overlay;
+ // Special post-processing step for Acquisition story cards.
+ // This is needed to splice in data not included in initial response.
+ applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, videoCard, cardConfig, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle), data, augmentingData, context);
+ popTodayCardLocation(context);
+ return videoCard;
+ });
+}
+//# sourceMappingURL=today-video-card-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/onboarding-cards.js b/node_modules/@jet-app/app-store/tmp/src/common/today/onboarding-cards.js
new file mode 100644
index 0000000..86af694
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/onboarding-cards.js
@@ -0,0 +1,23 @@
+/**
+ * Created by ls on 6/23/17.
+ */
+import * as serverData from "../../foundation/json-parsing/server-data";
+const isOnboardingCardKey = "isOnBoardingCard";
+/**
+ * Tag a data collection as onboarding cards for metrics.
+ * @param cardsData
+ */
+export function markDataCollectionAsOnboardingCards(objectGraph, cardsData) {
+ for (const card of cardsData) {
+ card[isOnboardingCardKey] = true;
+ }
+}
+/**
+ * Check whether given data was injected as onboarding card.
+ * @param cardData Media API data for single card.
+ * @returns A boolean returning `true` if `cardData` was from `makeOnBoardingCardToken`, `false` otherwise.
+ */
+export function isCardDataOnboardingCard(objectGraph, cardData) {
+ return serverData.asBooleanOrFalse(cardData, isOnboardingCardKey);
+}
+//# sourceMappingURL=onboarding-cards.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/routable-article-page-url-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/today/routable-article-page-url-utils.js
new file mode 100644
index 0000000..5603a86
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/routable-article-page-url-utils.js
@@ -0,0 +1,32 @@
+import { makeRoutableArticlePageIntent, } from "../../api/intents/routable-article-page-intent";
+import { generateRoutes } from "../util/generate-routes";
+const { routes: routableArticlePageWithPlatformRoutes, makeCanonicalUrl: makeRoutableArticlePageWithPlatformUrl } = generateRoutes(makeRoutableArticlePageIntent, "/{platform}/story/{id}");
+const { routes: routableArticlePageWithoutPlatformRoutes, makeCanonicalUrl: makeRoutableArticlePageWithoutPlatformUrl, } = generateRoutes(makeRoutableArticlePageIntent, "/story/{id}");
+export { routableArticlePageWithPlatformRoutes, routableArticlePageWithoutPlatformRoutes };
+/**
+ * Generate the URL for an "Article Page" based on the {@linkcode intent}
+ *
+ * If the {@linkcode intent} has an explicitly-configured `platform` property,
+ * then the resulting URL will include the explicitly as the `{platform}` prefix
+ * segment. If the `platform` property is not provided, the resulting URL will
+ * not contain an explicit `{platform}` segment at all.
+ *
+ * @param objectGraph
+ * @param intent
+ * @returns the URL that, when parsed, produces the given `intent`
+ */
+export function makeRoutableArticlePageCanonicalUrl(objectGraph, intent) {
+ const intentWithExpectedID = {
+ ...intent,
+ // The `{id}` segment of the URL is expected to include the `id` prefix, which is removed
+ // when parsing the incoming URL into the `Intent`
+ id: `id${intent.id}`,
+ };
+ if (intent.platform) {
+ return makeRoutableArticlePageWithPlatformUrl(objectGraph, intentWithExpectedID);
+ }
+ else {
+ return makeRoutableArticlePageWithoutPlatformUrl(objectGraph, intentWithExpectedID);
+ }
+}
+//# sourceMappingURL=routable-article-page-url-utils.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-overlay-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-overlay-util.js
new file mode 100644
index 0000000..ab0829f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-overlay-util.js
@@ -0,0 +1,111 @@
+import { isSome } from "@jet/environment";
+import { isNothing } from "@jet/environment/types/optional";
+import { marketingItemContextFromString, TodayCardArcadeLockupOverlay, TodayCardLockupListOverlay, TodayCardLockupOverlay, TodayCardThreeLineOverlay, } from "../../api/models";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import { upsellFromContentsOfUpsellResponse, upsellFromRelationshipOf, } from "../arcade/arcade-common";
+import { extractEditorialClientParams } from "../editorial-pages/editorial-data-util";
+import { arcadeLockupFromData } from "../lockups/lockups";
+import { todayCardArtworkDetails } from "./artwork/today-artwork-util";
+import { cardStyleFromJoeColors, offerStyleForTodayCard } from "./today-card-util";
+// MARK: - Paragraph / Related Content Overlays
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param card The card being built that we'd like to add the overlay to
+ * @param cardConfig The configuration used to create the card
+ * @param data The media api data to build the card from
+ * @param relatedContentLockups The lockups created from the related content
+ * @returns A TodayCardLockupOverlay, TodayCardLockupListOverlay, TodayCardThreeLineOverlay, depending on whether
+ * the card should ignore the short notes, has a deep link, or has related content lockups.
+ */
+export function relatedContentOverlayFromData(objectGraph, card, cardConfig, data, relatedContentLockups) {
+ const editorialClientParams = extractEditorialClientParams(objectGraph, data);
+ if (isSome(editorialClientParams.suppressLockup) && editorialClientParams.suppressLockup) {
+ return null;
+ }
+ let overlay = null;
+ const hasSingleLockup = isSome(relatedContentLockups) && relatedContentLockups.length === 1;
+ const hasMultipleLockups = isSome(relatedContentLockups) && relatedContentLockups.length > 1;
+ const hasThreeLineCards = objectGraph.client.isMac || objectGraph.client.isWatch;
+ if (hasThreeLineCards) {
+ overlay = new TodayCardThreeLineOverlay(card.heading, card.title, card.inlineDescription);
+ }
+ else if (hasSingleLockup) {
+ overlay = new TodayCardLockupOverlay(relatedContentLockups[0]);
+ }
+ else if (hasMultipleLockups) {
+ overlay = new TodayCardLockupListOverlay(relatedContentLockups);
+ }
+ return overlay;
+}
+// MARK: - Arcade Acquisitions
+/**
+ * Applies a set of overrides to an existing `card` built by the standard pipelie using the contents of augmenting data fetched externally.
+ * This function provides support for parsing editorial-items fetched for:
+ * - Today
+ * - Articles (with data augmentation)
+ * - Groupings (with data augmentation)
+ *
+ * @param card Card to override behavior of.
+ * @param data Original data `card` was created with
+ * @param augmentingData Data used to augment `card`.
+ * @param metricsContext Metrics context to use for overrides.
+ * @returns Overridden Card, or `null` if overrides failed. Note that `card` is modified *IN PLACE*.
+ */
+export function applyTodayCardOverridesForAcquisitionStoryIfNecessary(objectGraph, card, cardConfig, cardDisplayStyle, data, augmentingData, context) {
+ const isAcquisitionStory = mediaAttributes.attributeAsBooleanOrFalse(data, "isAcquisition");
+ if (!isAcquisitionStory) {
+ return;
+ }
+ // Try to build upsell data from upsell relationship, if that exists.
+ let upsellData = upsellFromRelationshipOf(objectGraph, data);
+ if (isNothing(upsellData) && isSome(augmentingData)) {
+ // Fallback: Try to build off upsell data off `augmentingData`. This relies on the caller being proactive about fetching upsell separately for endpoints that don't return upsell relationship.
+ upsellData = upsellFromContentsOfUpsellResponse(objectGraph, augmentingData.arcadeUpsellEditorialResponse);
+ }
+ // Override: Arcade Overlay
+ const arcadeLockupOverlay = arcadeOverlayFromData(objectGraph, data, card, cardConfig, cardDisplayStyle, upsellData, context);
+ if (arcadeLockupOverlay) {
+ card.overlay = arcadeLockupOverlay;
+ card.impressionMetrics.fields["displaysArcadeUpsell"] = true;
+ // If we're overriding the card style used with the overlay we should also update the style for the card itself.
+ const style = cardStyleForArcadeOverlay(objectGraph, data, card, cardConfig);
+ if (card.style !== style) {
+ card.style = style;
+ }
+ }
+}
+/**
+ * Whether or not current platform supports arcade overlay.
+ */
+function currentPlatformSupportsArcadeOverlay(objectGraph) {
+ // Check if on platform that will actually display data.
+ const platform = objectGraph.host.platform;
+ const platformSupportsArcadeLockupOverlay = platform === "iOS" || platform === "macOS";
+ return platformSupportsArcadeLockupOverlay;
+}
+/**
+ * Creates an instance of `TodayCardArcadeLockupOverlay` to use for Acquisition Editorial Items.
+ * @param upsellData Upsell data containing editorial and iAP data.
+ * @param metricsContext Metrics context to use for generated lockup.
+ */
+function arcadeOverlayFromData(objectGraph, data, card, cardConfig, cardDisplayStyle, upsellData, context) {
+ if (!currentPlatformSupportsArcadeOverlay(objectGraph)) {
+ return null;
+ }
+ const style = cardStyleForArcadeOverlay(objectGraph, data, card, cardConfig);
+ const offerStyle = offerStyleForTodayCard(objectGraph, style);
+ const lockup = arcadeLockupFromData(objectGraph, upsellData, context, marketingItemContextFromString("editorialItem"), offerStyle, "todayCard");
+ return new TodayCardArcadeLockupOverlay(lockup);
+}
+/**
+ * Returns the `TodayCardStyle` that should be used for the arcade overlay.
+ * Some cards use different styles when fetched as part of an article vs. today feed. Using the media artwork to determine the
+ * cardStyle in these cases avoids issues with today cards changing styles when transitionin from to today feed to articles.
+ */
+function cardStyleForArcadeOverlay(objectGraph, data, card, cardConfig) {
+ const useCardStyle = !cardConfig.enableListCardToMultiAppFallback;
+ const mediaDetails = todayCardArtworkDetails(objectGraph, data, cardConfig);
+ const styleFromMedia = cardStyleFromJoeColors(mediaDetails === null || mediaDetails === void 0 ? void 0 : mediaDetails.joeColors);
+ return useCardStyle ? card.style : styleFromMedia;
+}
+//# sourceMappingURL=today-card-overlay-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-util.js
new file mode 100644
index 0000000..688f0fb
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-card-util.js
@@ -0,0 +1,785 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import { ArtworkContentMode, ExternalUrlAction, FlowAction, TodayCardActionOverlay, TodayCardMediaArtwork, TodayCardMediaHero, TodayCardMediaMultiApp, TodayCardMediaVideo, TodayCardMediaWithArtwork, } from "../../api/models";
+import { asBoolean, asDictionary, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, isNullOrEmpty, objectPathToString, } from "../../foundation/json-parsing/server-data";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import { relationshipCollection } from "../../foundation/media/relationships";
+import { Parameters } from "../../foundation/network/url-constants";
+import { URL } from "../../foundation/network/urls";
+import * as color from "../../foundation/util/color-util";
+import { artworkFromApiArtwork, editorialNotesFromData, hasMessagesExtensionFromData, isHiddenFromSpringboardFromData, joeColorSetFromData, notesFromData, screenSizeIPhone134, } from "../content/content";
+import { extractEditorialClientParams } from "../editorial-pages/editorial-data-util";
+import * as lockupsEditorialContext from "../lockups/editorial-context";
+import { lockupsFromData } from "../lockups/lockups";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import * as metricsHelpersImpressions from "./../metrics/helpers/impressions";
+import { createTodayAppEventCard } from "./cards/today-app-event-card-builder";
+import { createTodayBrandedCard } from "./cards/today-branded-card-builder";
+import { createTodayFullBleedImageCard } from "./cards/today-full-bleed-image-card-builder";
+import { createTodayGridCard } from "./cards/today-grid-card-builder";
+import { createTodayInAppPurchaseCard } from "./cards/today-in-app-purchase-card-builder";
+import { createTodayListCard } from "./cards/today-list-card-builder";
+import { createTodayRiverCard } from "./cards/today-river-card-builder";
+import { createTodayShortImageCard } from "./cards/today-short-image-card-builder";
+import { createTodaySingleAppCard } from "./cards/today-single-app-card-builder";
+import { createTodayVideoCard } from "./cards/today-video-card-builder";
+import { HeroMediaDisplayContext, OfTheDayIntention, TodayCardDisplayStyle, } from "./today-types";
+import { createTodayBaseCard } from "./cards/today-base-card-builder";
+// MARK: - Today Card Creation
+/**
+ * Create a card with some appearance configuration within today page.
+ * @param {AppStoreObjectGraph} objectGraph The object graph for the app store.
+ * @param {Data} data to build card of.
+ * @param {TodayCardConfiguration} cardConfig flags on how to configure a given card.
+ * @param {TodayParseContext} context to store state updated throughout parsing.
+ * @param {TodayCardAugmentingData} augmentingData that stores some additional responses that may be used to enhance the contents of `data`
+ */
+export function todayCardFromData(objectGraph, data, cardConfig, context, augmentingData) {
+ return validation.catchingContext("todayCardFromData", () => {
+ const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle);
+ const clientIdentifier = lockupsEditorialContext.clientIdentifierForEditorialContextInData(objectGraph, data);
+ // If clientIdentifier is empty, don't override the cardConfig as this may have been set
+ // at a higher level.
+ if (isSome(clientIdentifier)) {
+ if (shouldRespectClientIdentifierOverride(objectGraph, data, clientIdentifier, cardConfig)) {
+ cardConfig.clientIdentifierOverride = clientIdentifier;
+ }
+ else {
+ cardConfig.clientIdentifierOverride = null;
+ }
+ }
+ // Configure subtitle for cross link.
+ cardConfig.crossLinkSubtitle = crossLinkSubtitleFromData(objectGraph, data);
+ let card = null;
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.AppOfTheDay:
+ case TodayCardDisplayStyle.GameOfTheDay:
+ card = createTodayBrandedCard(objectGraph, data, cardConfig, context, augmentingData);
+ break;
+ case TodayCardDisplayStyle.Video:
+ card = createTodayVideoCard(objectGraph, data, cardConfig, context, augmentingData);
+ break;
+ case TodayCardDisplayStyle.FullBleedImage:
+ card = createTodayFullBleedImageCard(objectGraph, data, cardConfig, context, augmentingData);
+ break;
+ case TodayCardDisplayStyle.InAppPurchase:
+ card = createTodayInAppPurchaseCard(objectGraph, data, cardConfig, context, augmentingData);
+ break;
+ case TodayCardDisplayStyle.AppEventCard:
+ card = createTodayAppEventCard(objectGraph, data, cardConfig, context, augmentingData);
+ break;
+ case TodayCardDisplayStyle.List:
+ case TodayCardDisplayStyle.NumberedList:
+ card = createTodayListCard(objectGraph, data, cardConfig, context, augmentingData);
+ break;
+ case TodayCardDisplayStyle.River:
+ case TodayCardDisplayStyle.Grid:
+ if (objectGraph.client.isMac) {
+ card = createTodayGridCard(objectGraph, data, cardConfig, context, augmentingData);
+ }
+ else {
+ card = createTodayRiverCard(objectGraph, data, cardConfig, context, augmentingData);
+ }
+ break;
+ case TodayCardDisplayStyle.SingleApp:
+ card = createTodaySingleAppCard(objectGraph, data, cardConfig, context, augmentingData);
+ break;
+ case TodayCardDisplayStyle.ShortImage:
+ card = createTodayShortImageCard(objectGraph, data, cardConfig, context, augmentingData);
+ break;
+ default:
+ card = null;
+ break;
+ }
+ if (isNull(card)) {
+ objectGraph.console.log(`Unknown style: ${cardDisplayStyle}`);
+ return card;
+ }
+ // For certain platforms we add hero media to the card.
+ addHeroMediaToTodayCardIfNecessary(objectGraph, card, cardConfig, data);
+ addExternalLinkOverlayToTodayCardIfNecessary(card);
+ addOTDStyleToCardIfNecessary(card, cardConfig);
+ enableFlipAndBlurIfNecessary(objectGraph, card);
+ if (isNothing(card.media)) {
+ objectGraph.console.log(`Missing required media: ${cardDisplayStyle}`);
+ card = null;
+ }
+ return card;
+ }, (error) => {
+ objectGraph.console.log(error);
+ return null;
+ });
+}
+/**
+ * On the watch we need the todayCard on the article to get the title and subtitle,
+ * so even if we dont successfully make a card for the article we create a basic one with an empty media so the title / subtitle can be used
+ * @param {AppStoreObjectGraph} objectGraph The object graph for the app store.
+ * @param {Data} data to build card of.
+ * @param {TodayCardConfiguration} cardConfig flags on how to configure a given card.
+ * @param {TodayParseContext} context to store state updated throughout parsing.
+ */
+export function fallbackWatchTodayCardFromData(objectGraph, data, cardConfig, context) {
+ if (!objectGraph.client.isWatch) {
+ return null;
+ }
+ const fallbackCard = createTodayBaseCard(objectGraph, data, cardConfig, context);
+ fallbackCard.media = new TodayCardMediaArtwork([], [], []);
+ return fallbackCard;
+}
+/**
+ * Create the {EditorialDisplayOptions for a card given the MAPI data
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The MAPI data for this today card
+ * @param cardConfig The configuration for the card
+ * @returns The editorialDisplayOptions for this card
+ */
+export function todayCardEditorialDisplayOptionsFromData(objectGraph, data, cardConfig) {
+ var _a;
+ // here is where we make the display options
+ const editorialClientParams = extractEditorialClientParams(objectGraph, data);
+ const isExtraWideCard = objectGraph.client.isPad && (cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.isHeroCard);
+ const editorialDisplayOptions = {
+ suppressTagline: mediaAttributes.attributeAsBoolean(data, "ignoreITunesShortNotes"),
+ suppressLockup: asBoolean(editorialClientParams["suppressLockup"]),
+ showBadgeInSmallCards: (_a = cardConfig.alwaysShowBadgeInSmallCards) !== null && _a !== void 0 ? _a : asBoolean(editorialClientParams["showBadgeInSmallCards"]),
+ useMaterialBlur: cardConfig.alwaysUseMaterialBlur || isExtraWideCard || asBoolean(editorialClientParams["useMaterialBlur"]),
+ };
+ return editorialDisplayOptions;
+}
+// MARK: - Today Configuration
+/**
+ * Create the "default" card configuration that callers can generally base their configuration from.
+ */
+export function defaultTodayCardConfiguration(objectGraph) {
+ return {
+ useOTDTextStyle: false,
+ enableFullScreenVideo: true,
+ enableListCardToMultiAppFallback: true,
+ canDisplayArcadeOfferButton: true,
+ isHeroCard: false,
+ };
+}
+// MARK: - Metrics / Location Tracking
+/**
+ * Allows for specialized card builders to share the same location tracking push / pop code.
+ *
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data to get location information from
+ * @param cardConfig The configuration for the card
+ * @param context The parse context for the over all today page
+ */
+export function pushTodayCardLocation(objectGraph, data, cardConfig, context, titleOverride) {
+ const title = isDefinedNonNullNonEmpty(titleOverride)
+ ? titleOverride
+ : todayCardTitleFromData(objectGraph, data, cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle));
+ metricsHelpersLocation.pushContentLocation(objectGraph, todayCardMetricsOptions(objectGraph, data, cardConfig, context, title), title !== null && title !== void 0 ? title : "");
+}
+export function todayCardMetricsOptions(objectGraph, data, cardConfig, context, titleOverride) {
+ var _a;
+ return metricsHelpersImpressions.impressionOptions(objectGraph, data, titleOverride !== null && titleOverride !== void 0 ? titleOverride : "", {
+ ...cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.baseMetricsOptions,
+ targetType: "todayCard",
+ pageInformation: context.pageInformation,
+ locationTracker: context.locationTracker,
+ isAdEligible: (_a = cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.isAdEligible) !== null && _a !== void 0 ? _a : false,
+ optimizationId: asString(data, "meta.personalizationData.optimizationId"),
+ optimizationEntityId: asString(data, "meta.personalizationData.optimizationEntityId"),
+ rowIndex: cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.currentRowIndex,
+ displayStyle: cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.metricsDisplayStyle,
+ });
+}
+/**
+ * Pop the today card location that was pushed by the pushTodayCardLocation function.
+ *
+ * @param context The parse context for the over all today page
+ */
+export function popTodayCardLocation(context) {
+ metricsHelpersLocation.popLocation(context.locationTracker);
+}
+// MARK: - Card Title
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media api data used to determine the title of the card
+ * @param cardDisplayStyle The display style of the card
+ * @returns The title of the card
+ */
+export function todayCardTitleFromData(objectGraph, data, cardDisplayStyle) {
+ let title = notesFromData(objectGraph, data, "name");
+ if (isNullOrEmpty(title)) {
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.AppOfTheDay:
+ case TodayCardDisplayStyle.GameOfTheDay:
+ title = mediaAttributes.attributeAsString(data, "label");
+ break;
+ default:
+ break;
+ }
+ }
+ return title;
+}
+// MARK: - Card Display Styles
+/**
+ * These are today card types that can be coerced into an override type, there are times where we want to
+ * have all List cards for instance, render as grids, this set denotes the types that can be coerced.
+ */
+const coercibleTodayCardStyles = new Set([
+ TodayCardDisplayStyle.Grid,
+ TodayCardDisplayStyle.List,
+ TodayCardDisplayStyle.NumberedList,
+ TodayCardDisplayStyle.River,
+]);
+/**
+ * Find the today card style for the given data, allowing coercion if necessary.
+ * @param data The media api data for an editorial item
+ * @param styleOverride The override style to use if the data has a style that can be coerced
+ * @returns The resolved todayCardDisplayStyle
+ */
+export function cardDisplayStyleFromData(data, styleOverride) {
+ const cardDisplayStyle = mediaAttributes.attributeAsString(data, "cardDisplayStyle");
+ if (coercibleTodayCardStyles.has(cardDisplayStyle) && isSome(styleOverride)) {
+ return styleOverride;
+ }
+ return cardDisplayStyle;
+}
+// MARK: - Collection Cards
+/**
+ * @param objectGraph Dependency graph for the App Store
+ * @param data MAPI data for the card these lockups are included in
+ * @param cardConfig The configuration for the card these lockups are included in
+ * @param context The parse context for the over all today page
+ * @param includeLockupClickActions Whether we should include the click actions for the lockups, this is only needed
+ * when a card allows clicks on a lockup
+ * @returns The list of lockups for the collection displayed on a TodayCard
+ */
+export function lockupsForCollectionCardFromData(objectGraph, data, cardConfig, context, includeLockupClickActions) {
+ const relatedContent = relatedCardContentsContentsFromData(objectGraph, data);
+ const filteredRelatedContent = relatedContent.filter((cardData) => {
+ const isHiddenFromSpringboard = isHiddenFromSpringboardFromData(objectGraph, cardData);
+ const hasMessagesExtension = hasMessagesExtensionFromData(objectGraph, cardData);
+ return !hasMessagesExtension || !isHiddenFromSpringboard;
+ });
+ return lockupsForRelatedContent(objectGraph, filteredRelatedContent, cardConfig, context.pageInformation, context.locationTracker, undefined, undefined, undefined, includeLockupClickActions);
+}
+/**
+ * @param objectGraph Dependency graph for the App Store
+ * @param relatedContent The list of MAPI data objects to generate lockups for
+ * @param cardConfig The configuration for the card these lockups are included in
+ * @param pageInformation The pageInformation for the page these lockups are included in
+ * @param locationTracker The locationTracker used for impressions on these lockups
+ * @param offerEnvironment The offer environment to use for the lockups
+ * @param offerStyle The offerStyle to use for the lockups
+ * @param externalDeepLinkUrl The promotional deep link url to use on the lockup's offer.
+ * @param includeLockupClickActions Whether we should include the click actions for the lockups, this is only needed
+ * when a card allows clicks on a lockup
+ * @returns The list of lockups for the collection displayed on a TodayCard
+ */
+export function lockupsForRelatedContent(objectGraph, relatedContent, cardConfig, pageInformation, locationTracker, offerEnvironment, offerStyle, externalDeepLinkUrl, includeLockupClickActions = true) {
+ if (isNullOrEmpty(relatedContent)) {
+ return [];
+ }
+ // IAPs are only suitable for the TodayCardInAppPurchase card. That card follows a separate path, so we should
+ // filter them out here.
+ const filteredRelatedContent = relatedContent.filter((cardData) => {
+ if (isDefinedNonNullNonEmpty(cardData.attributes)) {
+ return cardData.type !== "in-apps";
+ }
+ return true;
+ });
+ const options = {
+ lockupOptions: {
+ metricsOptions: {
+ pageInformation: pageInformation,
+ locationTracker: locationTracker,
+ },
+ offerEnvironment: offerEnvironment,
+ offerStyle: offerStyle,
+ clientIdentifierOverride: cardConfig.clientIdentifierOverride,
+ externalDeepLinkUrl: externalDeepLinkUrl,
+ crossLinkSubtitle: cardConfig.crossLinkSubtitle,
+ artworkUseCase: 1 /* ArtworkUseCase.LockupIconSmall */,
+ canDisplayArcadeOfferButton: cardConfig.canDisplayArcadeOfferButton,
+ useJoeColorIconPlaceholder: cardConfig.useJoeColorIconPlaceholder,
+ skipDefaultClickAction: !includeLockupClickActions,
+ },
+ filter: 76670 /* Filter.TodayCard */,
+ };
+ return lockupsFromData(objectGraph, filteredRelatedContent, options);
+}
+// MARK: - Related Content
+/**
+ * @param objectGraph Dependency graph for the App Store
+ * @param data MAPI data to get the relationship from
+ * @returns The card-contents relationship from the MAPI data
+ */
+export function relatedCardContentsContentsFromData(objectGraph, data) {
+ return relationshipCollection(data, "card-contents");
+}
+// MARK: - Grid / List Collection Card Fallback
+/**
+ * @param objectGraph The object graph for the app store
+ * @returns The min icon count that a Grid card should use before falling back to fallback media
+ */
+export function gridFallbackLimit(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "tv":
+ return 2;
+ default:
+ return 4;
+ }
+}
+/**
+ * @param objectGraph The object graph for the app store
+ * @returns The min icon count that a List card should use before falling back to fallback media
+ */
+export function listFallbackLimit(objectGraph) {
+ // Limits before using fallback media card for list-types and grid-types (including river).
+ switch (objectGraph.client.deviceType) {
+ case "tv":
+ return 3;
+ default:
+ return 4;
+ }
+}
+/**
+ * When we do not have enough content to render a list or grid card, we will use a multi
+ * app fallback media in its place. This also updates the click action to include a param
+ * indicating we're using fallback media.
+ *
+ * @param objectGraph The object graph for the app store
+ * @param data The MAPI data for the card
+ * @param fallbackItems The list of lockups to use as fallback media
+ * @param card The base card to apply the fallback media to
+ */
+export function applyMultiAppFallbackToCollectionCard(objectGraph, data, fallbackItems, card) {
+ const extraText = notesFromData(objectGraph, data, "short");
+ card.media = new TodayCardMediaMultiApp(fallbackItems, extraText);
+ card.style = "dark";
+ if (card.clickAction instanceof FlowAction) {
+ const updatedPageUrl = URL.from(card.clickAction.pageUrl);
+ updatedPageUrl.param(Parameters.showingFallbackMedia, "true");
+ card.clickAction.pageUrl = updatedPageUrl.build();
+ }
+}
+// MARK: - Editorial Artwork
+/**
+ * @param objectGraph The object graph for the app store
+ * @param cardDisplayStyle The display style for the card we're getting the key path for
+ * @returns The keypath used to find the editorial artwork for the card
+ */
+export function editorialArtKeyPathForCardDisplayStyle(objectGraph, cardDisplayStyle) {
+ if (objectGraph.client.isWatch) {
+ return "editorialArtwork.subscriptionHero";
+ }
+ else if (objectGraph.client.isVision) {
+ return "editorialArtwork.storyCenteredStatic16x9";
+ }
+ else {
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.AppOfTheDay:
+ case TodayCardDisplayStyle.GameOfTheDay:
+ return "editorialArtwork.dayCard";
+ case TodayCardDisplayStyle.AppEventCard:
+ return "editorialArtwork.eventCard";
+ case TodayCardDisplayStyle.Video:
+ case TodayCardDisplayStyle.FullBleedImage:
+ return "editorialArtwork.mediaCard";
+ default:
+ return "editorialArtwork.generalCard";
+ }
+ }
+}
+/**
+ * @param objectGraph The object graph for the app store
+ * @param artworkData The artwork data to create the artwork from, from the cards media api data
+ * @param cropCode The crop code to use for the artwork, otherwise we use the correct crop for the platform
+ * @returns The artwork model for the card
+ */
+export function todayCardArtworkFromArtworkData(objectGraph, artworkData, cropCode) {
+ if (isNullOrEmpty(artworkData)) {
+ return null;
+ }
+ const artwork = artworkFromApiArtwork(objectGraph, artworkData, {
+ withJoeColorPlaceholder: true,
+ useCase: 15 /* ArtworkUseCase.TodayCardMedia */,
+ });
+ if (cropCode) {
+ artwork.crop = cropCode;
+ }
+ else if (objectGraph.client.isMac || objectGraph.client.isTV) {
+ // <rdar://problem/39155084> Articles: Use new crossover crop code for macOS story card art
+ artwork.crop = "fn";
+ }
+ else {
+ artwork.crop = "sr";
+ }
+ return artwork;
+}
+/**
+ * Returns a branded title `Artwork` model object from the API `editorialArtwork.contentLogoTrimmed` response.
+ * This artwork can be used in place of a text title for a Today Card.
+ * @param objectGraph The dependency graph for the App Store
+ * @param data The media API data to fetch the Artwork from
+ * @returns The branded title `Artwork` object, or `undefined` if a branded title could not be found
+ */
+export function brandedTitleArtworkForCard(objectGraph, data) {
+ const brandedTitleData = mediaAttributes.attributeAsDictionary(data, "editorialArtwork.contentLogoTrimmed");
+ return artworkFromApiArtwork(objectGraph, brandedTitleData, {
+ contentMode: ArtworkContentMode.scaleAspectFit,
+ allowingTransparency: true,
+ useCase: 17 /* ArtworkUseCase.TodayCardBrandedTitle */,
+ });
+}
+/**
+ * @param objectGraph The object graph for the app store
+ * @param data The media api data for the card
+ * @param cardConfig The configuration for the card being built
+ * @returns The `bgGradientKind` field from the correct artwork dictionary
+ */
+export function todayCardArtworkTitleBackingGradientForKey(objectGraph, data, cardConfig) {
+ const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle);
+ const artworkKey = editorialArtKeyPathForCardDisplayStyle(objectGraph, cardDisplayStyle);
+ const artworkData = mediaAttributes.attributeAsDictionary(data, artworkKey);
+ if (!isDefinedNonNull(artworkData)) {
+ return null;
+ }
+ return asString(artworkData, "bgGradientKind");
+}
+/**
+ * @param objectGraph The object graph for the app store
+ * @param artworkData The artwork data from the media api data for a card, that allows us to determine the style
+ * @returns The light or dark style for this card, if we can determine it.
+ */
+export function todayCardStyleFromArtwork(objectGraph, artworkData) {
+ var _a;
+ if (isNullOrEmpty(artworkData)) {
+ return undefined;
+ }
+ const joeColors = joeColorSetFromData(artworkData);
+ const backgroundColor = joeColors.bgColor;
+ const hasGradient = ((_a = joeColors.textGradient) === null || _a === void 0 ? void 0 : _a.length) === 2;
+ if (!backgroundColor && !hasGradient) {
+ return undefined;
+ }
+ if (objectGraph.client.isiOS || objectGraph.client.isWeb) {
+ return cardStyleFromJoeColors(joeColors, "bgColor");
+ }
+ else if (hasGradient) {
+ // Gradient colors are the text color, so if the gradient is a dark color, then
+ // the environment is a light environment
+ return color.isDarkColor(joeColors.textGradient[0]) ? "light" : "dark";
+ }
+ else {
+ return color.isDarkColor(backgroundColor) ? "dark" : "light";
+ }
+}
+export function cardStyleFromJoeColorsWithoutFallback(joeColors, preferredColorKeypath = "bgColor") {
+ if (isNothing(joeColors)) {
+ return undefined;
+ }
+ if (isSome(joeColors === null || joeColors === void 0 ? void 0 : joeColors.textGradient) && joeColors.textGradient.length === 2) {
+ // Rare, but if gradient colors are present, defer to them
+ return color.isDarkColor(joeColors.textGradient[0]) ? "white" : "dark";
+ }
+ const preferredColor = joeColors[preferredColorKeypath];
+ if (isNothing(preferredColor)) {
+ return undefined;
+ }
+ const luminance = color.luminanceFrom(preferredColor);
+ if (luminance <= 0.1) {
+ return "dark";
+ }
+ else if (luminance >= 0.84) {
+ return "white";
+ }
+ else {
+ return "light";
+ }
+}
+export function cardStyleFromJoeColors(joeColors, preferredColorKeypath = "bgColor") {
+ var _a;
+ return (_a = cardStyleFromJoeColorsWithoutFallback(joeColors, preferredColorKeypath)) !== null && _a !== void 0 ? _a : "light";
+}
+// MARK: - Editorial Text
+/**
+ * Determines the heading for the card, were it to appear inline.
+ * If the source card heading is null, we default to the existing
+ * inline heading, if any.
+ * @param objectGraph The object graph for the app store
+ * @param card The today card we're collapsing the heading for
+ * @returns string The title to use for the card.
+ */
+export function collapsedHeadingForTodayCard(objectGraph, card) {
+ if (isDefinedNonNull(card.heading)) {
+ return card.heading.replace(/\n/g, " ");
+ }
+ return card.collapsedHeading;
+}
+// MARK: - Offers
+/**
+ * Determines an appropriate OfferStyle for a TodayCard to ensure correct
+ * contrast with the card's contents.
+ *
+ * @param cardStyle The today style for the card
+ * @returns an appropriate offer style to use for the button
+ */
+export function offerStyleForTodayCard(objectGraph, cardStyle) {
+ return "transparent";
+}
+/**
+ * Determines an appropriate OfferEnvironment for a TodayCard to ensure correct
+ * contrast with the card's contents.
+ *
+ * @param cardStyle The today style for the card
+ * @returns an appropriate offer environment to use for the button
+ */
+export function offerEnvironmentForTodayCard(cardStyle) {
+ if (cardStyle === "white") {
+ return "light";
+ }
+ else {
+ return "todayCard";
+ }
+}
+// MARK: - Hero Media
+/**
+ * For mac and tv platforms, we add hero media to the today cards.
+ *
+ * @param objectGraph The dependency graph for the app store
+ * @param card The today card we're adding hero media to
+ * @param cardConfig The config used to create this card
+ * @param data The media api data used to create this card
+ */
+export function addHeroMediaToTodayCardIfNecessary(objectGraph, card, cardConfig, data) {
+ const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle);
+ // MacOS and tvOS: Hero
+ const heroArt = todayCardHeroArtForData(objectGraph, data, cardConfig.heroDisplayContext, cardDisplayStyle, cardConfig.prevailingCropCodes);
+ let artworks = [];
+ let videos = [];
+ if (isDefinedNonNull(heroArt)) {
+ artworks = [heroArt];
+ }
+ if (isDefinedNonNull(card.media) &&
+ (card.media instanceof TodayCardMediaVideo || card.media instanceof TodayCardMediaArtwork)) {
+ videos = card.media.videos;
+ }
+ const hero = new TodayCardMediaHero(artworks, videos);
+ const supportsHeroMedia = objectGraph.client.isMac ||
+ objectGraph.client.isTV ||
+ objectGraph.client.isWeb ||
+ (objectGraph.client.isVision && !cardConfig.isSearchContext) ||
+ preprocessor.GAMES_TARGET;
+ if (supportsHeroMedia && hero.isValid()) {
+ card.heroMedia = hero;
+ // Ensure we update the card styling. We prefer hero art, and fallback to hero video.
+ const heroArtworkData = mediaAttributes.attributeAsDictionary(data, heroMediaArtworkKeyForContext(objectGraph, cardConfig.heroDisplayContext, cardDisplayStyle, Object.keys(mediaAttributes.attributeAsDictionary(data, "editorialArtwork"))));
+ let style = todayCardStyleFromArtwork(objectGraph, heroArtworkData);
+ if (!isDefinedNonNull(style)) {
+ const heroVideoPreviewData = asDictionary(todayCardHeroVideoFromData(objectGraph, data), "previewFrame");
+ style = todayCardStyleFromArtwork(objectGraph, heroVideoPreviewData);
+ }
+ card.style = style;
+ }
+}
+/**
+ * For external links we will display the standard link overlay in place of whatever overlay we had previously
+ *
+ * @param card The today card we're adding hero media to
+ */
+export function addExternalLinkOverlayToTodayCardIfNecessary(card) {
+ if (card.clickAction instanceof ExternalUrlAction) {
+ card.overlay = new TodayCardActionOverlay(card.clickAction);
+ card.style = "white";
+ }
+}
+/**
+ * @param objectGraph The dependency graph for the app store
+ * @param data The media api data to search for hero art in
+ * @param context The context in which this hero media will be displayed
+ * @param displayStyle The display style of the card
+ * @param prevailingCropCodes The prevailing crop code to use for inline hero media
+ * @returns The artwork to be used in the hero position for a today card
+ */
+export function todayCardHeroArtForData(objectGraph, data, context, displayStyle, prevailingCropCodes) {
+ const heroArtworkKey = heroMediaArtworkKeyForContext(objectGraph, context, displayStyle, Object.keys(mediaAttributes.attributeAsDictionary(data, "editorialArtwork")));
+ const heroArtworkData = mediaAttributes.attributeAsDictionary(data, heroArtworkKey);
+ return todayCardArtworkFromArtworkData(objectGraph, heroArtworkData, heroMediaArtworkCropForContext(objectGraph, context, objectPathToString(heroArtworkKey), prevailingCropCodes));
+}
+/**
+ * @param objectGraph The dependency graph for the app store
+ * @param context The context in which this hero media will be displayed
+ * @param displayStyle The display style of the card
+ * @returns The key to use to find the hero media artwork
+ */
+function heroMediaArtworkKeyForContext(objectGraph, context, displayStyle, availableArtworkKeys) {
+ if (context === "article" && (objectGraph.client.isMac || objectGraph.client.isiOS)) {
+ // Use "iOS" art
+ return editorialArtKeyPathForCardDisplayStyle(objectGraph, displayStyle);
+ }
+ else if (objectGraph.client.isVision) {
+ return "editorialArtwork.heroStatic16x9";
+ }
+ else {
+ if (objectGraph.client.isTV && availableArtworkKeys.includes("categoryDetailStatic16x9")) {
+ return "editorialArtwork.categoryDetailStatic16x9";
+ }
+ return "editorialArtwork.crossoverCard";
+ }
+}
+/**
+ * @param objectGraph The dependency graph for the app store
+ * @param context The context in which this hero media will be displayed
+ * @param prevailingCropCodes The crop code to use if we're not overriding it
+ * @returns The crop code to use for hero artwork in a given context
+ */
+function heroMediaArtworkCropForContext(objectGraph, context, heroArtworkKey, prevailingCropCodes) {
+ if (context === HeroMediaDisplayContext.Article && objectGraph.client.isMac) {
+ // Use "iOS" art crop code
+ return "fn";
+ }
+ else if (context === HeroMediaDisplayContext.Article &&
+ objectGraph.client.isTV &&
+ heroArtworkKey === "editorialArtwork.categoryDetailStatic16x9") {
+ return "sr";
+ }
+ else {
+ return prevailingCropCodes === null || prevailingCropCodes === void 0 ? void 0 : prevailingCropCodes.defaultCrop;
+ }
+}
+/**
+ * @param objectGraph The dependency graph for the app store
+ * @param data The media api data to search for hero video in
+ * @returns The video to be used in the hero position for a today card
+ */
+function todayCardHeroVideoFromData(objectGraph, data) {
+ let videoData;
+ const videoData4x3 = mediaAttributes.attributeAsDictionary(data, "editorialVideo.storeFrontVideo4x3");
+ const videoData16x9 = mediaAttributes.attributeAsDictionary(data, "editorialVideo.storeFrontVideo");
+ // <rdar://problem/34339194> Today: Video Card: Incorrect key for 4x3 videos
+ // On pad prefer 4x3 fallback to 16x9 and on phone vice versa
+ if (objectGraph.client.isPad || objectGraph.client.screenSize.isEqualTo(screenSizeIPhone134)) {
+ videoData = videoData4x3 || videoData16x9;
+ }
+ else {
+ videoData = videoData16x9 || videoData4x3;
+ }
+ return videoData;
+}
+// MARK: - Crosslinks
+/**
+ * Determines the cross link subtitle from card data.
+ */
+export function crossLinkSubtitleFromData(objectGraph, data) {
+ // Start with short notes
+ let subtitle = editorialNotesFromData(objectGraph, data, "short");
+ // Fallback to name
+ if (!isDefinedNonNullNonEmpty(subtitle)) {
+ subtitle = notesFromData(objectGraph, data, "name");
+ }
+ // Fallback to label
+ if (!isDefinedNonNullNonEmpty(subtitle)) {
+ const cardDisplayStyle = mediaAttributes.attributeAsString(data, "displayStyle");
+ if (cardDisplayStyle === TodayCardDisplayStyle.AppOfTheDay ||
+ cardDisplayStyle === TodayCardDisplayStyle.GameOfTheDay) {
+ subtitle = mediaAttributes.attributeAsString(data, "label");
+ }
+ }
+ return subtitle;
+}
+// MARK: - Client Identifier Overrids
+function shouldRespectClientIdentifierOverride(objectGraph, data, clientIdentifier, cardConfig) {
+ var _a;
+ if (clientIdentifier === "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */ || clientIdentifier === "VisionAppStore" /* ClientIdentifier.VisionAppStore */) {
+ // If using the companion app, it's expected that we always respect the given identifier.
+ return true;
+ }
+ const relatedContent = relatedCardContentsContentsFromData(objectGraph, data);
+ const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig.coercedCollectionTodayCardDisplayStyle);
+ // Do not apply the override if this is not a Content type card.
+ const cardTypesRequiringClientIdentifierOverride = new Set([
+ TodayCardDisplayStyle.AppEventCard,
+ TodayCardDisplayStyle.Grid,
+ TodayCardDisplayStyle.InAppPurchase,
+ TodayCardDisplayStyle.List,
+ TodayCardDisplayStyle.NumberedList,
+ TodayCardDisplayStyle.River,
+ TodayCardDisplayStyle.ShortImage,
+ TodayCardDisplayStyle.SingleApp,
+ ]);
+ if (!cardTypesRequiringClientIdentifierOverride.has(cardDisplayStyle)) {
+ return false;
+ }
+ // Grids with >= 6 items are unsupported
+ if (relatedContent.length >= 6 && cardDisplayStyle === TodayCardDisplayStyle.Grid) {
+ return false;
+ }
+ // Content cards with Artwork are unsupported
+ const artworkKey = editorialArtKeyPathForCardDisplayStyle(objectGraph, cardDisplayStyle);
+ if (!mediaAttributes.attributeAsBooleanOrFalse(data, "ignoreEditorialArt") &&
+ todayCardArtworkFromArtworkData(objectGraph, mediaAttributes.attributeAsDictionary(data, artworkKey), (_a = cardConfig.prevailingCropCodes) === null || _a === void 0 ? void 0 : _a.defaultCrop)) {
+ return false;
+ }
+ // Content cards with iAP are unsupported
+ if (inAppPurchaseDataFromRelatedContent(objectGraph, relatedContent)) {
+ return false;
+ }
+ return true;
+}
+function inAppPurchaseDataFromRelatedContent(objectGraph, relatedContent) {
+ if (relatedContent.length === 1) {
+ const contentData = relatedContent[0];
+ if (contentData.type === "in-apps") {
+ return contentData;
+ }
+ }
+ return null;
+}
+/**
+ * If a card should be using the OTD style we need to make sure we set that on the card
+ *
+ * @param card The today card we're adding the style to
+ * @param cardConfig The config used to create this card
+ */
+function addOTDStyleToCardIfNecessary(card, cardConfig) {
+ if (isNothing(card.media)) {
+ return;
+ }
+ card.media.otdTextStyle = cardConfig.useOTDTextStyle;
+}
+/**
+ * Check if this card should have the flip and blur enabled, this is the case when a card has videos.
+ * @param objectGraph The dependency graph for the app store
+ * @param card The today card we're modifying
+ */
+function enableFlipAndBlurIfNecessary(objectGraph, card) {
+ const cardMedia = card.media;
+ const cardMediaHasArtwork = cardMedia instanceof TodayCardMediaWithArtwork;
+ if (!cardMediaHasArtwork) {
+ return;
+ }
+ const cardMdiaWithArtwork = cardMedia;
+ const hasVideos = isDefinedNonNullNonEmpty(cardMdiaWithArtwork.videos);
+ card.supportsMediaMirroring = hasVideos;
+}
+/**
+ * @param data The media API data used to determine the intention of the card
+ * @param cardConfig The configuration for the card
+ * @returns Whether this app is an App of the Day or Game of the Day
+ */
+export function isCardOTDIntention(data, cardConfig) {
+ let otdIntention = mediaAttributes.attributeAsString(data, "ofTheDayIntent");
+ if (isNothing(otdIntention)) {
+ const cardDisplayStyle = cardDisplayStyleFromData(data, cardConfig === null || cardConfig === void 0 ? void 0 : cardConfig.coercedCollectionTodayCardDisplayStyle);
+ switch (cardDisplayStyle) {
+ case TodayCardDisplayStyle.AppOfTheDay:
+ otdIntention = OfTheDayIntention.AppOfTheDay;
+ break;
+ case TodayCardDisplayStyle.GameOfTheDay:
+ otdIntention = OfTheDayIntention.GameOfTheDay;
+ break;
+ default:
+ break;
+ }
+ }
+ return otdIntention === OfTheDayIntention.AppOfTheDay || otdIntention === OfTheDayIntention.GameOfTheDay;
+}
+//# sourceMappingURL=today-card-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-controller-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-controller-util.js
new file mode 100644
index 0000000..98f9fbc
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-controller-util.js
@@ -0,0 +1,534 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import { FetchTimingMetricsBuilder } from "@jet/environment/metrics";
+import * as models from "../../api/models";
+import * as impressionDemotion from "../../common/personalization/on-device-impression-demotion";
+import { asArrayOrEmpty, asBoolean, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNullOrEmpty, } from "../../foundation/json-parsing/server-data";
+import { hasAttributes } from "../../foundation/media/attributes";
+import * as mediaDataFetching from "../../foundation/media/data-fetching";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import { fetchData } from "../../foundation/media/network";
+import { Parameters, Path } from "../../foundation/network/url-constants";
+import { URL } from "../../foundation/network/urls";
+import { optional, required } from "../../foundation/util/promise-util";
+import { AppEventsAttributes } from "../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types";
+import { applySearchAdMissedOpportunityToShelvesIfNeeded, eligibleSlotPositionsForAdPlacement, iadInfoFromOnDeviceAdResponse, isAdPlacementEnabled, } from "../ads/ad-common";
+import * as adIncidents from "../ads/ad-incident-recorder";
+import { fetchAds, parallelOrganicRequestDidFinish } from "../ads/on-device-ad-fetch";
+import * as appPromotionsCommon from "../app-promotions/app-promotions-common";
+import { fetchPageWithAdditionalPageRequirements } from "../builders/additional-page-requirement-util";
+import { pageRouter } from "../builders/routing";
+import { shouldUsePrerenderedIconArtwork } from "../content/content";
+import { shelfForFooterButtons, shelfForTermsAndConditions, shelfForUnifiedMessage } from "../grouping/grouping-common";
+import { newLocationTracker } from "../metrics/helpers/location";
+import { iAdInformationFromMediaApiResponse } from "../metrics/helpers/models";
+import { addMetricsEventsToPageWithInformation, metricsPageInformationFromMediaApiResponse, } from "../metrics/helpers/page";
+import { combinedRecoMetricsDataFromMetricsData } from "../metrics/helpers/util";
+import * as onDevicePersonalization from "../personalization/on-device-personalization";
+import { fetchTodayRecommendationsWithTimeout, isTodayTabArcadePersonalizationAvailable, } from "../personalization/on-device-recommendations-today";
+import { setPreviewPlatform } from "../preview-platform";
+import * as productPageVariants from "../product-page/product-page-variants";
+import { newPageRefreshControllerFromResponse } from "../refresh/page-refresh-controller";
+import { storeCohortIdForUserFromResponse } from "../search/landing/search-landing-cohort";
+import { FlattenedTodayItemType, createAdShelfForTodayPageContextIfNecessary, feedPreviewUrlFromFlattenedTodayItems, flattenTodayModules, nextFlattenedItemIsHydrated, todayShelfForEditorialItem, todayShelfForEditorialItemGroup, } from "./today-parse-util";
+import { TodayPageContext } from "./today-types";
+/**
+ * Keys used to manage additional data needed to render the today page.
+ *
+ * - onboardingCards: key used to fetch the onboarding cards for the feed.
+ *
+ * - ads: Key used for fetching the today placement ads
+ */
+export var TodayControllerAdditionalDataKey;
+(function (TodayControllerAdditionalDataKey) {
+ TodayControllerAdditionalDataKey["OnboardingCards"] = "onboardingCards";
+ TodayControllerAdditionalDataKey["Ads"] = "ads";
+ TodayControllerAdditionalDataKey["ODP"] = "ODP";
+ TodayControllerAdditionalDataKey["AMDData"] = "amdData";
+})(TodayControllerAdditionalDataKey || (TodayControllerAdditionalDataKey = {}));
+export function defaultPlatforms(objectGraph) {
+ return mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph);
+}
+/// Resource attributes to include for `editorial-items` fetched for displaying in today page.
+export function defaultAttributes(objectGraph) {
+ const attributes = [
+ "editorialArtwork",
+ "editorialVideo",
+ "enrichedEditorialNotes",
+ "minimumOSVersion",
+ "headerName",
+ "headerBadge",
+ "headerTagline",
+ "editorialClientParams",
+ "requiredCapabilities",
+ "shortEditorialNotes",
+ ];
+ if (objectGraph.bag.enableUpdatedAgeRatings) {
+ attributes.push("ageRating");
+ }
+ if (shouldUsePrerenderedIconArtwork(objectGraph)) {
+ attributes.push("iconArtwork");
+ }
+ return attributes;
+}
+export function prepareRequest(objectGraph, request, isPageRequest) {
+ var _a, _b;
+ request
+ .includingAgeRestrictions()
+ .includingAdditionalPlatforms(defaultPlatforms(objectGraph))
+ .includingAttributes(defaultAttributes(objectGraph))
+ .includingScopedAttributes("editorial-item-groups", ["editorialClientParams"])
+ .includingScopedRelationships("editorial-items", ["primary-content", "header-contents"])
+ .includingScopedRelationships("editorial-item-groups", ["header-contents"])
+ .enablingFeature("editorialItemGroups")
+ .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph))
+ .includingRelationshipsForUpsell(true);
+ if (isPageRequest) {
+ request.includingAssociateKeys("editorial-item-groups", ["recommendations", "editorial-cards"]);
+ request.includingAssociateKeys("editorial-items", ["editorial-cards"]);
+ }
+ if (appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
+ request.enablingFeature("appEvents");
+ request.addingQuery("meta", "personalizationData");
+ request.includingScopedRelationships("app-events", ["app"]);
+ request.includingScopedAttributes("app-events", AppEventsAttributes);
+ }
+ if (appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) {
+ request.enablingFeature("offerItems");
+ if (isPageRequest) {
+ request.includingKindsKeys("offer-items", ["winback"]);
+ }
+ }
+ if (impressionDemotion.isImpressionDemotionAvailable(objectGraph)) {
+ request.enablingFeature("eiGroupEISelectionOnDevice");
+ }
+ if (isAdPlacementEnabled(objectGraph, "today")) {
+ request.enablingFeature("adSupport");
+ }
+ request.enablingFeature("heroStyles");
+ const displayDeviceDrivenContent = (_b = (_a = objectGraph.userDefaults) === null || _a === void 0 ? void 0 : _a.bool("displayDeviceDrivenContent")) !== null && _b !== void 0 ? _b : false;
+ if (objectGraph.bag.enableDeviceDrivenDiscoveryContent &&
+ displayDeviceDrivenContent &&
+ request.resourceType === "today") {
+ request.addingQuery("pairedDevices", "visionPro");
+ }
+}
+// MARK: - Page Fetching
+export function generateTodayPageRequest(objectGraph) {
+ // Note: The sparse limit is now 2 because story groups will include all of their content which means
+ // if we continue to use the original 6 count we'll end up with way too many EI's
+ // rdar://108635958 ([Network Launch | 283ms | 19%] App Store - Dawn - D33: 21A236 vs 20E245 network-warm-launch:
+ // extendedLaunchTime is 1731.0ms vs 1448.0ms)
+ const sparseCount = objectGraph.client.isWeb ? 40 : 1;
+ const sparseLimit = objectGraph.client.isWeb ? 40 : 2;
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph)
+ .forType("today")
+ .withSparseCount(sparseCount)
+ .withSparseLimit(sparseLimit);
+ return setPreviewPlatform(objectGraph, mediaApiRequest);
+}
+/**
+ * @param objectGraph The object graph for the AppStore.
+ * @param onboardingCardIds The onboardingCard ids to fetch.
+ * @returns The map of additional page requirements.
+ */
+function generateAdditionalPageRequirements(objectGraph, onboardingCardIds) {
+ const additionalRequirements = {};
+ /**
+ * Currently, to avoid double filtering, the fix for:
+ * <rdar://problem/45031644> If an onboarding card is empty don't add it to the request
+ * depends on the `Request` object filtering IDs from under caller. The caller is expected to perform some validation to
+ * guarantee that this `Request` object is not malformed after it was processed here, and it can still build a valid URL.
+ *
+ * This mechanic is undesired, and once this filtering is removed from `Request`, the caller should filter empty onboarding
+ * IDs via an explicit method instead before instantiating an malformed `Request`.
+ */
+ if (isDefinedNonNullNonEmpty(onboardingCardIds)) {
+ const onboardingCardsRequest = new mediaDataFetching.Request(objectGraph)
+ .withIdsOfType(onboardingCardIds, "editorial-items")
+ .includingAdditionalPlatforms(defaultPlatforms(objectGraph))
+ .includingAttributes(defaultAttributes(objectGraph));
+ additionalRequirements[TodayControllerAdditionalDataKey.OnboardingCards] = optional(fetchData(objectGraph, onboardingCardsRequest, {}));
+ }
+ if (isAdPlacementEnabled(objectGraph, "today")) {
+ additionalRequirements[TodayControllerAdditionalDataKey.Ads] = optional(fetchAds(objectGraph, "today"));
+ }
+ if (isTodayTabArcadePersonalizationAvailable(objectGraph)) {
+ additionalRequirements[TodayControllerAdditionalDataKey.ODP] = optional(fetchTodayRecommendationsWithTimeout(objectGraph, undefined));
+ }
+ const amsEngagement = objectGraph.amsEngagement;
+ if (amsEngagement && impressionDemotion.isImpressionDemotionAvailable(objectGraph)) {
+ const request = {
+ timeout: 1000,
+ eventType: impressionDemotion.AMSEngagementAppStoreEventKey,
+ tab: "today",
+ };
+ additionalRequirements[TodayControllerAdditionalDataKey.AMDData] = optional(amsEngagement.performRequest(request));
+ }
+ return additionalRequirements;
+}
+export async function fetchTodayPage(objectGraph, options) {
+ const router = objectGraph.required(pageRouter);
+ const isTodayTestPage = options.url === "x-as3-internal:/today/test";
+ const isTodayCardPreviewPage = options.url.indexOf(Path.todayCardPreview) !== -1;
+ if (isTodayTestPage) {
+ return await router.fetchPage(objectGraph, options.url, models.TodayPage);
+ }
+ else if (isTodayCardPreviewPage) {
+ const dataFetchUrl = URL.from(options.url);
+ dataFetchUrl.param(Parameters.fetchData, "true");
+ return await router.fetchPage(objectGraph, dataFetchUrl.build(), models.TodayPage);
+ }
+ else {
+ const request = generateTodayPageRequest(objectGraph);
+ prepareRequest(objectGraph, request, true);
+ const requestHeaders = isSome(options.experimentIdHeader)
+ ? { "X-Apple-User-Experiment-Ids": options.experimentIdHeader }
+ : undefined;
+ return await buildTodayPageFromRequest(objectGraph, request, requestHeaders, options);
+ }
+}
+export async function buildTodayPageFromRequest(objectGraph, request, requestHeaders, options) {
+ var _a;
+ const fetchTimingMetricsBuilder = new FetchTimingMetricsBuilder();
+ const modifiedObjectGraph = objectGraph.addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder);
+ const primaryDataFetchPromise = fetchData(modifiedObjectGraph, request, {
+ headers: requestHeaders !== null && requestHeaders !== void 0 ? requestHeaders : undefined,
+ });
+ // Should this be needed on web, this should have a .catch() added to avoid
+ // an unhandled rejection.
+ if (!objectGraph.client.isWeb) {
+ void primaryDataFetchPromise.then((response) => {
+ if (isAdPlacementEnabled(objectGraph, "today")) {
+ parallelOrganicRequestDidFinish(objectGraph, "today");
+ }
+ storeCohortIdForUserFromResponse(objectGraph, objectGraph.user.dsid, response);
+ });
+ }
+ const primaryDataFetchInput = required(primaryDataFetchPromise);
+ const additionalRequirements = generateAdditionalPageRequirements(objectGraph, (_a = options === null || options === void 0 ? void 0 : options.onboardingCardIds) !== null && _a !== void 0 ? _a : []);
+ const pageDataRequirements = await fetchPageWithAdditionalPageRequirements(objectGraph, primaryDataFetchInput, additionalRequirements);
+ return fetchTimingMetricsBuilder.measureModelConstruction(() => {
+ var _a, _b;
+ const onboardingCardsResponse = pageDataRequirements.additionalData[TodayControllerAdditionalDataKey.OnboardingCards];
+ const onboardingCards = mediaDataStructure.dataCollectionFromDataContainer(onboardingCardsResponse);
+ const adsResponse = pageDataRequirements.additionalData[TodayControllerAdditionalDataKey.Ads];
+ const todayRecommendations = pageDataRequirements.additionalData[TodayControllerAdditionalDataKey.ODP];
+ const amdData = pageDataRequirements.additionalData[TodayControllerAdditionalDataKey.AMDData];
+ const flattenedTodayItems = flattenTodayModules(objectGraph, (_b = (_a = pageDataRequirements.primaryPageData.results) === null || _a === void 0 ? void 0 : _a.data) !== null && _b !== void 0 ? _b : [], todayRecommendations, onboardingCards, adsResponse);
+ return buildTodayPage(modifiedObjectGraph, flattenedTodayItems, pageDataRequirements.primaryPageData, adsResponse, amdData);
+ });
+}
+// MARK: - Today Page Creation
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param flattenedTodayItems The flattened Today items used to render the page
+ * @param primaryPageData The primary page data for the Today page
+ * @param adsResponse The response from ad platforms
+ * @returns A TodayPage model
+ */
+export function buildTodayPage(objectGraph, flattenedTodayItems, primaryPageData, adsResponse, amdResponse) {
+ return validation.context("renderPage", () => {
+ if (isNullOrEmpty(flattenedTodayItems)) {
+ return null;
+ }
+ const pageContext = createTodayPageContext(objectGraph, flattenedTodayItems, primaryPageData, adsResponse, amdResponse);
+ const todayPage = todayPageFromPageContext(objectGraph, pageContext);
+ todayPage.shelves.splice(0, 0, shelfForUnifiedMessage(objectGraph, "todayPageHeader"));
+ return todayPage;
+ });
+}
+/**
+ * From the provided today page context, create a new today page with the hydrated content
+ * @param objectGraph The dependency graph for the App Store
+ * @param pageContext The page context for the Today page
+ */
+export function todayPageFromPageContext(objectGraph, pageContext) {
+ var _a;
+ const shelves = [];
+ const feedPreviewUrl = feedPreviewUrlFromFlattenedTodayItems(objectGraph, pageContext.remainingContent);
+ let hasMoreHydratedContent = nextFlattenedItemIsHydrated(pageContext.remainingContent);
+ const isPageLoadNotPaginated = (_a = pageContext.remainingContent[0]) === null || _a === void 0 ? void 0 : _a.isFirstItemInModule;
+ while (hasMoreHydratedContent) {
+ const todayItem = pageContext.remainingContent.shift();
+ if (isNothing(todayItem)) {
+ hasMoreHydratedContent = false;
+ break;
+ }
+ const preItemAdShelf = createAdShelfForTodayPageContextIfNecessary(objectGraph, pageContext);
+ if (isSome(preItemAdShelf)) {
+ shelves.push(preItemAdShelf);
+ }
+ pageContext.currentTodayItem = todayItem;
+ let todayItemShelf = null;
+ switch (todayItem.type) {
+ case FlattenedTodayItemType.EditorialItem:
+ todayItemShelf = todayShelfForEditorialItem(objectGraph, todayItem, pageContext);
+ break;
+ case FlattenedTodayItemType.EditorialItemGroup:
+ todayItemShelf = todayShelfForEditorialItemGroup(objectGraph, todayItem, pageContext);
+ break;
+ default:
+ break;
+ }
+ pageContext.currentTodayItem = undefined;
+ if (isSome(todayItemShelf)) {
+ shelves.push(todayItemShelf);
+ }
+ const postItemAdShelf = createAdShelfForTodayPageContextIfNecessary(objectGraph, pageContext);
+ if (isSome(postItemAdShelf)) {
+ shelves.push(postItemAdShelf);
+ }
+ hasMoreHydratedContent = nextFlattenedItemIsHydrated(pageContext.remainingContent);
+ }
+ if (isPageLoadNotPaginated) {
+ applySearchAdMissedOpportunityToShelvesIfNeeded(objectGraph, shelves, "today", "today", pageContext.pageInformation);
+ }
+ const todayPage = new models.TodayPage(shelves);
+ const todayTab = objectGraph.bag.tabsStandard.find((tab) => tab.id === "today");
+ const todayTabTitle = asString(todayTab, "title");
+ const tabImageIdentifier = asString(todayTab, "image-identifier");
+ const includeTitleDetail = isNothing(tabImageIdentifier) || tabImageIdentifier === "doc.text.image";
+ const todayTitle = todayTabTitle !== null && todayTabTitle !== void 0 ? todayTabTitle : objectGraph.loc.string("PAGE_TITLE_TODAY");
+ todayPage.title = todayTitle;
+ todayPage.tabTitle = todayTitle;
+ todayPage.titleDetail = includeTitleDetail ? todayPageTitleDetail(objectGraph) : undefined;
+ todayPage.shortTitleDetail = includeTitleDetail ? todayPageShortTitleDetail(objectGraph) : undefined;
+ todayPage.longTitle = includeTitleDetail ? todayPageLongTitle(objectGraph) : undefined;
+ todayPage.feedPreviewUrl = feedPreviewUrl;
+ if (isDefinedNonNullNonEmpty(pageContext.remainingContent)) {
+ pageContext.pageHasDisplayedContent =
+ pageContext.pageHasDisplayedContent ||
+ shelves.some((shelf) => {
+ isDefinedNonNullNonEmpty(shelf.items);
+ });
+ todayPage.nextPage = pageContext;
+ }
+ else if (!objectGraph.client.isWeb) {
+ footerShelvesForTodayPage(objectGraph, pageContext.pageInformation, pageContext.locationTracker).forEach((shelf) => {
+ todayPage.shelves.push(shelf);
+ });
+ }
+ if (isAdPlacementEnabled(objectGraph, "today")) {
+ // Ad Incidents
+ todayPage.adIncidents = adIncidents.recordedIncidents(objectGraph, pageContext.adIncidentRecorder);
+ }
+ addMetricsEventsToPageWithInformation(objectGraph, todayPage, pageContext.pageInformation);
+ return todayPage;
+}
+/**
+ * Fetch the next batch of content for the Today page, and hydrate the page context
+ * @param objectGraph The dependency graph for the App Store
+ * @param pageContext The page context for the Today page
+ * @returns
+ */
+export async function hydrateNextBatchOfContent(objectGraph, pageContext) {
+ const batchLoadSize = pageContext.pageHasDisplayedContent ? 6 : 12;
+ let continueAddingContent = true;
+ let unhydratedItemIndex = 0;
+ let unhydratedContents = [];
+ while (continueAddingContent) {
+ const nextContent = pageContext.remainingContent[unhydratedItemIndex];
+ switch (nextContent.type) {
+ case FlattenedTodayItemType.EditorialItem:
+ unhydratedContents.push(nextContent.data);
+ break;
+ case FlattenedTodayItemType.EditorialItemGroup:
+ unhydratedContents.push(nextContent.data);
+ const groupItems = asArrayOrEmpty(nextContent.data, "meta.associations.recommendations.data");
+ unhydratedContents = [...unhydratedContents, ...groupItems];
+ break;
+ default:
+ break;
+ }
+ unhydratedItemIndex++;
+ continueAddingContent =
+ unhydratedContents.length < batchLoadSize && unhydratedItemIndex < pageContext.remainingContent.length;
+ }
+ // MAPI request for module slice's contents
+ const requestedContentIds = new Set(unhydratedContents.map((content) => content.id));
+ const contentsFetchRequest = new mediaDataFetching.Request(objectGraph, unhydratedContents, true, [
+ "recommendations",
+ "editorial-cards",
+ ]);
+ prepareRequest(objectGraph, contentsFetchRequest, false);
+ const fetchedData = await fetchData(objectGraph, contentsFetchRequest, { allowEmptyDataResponse: true });
+ const fetchedDataMap = {};
+ for (const data of fetchedData.data) {
+ fetchedDataMap[data.id] = data;
+ }
+ // Hydrate the page context with the fetched data
+ for (const content of pageContext.remainingContent) {
+ hydrateFlattenedItemWithFetchedDataMap(content, fetchedDataMap);
+ }
+ /// Now take a pass and remove any items that are still unhydrated after performing the fetch
+ pageContext.remainingContent = pageContext.remainingContent.filter((content) => {
+ return content.isDataHydrated || !requestedContentIds.has(content.data.id);
+ });
+ return pageContext;
+}
+/**
+ * For a given today item, take the fetched MAPI data and apply that to the item.
+ *
+ * @param flattenedItem The today item we're going to be hydrating
+ * @param fetchedDataMap The mapping from id to fetched media api data.
+ */
+function hydrateFlattenedItemWithFetchedDataMap(flattenedItem, fetchedDataMap) {
+ var _a, _b, _c;
+ switch (flattenedItem.type) {
+ case FlattenedTodayItemType.EditorialItem:
+ if (isDefinedNonNullNonEmpty(fetchedDataMap[flattenedItem.data.id])) {
+ flattenedItem.data = {
+ ...flattenedItem.data,
+ ...fetchedDataMap[flattenedItem.data.id],
+ };
+ }
+ flattenedItem.isDataHydrated = hasAttributes(flattenedItem.data);
+ break;
+ case FlattenedTodayItemType.EditorialItemGroup:
+ if (isDefinedNonNullNonEmpty(fetchedDataMap[flattenedItem.data.id])) {
+ flattenedItem.data = {
+ ...flattenedItem.data,
+ attributes: (_a = fetchedDataMap[flattenedItem.data.id]) === null || _a === void 0 ? void 0 : _a.attributes,
+ relationships: (_b = fetchedDataMap[flattenedItem.data.id]) === null || _b === void 0 ? void 0 : _b.relationships,
+ meta: {
+ ...flattenedItem.data.meta,
+ ...(_c = fetchedDataMap[flattenedItem.data.id]) === null || _c === void 0 ? void 0 : _c.meta,
+ },
+ };
+ }
+ flattenedItem.isDataHydrated = hasAttributes(flattenedItem.data);
+ break;
+ default:
+ break;
+ }
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayItems The list of all the items displayed on the today page.
+ * @param todayResponse The response from the today endpoint
+ * @param adResponse The response from the ads service
+ * @returns The context to use when parsing today cards
+ */
+function createTodayPageContext(objectGraph, todayItems, todayResponse, adsResponse, amdResponse) {
+ var _a, _b, _c;
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "Today", "today", todayResponse, null, (_a = iadInfoFromOnDeviceAdResponse(objectGraph, "today", adsResponse, todayItems)) !== null && _a !== void 0 ? _a : iAdInformationFromMediaApiResponse(objectGraph, "today", todayResponse, (_b = adsResponse === null || adsResponse === void 0 ? void 0 : adsResponse.onDeviceAd) === null || _b === void 0 ? void 0 : _b.positionInfo, todayItems));
+ const onDevicePersonalizationMetricsData = onDevicePersonalization.metricsData(objectGraph);
+ pageInformation.recoMetricsData = combinedRecoMetricsDataFromMetricsData(pageInformation.recoMetricsData, null, onDevicePersonalizationMetricsData);
+ const context = new TodayPageContext(todayItems, pageInformation, newLocationTracker(), newPageRefreshControllerFromResponse(todayResponse), impressionDemotion.impressionEventsFromData(objectGraph, amdResponse));
+ if (isAdPlacementEnabled(objectGraph, "today")) {
+ const eligibleAdPositions = eligibleSlotPositionsForAdPlacement(objectGraph, "today");
+ if (isSome(eligibleAdPositions)) {
+ // The slot as provided by ad platforms is one-based - adjust it so we're working with zero-based numbers.
+ context.eligibleAdLocations = eligibleAdPositions.map((position) => position.slot);
+ }
+ const adIncidentRecorder = adIncidents.newRecorder(objectGraph, pageInformation.iAdInfo);
+ adIncidents.recordAdResponseEventsIfNeeded(objectGraph, adIncidentRecorder, adsResponse);
+ context.adIncidentRecorder = adIncidentRecorder;
+ context.adPlacementBehavior = getAdPlacementBehaviorForPage(todayItems);
+ if (isNothing(adsResponse === null || adsResponse === void 0 ? void 0 : adsResponse.failureReason)) {
+ context.adData = mediaDataStructure.dataFromDataContainer(objectGraph, adsResponse === null || adsResponse === void 0 ? void 0 : adsResponse.mediaResponse);
+ const rawPositionInfo = (_c = adsResponse === null || adsResponse === void 0 ? void 0 : adsResponse.onDeviceAd) === null || _c === void 0 ? void 0 : _c.positionInfo;
+ if (isDefinedNonNullNonEmpty(rawPositionInfo)) {
+ // The slot as provided by ad platforms is one-based - adjust it so we're working with zero-based numbers.
+ const adjustedAdLocation = rawPositionInfo.slot - 1;
+ context.adLocation = adjustedAdLocation;
+ }
+ }
+ }
+ return context;
+}
+function getAdPlacementBehaviorForPage(todayItems) {
+ let adPlacementBehavior = models.AdPlacementBehavior.insertIntoShelf;
+ for (const todayItem of todayItems) {
+ let todayItemAdPlacementBehavior = adPlacementBehavior;
+ switch (todayItem.type) {
+ case FlattenedTodayItemType.EditorialItem:
+ todayItemAdPlacementBehavior = getAdPlacementBehaviorForData(todayItem.data);
+ break;
+ case FlattenedTodayItemType.EditorialItemGroup:
+ const groupItems = asArrayOrEmpty(todayItem.data, "meta.associations.recommendations.data");
+ if (isDefinedNonNullNonEmpty(groupItems)) {
+ for (const groupItem of groupItems) {
+ todayItemAdPlacementBehavior = getAdPlacementBehaviorForData(groupItem);
+ if (todayItemAdPlacementBehavior !== models.AdPlacementBehavior.insertIntoShelf) {
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ if (todayItemAdPlacementBehavior !== models.AdPlacementBehavior.insertIntoShelf) {
+ adPlacementBehavior = todayItemAdPlacementBehavior;
+ break;
+ }
+ }
+ return adPlacementBehavior;
+}
+function getAdPlacementBehaviorForData(data) {
+ const replaceIfAdPresent = asBoolean(data, "meta.personalizationData.replaceIfAdPresent");
+ if (isDefinedNonNull(replaceIfAdPresent) && replaceIfAdPresent) {
+ return models.AdPlacementBehavior.replaceOrganic;
+ }
+ else if (isDefinedNonNull(replaceIfAdPresent) && !replaceIfAdPresent) {
+ return models.AdPlacementBehavior.dropAd;
+ }
+ else {
+ return models.AdPlacementBehavior.insertIntoShelf;
+ }
+}
+/**
+ * Return the first shelf title for the today page. This is used to generate the tab title.
+ * @param objectGraph The dependency graph of the app store
+ */
+function todayPageTitleDetail(objectGraph) {
+ const now = new Date();
+ const dateFormat = objectGraph.loc.string("Today.ShelfTitle.DateFormat");
+ return objectGraph.loc.formatDate(dateFormat, now);
+}
+/**
+ * Return the first shelf title for the today page on iPad. This is used to generate the tab title.
+ * @param objectGraph The dependency graph of the app store
+ */
+function todayPageLongTitle(objectGraph) {
+ if (!objectGraph.client.isPad) {
+ return undefined;
+ }
+ const now = new Date();
+ const dateFormat = objectGraph.loc.string("Today.ShelfTitle.DateFormat.IPad");
+ return objectGraph.loc.formatDate(dateFormat, now);
+}
+/**
+ * Return a short version of the first shelf title for the today page. This is used to generate the tab title,
+ * and is used where less horizontal space is available.
+ * @param objectGraph The dependency graph of the app store
+ */
+export function todayPageShortTitleDetail(objectGraph) {
+ const now = new Date();
+ return objectGraph.loc.formatDate("MMM d", now);
+}
+/**
+ * Generate the last shelves of the today page
+ * @param objectGraph The dependency graph for the app
+ * @param pageInformation The page information for the today page
+ * @param locationTracker The Location tracker for the current today page
+ * @returns The shelves that appear tha the bottom of the today page
+ */
+function footerShelvesForTodayPage(objectGraph, pageInformation, locationTracker) {
+ const shelves = [];
+ // Add Footer Buttons
+ const footerButtonShelf = shelfForFooterButtons(objectGraph, pageInformation, locationTracker);
+ if (isDefinedNonNull(footerButtonShelf)) {
+ shelves.push(footerButtonShelf);
+ }
+ // Add T&C
+ const url = objectGraph.bag.termsAndConditionsURL;
+ if (isDefinedNonNull(url)) {
+ const termsAndConditionsShelf = shelfForTermsAndConditions(objectGraph, url);
+ shelves.push(termsAndConditionsShelf);
+ }
+ return shelves;
+}
+//# sourceMappingURL=today-controller-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-horizontal-card-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-horizontal-card-util.js
new file mode 100644
index 0000000..0a2c0cb
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-horizontal-card-util.js
@@ -0,0 +1,352 @@
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { attributeAsBooleanOrFalse } from "../../foundation/media/attributes";
+import { openTVArcadeAppAction } from "../arcade/arcade-common";
+import { editorialNotesFromData } from "../content/content";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+import { cardDisplayStyleFromData, collapsedHeadingForTodayCard, defaultTodayCardConfiguration, todayCardFromData, } from "./today-card-util";
+import { TodayCardDisplayStyle, TodayCardMetricsDisplayStyle, TodayParseContext, } from "./today-types";
+const supportedSmallHorizontalCardKinds = new Set([
+ "artwork",
+ "appIcon",
+ "grid",
+ "multiApp",
+ "video",
+]);
+const supportedMediumHorizontalCardKinds = new Set([
+ "brandedSingleApp",
+ "artwork",
+ "appIcon",
+ "grid",
+ "multiApp",
+ "video",
+]);
+const supportedLargeHorizontalCardKinds = new Set([
+ "brandedSingleApp",
+ "artwork",
+ "appIcon",
+ "grid",
+ "multiApp",
+ "video",
+]);
+const iOSSupportedSmallHorizontalCardKinds = new Set([
+ "brandedSingleApp",
+ "artwork",
+ "grid",
+ "video",
+]);
+const webSupportedSmallHorizontalCardKinds = new Set([
+ "brandedSingleApp",
+ "artwork",
+ "river",
+ "appIcon",
+ "video",
+]);
+// MARK: - Horizontal Cards
+/**
+ * The crop code to use for a card using the provided horizontal card shelf content type.
+ * @param {ShelfContentType} contentType The content type of the shelf's items.
+ * @returns {CropCode} The crop code to use for the artwork.
+ */
+function horizontalCardCropCodeForContentType(objectGraph, contentType, cardDisplayStyle) {
+ switch (contentType) {
+ case "smallStoryCard":
+ if (objectGraph.host.isTV) {
+ return cardDisplayStyle === TodayCardDisplayStyle.Video ? null : { defaultCrop: "fo" };
+ }
+ else if (objectGraph.host.isiOS) {
+ return null;
+ }
+ else if (objectGraph.client.isWeb) {
+ return { defaultCrop: "sr" };
+ }
+ else {
+ return { defaultCrop: "em" };
+ }
+ case "mediumStoryCard":
+ return { defaultCrop: "el" };
+ case "largeStoryCard":
+ return cardDisplayStyle === TodayCardDisplayStyle.Video ? { defaultCrop: "sr" } : { defaultCrop: "ek" };
+ default:
+ return null;
+ }
+}
+export function isHorizontalCardSupportedForKind(objectGraph, kind, contentType) {
+ switch (contentType) {
+ case "smallStoryCard":
+ if (objectGraph.host.isiOS) {
+ return iOSSupportedSmallHorizontalCardKinds.has(kind);
+ }
+ else if (objectGraph.client.isWeb) {
+ return webSupportedSmallHorizontalCardKinds.has(kind);
+ }
+ else {
+ return supportedSmallHorizontalCardKinds.has(kind);
+ }
+ case "mediumStoryCard":
+ return supportedMediumHorizontalCardKinds.has(kind);
+ case "largeStoryCard":
+ return supportedLargeHorizontalCardKinds.has(kind);
+ case "todayCard":
+ return objectGraph.client.isWatch;
+ default:
+ return false;
+ }
+}
+export function horizontalCardItemsFromCards(objectGraph, cards, contentType, context, contentUnavailable, horizontalShelfCardConfig) {
+ const items = [];
+ for (const cardData of cards) {
+ const todayCard = horizontalCardItemFromCard(objectGraph, cardData, contentType, context, contentUnavailable, horizontalShelfCardConfig);
+ if (serverData.isNullOrEmpty(todayCard)) {
+ continue;
+ }
+ items.push(todayCard);
+ metricsHelpersLocation.nextPosition(context.metricsLocationTracker);
+ }
+ return items;
+}
+export function horizontalCardItemFromCard(objectGraph, cardData, contentType, context, contentUnavailable, horizontalShelfCardConfig) {
+ const parseContext = new TodayParseContext(context.metricsPageInformation, context.metricsLocationTracker);
+ if (horizontalShelfCardConfig === null || horizontalShelfCardConfig === undefined) {
+ horizontalShelfCardConfig = defaultTodayCardConfiguration(objectGraph);
+ horizontalShelfCardConfig.isHorizontalShelfContext = true;
+ }
+ if (objectGraph.client.deviceType !== "watch") {
+ // All today cards are deserialized through this code path on watchOS.
+ // We support all substyles, so we need to not specify this override.
+ horizontalShelfCardConfig.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.Grid;
+ }
+ const cardDisplayStyle = cardDisplayStyleFromData(cardData, horizontalShelfCardConfig.coercedCollectionTodayCardDisplayStyle);
+ horizontalShelfCardConfig.prevailingCropCodes = horizontalCardCropCodeForContentType(objectGraph, contentType, cardDisplayStyle);
+ horizontalShelfCardConfig.horizontalCardContentType = contentType;
+ if (!serverData.isDefinedNonNull(cardData.attributes)) {
+ if (contentUnavailable) {
+ contentUnavailable(cardData);
+ }
+ return null;
+ }
+ const todayCard = todayCardFromData(objectGraph, cardData, horizontalShelfCardConfig, parseContext, context.augmentingData);
+ // Ignore the Date here, as we don't care about using it as a refresh signal when embedded in a page other than Today for now.
+ if (serverData.isNull(todayCard)) {
+ return null;
+ }
+ if (objectGraph.client.isiOS) {
+ todayCard.collapsedHeading = collapsedHeadingForTodayCard(objectGraph, todayCard);
+ todayCard.inlineDescription = featuredInDescriptionForCard(objectGraph, todayCard, cardData);
+ // In certain fallback cases, todayCardFromData() will make title and heading the same.
+ // If our generated heading and description are the same, we just show the heading.
+ if (todayCard.inlineDescription === todayCard.collapsedHeading) {
+ todayCard.inlineDescription = null;
+ }
+ // We want to avoid displaying the branded title over AotD/GotD cards, but only when they are not using
+ // the fallback art.
+ const brandedMedia = todayCard.media;
+ if (brandedMedia && brandedMedia.kind === "brandedSingleApp") {
+ // <rdar://problem/37249620> LOC: GLOBAL: App Store on iOS: Word wrapping in App/Game of the...
+ // Branded text on inline today cards is kind of a mess visually, so rely on the inline
+ // description.
+ todayCard.title = null;
+ }
+ }
+ if (contentType === "largeStoryCard") {
+ const heroMedia = todayCard.heroMedia;
+ if (!serverData.isDefinedNonNull(heroMedia) ||
+ (heroMedia.artworks.length === 0 && heroMedia.videos.length === 0)) {
+ return null;
+ }
+ }
+ if (isHorizontalCardSupportedForKind(objectGraph, todayCard.media.kind, contentType)) {
+ if (serverData.isDefinedNonNull(context.filterOutMediaCardKinds) &&
+ context.filterOutMediaCardKinds.has(todayCard.media.kind)) {
+ return null;
+ }
+ }
+ // Override `todayCard.clickAction` for some special cards.
+ overrideClickActionForTVAcquisitionCardIfNeeded(objectGraph, todayCard, cardData);
+ return todayCard;
+}
+export function shelfForHorizontalCardItems(objectGraph, cards, contentType, title, subtitle, context, contentUnavailable) {
+ if (!serverData.isDefinedNonNull(contentType)) {
+ return null;
+ }
+ const shelf = new models.Shelf(contentType);
+ if (title) {
+ shelf.title = title;
+ }
+ shelf.subtitle = subtitle;
+ shelf.isHorizontal = true;
+ switch (contentType) {
+ case "todayBrick": // As iOS doesn't support horizontalCardItemsFromCards, we map the small/medium/large story cards to todayBricks instead.
+ shelf.items = [
+ featuredInTodayCardsFromData(objectGraph, cards, context.metricsPageInformation, context.metricsLocationTracker, () => true, contentUnavailable),
+ ];
+ break;
+ default:
+ shelf.items = horizontalCardItemsFromCards(objectGraph, cards, contentType, context, contentUnavailable);
+ break;
+ }
+ return shelf;
+}
+/**
+ * Creates a shelf for mini today cards.
+ *
+ * @param objectGraph - The application store object graph.
+ * @param cards - An array of media data structures.
+ * @param contentType - The type of content for the shelf.
+ * @param title - The title of the shelf.
+ * @param subtitle - The subtitle of the shelf.
+ * @param context - The context for parsing today data.
+ * @param contentUnavailable - Optional handler for unavailable content.
+ * @returns A shelf containing today cards.
+ */
+export function shelfForMiniTodayCards(objectGraph, cards, title, subtitle, context, contentUnavailable) {
+ const shelf = new models.Shelf("miniTodayCard");
+ if (title) {
+ shelf.title = title;
+ }
+ shelf.subtitle = subtitle;
+ shelf.isHorizontal = true;
+ const items = [];
+ const cardConfig = defaultTodayCardConfiguration(objectGraph);
+ cardConfig.metricsDisplayStyle = TodayCardMetricsDisplayStyle.SmallCard;
+ cardConfig.alwaysShowBadgeInSmallCards = true;
+ cardConfig.alwaysUseMaterialBlur = true;
+ for (const cardData of cards) {
+ if (!serverData.isDefinedNonNull(cardData.attributes)) {
+ if (contentUnavailable) {
+ contentUnavailable(cardData);
+ }
+ continue;
+ }
+ const todayCard = todayCardFromData(objectGraph, cardData, cardConfig, context);
+ if (serverData.isNullOrEmpty(todayCard)) {
+ continue;
+ }
+ items.push(todayCard);
+ metricsHelpersLocation.nextPosition(context.locationTracker);
+ }
+ shelf.items = items;
+ return shelf;
+}
+// MARK: - Featured In
+/**
+ * Determines the description for the card, were it to appear inline.
+ * @param {TodayCard} card The card in question (for legacy support)
+ * @param {Data} data the card data to get the description from
+ * @returns {string} The description text to use for the card.
+ */
+function featuredInDescriptionForCard(objectGraph, card, data) {
+ const isSmallStoryCardsSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isWeb;
+ if (isSmallStoryCardsSupported) {
+ let editorialName = editorialNotesFromData(objectGraph, data, "name");
+ if (serverData.isDefinedNonNull(editorialName)) {
+ editorialName = editorialName.replace(/\n/g, " ");
+ }
+ switch (card.media.kind) {
+ case "brandedSingleApp":
+ if (card.overlay instanceof models.TodayCardMarketingLockupOverlay &&
+ serverData.isDefinedNonNull(card.overlay.lockup)) {
+ return card.overlay.lockup.title;
+ }
+ else {
+ return editorialName;
+ }
+ default:
+ return editorialName;
+ }
+ }
+ switch (card.media.kind) {
+ case "brandedSingleApp":
+ if (card.overlay instanceof models.TodayCardThreeLineOverlay) {
+ return card.overlay.heading;
+ }
+ else if (serverData.isDefinedNonNull(card.title)) {
+ return card.title.replace(/\n/g, " ");
+ }
+ return null;
+ default:
+ const unformattedDescription = objectGraph.loc.string("TODAY_BRICK_STANDARD_DESCRIPTION");
+ let heading;
+ let title;
+ if (card.overlay instanceof models.TodayCardThreeLineOverlay) {
+ heading = card.overlay.heading;
+ title = card.overlay.title;
+ }
+ else {
+ heading = card.heading;
+ title = card.title;
+ }
+ if (!unformattedDescription || !heading || !title) {
+ return null;
+ }
+ return unformattedDescription.replace("{heading}", heading).replace("{title}", title);
+ }
+}
+export function featuredInTodayCardsFromData(objectGraph, dataArray, metricsPageInformation, metricsLocationTracker, isIncluded, contentUnavailable) {
+ return validation.context("todayCardsFromPlatformData", () => {
+ if (!dataArray) {
+ return null;
+ }
+ const featuredInCardConfig = defaultTodayCardConfiguration(objectGraph);
+ featuredInCardConfig.enableFullScreenVideo = false;
+ featuredInCardConfig.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.Grid;
+ featuredInCardConfig.prevailingCropCodes = { defaultCrop: "fo" };
+ featuredInCardConfig.isHorizontalShelfContext = true;
+ const context = new TodayParseContext(metricsPageInformation, metricsLocationTracker);
+ const todayCards = [];
+ for (const data of dataArray) {
+ if (!serverData.isDefinedNonNull(data.attributes)) {
+ if (contentUnavailable) {
+ contentUnavailable(data);
+ }
+ continue;
+ }
+ const todayCard = todayCardFromData(objectGraph, data, featuredInCardConfig, context);
+ // Ignore the Date here, as we don't care about using it as a refresh signal when embedded in a page other than Today for now.
+ if (serverData.isNull(todayCard)) {
+ continue;
+ }
+ if (isIncluded(todayCard)) {
+ todayCard.collapsedHeading = collapsedHeadingForTodayCard(objectGraph, todayCard);
+ todayCard.inlineDescription = featuredInDescriptionForCard(objectGraph, todayCard, data);
+ // In certain fallback cases, todayCardFromData() will make title and heading the same.
+ // If our generated heading and description are the same, we just show the heading.
+ const isSmallStoryCardsSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isWeb;
+ if (isSmallStoryCardsSupported && todayCard.inlineDescription === todayCard.collapsedHeading) {
+ todayCard.inlineDescription = null;
+ }
+ // We want to avoid displaying the branded title over AotD/GotD cards, but only when they are not using
+ // the fallback art.
+ const brandedMedia = todayCard.media;
+ if (brandedMedia && brandedMedia.kind === "brandedSingleApp") {
+ // <rdar://problem/37249620> LOC: GLOBAL: App Store on iOS: Word wrapping in App/Game of the...
+ // Branded text on inline today cards is kind of a mess visually, so rely on the inline
+ // description.
+ todayCard.title = null;
+ }
+ todayCards.push(todayCard);
+ }
+ }
+ if (!todayCards.length) {
+ return null;
+ }
+ return new models.InlineTodayCards(todayCards);
+ });
+}
+// MARK: - tvOS Acquisition EI Card
+/**
+ * On tvOS, some special editorial items are supposed to link directly into the Apple Arcade app.
+ * This is to override the click action if needed.
+ * @param card Today card to potentially modify.
+ * @param data Data that card was built from.
+ */
+function overrideClickActionForTVAcquisitionCardIfNeeded(objectGraph, card, data) {
+ if (objectGraph.client.deviceType !== "tv" || !attributeAsBooleanOrFalse(data, "isAcquisition")) {
+ return; // Only override on acquisition stories on tvOS.
+ }
+ const openArcadeAppAction = openTVArcadeAppAction(objectGraph);
+ card.clickAction = openArcadeAppAction;
+}
+// endregion
+//# sourceMappingURL=today-horizontal-card-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js
new file mode 100644
index 0000000..a1e27b0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js
@@ -0,0 +1,1047 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import { asArrayOrEmpty, asBoolean, asDictionary, asInterface, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, isNullOrEmpty, } from "../../foundation/json-parsing/server-data";
+import { editorialCardFromData } from "../../foundation/media/associations";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import { attributeAsString, hasAttributes } from "../../foundation/media/attributes";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import { relationshipData } from "../../foundation/media/relationships";
+import { Parameters, Path, Protocol } from "../../foundation/network/url-constants";
+import { URL } from "../../foundation/network/urls";
+import { isAdPlacementEnabled } from "../ads/ad-common";
+import { categoryArtworkData } from "../categories";
+import { artworkFromApiArtwork, iconFromData } from "../content/content";
+import { extractEditorialClientParams } from "../editorial-pages/editorial-data-util";
+import { addImpressionFields } from "../metrics/helpers/impressions";
+import { nextPosition, popLocation, pushContentLocation } from "../metrics/helpers/location";
+import { combinedRecoMetricsDataFromMetricsData } from "../metrics/helpers/util";
+import * as onDevicePersonalization from "../personalization/on-device-personalization";
+import * as color from "./../../foundation/util/color-util";
+import { asNumber } from "@apple-media-services/media-api";
+import { createTodayAdCard } from "./cards/today-ad-card-builder";
+import { defaultTodayCardConfiguration, todayCardFromData } from "./today-card-util";
+import * as impressionDemotion from "../../common/personalization/on-device-impression-demotion";
+import { EditorialBackgroundType, TodayCardMetricsDisplayStyle, TodayHeaderArtworkBehavior, TodaySectionHeaderArtworkPlacement, } from "./today-types";
+/**
+ * We try to flatten the today response so we can treat it as a single list
+ * of items. These are the different item types in the flattened list.
+ */
+export var FlattenedTodayItemType;
+(function (FlattenedTodayItemType) {
+ FlattenedTodayItemType["EditorialItem"] = "editorialItem";
+ FlattenedTodayItemType["EditorialItemGroup"] = "editorialItemGroup";
+})(FlattenedTodayItemType || (FlattenedTodayItemType = {}));
+/**
+ * Flatten the entire modules array from the TodayPage response into a single list of items.
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayModules The today modules to flatten from the page response
+ * @param onboardingCardsData The media api data for the onboarding cards, inserted into the flattened list
+ * @param todayRecommendations The today recommendations result, inserted into the flattened list
+ * @param adsResponse The response from ad platforms
+ * @returns The flattened list of today items
+ */
+export function flattenTodayModules(objectGraph, todayModules, todayRecommendations, onboardingCardsData, adsResponse) {
+ const flattenedItems = [];
+ let insertedOnboardingCards = isNullOrEmpty(onboardingCardsData);
+ // Retrieve our personalization data
+ const personalizationDataContainer = personalizationDataContainerForTodayModules(objectGraph, todayModules);
+ let absoluteFeedIndex = 0;
+ for (const todayModule of todayModules) {
+ if (isNullOrEmpty(todayModule.contents)) {
+ continue;
+ }
+ const personalizedModuleDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "today", todayModule.contents, true, personalizationDataContainer);
+ todayModule.contents = personalizedModuleDataResult.personalizedData;
+ todayModule.onDevicePersonalizationProcessingType = personalizedModuleDataResult.processingType;
+ let isFirstItemInModule = true;
+ const moduleMetadata = {
+ label: todayModule.label,
+ title: todayModule.title,
+ meta: todayModule.meta,
+ date: todayModule.date,
+ onDevicePersonalizationProcessingType: todayModule.onDevicePersonalizationProcessingType,
+ isTodayForAppsModule: mediaDataStructure.isModuleTodayForApps(todayModule),
+ };
+ if (!insertedOnboardingCards) {
+ for (const onboardingCardData of onboardingCardsData) {
+ flattenedItems.push({
+ type: FlattenedTodayItemType.EditorialItem,
+ data: onboardingCardData,
+ isDataHydrated: hasAttributes(onboardingCardData),
+ isFirstItemInModule,
+ moduleMetadata: { ...moduleMetadata },
+ containedAdSlots: [absoluteFeedIndex],
+ });
+ }
+ isFirstItemInModule = false;
+ insertedOnboardingCards = true;
+ absoluteFeedIndex += 1;
+ }
+ for (let moduleItem of todayModule.contents) {
+ const onDeviceUseCase = asString(moduleItem, "meta.personalizationData.onDeviceUseCase");
+ switch (moduleItem.type) {
+ case "editorial-items":
+ if (isSome(onDeviceUseCase)) {
+ // ODP personalization for Today Arcade stories.
+ const storyData = todayRecommendations === null || todayRecommendations === void 0 ? void 0 : todayRecommendations.storyData(onDeviceUseCase);
+ if (isSome(storyData)) {
+ moduleItem = storyData;
+ }
+ }
+ flattenedItems.push({
+ type: FlattenedTodayItemType.EditorialItem,
+ data: moduleItem,
+ isDataHydrated: hasAttributes(moduleItem),
+ isFirstItemInModule,
+ moduleMetadata: { ...moduleMetadata },
+ containedAdSlots: [absoluteFeedIndex],
+ });
+ isFirstItemInModule = false;
+ absoluteFeedIndex += 1;
+ break;
+ case "editorial-item-groups":
+ const groupContents = asArrayOrEmpty(moduleItem, "meta.associations.recommendations.data");
+ if (isNullOrEmpty(groupContents)) {
+ continue;
+ }
+ let storyGroupData;
+ if (isSome(onDeviceUseCase)) {
+ // ODP personalization for Today Arcade story groups.
+ storyGroupData = todayRecommendations === null || todayRecommendations === void 0 ? void 0 : todayRecommendations.storyGroupData(onDeviceUseCase);
+ }
+ if (isSome(storyGroupData)) {
+ moduleItem = storyGroupData;
+ }
+ else {
+ // ODP personalization for in-app event story groups.
+ const personalizedStoryGroupDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "today", groupContents, true, personalizationDataContainer);
+ moduleItem["meta"]["associations"]["recommendations"]["data"] =
+ personalizedStoryGroupDataResult.personalizedData;
+ todayModule.onDevicePersonalizationProcessingType =
+ personalizedStoryGroupDataResult.processingType;
+ }
+ flattenedItems.push({
+ type: FlattenedTodayItemType.EditorialItemGroup,
+ data: moduleItem,
+ isDataHydrated: hasAttributes(moduleItem),
+ isFirstItemInModule,
+ moduleMetadata: { ...moduleMetadata },
+ containedAdSlots: Array.from({ length: groupContents.length }, (key, value) => value + absoluteFeedIndex),
+ });
+ isFirstItemInModule = false;
+ absoluteFeedIndex += groupContents.length;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return flattenedItems;
+}
+/**
+ * Whether the next content item in the flattened list is hydrated.
+ * @param flattenedItems The flattened list of today items
+ * @returns True if the next content item is hydrated, false otherwise
+ */
+export function nextFlattenedItemIsHydrated(flattenedItems) {
+ for (const flattenedItem of flattenedItems) {
+ switch (flattenedItem.type) {
+ case FlattenedTodayItemType.EditorialItem:
+ case FlattenedTodayItemType.EditorialItemGroup:
+ return hasAttributes(flattenedItem.data);
+ default:
+ break;
+ }
+ }
+ return false;
+}
+/**
+ * Iterates through all the today modules, and creates a set of personalization
+ * data that is targetted only to the contents of these modules.
+ *
+ * @param dataArray The input array of today modules.
+ * @returns Any relevant OnDevicePersonalizaionData
+ */
+export function personalizationDataContainerForTodayModules(objectGraph, todayModules) {
+ if (!onDevicePersonalization.isPersonalizationAvailable(objectGraph)) {
+ return null;
+ }
+ // Here we iterate through all the modules, and look inside each content items meta for personalizationData.
+ const appIds = new Set();
+ for (const todayModule of todayModules) {
+ if (isNull(todayModule.contents)) {
+ continue;
+ }
+ const appIdFromContent = (contentData) => {
+ return asString(contentData, "meta.personalizationData.appId");
+ };
+ for (const contentData of todayModule.contents) {
+ switch (contentData.type) {
+ case "editorial-item-groups":
+ const groupItems = asArrayOrEmpty(contentData.meta, "associations.recommendations.data");
+ for (const groupItem of groupItems) {
+ const appId = appIdFromContent(groupItem);
+ if (isDefinedNonNullNonEmpty(appId)) {
+ appIds.add(appId.toString());
+ }
+ }
+ break;
+ default:
+ const appId = appIdFromContent(contentData);
+ if (isDefinedNonNullNonEmpty(appId)) {
+ appIds.add(appId.toString());
+ }
+ break;
+ }
+ }
+ }
+ return onDevicePersonalization.personalizationDataContainerForAppIds(objectGraph, appIds);
+}
+// MARK: - Shelf Creation
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayItem The today item that this card is contained in, this could be a single item or a story group item
+ * @param todayCardData The MAPI data that will be used to create this today card
+ * @param isAdEligible Whether the current card is being placed in a slot that was eligible for an ad
+ * @param currentRowIndex The current row index this card is going to be in
+ * @param metricsDisplayStyle: The display style for impressions and location
+ * @param isHeroCard: Whether this is the first card in a hero story group
+ * @returns The configuration to use when parsing a today card
+ */
+function createTodayCardConfiguration(objectGraph, todayCardData, isAdEligible, currentRowIndex, metricsDisplayStyle, isHeroCard) {
+ var _a;
+ const cardConfig = defaultTodayCardConfiguration(objectGraph);
+ cardConfig.useOTDTextStyle = (_a = asBoolean(todayCardData, "meta.personalizationData.isOfTheDay")) !== null && _a !== void 0 ? _a : false;
+ cardConfig.replaceIfAdPresent = asBoolean(todayCardData, "meta.personalizationData.replaceIfAdPresent");
+ cardConfig.isAdEligible = isAdEligible;
+ cardConfig.currentRowIndex = currentRowIndex;
+ cardConfig.metricsDisplayStyle = metricsDisplayStyle;
+ cardConfig.isHeroCard = isHeroCard;
+ if (objectGraph.client.isWeb) {
+ cardConfig.prevailingCropCodes = {
+ "defaultCrop": "sr",
+ "editorialArtwork.dayCard": "grav.west",
+ };
+ }
+ return cardConfig;
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param pageContext The page context for the today page
+ * @returns The currentRowIndex from the pageContext, this is only used on iPhone, otherwise return
+ * null since we cant reliably know the row index
+ */
+function rowCountFromContext(objectGraph, pageContext) {
+ if (!objectGraph.client.isPhone) {
+ return undefined;
+ }
+ return pageContext.currentRowIndex;
+}
+/**
+ * Update the page context with the current row count, this uses the groupDisplayStyle to determine
+ * the display style for the cards in the current row
+ * @param pageContext The page context for the today page we're parsing
+ * @param currentGroupDisplayStyle The current groupDisplayItem from the TodayItem we're parsing
+ * @param currentItemIndex The index of the current item in the shelf, that we just created a card
+ * for, this method is called after creating a card
+ */
+function incrementRowIndexIfNecessary(pageContext, currentGroupDisplayStyle, currentItemIndex) {
+ switch (currentGroupDisplayStyle) {
+ case models.GroupDisplayStyle.Grid:
+ if (currentItemIndex % 2 === 1) {
+ pageContext.currentRowIndex++;
+ }
+ break;
+ case models.GroupDisplayStyle.Hero:
+ if (currentItemIndex === 0 || (currentItemIndex - 1) % 2 === 1) {
+ pageContext.currentRowIndex++;
+ }
+ break;
+ case models.GroupDisplayStyle.Standard:
+ pageContext.currentRowIndex++;
+ break;
+ default:
+ break;
+ }
+}
+/**
+ * Update the page context with the current metrics display style, this is based off the groupDisplayStyle
+ * @param objectGraph The dependency graph for the App Store
+ * @param pageContext The page context for the today page we're parsing
+ * @param currentGroupDisplayStyle The current groupDisplayItem from the TodayItem we're parsing
+ * @param currentItemIndex The index of the current item in the shelf, that we just created a card
+ * for, this method is called after creating a card
+ */
+function updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, currentGroupDisplayStyle, currentItemIndex) {
+ if (objectGraph.client.isPad) {
+ pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard;
+ return;
+ }
+ switch (currentGroupDisplayStyle) {
+ case models.GroupDisplayStyle.Grid:
+ pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.SmallCard;
+ break;
+ case models.GroupDisplayStyle.Hero:
+ if (currentItemIndex === 0) {
+ pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard;
+ }
+ else {
+ pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.SmallCard;
+ }
+ break;
+ case models.GroupDisplayStyle.Standard:
+ pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard;
+ break;
+ default:
+ break;
+ }
+}
+/**
+ * @param objectGraph The app store object graph
+ * @param data The data from MAPI for a single item in the today feed, group or single EI
+ * @returns The group display style for the given data
+ */
+function groupDisplayStyleFromData(objectGraph, data) {
+ var _a;
+ if (data.type === "editorial-items") {
+ return models.GroupDisplayStyle.Standard;
+ }
+ let groupDisplayStyle;
+ const editorialCard = editorialCardFromData(data);
+ if (hasAttributes(editorialCard)) {
+ groupDisplayStyle = mediaAttributes.attributeAsString(editorialCard, "editorialItemGroupDisplayStyle");
+ }
+ if (isNothing(groupDisplayStyle)) {
+ groupDisplayStyle =
+ (_a = mediaAttributes.attributeAsString(data, "displayStyle")) !== null && _a !== void 0 ? _a : models.GroupDisplayStyle.Standard;
+ }
+ return isGroupDisplayStyleSupported(objectGraph, groupDisplayStyle)
+ ? groupDisplayStyle
+ : models.GroupDisplayStyle.Standard;
+}
+/**
+ * @param objectGraph The app dependency graph
+ * @param displayStyle The display style to check support for
+ * @returns Whether group display style is supported for this platform
+ */
+function isGroupDisplayStyleSupported(objectGraph, displayStyle) {
+ if (isNothing(displayStyle)) {
+ return false;
+ }
+ switch (displayStyle) {
+ case models.GroupDisplayStyle.Grid:
+ return objectGraph.client.isPhone;
+ default:
+ return true;
+ }
+}
+/**
+ * Determine if the current card is eligible for an ad, based on the parsed card count, and the
+ * bag defined values for slots that can have ads
+ * @param pageContext The page context for the today page
+ * @returns Whether the current card is eligible for an ad
+ */
+function isTodayCardConfigurationAdEligible(pageContext) {
+ if (pageContext.adLocation === pageContext.parsedCardCount) {
+ return true;
+ }
+ if (isNothing(pageContext.eligibleAdLocations)) {
+ return false;
+ }
+ return pageContext.eligibleAdLocations.includes(pageContext.parsedCardCount);
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayItem The flattened today item to create a shelf for
+ * @param pageContext The page context for the today page
+ * @returns The shelf for the given today item, or null if we cant create one
+ */
+export function todayShelfForEditorialItem(objectGraph, todayItem, pageContext) {
+ var _a;
+ const shelf = createTodayShelfWithItems(objectGraph, todayItem, pageContext, () => {
+ var _a, _b, _c;
+ const shelfItems = [];
+ (_a = pageContext.pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.updateContainerId((_b = pageContext.pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.containerIdForSlotIndex((_c = pageContext.parsedCardCount) !== null && _c !== void 0 ? _c : 0));
+ const groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data);
+ updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, groupDisplayStyle, 0);
+ const cardConfig = createTodayCardConfiguration(objectGraph, todayItem.data, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), pageContext.currentRowMetricsDisplayStyle, false);
+ cardConfig.baseMetricsOptions = {
+ recoMetricsData: recoMetricsFromTodayItem(todayItem),
+ };
+ const editorialItemTodayCard = createTodayCardForPageContext(objectGraph, pageContext, cardConfig, todayItem.data);
+ if (isNothing(editorialItemTodayCard)) {
+ return shelfItems;
+ }
+ shelfItems.push(editorialItemTodayCard);
+ nextPosition(pageContext.locationTracker);
+ pageContext.parsedCardCount++;
+ incrementRowIndexIfNecessary(pageContext, groupDisplayStyle, 0);
+ return shelfItems;
+ });
+ shelf.contentsMetadata = {
+ type: "todaySection",
+ debugSectionTypeIndicatorColor: todayItem.type === FlattenedTodayItemType.EditorialItemGroup
+ ? color.named("systemGreen")
+ : color.named("systemBlue"),
+ groupDisplayStyle: models.GroupDisplayStyle.Standard,
+ };
+ // If this is not the first item in the module, attempt to set the background
+ if (!todayItem.isFirstItemInModule) {
+ const editorialShelfBackgroundInfo = todaySectionEditorialBackground(objectGraph, todayItem);
+ if (isSome(editorialShelfBackgroundInfo)) {
+ shelf.background = editorialShelfBackgroundInfo.shelfBackground;
+ if (isSome((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.configuration)) {
+ shelf.header.configuration.eyebrowColor = editorialShelfBackgroundInfo.eyebrowColor;
+ shelf.header.configuration.titleColor = editorialShelfBackgroundInfo.titleColor;
+ shelf.header.configuration.subtitleColor = editorialShelfBackgroundInfo.subtitleColor;
+ }
+ }
+ }
+ return shelf;
+}
+/**
+
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayItem The flattened today item, of type EditorialItemGroup to create a shelf for
+ * @param pageContext The page context for the today page
+ * @returns The shelf for the given today item, or null if we cant create one
+ */
+export function todayShelfForEditorialItemGroup(objectGraph, todayItem, pageContext) {
+ var _a;
+ let isValidHeroStoryGroup = true;
+ const shelf = createTodayShelfWithItems(objectGraph, todayItem, pageContext, () => {
+ var _a, _b, _c, _d, _e, _f, _g;
+ const shelfItems = [];
+ const displayLimit = impressionDemotion.isImpressionDemotionAvailable(objectGraph)
+ ? (_a = asNumber(todayItem.data, "meta.personalizationData.displayEICount")) !== null && _a !== void 0 ? _a : 100
+ : 100;
+ let groupItems = asArrayOrEmpty(todayItem.data.meta, "associations.recommendations.data");
+ if (isSome(pageContext.recoImpressionData)) {
+ groupItems = impressionDemotion.personalizeDataItems(groupItems, pageContext.recoImpressionData, (_b = pageContext.pageInformation.recoMetricsData) !== null && _b !== void 0 ? _b : {});
+ }
+ const groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data);
+ let parsedGroupItemCount = 0;
+ for (const [index, groupItem] of groupItems.entries()) {
+ (_c = pageContext.pageInformation.iAdInfo) === null || _c === void 0 ? void 0 : _c.updateContainerId((_d = pageContext.pageInformation.iAdInfo) === null || _d === void 0 ? void 0 : _d.containerIdForSlotIndex((_e = pageContext.parsedCardCount) !== null && _e !== void 0 ? _e : 0));
+ updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, groupDisplayStyle, parsedGroupItemCount);
+ const cardConfig = createTodayCardConfiguration(objectGraph, groupItem, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), pageContext.currentRowMetricsDisplayStyle, groupDisplayStyle === models.GroupDisplayStyle.Hero && index === 0);
+ const shelfItem = createTodayCardForPageContext(objectGraph, pageContext, cardConfig, groupItem);
+ if (isSome(shelfItem)) {
+ shelfItems.push(shelfItem);
+ nextPosition(pageContext.locationTracker);
+ pageContext.parsedCardCount++;
+ incrementRowIndexIfNecessary(pageContext, groupDisplayStyle, parsedGroupItemCount);
+ parsedGroupItemCount++;
+ }
+ if (cardConfig.isHeroCard && isNothing(shelfItem)) {
+ if (["debug", "internal"].includes(objectGraph.client.buildType)) {
+ validation.unexpectedType("defaultValue", `Hero story group ${(_f = todayItem.data) === null || _f === void 0 ? void 0 : _f.id} must contain a valid hero card at index ${index}. Unable to parse card ${groupItem.id}.`, null);
+ }
+ isValidHeroStoryGroup = false;
+ }
+ if (index < groupItems.length - 1 &&
+ pageContext.adPlacementBehavior === models.AdPlacementBehavior.insertIntoShelf) {
+ // Attempt to insert the ad card after creating the next item in the group, but only
+ // if we're still **within** the story group, ads that fall at the beginning or end of
+ // the group are handled at the top level of the page parsing.
+ const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, createTodayCardConfiguration(objectGraph, pageContext.adData, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), undefined, false));
+ if (isSome(adCard)) {
+ pageContext.parsedCardCount++;
+ incrementRowIndexIfNecessary(pageContext, models.GroupDisplayStyle.Standard, 0);
+ nextPosition(pageContext.locationTracker);
+ shelfItems.push(adCard);
+ }
+ }
+ // if the count is equal to the story limit. stop here and break.
+ if (shelfItems.length === displayLimit) {
+ break;
+ }
+ }
+ if (isValidHeroStoryGroup) {
+ // The number of items required to be a valid hero story group, this includes the hero
+ // card, and two additional cards to form a row
+ const heroStoryGroupRequiredItemCount = 3;
+ if (shelfItems.length !== heroStoryGroupRequiredItemCount) {
+ if (["debug", "internal"].includes(objectGraph.client.buildType)) {
+ validation.unexpectedType("defaultValue", `Hero story group ${(_g = todayItem.data) === null || _g === void 0 ? void 0 : _g.id} must contain exactly ${heroStoryGroupRequiredItemCount} items but only found ${shelfItems.length} items.`, null);
+ }
+ isValidHeroStoryGroup = false;
+ }
+ }
+ return shelfItems;
+ });
+ // Fallback to standard group display style if we had an issue creating a valid hero group
+ let groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data);
+ if (groupDisplayStyle === models.GroupDisplayStyle.Hero && !isValidHeroStoryGroup) {
+ groupDisplayStyle = models.GroupDisplayStyle.Standard;
+ }
+ shelf.contentsMetadata = {
+ type: "todaySection",
+ debugSectionTypeIndicatorColor: color.named("systemGreen"),
+ groupDisplayStyle: groupDisplayStyle,
+ };
+ // If this is not the first item in the module, attempt to set the background
+ if (!todayItem.isFirstItemInModule) {
+ const editorialShelfBackgroundInfo = todaySectionEditorialBackground(objectGraph, todayItem);
+ if (isSome(editorialShelfBackgroundInfo)) {
+ shelf.background = editorialShelfBackgroundInfo.shelfBackground;
+ if (isSome((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.configuration)) {
+ shelf.header.configuration.eyebrowColor = editorialShelfBackgroundInfo.eyebrowColor;
+ shelf.header.configuration.titleColor = editorialShelfBackgroundInfo.titleColor;
+ shelf.header.configuration.subtitleColor = editorialShelfBackgroundInfo.subtitleColor;
+ }
+ }
+ else if (groupDisplayStyle === models.GroupDisplayStyle.Hero && Array.isArray(shelf.items)) {
+ shelf.background = todaySectionBackgroundForHeroDisplayStyle(shelf.items);
+ }
+ }
+ return shelf;
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayItem The flattened today item, used to create the items in this shelf
+ * @param pageContext The page context for the today page
+ * @param itemProvider The function that will provide the items for this shelf
+ * @returns The shelf for the given today item
+ */
+function createTodayShelfWithItems(objectGraph, todayItem, pageContext, itemProvider) {
+ const shouldRecordShelfMetrics = todayItem.type === FlattenedTodayItemType.EditorialItemGroup;
+ const shelf = new models.Shelf("todayCard");
+ shelf.id = todayItem.data.id;
+ shelf.isHorizontal = false;
+ shelf.header = createTodayShelfHeaderForTodayItem(objectGraph, todayItem, pageContext);
+ if (shouldRecordShelfMetrics) {
+ const shelfMetricsOptions = {
+ id: shelf.id,
+ kind: "editorialItemGroup",
+ softwareType: null,
+ targetType: "swoosh",
+ title: sectionTitleFromTodayItem(objectGraph, todayItem, true),
+ pageInformation: pageContext.pageInformation,
+ locationTracker: pageContext.locationTracker,
+ idType: "its_id",
+ recoMetricsData: recoMetricsFromTodayItem(todayItem),
+ };
+ if (todayItem.type === FlattenedTodayItemType.EditorialItemGroup) {
+ shelfMetricsOptions["optimizationId"] = asString(todayItem.data, "meta.personalizationData.optimizationId");
+ shelfMetricsOptions["optimizationEntityId"] = asString(todayItem.data, "meta.personalizationData.optimizationEntityId");
+ }
+ addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ pushContentLocation(objectGraph, shelfMetricsOptions, shelfMetricsOptions.title);
+ }
+ shelf.items = itemProvider();
+ shelf.isHidden = isNullOrEmpty(shelf.items);
+ if (shouldRecordShelfMetrics) {
+ popLocation(pageContext.locationTracker);
+ nextPosition(pageContext.locationTracker);
+ }
+ return shelf;
+}
+function createTodayShelfHeaderForTodayItem(objectGraph, todayItem, pageContext) {
+ var _a;
+ const shouldSuppressHeader = (_a = asBoolean(todayItem.data, "meta.personalizationData.suppressHeader")) !== null && _a !== void 0 ? _a : false;
+ if (shouldSuppressHeader) {
+ return null;
+ }
+ const shelfHeaderConfiguration = {
+ eyebrowImageColor: null,
+ titleImageColor: null,
+ includeSeparator: false,
+ };
+ const shelfHeader = {
+ eyebrow: sectionEyebrowFromTodayItem(objectGraph, todayItem),
+ eyebrowArtwork: sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Eyebrow),
+ eyebrowArtworkType: sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Eyebrow),
+ title: sectionTitleFromTodayItem(objectGraph, todayItem),
+ titleArtwork: sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Title),
+ titleArtworkType: sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Title),
+ subtitle: sectionSubtitleFromTodayItem(objectGraph, todayItem),
+ configuration: shelfHeaderConfiguration,
+ };
+ if (isSome(shelfHeader.eyebrow) || isSome(shelfHeader.title) || isSome(shelfHeader.subtitle)) {
+ return shelfHeader;
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Create a today card for a given today item in a page context. This handles requirements for ad replacement if needed
+ * @param objectGraph The dependency graph of the app store
+ * @param pageContext The context of the page so we can tell whether its time to insert an ad
+ * @param cardConfig The configuration for the today item
+ * @param todayItem The flattened today item that should be placed in the shelf or replaced with an ad card
+ * @returns The today card to insert in a shelf
+ */
+function createTodayCardForPageContext(objectGraph, pageContext, cardConfig, todayItem) {
+ var _a, _b;
+ let editorialItemTodayCard;
+ if (pageContext.adPlacementBehavior === models.AdPlacementBehavior.replaceOrganic &&
+ isDefinedNonNull(cardConfig.replaceIfAdPresent) &&
+ asBoolean(cardConfig.replaceIfAdPresent)) {
+ // the ad card should replace the organic
+ const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, cardConfig);
+ if (isDefinedNonNullNonEmpty(adCard)) {
+ editorialItemTodayCard = adCard;
+ }
+ else {
+ editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext);
+ }
+ }
+ else if (pageContext.adPlacementBehavior === models.AdPlacementBehavior.dropAd &&
+ isDefinedNonNull(cardConfig.replaceIfAdPresent) &&
+ !asBoolean(cardConfig.replaceIfAdPresent)) {
+ // the organic is not replaceable
+ editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext);
+ if (isDefinedNonNullNonEmpty(pageContext.adData)) {
+ const recorder = pageContext.adIncidentRecorder;
+ (_a = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _a === void 0 ? void 0 : _a.setMissedOpportunity(objectGraph, "EDITORIALTAKEOVER", (_b = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _b === void 0 ? void 0 : _b.placementType);
+ }
+ }
+ else {
+ // we're using existing insertion logic that will insert an ad elsewhere. create a regular card for the flattened item
+ editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext);
+ }
+ return editorialItemTodayCard;
+}
+// MARK: - Ads
+/**
+ * Try create a shelf to display an ad if we're currently at the correct spot in the feed. This is only used
+ * for single editorialItem shelves, and the end of an editorial item group, since these locations it does not
+ * make sense to include the ad within the shelf for that TodayItem.
+ * @param objectGraph The dependency graph of the app store
+ * @param pageContext The context of the page so we can tell whether its time to insert an ad
+ * @returns The card to insert at the ad slot if necessary
+ */
+export function createAdShelfForTodayPageContextIfNecessary(objectGraph, pageContext) {
+ let adShelf = null;
+ if (!isAdPlacementEnabled(objectGraph, "today") ||
+ isNothing(pageContext.adData) ||
+ pageContext.adPlacementBehavior !== models.AdPlacementBehavior.insertIntoShelf) {
+ return adShelf;
+ }
+ if (pageContext.adLocation !== pageContext.parsedCardCount) {
+ return adShelf;
+ }
+ adShelf = new models.Shelf("todayCard");
+ adShelf.id = pageContext.adData.id;
+ adShelf.isHorizontal = false;
+ adShelf.contentsMetadata = {
+ type: "todaySection",
+ debugSectionTypeIndicatorColor: color.named("systemBlue"),
+ groupDisplayStyle: models.GroupDisplayStyle.Standard,
+ };
+ const shelfItems = [];
+ /// Attempt to create an ad card before creating the next item in the group
+ const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, createTodayCardConfiguration(objectGraph, pageContext.adData, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), undefined, false));
+ if (isSome(adCard)) {
+ pageContext.parsedCardCount++;
+ incrementRowIndexIfNecessary(pageContext, models.GroupDisplayStyle.Standard, 0);
+ nextPosition(pageContext.locationTracker);
+ shelfItems.push(adCard);
+ }
+ adShelf.items = shelfItems;
+ return isDefinedNonNullNonEmpty(adShelf.items) ? adShelf : null;
+}
+/**
+ * Try to create an ad card for the page context if the requirements are met
+ * @param objectGraph The dependency graph of the app store
+ * @param pageContext The context of the page so we can tell whether its time to insert an ad
+ * @param cardConfig The configuration of the card
+ * @returns The card to insert at the ad slot if necessary
+ */
+export function createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, cardConfig) {
+ var _a, _b, _c;
+ if (!isAdPlacementEnabled(objectGraph, "today")) {
+ return null;
+ }
+ // The ad should respect its slot, unless we're using reco logic for organic replacement. If we need to replace
+ // the organic, we will arbitrarily respect the reco flag to avoid edge cases with mismatching data
+ if (pageContext.adLocation !== pageContext.parsedCardCount &&
+ pageContext.adPlacementBehavior !== models.AdPlacementBehavior.replaceOrganic) {
+ return null;
+ }
+ (_a = pageContext.pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.updateContainerId((_b = pageContext.pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.containerIdForSlotIndex((_c = pageContext.parsedCardCount) !== null && _c !== void 0 ? _c : 0));
+ const adCard = createTodayAdCard(objectGraph, pageContext.adData, pageContext.adIncidentRecorder, cardConfig, pageContext);
+ if (isSome(adCard)) {
+ return adCard;
+ }
+ else {
+ return null;
+ }
+}
+// MARK: - Shelf Header Content
+/**
+ * @param objectGraph The dependency graph of the app store
+ * @param item The today item to look for the eyebrow in
+ * @returns The text displayed above the title on a section header
+ */
+export function sectionEyebrowFromTodayItem(objectGraph, item) {
+ const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
+ if (item.isFirstItemInModule || editorialClientParams.suppressHeaderBadge) {
+ return null;
+ }
+ let sectionBadge;
+ const editorialCard = editorialCardFromData(item.data);
+ if (hasAttributes(editorialCard)) {
+ sectionBadge = mediaAttributes.attributeAsString(editorialCard, "headerBadge");
+ }
+ if (isSome(sectionBadge)) {
+ return sectionBadge;
+ }
+ switch (item.data.type) {
+ case "editorial-items":
+ sectionBadge = attributeAsString(item.data, "headerBadge");
+ break;
+ case "editorial-item-groups":
+ sectionBadge = attributeAsString(item.data, ["editorialNotes", "badge"]);
+ break;
+ default:
+ break;
+ }
+ return sectionBadge;
+}
+/**
+ * @param objectGraph The dependency graph of the app store
+ * @param item The today item to look for the title in
+ * @param alwaysReturnTitle If true, the title will be returned even if it is the first item in a module
+ * @returns The title displayed above a section
+ */
+export function sectionTitleFromTodayItem(objectGraph, item, alwaysReturnTitle = false) {
+ const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
+ if ((item.isFirstItemInModule || editorialClientParams.suppressHeaderName) && !alwaysReturnTitle) {
+ return null;
+ }
+ let sectionTitle;
+ const editorialCard = editorialCardFromData(item.data);
+ if (hasAttributes(editorialCard)) {
+ sectionTitle = mediaAttributes.attributeAsString(editorialCard, "headerName");
+ }
+ if (isSome(sectionTitle)) {
+ return sectionTitle;
+ }
+ switch (item.data.type) {
+ case "editorial-items":
+ sectionTitle = attributeAsString(item.data, "headerName");
+ break;
+ case "editorial-item-groups":
+ sectionTitle = attributeAsString(item.data, ["editorialNotes", "name"]);
+ break;
+ default:
+ break;
+ }
+ return sectionTitle;
+}
+/**
+ * @param objectGraph The dependency graph of the app store
+ * @param item The today item to look for the subtitle in
+ * @returns The subtitle displayed below the title on a section header
+ */
+function sectionSubtitleFromTodayItem(objectGraph, item) {
+ const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
+ if (item.isFirstItemInModule || editorialClientParams.suppressHeaderTagline) {
+ return null;
+ }
+ let sectionSubtitle;
+ const editorialCard = editorialCardFromData(item.data);
+ if (hasAttributes(editorialCard)) {
+ sectionSubtitle = mediaAttributes.attributeAsString(editorialCard, "headerTagline");
+ }
+ if (isSome(sectionSubtitle)) {
+ return sectionSubtitle;
+ }
+ switch (item.data.type) {
+ case "editorial-items":
+ sectionSubtitle = attributeAsString(item.data, "headerTagline");
+ break;
+ case "editorial-item-groups":
+ sectionSubtitle = attributeAsString(item.data, ["editorialNotes", "tagline"]);
+ break;
+ default:
+ break;
+ }
+ return sectionSubtitle;
+}
+/**
+ * @param objectGraph The dependency graph of the app store
+ * @param item The today item to look for the subtitle in
+ * @returns The artwork displayed in the eyebrow of a today section.
+ */
+function sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, item, placement) {
+ var _a;
+ const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
+ const headerContents = relationshipData(objectGraph, item.data, "header-contents");
+ const artworkBehavior = (_a = editorialClientParams.headerArtworkBehavior) !== null && _a !== void 0 ? _a : TodayHeaderArtworkBehavior.NoArtwork;
+ switch (placement) {
+ case TodaySectionHeaderArtworkPlacement.Eyebrow:
+ switch (artworkBehavior) {
+ case TodayHeaderArtworkBehavior.CategoryArtworkWithBadge:
+ return categoryArtworkFromData(objectGraph, headerContents);
+ default:
+ return null;
+ }
+ case TodaySectionHeaderArtworkPlacement.Title:
+ switch (artworkBehavior) {
+ case TodayHeaderArtworkBehavior.CategoryArtworkWithTitle:
+ return categoryArtworkFromData(objectGraph, headerContents);
+ case TodayHeaderArtworkBehavior.ContentArtworkWithTitle:
+ return iconFromData(objectGraph, headerContents, {
+ useCase: 1 /* ArtworkUseCase.LockupIconSmall */,
+ });
+ default:
+ return null;
+ }
+ default:
+ return null;
+ }
+}
+/**
+ * @param objectGraph The dependency graph of the app store
+ * @param item The today item to look for the subtitle in
+ * @returns The artwork type displayed in the eyebrow of a today section.
+ */
+function sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, item, placement) {
+ const headerContents = relationshipData(objectGraph, item.data, "header-contents");
+ const artworkBehavior = attributeAsString(item.data, [
+ "editorialClientParams",
+ "headerArtworkBehavior",
+ ]);
+ switch (placement) {
+ case TodaySectionHeaderArtworkPlacement.Eyebrow:
+ switch (artworkBehavior) {
+ case TodayHeaderArtworkBehavior.CategoryArtworkWithBadge:
+ const hasCategoryArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents));
+ return hasCategoryArtwork ? models.ShelfHeaderArtworkType.Category : null;
+ default:
+ return null;
+ }
+ case TodaySectionHeaderArtworkPlacement.Title:
+ switch (artworkBehavior) {
+ case TodayHeaderArtworkBehavior.CategoryArtworkWithTitle:
+ const hasCategoryArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents));
+ return hasCategoryArtwork ? models.ShelfHeaderArtworkType.Category : null;
+ case TodayHeaderArtworkBehavior.ContentArtworkWithTitle:
+ const hasIconArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents));
+ return hasIconArtwork ? models.ShelfHeaderArtworkType.Icon : null;
+ default:
+ return null;
+ }
+ default:
+ return null;
+ }
+}
+/**
+ *
+ * @param objectGraph The dependency graph of the app store
+ * @param data The MAPI data for the related content for a today item
+ * @returns The artwork to use for the related content
+ */
+function categoryArtworkFromData(objectGraph, data) {
+ const artworkData = categoryArtworkData(objectGraph, data, false, false, false);
+ if (isNothing(artworkData)) {
+ return null;
+ }
+ const artwork = artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 20 /* ArtworkUseCase.CategoryIcon */,
+ allowingTransparency: true,
+ cropCode: "sr",
+ });
+ return artwork;
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayItem The today item that contains the data for a editorial-item or editorial-item-group, this is used to look for
+ * editorialBackground, which can then be used to generate the section gradient background
+ * @returns The background to use for the section
+ */
+function todaySectionEditorialBackground(objectGraph, todayItem) {
+ const editorialBackground = mediaAttributes.attributeAsDictionary(todayItem.data, "editorialBackground", null);
+ const editorialBackgroundType = editorialBackground === null || editorialBackground === void 0 ? void 0 : editorialBackground["type"];
+ if (isNothing(editorialBackgroundType)) {
+ return null;
+ }
+ let backgroundInfo = null;
+ switch (editorialBackgroundType) {
+ case EditorialBackgroundType.LinearGradient:
+ const linearGradientData = asInterface(editorialBackground);
+ const colors = linearGradientData.stops.map((stop) => color.fromHex(stop.color));
+ const shelfBackground = {
+ type: "gradient",
+ colors: colors,
+ start: models.ShelfBackgroundGradientLocation.Top,
+ end: models.ShelfBackgroundGradientLocation.Bottom,
+ };
+ const isDark = color.isDarkColor(colors[0]);
+ const secondaryLabelLightColor = {
+ type: "rgb",
+ red: 60 / 255,
+ green: 60 / 255,
+ blue: 67 / 255,
+ alpha: 0.6,
+ };
+ const secondaryLabelDarkColor = {
+ type: "rgb",
+ red: 235.0 / 255,
+ green: 235.0 / 255,
+ blue: 245.0 / 255,
+ alpha: 0.6,
+ };
+ backgroundInfo = {
+ shelfBackground: shelfBackground,
+ eyebrowColor: isDark ? secondaryLabelDarkColor : secondaryLabelLightColor,
+ titleColor: isDark ? color.named("white") : color.named("black"),
+ subtitleColor: isDark ? secondaryLabelDarkColor : secondaryLabelLightColor,
+ };
+ break;
+ default:
+ backgroundInfo = null;
+ break;
+ }
+ return backgroundInfo;
+}
+/**
+ * @param todayCards The today cards that are contained in the section
+ * @returns The background to use for the section
+ */
+function todaySectionBackgroundForHeroDisplayStyle(todayCards) {
+ const backgroundColors = todayCards
+ .map((todayCard) => {
+ return todayCard.media.bestBackgroundColor();
+ })
+ .filter((backgroundColor) => isSome(backgroundColor));
+ let shelfBackground = null;
+ if (backgroundColors.length > 0 && backgroundColors.length <= 4 && backgroundColors.length === todayCards.length) {
+ switch (backgroundColors.length) {
+ case 1:
+ shelfBackground = {
+ type: "materialGradient",
+ colors: {
+ colorCount: "oneColor",
+ color: backgroundColors[0],
+ },
+ };
+ break;
+ case 2:
+ shelfBackground = {
+ type: "materialGradient",
+ colors: {
+ colorCount: "twoColor",
+ top: backgroundColors[0],
+ bottom: backgroundColors[1],
+ },
+ };
+ break;
+ case 3:
+ shelfBackground = {
+ type: "materialGradient",
+ colors: {
+ colorCount: "threeColor",
+ top: backgroundColors[0],
+ bottomLeading: backgroundColors[1],
+ bottomTrailing: backgroundColors[2],
+ },
+ };
+ break;
+ case 4:
+ shelfBackground = {
+ type: "materialGradient",
+ colors: {
+ colorCount: "fourColor",
+ topLeading: backgroundColors[0],
+ topTrailing: backgroundColors[1],
+ bottomLeading: backgroundColors[2],
+ bottomTrailing: backgroundColors[3],
+ },
+ };
+ break;
+ default:
+ break;
+ }
+ }
+ else {
+ shelfBackground = {
+ type: "color",
+ color: color.named("secondarySystemBackground"),
+ };
+ }
+ return shelfBackground;
+}
+// MARK: - Metrics
+/**
+ * Retrieves the recommendation metrics data from a FlattenedTodayItem object.
+ * @param todayItem - The FlattenedTodayItem object containing the module metadata.
+ * @returns The recommendation metrics data as a JSONData object.
+ */
+export function recoMetricsFromTodayItem(todayItem) {
+ var _a, _b;
+ if (isNothing(todayItem)) {
+ return {};
+ }
+ const recoMetricsData = (_a = asDictionary(todayItem.moduleMetadata, "meta.metrics")) !== null && _a !== void 0 ? _a : {};
+ const combinedRecoMetricsData = (_b = combinedRecoMetricsDataFromMetricsData(recoMetricsData, todayItem.moduleMetadata.onDevicePersonalizationProcessingType, null)) !== null && _b !== void 0 ? _b : {};
+ return combinedRecoMetricsData;
+}
+// MARK: - Debug Util
+/**
+ * Generate a todayCardPreview url that contains the entire original today feed.
+ * @param objectGraph The dependency graph of the app store
+ * @param todayItems The flattened today feed items
+ * @returns The url to add to the today page for debug purposes
+ */
+export function feedPreviewUrlFromFlattenedTodayItems(objectGraph, todayItems) {
+ switch (objectGraph.client.buildType) {
+ case "debug":
+ case "internal":
+ const feedPreviewUrl = new URL();
+ feedPreviewUrl.protocol = Protocol.https;
+ feedPreviewUrl.host = "apps.apple.com";
+ feedPreviewUrl.pathname = `/${Path.todayCardPreview}`;
+ const idsParamValues = [];
+ for (const item of todayItems) {
+ switch (item.type) {
+ case FlattenedTodayItemType.EditorialItem:
+ idsParamValues.push(item.data.id);
+ break;
+ case FlattenedTodayItemType.EditorialItemGroup:
+ const groupItems = asArrayOrEmpty(item.data.meta, "associations.recommendations.data");
+ idsParamValues.push(`${item.data.id}:[${groupItems.map((groupItem) => groupItem.id).join(",")}]`);
+ break;
+ default:
+ break;
+ }
+ }
+ feedPreviewUrl.param(Parameters.ids, idsParamValues.join(","));
+ feedPreviewUrl.param(Parameters.isTodayFeedPreview, "true");
+ return decodeURIComponent(feedPreviewUrl.build());
+ default:
+ return null;
+ }
+}
+/**
+ * Generate a todayCardPreview url that can display a single today card
+ * @param objectGraph The dependency graph of the app store
+ * @param todayCardId The id of the created today card
+ * @param cardConfig The card config for the created today card
+ * @returns The url to add to the today page for debug purposes
+ */
+export function todayCardPreviewUrlForTodayCard(objectGraph, todayCardId, cardConfig) {
+ if (isNothing(todayCardId) || !objectGraph.client.isiOS) {
+ return null;
+ }
+ switch (objectGraph.client.buildType) {
+ case "debug":
+ case "internal":
+ const feedPreviewUrl = new URL();
+ feedPreviewUrl.protocol = Protocol.https;
+ feedPreviewUrl.host = "apps.apple.com";
+ feedPreviewUrl.pathname = `/${Path.todayCardPreview}`;
+ feedPreviewUrl.param(Parameters.ids, `${todayCardId}`);
+ feedPreviewUrl.param(Parameters.isTodayFeedPreview, "true");
+ feedPreviewUrl.param(Parameters.isTodaySection, cardConfig.useOTDTextStyle ? "true" : "false");
+ return decodeURIComponent(feedPreviewUrl.build());
+ default:
+ return null;
+ }
+}
+//# sourceMappingURL=today-parse-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-types.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-types.js
new file mode 100644
index 0000000..3c02e97
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-types.js
@@ -0,0 +1,118 @@
+import * as models from "../../api/models";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+/**
+ * Stateful container passed along pipeline from initial page building, mutating while components are being built.
+ * Properties on this *can* be snapshotted to re-initialize a context for shelf-reloading and pagination.
+ */
+export class TodayParseContext {
+ constructor(pageInformation, locationTracker, refreshController) {
+ // The location tracker for building metrics location information
+ this.locationTracker = metricsHelpersLocation.newLocationTracker();
+ /// The number of cards that we've successfully parsed, This excludes failed cards
+ this.parsedCardCount = 0;
+ /// The number of rows of cards that we've successfully added to the page. This is only applicable on
+ /// iPhone where we know there is only one layout per page. This differs from parsedCardCount in that
+ /// rows can contain multiple cards, such is the case with hero / grid shelves.
+ this.currentRowIndex = 0;
+ this.pageInformation = pageInformation;
+ this.locationTracker = locationTracker !== null && locationTracker !== void 0 ? locationTracker : metricsHelpersLocation.newLocationTracker();
+ this.refreshController = refreshController;
+ }
+}
+/**
+ * The `TodayPageContext` is used for building the initial page. It is used to maintain a record
+ * of the remaining items left in the today page, so it is also used for pagination
+ */
+export class TodayPageContext extends TodayParseContext {
+ constructor(remainingContent, pageInformation, locationTracker, refreshController, recoImpressionData) {
+ super(pageInformation, locationTracker, refreshController);
+ // Flag indicating whether we've ever successfully displayed any content on the page, if we're
+ // doing a secondary fetch and this is false, we try to fetch a bit more than normal pagination
+ // to have a higher chance of getting **some** content back.
+ this.pageHasDisplayedContent = false;
+ this.remainingContent = remainingContent;
+ this.adPlacementBehavior = models.AdPlacementBehavior.insertIntoShelf;
+ this.recoImpressionData = recoImpressionData;
+ }
+}
+/**
+ * A `TodayShelfToken` is used to maintain a record of each shelf's contents when subsequent content fetch is needed. It is encoded in the `url` property of `Shelf`.
+ */
+export class TodayShelfToken {
+ // endregion
+ constructor(todayModule, contentOffsetWithinModule, metricsPageInformation, metricsLocationTracker) {
+ this.todayModule = todayModule;
+ this.metricsPageInformation = metricsPageInformation;
+ this.contentOffsetWithinModule = contentOffsetWithinModule;
+ this.metricsLocationTracker = metricsLocationTracker;
+ }
+}
+/**
+ * These are the different ways we can display a today card
+ */
+export var TodayCardDisplayStyle;
+(function (TodayCardDisplayStyle) {
+ TodayCardDisplayStyle["AppEventCard"] = "AppEventCard";
+ TodayCardDisplayStyle["AppOfTheDay"] = "AppOfTheDay";
+ TodayCardDisplayStyle["FullBleedImage"] = "FullBleedImage";
+ TodayCardDisplayStyle["GameOfTheDay"] = "GameOfTheDay";
+ TodayCardDisplayStyle["Grid"] = "Grid";
+ TodayCardDisplayStyle["InAppPurchase"] = "InAppPurchase";
+ TodayCardDisplayStyle["List"] = "List";
+ TodayCardDisplayStyle["NumberedList"] = "NumberedList";
+ TodayCardDisplayStyle["River"] = "River";
+ TodayCardDisplayStyle["ShortImage"] = "ShortImage";
+ TodayCardDisplayStyle["SingleApp"] = "SingleApp";
+ TodayCardDisplayStyle["Video"] = "Video";
+})(TodayCardDisplayStyle || (TodayCardDisplayStyle = {}));
+/**
+ * These are the different sizes of today card we can have,
+ * the reason this uses the term `DisplayStyle` is because the the metrics spec
+ * already uses the term `DisplayStyle` to describe the size of different components
+ * so we're reusing that term here. This is not to be confused with `TodayCardDisplayStyle`
+ */
+export var TodayCardMetricsDisplayStyle;
+(function (TodayCardMetricsDisplayStyle) {
+ TodayCardMetricsDisplayStyle["SmallCard"] = "smallCard";
+ TodayCardMetricsDisplayStyle["MediumCard"] = "mediumCard";
+})(TodayCardMetricsDisplayStyle || (TodayCardMetricsDisplayStyle = {}));
+/**
+ * These are the different ways we can display artwork within a today section header
+ */
+export var TodayHeaderArtworkBehavior;
+(function (TodayHeaderArtworkBehavior) {
+ TodayHeaderArtworkBehavior["NoArtwork"] = "no-artwork";
+ TodayHeaderArtworkBehavior["ContentArtworkWithTitle"] = "content-artwork-with-title";
+ TodayHeaderArtworkBehavior["CategoryArtworkWithTitle"] = "category-artwork-with-title";
+ TodayHeaderArtworkBehavior["CategoryArtworkWithBadge"] = "category-artwork-with-badge";
+})(TodayHeaderArtworkBehavior || (TodayHeaderArtworkBehavior = {}));
+/**
+ * The different types of today card OTD intentions
+ */
+export var OfTheDayIntention;
+(function (OfTheDayIntention) {
+ OfTheDayIntention["AppOfTheDay"] = "app-of-the-day";
+ OfTheDayIntention["GameOfTheDay"] = "game-of-the-day";
+})(OfTheDayIntention || (OfTheDayIntention = {}));
+/**
+ * Indicates the different places we can have artwork in the section header
+ */
+export var TodaySectionHeaderArtworkPlacement;
+(function (TodaySectionHeaderArtworkPlacement) {
+ TodaySectionHeaderArtworkPlacement["Eyebrow"] = "eyebrow";
+ TodaySectionHeaderArtworkPlacement["Title"] = "title";
+})(TodaySectionHeaderArtworkPlacement || (TodaySectionHeaderArtworkPlacement = {}));
+export var EditorialBackgroundType;
+(function (EditorialBackgroundType) {
+ EditorialBackgroundType["LinearGradient"] = "linear-gradient";
+})(EditorialBackgroundType || (EditorialBackgroundType = {}));
+/**
+ * Describes the context in which hero media appears. Used to
+ * configure cards differently on macOS depending on where it appears.
+ */
+export var HeroMediaDisplayContext;
+(function (HeroMediaDisplayContext) {
+ HeroMediaDisplayContext["Inline"] = "inline";
+ HeroMediaDisplayContext["Article"] = "article";
+})(HeroMediaDisplayContext || (HeroMediaDisplayContext = {}));
+//# sourceMappingURL=today-types.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/top-charts-common.js b/node_modules/@jet-app/app-store/tmp/src/common/top-charts-common.js
new file mode 100644
index 0000000..c93843b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/top-charts-common.js
@@ -0,0 +1,212 @@
+/**
+ * Common for top-charts.
+ * Incrementally migrate `top-charts-controller` builder logic here.
+ */
+import { isSome, isNothing } from "@jet/environment/types/optional";
+import * as categories from "../common/categories";
+import { isDefinedNonNullNonEmpty } from "../foundation/json-parsing/server-data";
+import { attributeAsString } from "../foundation/media/attributes";
+import { fetchData } from "../foundation/media/network";
+import { Parameters } from "../foundation/network/url-constants";
+import { URL } from "../foundation/network/urls";
+import * as client from "../foundation/wrappers/client";
+import { mediaApiChartRequestForGenre } from "./builders/url-mapping";
+// region API - URLs
+/**
+ * Given a chart type, lookup the most appropriate chart to show next to it using
+ * fallback mapping logic, e.g. `top-paid` for `top-free` and visa versa.
+ *
+ * MAINTAINERS NOTE: This is typically driven by Editorial's programming but this can be used as a fallback.
+ *
+ * @param objectGraph The App Store object graph.
+ * @param chartType The type of chart to lookup a sibling for, e.g. `top-free`.
+ * @returns The sibling of the provided chart type, e.g. `top-paid`.
+ */
+export function lookupFallbackSiblingChart(objectGraph, chartType) {
+ // Vision currently doesn't support sibling charts - don't attempt to find one.
+ if (isNothing(chartType) || objectGraph.client.isVision) {
+ return undefined;
+ }
+ switch (chartType) {
+ case "top-free" /* TopChartType.TopFree */:
+ return "top-paid" /* TopChartType.TopPaid */;
+ case "top-paid" /* TopChartType.TopPaid */:
+ return "top-free" /* TopChartType.TopFree */;
+ /*
+ ** MAINTAINERS NOTE **
+ We don't want to show "Top Apps" / "Paid Apps" for extensions, trending, or emerging,
+ so don't map these together until we can confirm the desired UX we want for this.
+
+ case TopChartType.TopFreeSafariExtensions:
+ return TopChartType.TopPaidSafariExtensions;
+ case TopChartType.TopPaidSafariExtensions:
+ return TopChartType.TopFreeSafariExtensions;
+ case TopChartType.TopFreeTrending:
+ return TopChartType.TopPaidTrending;
+ case TopChartType.TopPaidTrending:
+ return TopChartType.TopFreeTrending;
+ case TopChartType.TopFreeEmerging:
+ return TopChartType.TopPaidEmerging;
+ case TopChartType.TopPaidEmerging:
+ return TopChartType.TopFreeEmerging;
+ */
+ default:
+ return undefined;
+ }
+}
+/**
+ * Creates a charts parameter value for an invidual chart shelf's see all flow action URL, which may show multiple charts.
+ *
+ * ** MAINTAINERS NOTE **
+ * This function guarantees the selected chart is always included in charts.
+ * This helps make the user experience more resilient to either unexpected editorial programming or client JS programmer error.
+ * For example, if provided chart set is missing the selected chart or is missing any siblings, we lookup hardcoded fallback
+ * mappings, e.g. `top-paid` for `top-free`.
+ *
+ * @param objectGraph The App Store object graph.
+ * @param selectedChartType The selected chart, e.g. `top-free`.
+ * @param chartSet The collection fo charts to be shown as sibligs, e.g. `top-free` and `top-paid` in a segmented control
+ * @returns Value for charts URL query paramater.
+ */
+function makeChartsParameterValue(objectGraph, selectedChartType, chartSet) {
+ var _a;
+ const hasMultipleCharts = (_a = (chartSet === null || chartSet === void 0 ? void 0 : chartSet.editorialCharts.length) > 1) !== null && _a !== void 0 ? _a : false;
+ const charts = chartSet === null || chartSet === void 0 ? void 0 : chartSet.editorialCharts.map((chart) => chart.type.toString()).join(",");
+ // use editorially programmed chart set if defined with multiple charts, including the selection.
+ if (isSome(charts) && charts.includes(selectedChartType) && hasMultipleCharts) {
+ return charts;
+ }
+ // otherwise, lookup hardcoded fallback sibling if available, e.g. `top-paid` for `top-free`.
+ const siblingChartType = lookupFallbackSiblingChart(objectGraph, selectedChartType);
+ if (siblingChartType === undefined) {
+ return selectedChartType; // no sibling found, show just the single selected chart
+ }
+ return [selectedChartType, siblingChartType].join(","); // show harcoded pair of charts
+}
+/**
+ * Construct the URL for a single chart shelf "See All", that might be part of an editorial chart set.
+ * @param objectGraph The App Store object graph.
+ * @param chartData The media API data for the chart shelf, e.g. `top-paid`.
+ * @param chartSet The set of charts shown when tapping "See All", e.g. `top-free` and `top-paid`.
+ */
+export function makeSeeAllUrlFromMediaApiData(objectGraph, chartData, chartSet) {
+ const baseUrl = attributeAsString(chartData, "chartHref");
+ const chartSeeAllUrl = new URL(baseUrl);
+ /**
+ * The selected chart to show, e.g. `top-free`.
+ */
+ const selectedChart = attributeAsString(chartData, "chart");
+ if (isDefinedNonNullNonEmpty(selectedChart)) {
+ chartSeeAllUrl.param(Parameters.chart, selectedChart);
+ }
+ /**
+ * The collection of charts to show, e.g. `top-free` and `top-paid`.
+ * This is needed since a chart sets are vended down as a single fcKind item with children that is divided into multiple shelves.
+ * Tapping "See All" on a chart shelf, should show a top charts page with all `charts` provided and `chart` selected.
+ */
+ const charts = makeChartsParameterValue(objectGraph, selectedChart, chartSet);
+ if (isDefinedNonNullNonEmpty(charts)) {
+ chartSeeAllUrl.param(Parameters.charts, charts);
+ }
+ return chartSeeAllUrl.toString();
+}
+/**
+ * Returns whether or not given chart page URL charts parameter is for safari extensions.
+ * Charts for safari extensions must be handled as a special case since they don't have an associated specific genre.
+ *
+ * @param url The URL to check if it is Safari Extension charts.
+ */
+function isSafariExtensionCharts(charts) {
+ return (isDefinedNonNullNonEmpty(charts) &&
+ (charts.indexOf("top-free-safari-extensions" /* TopChartType.TopFreeSafariExtensions */) !== -1 ||
+ charts.indexOf("top-paid-safari-extensions" /* TopChartType.TopPaidSafariExtensions */) !== -1));
+}
+// endregion API
+// region MAPI Requests
+/**
+ * Fetch the top-level structural data for top charts.
+ *
+ * @param objectGraph The App Store object graph.
+ * @param genreId The genre to fetch charts for, e.g. Apps or Games.
+ * @param charts The specific charts in this genre.
+ */
+export async function fetchTopChartsData(objectGraph, genreId, charts, ages = null) {
+ // Request
+ const topChartsRequest = mediaApiChartRequestForGenre(objectGraph, genreId, charts, ages);
+ // Pagination + Sparse limits
+ topChartsRequest.withLimit(200);
+ if (objectGraph.client.isMac || objectGraph.client.isWeb) {
+ topChartsRequest.withSparseLimit(25);
+ }
+ topChartsRequest.enablingFeature("newChartsElements");
+ return await fetchData(objectGraph, topChartsRequest);
+}
+/**
+ * Fetch the categories data promise accompanying given `chartUrl`.
+ *
+ * @param objectGraph Object graph
+ * @param charts The charts to fetch categories data for, e.g. `top-free,top-paid`
+ * @param genreId The genre for chart to fetch categories data for, e.g. Apps or Games
+ */
+export async function fetchCategoriesDataForPlatform(objectGraph, charts, genreId) {
+ let categoriesRequestPromise;
+ /**
+ * No categories filter on:
+ * - All tvOS charts
+ * - All Messages AppStore charts
+ * - Safari Extension Charts (Genre-less Genre)
+ */
+ if (objectGraph.client.isTV ||
+ objectGraph.host.clientIdentifier === client.messagesIdentifier ||
+ isSafariExtensionCharts(charts)) {
+ categoriesRequestPromise = Promise.resolve({});
+ }
+ else {
+ const categoriesRequest = categories.createRequest(objectGraph, genreId);
+ categoriesRequestPromise = fetchData(objectGraph, categoriesRequest).catch(() => undefined);
+ }
+ return await categoriesRequestPromise;
+}
+// endregion
+// region Page Title
+/**
+ * Return the top charts page title to use for a collection of top chart segments.
+ *
+ * @param objectGraph Object graph
+ * @param segments The segments to derive page title for
+ */
+export function topChartsPageTitleForSegments(objectGraph, segments) {
+ const everySegmentIsTrending = segments.every((seg) => isTrendingChartType(seg.chart));
+ if (everySegmentIsTrending) {
+ return objectGraph.loc.string("PAGE_TITLE_TRENDING_CHARTS");
+ }
+ const everySegmentIsEmerging = segments.every((seg) => isEmergingChartType(seg.chart));
+ if (everySegmentIsEmerging) {
+ return objectGraph.loc.string("PAGE_TITLE_EMERGING_CHARTS");
+ }
+ return objectGraph.loc.string("PAGE_TITLE_TOP_CHARTS");
+}
+// endregion
+// region Top Charts Type
+/// Whether or not given chart type is trending chart type
+function isTrendingChartType(chart) {
+ switch (chart) {
+ case "top-trending-free" /* TopChartType.TopFreeTrending */:
+ case "top-trending-paid" /* TopChartType.TopPaidTrending */:
+ return true;
+ default:
+ return false;
+ }
+}
+/// Whether or not given chart type is emerging chart type
+function isEmergingChartType(chart) {
+ switch (chart) {
+ case "top-emerging-free" /* TopChartType.TopFreeEmerging */:
+ case "top-emerging-paid" /* TopChartType.TopPaidEmerging */:
+ return true;
+ default:
+ return false;
+ }
+}
+// endregion
+//# sourceMappingURL=top-charts-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/top-charts/top-charts-page.js b/node_modules/@jet-app/app-store/tmp/src/common/top-charts/top-charts-page.js
new file mode 100644
index 0000000..0f24279
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/top-charts/top-charts-page.js
@@ -0,0 +1,161 @@
+import { FetchTimingMetricsBuilder } from "@jet/environment/metrics/fetch-timing-metrics-builder";
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import * as mediaUrlMapping from "../../common/builders/url-mapping";
+import * as categories from "../../common/categories";
+import * as topChartsCommon from "../../common/top-charts-common";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { isSome } from "@jet/environment/types/optional";
+import { makeChartsPageIntent } from "../../api/intents/charts-page-intent";
+import { segmentFromData } from "../../common/charts/charts-page-model";
+import { makeChartsPageURL } from "../../common/charts/charts-page-url";
+import { getLocale } from "../../common/locale";
+import { getPlatform } from "../../common/preview-platform";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import * as mediaUrlBuilder from "../../foundation/media/url-builder";
+import * as urls from "../../foundation/network/urls";
+export async function fetchAndRenderTopChartPage(objectGraph, genreId, charts, selectedChart, ages) {
+ if (serverData.isNullOrEmpty(charts)) {
+ const siblingChart = topChartsCommon.lookupFallbackSiblingChart(objectGraph, selectedChart);
+ if (siblingChart !== undefined) {
+ charts = `${selectedChart},${siblingChart}`; // charts not provided, use hardcoded sibling along with selected chart
+ }
+ else {
+ charts = selectedChart; // charts not provided, use selected chart
+ }
+ }
+ else if (!charts.includes(selectedChart)) {
+ charts += `,${selectedChart}`; // charts missing selected chart, so inject it.
+ }
+ // Request
+ // Note: `FetchTimingMetricsBuilder` API currently only supports instrumenting a single primary network call.
+ // We will choose to use the network metrics data from the top charts call.
+ const fetchTimingMetricsBuilder = new FetchTimingMetricsBuilder();
+ const modifiedObjectGraph = objectGraph.addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder);
+ const topChartsPromise = topChartsCommon.fetchTopChartsData(modifiedObjectGraph, genreId, charts, ages);
+ let categoriesRequestPromise;
+ // Vision currently doesn't support displaying categories on the Charts page.
+ if (!objectGraph.client.isVision) {
+ categoriesRequestPromise = topChartsCommon.fetchCategoriesDataForPlatform(objectGraph, charts, genreId);
+ }
+ // Response
+ return await Promise.all([topChartsPromise, categoriesRequestPromise]).then(([topChartsResponse, categoriesResponse]) => {
+ return fetchTimingMetricsBuilder.measureModelConstruction(() => {
+ const categoriesList = categories.categoryListFromApiResponse(objectGraph, categoriesResponse);
+ const context = serverData.isNull(categoriesList)
+ ? categories.createContextFromGenre(genreId)
+ : categories.createContextFromList(categoriesList);
+ return topChartsPageFromApiResponse(modifiedObjectGraph, topChartsResponse, genreId, categoriesList, selectedChart, context);
+ });
+ });
+}
+/**
+ * Creates a Top Charts Page model object from a server data structure.
+ *
+ * @param objectGraph The object graph for the App Store.
+ * @param response The server response to parse.
+ * @param genreId The genre identifier of the genre to which this page belongs.
+ * @param categoriesList The list of categories for top charts picker.
+ * @param selectedChart The selected chart, typically shown in a segemented control.
+ * @param context The context of the charts, .e.g. All, Apps, or Games.
+ * @returns A top charts page model object.
+ */
+export function topChartsPageFromApiResponse(objectGraph, response, genreId, categoriesList, selectedChart, context) {
+ return validation.context("topChartsPageFromApiResponse", () => {
+ var _a;
+ // Categories
+ const topChartsCategories = [];
+ if (isSome(categoriesList)) {
+ for (const categoryListItem of categoriesList.categories) {
+ if (isSome(categoryListItem.genreId)) {
+ topChartsCategories.push(convertCategoryToTopChartsCategory(objectGraph, categoryListItem));
+ }
+ }
+ }
+ // Construct the segments and sort by chart name to prevent the order from changing
+ const rawSegments = mediaDataStructure.chartResultsFromServerResponse(response);
+ const segments = rawSegments
+ .map((segment) => {
+ return segmentFromData(objectGraph, segment, response, genreId, context);
+ })
+ .sort((a, b) => {
+ return a.chart.localeCompare(b.chart);
+ });
+ const segmentHref = serverData.asString(rawSegments, "0.href");
+ const segmentURL = new urls.URL(segmentHref);
+ const segmentGenreId = (_a = segmentURL.query["genre"]) !== null && _a !== void 0 ? _a : genreId;
+ const ages = segmentURL.query["ages"];
+ const selectedCategory = findCategoryInTree(segmentGenreId, ages, topChartsCategories);
+ const title = topChartsCommon.topChartsPageTitleForSegments(objectGraph, segments);
+ // Build Page
+ const topChartsPage = new models.TopChartsPage(segmentGenreId, selectedCategory === null || selectedCategory === void 0 ? void 0 : selectedCategory.ageBandId, title, segments, selectedCategory === null || selectedCategory === void 0 ? void 0 : selectedCategory.name, topChartsCategories);
+ // The "web" client needs a `FlowAction` defined on each category and segment to perform navigation
+ if (objectGraph.client.isWeb) {
+ for (const category of topChartsCategories) {
+ const destination = makeChartsPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ genreId: category.genreId,
+ chart: selectedChart,
+ ageBandId: category.ageBandId,
+ });
+ const pageUrl = makeChartsPageURL(objectGraph, destination);
+ const action = new models.FlowAction("topCharts");
+ action.destination = destination;
+ action.pageUrl = pageUrl;
+ category.chartSelectAction = action;
+ }
+ for (const segment of segments) {
+ const destination = makeChartsPageIntent({
+ ...getLocale(objectGraph),
+ ...getPlatform(objectGraph),
+ genreId,
+ chart: segment.chart,
+ ageBandId: selectedCategory === null || selectedCategory === void 0 ? void 0 : selectedCategory.ageBandId,
+ });
+ const pageUrl = makeChartsPageURL(objectGraph, destination);
+ const action = new models.FlowAction("topCharts");
+ action.destination = destination;
+ action.pageUrl = pageUrl;
+ segment.segmentSelectAction = action;
+ }
+ }
+ // Find selected chart segment
+ const selectedIndex = segments.findIndex((segment) => segment.chart === selectedChart);
+ if (selectedIndex >= 0) {
+ topChartsPage.initialSegmentIndex = selectedIndex;
+ }
+ // Decorate the final page model with network performance metrics.
+ const fetchTimingMetricsBuilder = objectGraph.fetchTimingMetricsBuilder;
+ if (isSome(fetchTimingMetricsBuilder)) {
+ fetchTimingMetricsBuilder.decorate(topChartsPage);
+ }
+ return topChartsPage;
+ });
+}
+export function convertCategoryToTopChartsCategory(objectGraph, category) {
+ const topChartsPageRequest = mediaUrlMapping.mediaApiChartsRequest(objectGraph, category.genreId, category.ageBandId);
+ const children = category.children.map((child) => convertCategoryToTopChartsCategory(objectGraph, child));
+ return new models.TopChartCategory(category, mediaUrlBuilder.buildURLFromRequest(objectGraph, topChartsPageRequest).toString(), children);
+}
+/**
+ * Recursively search through a category list to find a matching genre/age
+ * @param genreId The genreId to search for
+ * @param ageBandId The age band to include when searching for selected genre
+ * @param categoryList The array of categories to search recursively through
+ * @returns A found category, or null if not found.
+ */
+function findCategoryInTree(genreId, ageBandId, categoryList) {
+ for (const category of categoryList) {
+ if ((ageBandId && category.ageBandId === ageBandId) ||
+ (!ageBandId && !category.ageBandId && category.genreId === genreId)) {
+ return category;
+ }
+ const result = findCategoryInTree(genreId, ageBandId, category.children);
+ if (result) {
+ return result;
+ }
+ }
+ return null;
+}
+//# sourceMappingURL=top-charts-page.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/util/app-tags-util.js b/node_modules/@jet-app/app-store/tmp/src/common/util/app-tags-util.js
new file mode 100644
index 0000000..0a7808a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/util/app-tags-util.js
@@ -0,0 +1,25 @@
+import { isNothing } from "@jet/environment";
+/**
+ * @param objectGraph The object graph for our dependencies
+ * @returns Whether we should fetch app tags
+ */
+export function areAppTagsEnabled(objectGraph, featureSurface) {
+ const isClientFeatureFlagEnabled = objectGraph.client.isPhone && objectGraph.featureFlags.isEnabled("app_genome_2025A");
+ if (!isClientFeatureFlagEnabled) {
+ return false;
+ }
+ if (isNothing(featureSurface)) {
+ return true;
+ }
+ switch (featureSurface) {
+ case "grouping":
+ return objectGraph.bag.isAppsGroupingTagsEnabled;
+ case "product":
+ return objectGraph.bag.isAppsProductPageTagsEnabled;
+ case "slp":
+ return objectGraph.bag.isAppsSlpTagsEnabled;
+ default:
+ return false;
+ }
+}
+//# sourceMappingURL=app-tags-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js b/node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js
new file mode 100644
index 0000000..f370dbe
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js
@@ -0,0 +1,183 @@
+import { isSome } from "@jet/environment";
+import { URL } from "@jet/environment/util/urls";
+import { Path, Parameters } from "../../foundation/network/url-constants";
+import { normalizeLocale, deriveLocaleForUrl } from "../locale";
+/**
+ * Generate the routes property for a `RouteProvider` and a canonical URL factory
+ * function for a {@link RoutableIntent} (non-detail page).
+ *
+ * `makeIntent` should be a function that accepts a single parameter,
+ * `options: Omit<YourIntent, '$kind'>`, and returns the intent.
+ *
+ * `path` is the static part of the path. For example, in `'/us/browse?l=es'`,
+ * `'/browse'` is the static part of the path. Path can contain multiple
+ * segments like `'/foo/bar'`, but it does not support parameters like
+ * `'/foo/{id}'`.
+ *
+ * `expectedQueryParameters` are the names of the query parameters for the routes
+ * that also double as properties on the intent. This instructs the intent
+ * builder on which properties to provide to `makeIntent`. For example,
+ * `SearchResultsPageIntent`'s `RouteProvider` recognizes a `'term'` query param, which
+ * is then stored in the intent as `{ term: 'valueFromParam' }`. For that case,
+ * this array should be `['term']`. Put another way, this should be a list of the
+ * non-optional (non-nullable, etc.) string properties on the intent:
+ *
+ * ```typescript
+ * interface SearchResultsPageIntent extends RouteableIntent {
+ * $kind: 'SearchResultsPageIntent'; // not this one
+ * term: string; // this one should be included!
+ * facet: Opt<String>; // but it doesn't include this; it's optional
+ * }
+ * ```
+ *
+ * NOTE: If you receive a type error like `Type 'string' is not assignable
+ * to type 'never'` for this argument, it means that your array of parameters
+ * doesn't match the intent definition. In the above example, if we added a
+ * `originalTerm: string` to the intent, then passing ['term'] would fail,
+ * because 'originalTerm' is missing. Adding it does mean, however, that you
+ * expect users to be able to pass it as a query parameter
+ * (ex. /search?term=foo&originalTerm=bar). If that's unintended, you may want
+ * to consider making this property Opt<> (and also maybe ?).
+ *
+ * Usage Example: For a contrived intent `FooIntent`,
+ *
+ * ```typescript
+ * interface FooIntent extends RouteableIntent {
+ * $kind: 'FooIntent';
+ * bar: string;
+ * }
+ * ```
+ *
+ * If we expect users to be able to route to this intent via `/us/foo?bar=baz`
+ * or `/foo?bar=baz&l=es`, then in the IntentController's file:
+ *
+ * ```typescript
+ * const {
+ * routes,
+ * makeCanonicalUrl,
+ * } = generateRoutes(makeFooIntent, '/foo', ['bar']);
+ * ```
+ *
+ * And then, `routes` can be used in the `RouteProvider`:
+ *
+ * ```typescript
+ * export const FooIntentController: RouteProvider<FooIntent> & ... = {
+ * routes,
+ *
+ * // ...
+ * };
+ * ```
+ *
+ * And then in perform, when returning a `Page`, append the `canonicalURL`:
+ *
+ * ```typescript
+ * async perform(intent: FooIntent, objectGraph: ObjectGraph): Promise<Page> {
+ * // ...
+ * return {
+ * ...page,
+ * canonicalURL: makeCanonicalUrl(intent),
+ * };
+ * },
+ * ```
+ */
+export function generateRoutes(makeIntent, path, requiredQueryParameters = [], extras) {
+ const ruleExtras = { exclusions: extras === null || extras === void 0 ? void 0 : extras.exclusions };
+ const queryParameters = requiredQueryParameters.slice();
+ if (extras === null || extras === void 0 ? void 0 : extras.optionalQuery) {
+ queryParameters.push(...extras.optionalQuery.map((p) => `${p}?`));
+ }
+ return {
+ routes(objectGraph) {
+ var _a;
+ return [
+ {
+ rules: [
+ {
+ ...ruleExtras,
+ path,
+ query: [`${Parameters.language}?`, ...queryParameters],
+ },
+ {
+ ...ruleExtras,
+ path: `/{${Path.storeFront}}${path}`,
+ query: [`${Parameters.language}?`, ...queryParameters],
+ },
+ ...((_a = extras === null || extras === void 0 ? void 0 : extras.extraRules) !== null && _a !== void 0 ? _a : []),
+ ],
+ handler(_, parameters) {
+ const { [Path.storeFront]: storefront, [Parameters.language]: language, ...rest } = parameters;
+ const typedRestParams = rest;
+ return makeIntent({
+ ...typedRestParams,
+ ...normalizeLocale(objectGraph, {
+ storefront,
+ language,
+ }),
+ });
+ },
+ },
+ ];
+ },
+ makeCanonicalUrl(objectGraph, intent) {
+ const url = buildBaseUrl(objectGraph, path, intent);
+ // Replace dynamic segments with `Intent` properties
+ const pathComponents = url.pathComponents();
+ for (const [i, pathComponent] of pathComponents.entries()) {
+ if (pathComponent.startsWith("{") && pathComponent.endsWith("}")) {
+ const variableInPathComponent = pathComponent.substring(1, pathComponent.length - 1);
+ pathComponents[i] = intent[variableInPathComponent];
+ }
+ }
+ url.set("pathname", "/" + pathComponents.join("/"));
+ // Set query params
+ const sortedQueryParameters = requiredQueryParameters.slice();
+ // Sorted for consistent order
+ sortedQueryParameters.sort((a, b) => a.localeCompare(b));
+ for (const queryParameter of sortedQueryParameters) {
+ if (!(queryParameter in intent)) {
+ throw new Error(`expected queryParmeters to contain: ${queryParameter}`);
+ }
+ const value = intent[queryParameter];
+ // NOTE: TypeScript is unfortunately not able to see that
+ // parameters[queryParameter] will always be string. We prove
+ // this in the type. queryParameters is required to contain the
+ // string names of all keys on I with a string value (see
+ // RequiredIntentOptions<I>). This escape hatch is preferable
+ // within this function as it means that the function is
+ // externally typesafe. If a user forgets to pass in params,
+ // there will be a type error.
+ url.param(queryParameter, value);
+ }
+ if (extras === null || extras === void 0 ? void 0 : extras.optionalQuery) {
+ const optionalParams = extras.optionalQuery.slice();
+ optionalParams.sort((a, b) => a.localeCompare(b));
+ optionalParams.forEach((query) => {
+ const value = intent[query];
+ if (value) {
+ url.param(query, value);
+ }
+ });
+ }
+ return url.toString();
+ },
+ };
+}
+/**
+ * Builds the basis for a "canonical URL" given a {@linkcode path} and {@linkcode locale}
+ *
+ * The locale data will be encoded into the resulting {@linkcode URL}; other "dynamic segments"
+ * within {@linkcode path} are expected to be replaced separately
+ */
+function buildBaseUrl(objectGraph, path, locale) {
+ const url = new URL("https://apps.apple.com");
+ const { storefront, language } = deriveLocaleForUrl(objectGraph, locale);
+ url.path(storefront);
+ // Actual path (ex. browse in https://apps.apple.com/us/browse)
+ // substr(1), because URL.path doesn't expect a leading slash
+ url.path(path.substr(1));
+ if (isSome(language)) {
+ url.param("l", language);
+ }
+ return url;
+}
+//# sourceMappingURL=generate-routes.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/util/lottery.js b/node_modules/@jet-app/app-store/tmp/src/common/util/lottery.js
new file mode 100644
index 0000000..5916128
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/util/lottery.js
@@ -0,0 +1,62 @@
+import { isNothing } from "@jet/environment";
+import { MetricsIdentifierType } from "../../foundation/metrics/metrics-identifiers-cache";
+export function isFeatureEnabledForCurrentUser(objectGraph, rolloutRate) {
+ var _a;
+ if (rolloutRate <= 0.0) {
+ return false;
+ }
+ if (rolloutRate >= 1.0) {
+ // It is important that we not require a metrics ID when our rollout rate is 100%
+ return true;
+ }
+ const metricsIdString = (_a = objectGraph.metricsIdentifiersCache) === null || _a === void 0 ? void 0 : _a.getMetricsIdForType(MetricsIdentifierType.user);
+ if (isNothing(metricsIdString) || metricsIdString.length === 0) {
+ // if the user does not have a DSID, err on the side of caution.
+ return false;
+ }
+ if (metricsIdString.length < 2) {
+ // If we don't have enough characters to normalize, which should never happen, safely return false.
+ return false;
+ }
+ return normalizedMetricsIdLotteryValue(metricsIdString) < rolloutRate;
+}
+/**
+ * Gets a normalized value based on the metrics ID to match against the rollout rate to bucket a user.
+ * @param metricsId The user's metrics ID string
+ * @returns A value from 0.0 to 1.0 that we use to bucket the user
+ */
+function normalizedMetricsIdLotteryValue(metricsId) {
+ // Character mapping function that converts a character to its corresponding number.
+ // This value is guaranteed to be between 0 and 61, inclusive.
+ function charToNumber(char) {
+ const charCode = char.charCodeAt(0);
+ if (charCode >= 48 && charCode <= 57) {
+ // '0'-'9'
+ return 52 + charCode - 48;
+ }
+ else if (charCode >= 65 && charCode <= 90) {
+ // 'A'-'Z'
+ return 26 + charCode - 65;
+ }
+ else if (charCode >= 97 && charCode <= 122) {
+ // 'a'-'z'
+ return charCode - 97;
+ }
+ // This should never happen, if it does, the metrics ID is corrupt, but we rely on that not happening.
+ // This would indeed break the value guarantee.
+ return charCode;
+ }
+ // Extract the last two characters from the string.
+ const lastTwoChars = metricsId.slice(-2);
+ // Convert each of the last two characters to their respective number representation.
+ const num1 = charToNumber(lastTwoChars[0]);
+ const num2 = charToNumber(lastTwoChars[1]);
+ // Combine the two numbers into a single base-62 number.
+ const combinedValue = num1 * 62 + num2;
+ // Calculate the maximum possible value for two characters (each char has a value of 0 to 61, inclusive).
+ const maxValue = 61 * 62 + 61;
+ // Normalize the combined value to be between 0.0 and 1.0.
+ const normalizedValue = combinedValue / maxValue;
+ return normalizedValue;
+}
+//# sourceMappingURL=lottery.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/util/page-common.js b/node_modules/@jet-app/app-store/tmp/src/common/util/page-common.js
new file mode 100644
index 0000000..706fa9e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/util/page-common.js
@@ -0,0 +1,25 @@
+/**
+ * Created by ls on 6/19/2019.
+ */
+/**
+ * On different platforms, different shelves are expected to be in certain multiples to not appear like there is a "gap"
+ * This function is used to truncate an array of models to fit given expected multiple.
+ * Note that ideally we want to request the exact number of items needed, but this accounts for cases where some data fails to build into full UI elements.
+ * @param items Array of items to truncate
+ * @param mod Modulo to apply.
+ */
+export function truncateItems(items, mod) {
+ if (!items) {
+ return null;
+ }
+ const length = items.length;
+ const remainder = length % mod;
+ // If we have less than mod items, we should return them all instead of 0 (because i - (i % mod)=0 when i < mod)
+ if (length >= mod) {
+ return items.slice(0, length - remainder);
+ }
+ else {
+ return items;
+ }
+}
+//# sourceMappingURL=page-common.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/flow-action-presentation.js b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/flow-action-presentation.js
new file mode 100644
index 0000000..41b5bb9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/flow-action-presentation.js
@@ -0,0 +1,79 @@
+import { isSystemImage } from "../../api/models";
+import { asString } from "../../foundation/json-parsing/server-data";
+import { createArtworkForSystemImage } from "../content/artwork/artwork";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import * as metricsHelpersLocation from "../metrics/helpers/location";
+/**
+ * Creates a {@linkcode PrepareAction} that sets the link's text and icon
+ */
+function createWebNavigationFlowActionConfiguration(id) {
+ return (objectGraph, action) => {
+ const tab = objectGraph.bag.tabsStandard.find((t) => t.id === id);
+ const imageIdentifier = asString(tab, "image-identifier");
+ const artwork = isSystemImage(imageIdentifier)
+ ? createArtworkForSystemImage(objectGraph, imageIdentifier)
+ : null;
+ action.title = asString(tab, "title");
+ action.artwork = artwork;
+ if (objectGraph.client.isWeb) {
+ metricsHelpersClicks.addClickEventToAction(objectGraph, action, {
+ targetType: "link",
+ id: id,
+ pageInformation: undefined,
+ locationTracker: metricsHelpersLocation.newLocationTracker(),
+ });
+ }
+ };
+}
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Apps" landing page
+ */
+export const appLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("apps");
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Apps & Games" landing page
+ */
+export const appAndGamesLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("apps-and-games");
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Arcade" landing page
+ */
+export const arcadeLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("arcade");
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Discover" landing page
+ */
+export const discoverLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("discover");
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Games" landing page
+ */
+export const gamesLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("games");
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Create" landing page
+ *
+ * Note: `create` refers to the specific "Create" landing page, rather than this being
+ * a function that creates landing page presentations
+ */
+export const createLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("create");
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Work" landing page
+ */
+export const workLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("work");
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Play" landing page
+ */
+export const playLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("play");
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Develop" landing page
+ */
+export const developLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("develop");
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Categories" landing page
+ */
+export const categoriesLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("categories");
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Today" landing page
+ */
+export const todayLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("today");
+/**
+ * {@linkcode PrepareAction} that presents a link to the "Apps & Games" landing page
+ */
+export const searchLandingPageFlowActionPresentation = createWebNavigationFlowActionConfiguration("search");
+//# sourceMappingURL=flow-action-presentation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/inject-web-navigation.js b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/inject-web-navigation.js
new file mode 100644
index 0000000..b882c17
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/inject-web-navigation.js
@@ -0,0 +1,32 @@
+import { createWebNavigation, setActivePlatform } from "./web-navigation";
+/**
+ * The "default" {@linkcode PreviewPlatform} that should be used for the navigation state in cases
+ * where an explicit value is not provided
+ */
+const FALLBACK_PREVIEW_PLATFORM = "iphone";
+/**
+ * Inject the `WebNavigation` into a page model
+ *
+ * @param objectGraph
+ * @param page the page to inject the navigation into
+ * @param platform the `PreviewPlatform` to render navigation links for
+ * @returns the web navigation shelf, in case further mutation is required
+ */
+export function injectWebNavigation(objectGraph, page, platform) {
+ const webNavigation = createWebNavigation(objectGraph, platform !== null && platform !== void 0 ? platform : FALLBACK_PREVIEW_PLATFORM);
+ if (page.title) {
+ webNavigation.title = page.title;
+ }
+ page.webNavigation = webNavigation;
+ return webNavigation;
+}
+/**
+ * Sets the `WebNavigation` contained by {@linkcode page} to reflect {@linkcode platform}
+ * as the active platform. Landing pages links will also be made active based on {@linkcode intent}
+ */
+export function updateWebNavigation(objectGraph, page, platform, intent) {
+ if (page.webNavigation) {
+ setActivePlatform(objectGraph, page.webNavigation, platform, intent);
+ }
+}
+//# sourceMappingURL=inject-web-navigation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/landing-page-links-by-platform.js b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/landing-page-links-by-platform.js
new file mode 100644
index 0000000..02aad6c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/landing-page-links-by-platform.js
@@ -0,0 +1,62 @@
+import { isPreviewPlatform } from "../../api/models/preview-platform";
+import { getLocale } from "../locale";
+import { landingPageIntentsAreEquivalent, } from "./platform-landing-page-utils";
+import { iPadTodayPageController, iPadAppsController, iPadArcadeController, iPadGamesController, iPadSearchLandingController, iPhoneTodayPageController, iPhoneAppsController, iPhoneGamesController, iPhoneArcadeController, iPhoneSearchLandingController, MacDiscoverController, MacArcadeController, MacCreateController, MacWorkController, MacPlayController, MacDevelopController, MacCategoriesController, MacSearchLandingController, TVAppsController, TVArcadeController, TVDiscoverController, TVGamesController, TVSearchLandingController, VisionAppsAndGamesController, VisionArcadeController, VisionSearchLandingController, WatchDiscoverController, WatchSearchLandingController, } from "./platform-landing-page-intent-controllers";
+const ARCADE_CONTROLLERS = new Set([
+ iPadArcadeController,
+ iPhoneArcadeController,
+ MacArcadeController,
+ TVArcadeController,
+ VisionArcadeController,
+]);
+export function createLandingPageLinks(objectGraph, platform, intent) {
+ if (!platform || !isPreviewPlatform(platform)) {
+ return [];
+ }
+ // NOTE: this map cannot be defined at the module level, for risk of creating
+ // a circular dependency between module-level declarations that will break the build
+ // tools used by the "web" client
+ const landingPageControllers = {
+ ipad: [
+ iPadTodayPageController,
+ iPadGamesController,
+ iPadAppsController,
+ iPadArcadeController,
+ iPadSearchLandingController,
+ ],
+ iphone: [
+ iPhoneTodayPageController,
+ iPhoneGamesController,
+ iPhoneAppsController,
+ iPhoneArcadeController,
+ iPhoneSearchLandingController,
+ ],
+ mac: [
+ MacDiscoverController,
+ MacArcadeController,
+ MacCreateController,
+ MacWorkController,
+ MacPlayController,
+ MacDevelopController,
+ MacCategoriesController,
+ MacSearchLandingController,
+ ],
+ tv: [TVDiscoverController, TVGamesController, TVAppsController, TVArcadeController, TVSearchLandingController],
+ vision: [VisionAppsAndGamesController, VisionArcadeController, VisionSearchLandingController],
+ watch: [WatchDiscoverController, WatchSearchLandingController],
+ };
+ const locale = getLocale(objectGraph);
+ const isArcadeEnabled = objectGraph.bag.isArcadeEnabled;
+ return landingPageControllers[platform]
+ .filter((landingPageController) => {
+ // Rejects Arcade controllers if not supported and let's all other controllers pass
+ const isArcadeController = ARCADE_CONTROLLERS.has(landingPageController);
+ return isArcadeEnabled || !isArcadeController;
+ })
+ .map((controller) => controller.buildAction(locale, objectGraph))
+ .map((action) => ({
+ action,
+ isActive: landingPageIntentsAreEquivalent(intent, action.destination),
+ }));
+}
+//# sourceMappingURL=landing-page-links-by-platform.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-landing-page-intent-controllers.js b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-landing-page-intent-controllers.js
new file mode 100644
index 0000000..d19d46b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-landing-page-intent-controllers.js
@@ -0,0 +1,155 @@
+// This module, despite exporting `IntentController` definitions, lives inside of
+// `src/common` because these controllers need to be import-able by other "common"
+// code in order to directly call their `buildIntent` method
+//
+// The "web" client expects all exports from this module to be `IntentController` instances
+import { defineLandingPageController, definePlatformRootRedirectController, defineRootRedirectController, } from "./platform-landing-page-utils";
+import { makeEditorialPageIntentByName } from "../../api/intents/editorial/editorial-page-intent";
+import { makeGroupingPageIntentByName } from "../../api/intents/grouping-page-intent";
+import { makeRoutableTodayPageIntent } from "../../api/intents/routable-today-page-intent";
+import { makeArcadeGroupingPageIntent } from "../../api/intents/arcade-grouping-page-intent";
+import { appLandingPageFlowActionPresentation, appAndGamesLandingPageFlowActionPresentation, arcadeLandingPageFlowActionPresentation, discoverLandingPageFlowActionPresentation, gamesLandingPageFlowActionPresentation, createLandingPageFlowActionPresentation, workLandingPageFlowActionPresentation, playLandingPageFlowActionPresentation, todayLandingPageFlowActionPresentation, developLandingPageFlowActionPresentation, categoriesLandingPageFlowActionPresentation, searchLandingPageFlowActionPresentation, } from "./flow-action-presentation";
+import { makeSearchLandingPageIntent } from "../../api/intents/search/search-landing-page-intent";
+// Ensures that the root URL (e.g. apps.apple.com) redirects to the iPhone Today page
+export const RootRedirectController = defineRootRedirectController((locale) => iPhoneTodayPageController.buildIntent(locale));
+/// MARK: Vision Pro Landing Pages
+export const VisionRootRedirectController = definePlatformRootRedirectController("/vision", (locale) => VisionAppsAndGamesController.buildIntent(locale));
+export const VisionAppsAndGamesController = defineLandingPageController("/vision/apps-and-games", (locale) => makeEditorialPageIntentByName({
+ ...locale,
+ platform: "vision",
+ name: "apps-and-games",
+}), appAndGamesLandingPageFlowActionPresentation);
+export const VisionArcadeController = defineLandingPageController("/vision/arcade", (locale) => makeEditorialPageIntentByName({
+ ...locale,
+ platform: "vision",
+ name: "arcade-subscriber",
+}), arcadeLandingPageFlowActionPresentation);
+export const VisionSearchLandingController = defineLandingPageController("/vision/search", (locale) => makeSearchLandingPageIntent({
+ ...locale,
+ platform: "vision",
+}), searchLandingPageFlowActionPresentation);
+/// MARK: Mac Landing Pages
+export const MacRootRedirectController = definePlatformRootRedirectController("/mac", (locale) => MacDiscoverController.buildIntent(locale));
+export const MacDiscoverController = defineLandingPageController("/mac/discover", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "mac",
+ name: "apps",
+}), discoverLandingPageFlowActionPresentation);
+export const MacArcadeController = defineLandingPageController("/mac/arcade", (locale) => makeArcadeGroupingPageIntent({
+ ...locale,
+ platform: "mac",
+}), arcadeLandingPageFlowActionPresentation);
+export const MacCreateController = defineLandingPageController("/mac/create", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "mac",
+ name: "create",
+}), createLandingPageFlowActionPresentation);
+export const MacWorkController = defineLandingPageController("/mac/work", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "mac",
+ name: "work",
+}), workLandingPageFlowActionPresentation);
+export const MacPlayController = defineLandingPageController("/mac/play", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "mac",
+ name: "play",
+}), playLandingPageFlowActionPresentation);
+export const MacDevelopController = defineLandingPageController("/mac/develop", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "mac",
+ name: "develop",
+}), developLandingPageFlowActionPresentation);
+export const MacCategoriesController = defineLandingPageController("/mac/categories", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "mac",
+ name: "categories",
+}), categoriesLandingPageFlowActionPresentation);
+export const MacSearchLandingController = defineLandingPageController("/mac/search", (locale) => makeSearchLandingPageIntent({
+ ...locale,
+ platform: "mac",
+}), searchLandingPageFlowActionPresentation);
+/// MARK: iPhone Landing Pages
+export const iPhoneRootRedirectController = definePlatformRootRedirectController("/iphone", (locale) => iPhoneTodayPageController.buildIntent(locale));
+export const iPhoneTodayPageController = defineLandingPageController("/iphone/today", (locale) => makeRoutableTodayPageIntent({
+ ...locale,
+ platform: "iphone",
+}), todayLandingPageFlowActionPresentation);
+export const iPhoneAppsController = defineLandingPageController("/iphone/apps", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "iphone",
+ name: "apps",
+}), appLandingPageFlowActionPresentation);
+export const iPhoneGamesController = defineLandingPageController("/iphone/games", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "iphone",
+ name: "games",
+}), gamesLandingPageFlowActionPresentation);
+export const iPhoneArcadeController = defineLandingPageController("/iphone/arcade", (locale) => makeArcadeGroupingPageIntent({
+ ...locale,
+ platform: "iphone",
+}), arcadeLandingPageFlowActionPresentation);
+export const iPhoneSearchLandingController = defineLandingPageController("/iphone/search", (locale) => makeSearchLandingPageIntent({
+ ...locale,
+ platform: "iphone",
+}), searchLandingPageFlowActionPresentation);
+/// MARK: iPad Landing Pages
+export const iPadRootRedirectController = definePlatformRootRedirectController("/ipad", (locale) => iPadTodayPageController.buildIntent(locale));
+export const iPadTodayPageController = defineLandingPageController("/ipad/today", (locale) => makeRoutableTodayPageIntent({
+ ...locale,
+ platform: "ipad",
+}), todayLandingPageFlowActionPresentation);
+export const iPadGamesController = defineLandingPageController("/ipad/games", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "ipad",
+ name: "games",
+}), gamesLandingPageFlowActionPresentation);
+export const iPadAppsController = defineLandingPageController("/ipad/apps", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "ipad",
+ name: "apps",
+}), appLandingPageFlowActionPresentation);
+export const iPadArcadeController = defineLandingPageController("/ipad/arcade", (locale) => makeArcadeGroupingPageIntent({
+ ...locale,
+ platform: "ipad",
+}), arcadeLandingPageFlowActionPresentation);
+export const iPadSearchLandingController = defineLandingPageController("/ipad/search", (locale) => makeSearchLandingPageIntent({
+ ...locale,
+ platform: "ipad",
+}), searchLandingPageFlowActionPresentation);
+/// MARK: Watch Landing Pages
+export const WatchRootRedirectController = definePlatformRootRedirectController("/watch", (locale) => WatchDiscoverController.buildIntent(locale));
+export const WatchDiscoverController = defineLandingPageController("/watch/apps-and-games", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "watch",
+ name: "apps",
+}), appAndGamesLandingPageFlowActionPresentation);
+export const WatchSearchLandingController = defineLandingPageController("/watch/search", (locale) => makeSearchLandingPageIntent({
+ ...locale,
+ platform: "watch",
+}), searchLandingPageFlowActionPresentation);
+/// MARK: TV Landing Pages
+export const TVRootRedirectController = definePlatformRootRedirectController("/tv", (locale) => TVDiscoverController.buildIntent(locale));
+export const TVDiscoverController = defineLandingPageController("/tv/discover", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "tv",
+ name: "discover",
+}), discoverLandingPageFlowActionPresentation);
+export const TVGamesController = defineLandingPageController("/tv/games", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "tv",
+ name: "games",
+}), gamesLandingPageFlowActionPresentation);
+export const TVAppsController = defineLandingPageController("/tv/apps", (locale) => makeGroupingPageIntentByName({
+ ...locale,
+ platform: "tv",
+ name: "apps",
+}), appLandingPageFlowActionPresentation);
+export const TVArcadeController = defineLandingPageController("/tv/arcade", (locale) => makeArcadeGroupingPageIntent({
+ ...locale,
+ platform: "tv",
+}), arcadeLandingPageFlowActionPresentation);
+export const TVSearchLandingController = defineLandingPageController("/tv/search", (locale) => makeSearchLandingPageIntent({
+ ...locale,
+ platform: "tv",
+}), searchLandingPageFlowActionPresentation);
+//# sourceMappingURL=platform-landing-page-intent-controllers.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-landing-page-utils.js b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-landing-page-utils.js
new file mode 100644
index 0000000..c408425
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-landing-page-utils.js
@@ -0,0 +1,139 @@
+import { isNothing, unwrapOptional as unwrap } from "@jet/environment/types/optional";
+import { FlowAction } from "../../api/models";
+import { generateRoutes } from "../util/generate-routes";
+import { updateWebNavigation } from "./inject-web-navigation";
+/**
+ * Determine if the two {@linkcode LandingPageIntent} arguments are equivalent
+ */
+export function landingPageIntentsAreEquivalent(activeIntent, navLinkIntent) {
+ if (isNothing(activeIntent) || isNothing(navLinkIntent)) {
+ return false;
+ }
+ for (const key of ["$kind", "storefront", "language"]) {
+ if (activeIntent[key] !== navLinkIntent[key]) {
+ return false;
+ }
+ }
+ return true;
+}
+/**
+ * Dynamically create an {@linkcode IntentController} for a platform "landing page"
+ *
+ * A "landing page" is a vanity URL within a specific platform that loads some other
+ * page under-the-hood; the "canonical URL" for the "landing page" should reflect this
+ * vanity URL, rather than the real canonical URL for the underlying Media API entity.
+ *
+ * @param url the vanity URL for the landing page
+ * @param intentFactory a function that produces the underlying `Intent` for the data backing the landing page
+ * @param prepareAction a function that can be used to provide additional configuration of the `FlowAction` for a landing page
+ */
+export function defineLandingPageController(url, intentFactory, prepareAction) {
+ const intentKind = `LandingPage_${url.substring(1)}_Intent`;
+ const { routes, makeCanonicalUrl } = generateRoutes((locale) => ({
+ ...locale,
+ $kind: intentKind,
+ }), url, [], {
+ exclusions: [
+ {
+ query: ["term"],
+ },
+ ],
+ });
+ return {
+ $intentKind: intentKind,
+ routes,
+ async perform(landingPageIntent, objectGraph) {
+ var _a;
+ const pageIntent = intentFactory({
+ storefront: landingPageIntent.storefront,
+ language: landingPageIntent.language,
+ });
+ const viewModel = await objectGraph.dispatcher.dispatch(pageIntent, objectGraph);
+ if (viewModel) {
+ // Override the default canonical URL to use the vanity URL instead; this ensures
+ // that the navigation mechanics of the "web" client respect the vanity URL, which the
+ // Media API would not know about when providing it's own value for this property
+ // @ts-expect-error TypeScript can't know that `landingPageIntent` satisfies the requirements of
+ // `makeCanonicalUrl`, but this will be safe at runtime because all we actually require
+ // from the intent is the locale information
+ viewModel.canonicalURL = makeCanonicalUrl(objectGraph, landingPageIntent);
+ (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.updateCanonicalURL(viewModel, viewModel.canonicalURL);
+ // If the web navigation has already been injected, the active state of the "tabs" will be incorrect
+ // since non-"landing pages" never have display active landing page links. We need to update the
+ // navigation shelf, if it exists, based on the `landingPageIntent`
+ updateWebNavigation(objectGraph, viewModel, unwrap(pageIntent.platform), landingPageIntent);
+ }
+ return viewModel;
+ },
+ buildIntent(locale) {
+ return {
+ ...locale,
+ $kind: intentKind,
+ };
+ },
+ buildAction(locale, objectGraph) {
+ const intent = this.buildIntent(locale);
+ return this.actionFor(intent, objectGraph, {});
+ },
+ actionFor(landingPageIntent, objectGraph) {
+ const action = new FlowAction("page");
+ action.destination = landingPageIntent;
+ // @ts-expect-error TypeScript can't know that `landingPageIntent` satisfies the requirements of
+ // `makeCanonicalUrl`, but this will be safe at runtime because all we actually require
+ // from the intent is the locale information
+ action.pageUrl = makeCanonicalUrl(objectGraph, landingPageIntent);
+ prepareAction === null || prepareAction === void 0 ? void 0 : prepareAction(objectGraph, action);
+ return action;
+ },
+ };
+}
+/**
+ * Dynamically create an {@linkcode IntentController} that redirects from the platform root
+ * to the default landing page
+ *
+ * @param url the URL for the platform to redirect for
+ * @param intentFactory a function that produces the `Intent` for the `LandingPageController` that should be delegated to
+ */
+export function definePlatformRootRedirectController(url, intentFactory) {
+ const intentKind = `PlatformRootRedirect_${url.substring(1)}_Intent`;
+ const { routes } = generateRoutes((locale) => ({
+ ...locale,
+ $kind: intentKind,
+ }), url);
+ return {
+ $intentKind: intentKind,
+ routes,
+ async perform(landingPageIntent, objectGraph) {
+ const pageIntent = intentFactory({
+ storefront: landingPageIntent.storefront,
+ language: landingPageIntent.language,
+ });
+ return await objectGraph.dispatcher.dispatch(pageIntent, objectGraph);
+ },
+ };
+}
+/**
+ * Dynamically create an {@linkcode IntentController} that redirects from the root
+ * to the default landing page
+ *
+ * @param intentFactory a function that produces the `Intent` for the `LandingPageController` that should be delegated to
+ */
+export function defineRootRedirectController(intentFactory) {
+ const intentKind = `RootRedirect_Intent`;
+ const { routes } = generateRoutes((locale) => ({
+ ...locale,
+ $kind: intentKind,
+ }), "/");
+ return {
+ $intentKind: intentKind,
+ routes,
+ async perform(landingPageIntent, objectGraph) {
+ const pageIntent = intentFactory({
+ storefront: landingPageIntent.storefront,
+ language: landingPageIntent.language,
+ });
+ return await objectGraph.dispatcher.dispatch(pageIntent, objectGraph);
+ },
+ };
+}
+//# sourceMappingURL=platform-landing-page-utils.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-selection.js b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-selection.js
new file mode 100644
index 0000000..b583859
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/platform-selection.js
@@ -0,0 +1,115 @@
+import { allPreviewPlatforms } from "../../api/models/preview-platform";
+import { iPadTodayPageController, WatchDiscoverController, VisionAppsAndGamesController, MacDiscoverController, iPhoneTodayPageController, TVDiscoverController, } from "./platform-landing-page-intent-controllers";
+import { unreachable } from "../../foundation/util/errors";
+import { getLocale } from "../locale";
+import { createArtworkForSystemImage } from "../content/artwork/artwork";
+import * as metricsHelpersClicks from "../metrics/helpers/clicks";
+import { newLocationTracker } from "../metrics/helpers/location";
+function makeiPhonePlatformSelector(objectGraph, isActive) {
+ const action = iPhoneTodayPageController.buildAction(getLocale(objectGraph), objectGraph);
+ action.title = objectGraph.loc.string("Web.Navigation.Platform.Phone");
+ action.artwork = createArtworkForSystemImage(objectGraph, "iphone.gen2");
+ return {
+ action,
+ isActive,
+ };
+}
+function makeiPadPlatformSelector(objectGraph, isActive) {
+ const action = iPadTodayPageController.buildAction(getLocale(objectGraph), objectGraph);
+ action.title = objectGraph.loc.string("Web.Navigation.Platform.Pad");
+ action.artwork = createArtworkForSystemImage(objectGraph, "ipad.gen2.landscape");
+ return {
+ action,
+ isActive,
+ };
+}
+function makeMacPlatformSelector(objectGraph, isActive) {
+ const action = MacDiscoverController.buildAction(getLocale(objectGraph), objectGraph);
+ action.title = objectGraph.loc.string("Web.Navigation.Platform.Mac");
+ action.artwork = createArtworkForSystemImage(objectGraph, "macbook.gen2");
+ return {
+ action,
+ isActive,
+ };
+}
+function makeVisionPlatformSelector(objectGraph, isActive) {
+ const action = VisionAppsAndGamesController.buildAction(getLocale(objectGraph), objectGraph);
+ action.title = objectGraph.loc.string("Web.Navigation.Platform.Vision");
+ action.artwork = createArtworkForSystemImage(objectGraph, "visionpro");
+ return {
+ action,
+ isActive,
+ };
+}
+function makeWatchPlatformSelector(objectGraph, isActive) {
+ const action = WatchDiscoverController.buildAction(getLocale(objectGraph), objectGraph);
+ action.title = objectGraph.loc.string("Web.Navigation.Platform.Watch");
+ action.artwork = createArtworkForSystemImage(objectGraph, "applewatch");
+ return {
+ action,
+ isActive,
+ };
+}
+function makeTVPlatformSelector(objectGraph, isActive) {
+ const action = TVDiscoverController.buildAction(getLocale(objectGraph), objectGraph);
+ action.title = objectGraph.loc.string("Web.Navigation.Platform.TV");
+ action.artwork = createArtworkForSystemImage(objectGraph, "tv");
+ return {
+ action,
+ isActive,
+ };
+}
+function makePlatformSelector(objectGraph, platform, activePlatform) {
+ let selector;
+ switch (platform) {
+ case "iphone": {
+ selector = makeiPhonePlatformSelector(objectGraph, activePlatform === "iphone");
+ break;
+ }
+ case "ipad": {
+ selector = makeiPadPlatformSelector(objectGraph, activePlatform === "ipad");
+ break;
+ }
+ case "mac": {
+ selector = makeMacPlatformSelector(objectGraph, activePlatform === "mac");
+ break;
+ }
+ case "vision": {
+ selector = makeVisionPlatformSelector(objectGraph, activePlatform === "vision");
+ break;
+ }
+ case "watch": {
+ selector = makeWatchPlatformSelector(objectGraph, activePlatform === "watch");
+ break;
+ }
+ case "tv": {
+ selector = makeTVPlatformSelector(objectGraph, activePlatform === "tv");
+ break;
+ }
+ default:
+ unreachable(platform);
+ }
+ if (objectGraph.client.isWeb) {
+ metricsHelpersClicks.addClickEventToAction(objectGraph, selector.action, {
+ id: platform,
+ actionType: "navigate",
+ locationTracker: newLocationTracker(),
+ pageInformation: undefined,
+ }, false, "link");
+ }
+ return selector;
+}
+/**
+ *
+ * @param objectGraph
+ * @param activePlatform the active platform; navigation links to it will be presented with an "active" style
+ * @returns
+ */
+export function createPlatformSelectors(objectGraph, activePlatform) {
+ const isVisionSupported = objectGraph.bag.enableVisionPlatform;
+ return (allPreviewPlatforms
+ // Rejects vision if its not supported and let's all other platforms pass
+ .filter((platform) => isVisionSupported || platform !== "vision")
+ .map((platform) => makePlatformSelector(objectGraph, platform, activePlatform)));
+}
+//# sourceMappingURL=platform-selection.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/search-results-platform-selection.js b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/search-results-platform-selection.js
new file mode 100644
index 0000000..00993f8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/search-results-platform-selection.js
@@ -0,0 +1,28 @@
+import { allPreviewPlatforms } from "../../api/models/preview-platform";
+import { makeCanonicalSearchResultsPageUrl } from "../search/search-page-url";
+function replaceDestinationIntent(objectGraph, action, intent, platform) {
+ const updatedIntent = {
+ ...intent,
+ platform,
+ };
+ action.destination = updatedIntent;
+ action.pageUrl = makeCanonicalSearchResultsPageUrl(objectGraph, updatedIntent);
+}
+/**
+ * Replaces each of the "platform selector" actions with one that navigates to the
+ * {@linkcode searchResultsPageIntent} in each platform
+ *
+ * This is necessary because the existing platform selector, which normally takes
+ * the user to a platform landing page, instead takes the user to the search results
+ * page within the selected platform
+ */
+export function replacePlatformSelectorDestinationWithSearchResults(objectGraph, webNavigation, searchResultsPageIntent) {
+ const isVisionSupported = objectGraph.bag.enableVisionPlatform;
+ allPreviewPlatforms
+ // Rejects vision if its not supported and let's all other platforms pass
+ .filter((platform) => isVisionSupported || platform !== "vision")
+ .forEach((platform, index) => {
+ replaceDestinationIntent(objectGraph, webNavigation.platforms[index].action, searchResultsPageIntent, platform);
+ });
+}
+//# sourceMappingURL=search-results-platform-selection.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/web-navigation.js b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/web-navigation.js
new file mode 100644
index 0000000..13b7f6d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/web-navigation/web-navigation.js
@@ -0,0 +1,28 @@
+import { createPlatformSelectors } from "./platform-selection";
+import { createLandingPageLinks } from "./landing-page-links-by-platform";
+import { makeWebSearchAction } from "../search/web-search-action";
+/**
+ * Create a {@linkcode WebNavigation} based on the active {@linkcode Intent}
+ */
+export function createWebNavigation(objectGraph, platform) {
+ const webNavigation = {
+ platforms: [],
+ tabs: [],
+ searchAction: makeWebSearchAction(objectGraph, platform),
+ };
+ setActivePlatform(objectGraph, webNavigation, platform);
+ return webNavigation;
+}
+/**
+ * Updates a {@linkcode WebNavigation} to reflect:
+ *
+ * 1. the platform specified by {@linkcode platform}
+ * 2. the "active landing page" specified by {@linkcode intent}
+ */
+export function setActivePlatform(objectGraph, shelf, platform, intent) {
+ shelf.platforms = createPlatformSelectors(objectGraph, platform);
+ const searchActionDestination = shelf.searchAction.destination;
+ shelf.tabs = createLandingPageLinks(objectGraph, platform, intent);
+ searchActionDestination.platform = platform;
+}
+//# sourceMappingURL=web-navigation.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/app-events/app-event-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/app-events/app-event-page-intent-controller.js
new file mode 100644
index 0000000..1cda6ab
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/app-events/app-event-page-intent-controller.js
@@ -0,0 +1,42 @@
+import { isNothing } from "@jet/environment/types/optional";
+import { appEventPageRoutes } from "../../common/product-page/intent-controller-routing";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { makeAppEventPageRequest, makeShelfBasedAppEventDetailPage } from "../../common/app-events/app-events-common";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { fetchData, NetworkError } from "../../foundation/media/network";
+import { URL } from "../../foundation/network/urls";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { validateAdamId } from "../../foundation/media/util";
+export const AppEventPageIntentController = {
+ $intentKind: "AppEventPageIntent",
+ routes: appEventPageRoutes,
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a;
+ // See: https://github.pie.apple.com/its/Jingle/blob/ce14e21b6ac3dd4be884aa12b26d4e79c6d8aa7a/MZStorePlatform/src/main/java/com/apple/jingle/store/mediaapi/resource/SFMediaAPICommonResourceType.java#L50
+ validateAdamId(objectGraph, intent.id);
+ const mediaApiRequest = makeAppEventPageRequest(objectGraph, intent);
+ const response = await fetchData(objectGraph, mediaApiRequest);
+ const page = makeShelfBasedAppEventDetailPage(objectGraph, response);
+ if (isNothing(page)) {
+ const notFoundError = new NetworkError("Media API response lacked required data");
+ notFoundError.statusCode = 404;
+ throw notFoundError;
+ }
+ // The page URL (`.canonicalURL` property on the view-model) from Media API does not account for the `platform`
+ // query param; we need to add that in ourselves. We can't rely on `makeCanonicalUrl` as that does not handle the
+ // `appName` segment; we need to take the Media API URL and append the `platform` ourselves
+ if (intent.platform && page.canonicalURL) {
+ const pageURL = new URL(page.canonicalURL);
+ pageURL.param("platform", intent.platform);
+ page.canonicalURL = pageURL.toString();
+ }
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, page, intent.platform);
+ injectSEOData(page, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForAppEventPage(objectGraph, page, response));
+ }
+ return page;
+ });
+ },
+};
+//# sourceMappingURL=app-event-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/arcade/arcade-grouping-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/arcade/arcade-grouping-page-intent-controller.js
new file mode 100644
index 0000000..ba1e42a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/arcade/arcade-grouping-page-intent-controller.js
@@ -0,0 +1,71 @@
+import { isNothing } from "@jet/environment/types/optional";
+import { fetchData } from "../../foundation/media/network";
+import { makeBaseGroupingPageRequest, prepareGroupingPageRequest } from "../../common/grouping/grouping-request";
+import { flattenedGroupingFromDataContainer, groupingPageFromFlattenedGrouping, groupingParseContextFromDataContainer, } from "../../common/grouping/render-grouping-page";
+import { arcadeAppsRequestForIcons } from "../../common/arcade/arcade-common";
+import { GroupingArcadeFooterShelfController } from "../../common/grouping/shelf-controllers/grouping-arcade-footer-shelf-controller";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { setPreviewPlatform } from "../../common/preview-platform";
+function makeMediaApiArcadeGroupingPageRequest(objectGraph, intent) {
+ const mediaApiRequest = makeBaseGroupingPageRequest(objectGraph)
+ .addingQuery("name", "arcade")
+ .addingQuery("tabs", "subscriber");
+ prepareGroupingPageRequest(objectGraph, mediaApiRequest);
+ setPreviewPlatform(objectGraph, mediaApiRequest);
+ return mediaApiRequest;
+}
+function makeArcadeSeeAllShelf(objectGraph, groupingParseContext, response) {
+ const token = {
+ id: "arcade-see-all-games-footer",
+ presentationHints: {},
+ featuredContentId: -1 /* FeaturedContentID.Native_GroupingShelf */,
+ featuredContentData: null,
+ nativeGroupingShelfId: 1 /* NativeGroupingShelfID.Arcade_SeeAllGamesFooter */,
+ metricsPageInformation: groupingParseContext.metricsPageInformation,
+ metricsLocationTracker: groupingParseContext.metricsLocationTracker,
+ pageGenreId: groupingParseContext.pageGenreId,
+ title: null,
+ shouldFilter: false,
+ remainingItems: [],
+ isFirstRender: true,
+ isDeferring: false,
+ showOrdinals: false,
+ hasExistingContent: false,
+ showingPlaceholders: false,
+ ordinalIndex: 1,
+ isSearchLandingPage: groupingParseContext.isSearchLandingPage,
+ isArcadePage: groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage,
+ };
+ const groupingArcadeFooterShelfController = new GroupingArcadeFooterShelfController();
+ const arcadeFooterShelf = groupingArcadeFooterShelfController.createShelf(objectGraph, response, groupingParseContext, token, null);
+ return arcadeFooterShelf;
+}
+export const ArcadeGroupingPageIntentController = {
+ $intentKind: "ArcadeGroupingPageIntent",
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a;
+ const groupingPageRequest = makeMediaApiArcadeGroupingPageRequest(objectGraph, intent);
+ const arcadeFooterRequest = arcadeAppsRequestForIcons(objectGraph, 20);
+ const [groupingPageResponse, arcadeFooterResponse] = await Promise.all([
+ fetchData(objectGraph, groupingPageRequest),
+ fetchData(objectGraph, arcadeFooterRequest),
+ ]);
+ const flattenedGrouping = flattenedGroupingFromDataContainer(objectGraph, groupingPageResponse);
+ const groupingParseContext = groupingParseContextFromDataContainer(objectGraph, groupingPageResponse);
+ if (isNothing(flattenedGrouping) || isNothing(groupingParseContext)) {
+ return null;
+ }
+ const page = groupingPageFromFlattenedGrouping(objectGraph, flattenedGrouping, groupingParseContext);
+ page.shelves.push(makeArcadeSeeAllShelf(objectGraph, groupingParseContext, arcadeFooterResponse));
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, page, intent.platform);
+ injectSEOData(page, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForGroupingPage(objectGraph, page, groupingPageResponse));
+ }
+ return page;
+ });
+ },
+};
+//# sourceMappingURL=arcade-grouping-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/arcade/routable-arcade-see-all-page-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/arcade/routable-arcade-see-all-page-controller.js
new file mode 100644
index 0000000..9751c7b
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/arcade/routable-arcade-see-all-page-controller.js
@@ -0,0 +1,42 @@
+import { ArcadeSeeAllGamesPage } from "../../api/models";
+import { defaultAdditionalPlatformsForClient } from "../../foundation/media/data-fetching";
+import { fetchData } from "../../foundation/media/network";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { arcadeAppsRequest } from "../../common/arcade/arcade-common";
+import { defaultRequestAttributes, prepareRequestWithSelectedFacets } from "../../common/arcade/arcade-see-all-request";
+import { createArcadeSeeAllGamesPaginationToken, createShelves, } from "../../common/arcade/render-arcade-see-all-games-page";
+import { shouldFetchCustomAttributes } from "../../common/product-page/product-page-variants";
+import { metricsPageInformationFromMediaApiResponse } from "../../common/metrics/helpers/page";
+import { asArrayOrEmpty } from "../../foundation/json-parsing/server-data";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { createDefaultSelectedFacetOptions } from "../../common/arcade/arcade-see-all-games-facets";
+import { arcadeSeeAllRoutes as routes, makeArcadeSeeAllCanonicalUrl } from "../../common/arcade/arcade-see-all-routing";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+export const RoutableArcadeSeeAllPageController = {
+ $intentKind: "RoutableArcadeSeeAllPageIntent",
+ routes,
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a;
+ const mediaApiRequest = arcadeAppsRequest(objectGraph)
+ .includingAdditionalPlatforms(defaultAdditionalPlatformsForClient(objectGraph))
+ .includingAttributes(defaultRequestAttributes(objectGraph))
+ .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
+ if (objectGraph.client.isWeb) {
+ mediaApiRequest.withSparseLimit(20);
+ }
+ prepareRequestWithSelectedFacets(mediaApiRequest, createDefaultSelectedFacetOptions(objectGraph));
+ const mediaApiResponse = await fetchData(objectGraph, mediaApiRequest);
+ const pageInformation = metricsPageInformationFromMediaApiResponse(objectGraph, "Room", "arcadeSeeAllGames", mediaApiResponse);
+ const paginationToken = createArcadeSeeAllGamesPaginationToken(objectGraph, { isCompactMode: false }, pageInformation);
+ const page = new ArcadeSeeAllGamesPage(createShelves(objectGraph, asArrayOrEmpty(mediaApiResponse, "results.groups"), paginationToken));
+ if (objectGraph.client.isWeb) {
+ page.canonicalURL = makeArcadeSeeAllCanonicalUrl(objectGraph, intent);
+ injectWebNavigation(objectGraph, page, intent.platform);
+ injectSEOData(page, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForArcadeSeeAllPage(objectGraph, page, mediaApiResponse));
+ }
+ return page;
+ });
+ },
+};
+//# sourceMappingURL=routable-arcade-see-all-page-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/developer/developer-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/developer/developer-page-intent-controller.js
new file mode 100644
index 0000000..458db1d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/developer/developer-page-intent-controller.js
@@ -0,0 +1,28 @@
+import { Path } from "../../foundation/network/url-constants";
+import { makeDeveloperPageIntent } from "../../api/intents/developer-page-intent";
+import { generateRoutes } from "../../common/util/generate-routes";
+import { makeDeveloperRequest } from "../../common/developer/developer-request";
+import { fetchData } from "../../foundation/media/network";
+import { developerPageFromResponse } from "../../common/developer/developer-common";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { validateAdamId } from "../../foundation/media/util";
+const { routes } = generateRoutes(makeDeveloperPageIntent, `/${Path.developer}/{name}/{id}`);
+export const DeveloperPageIntentController = {
+ $intentKind: "DeveloperPageIntent",
+ routes,
+ async perform(intent, objectGraph) {
+ var _a;
+ // See: https://github.pie.apple.com/its/Jingle/blob/ce14e21b6ac3dd4be884aa12b26d4e79c6d8aa7a/MZStorePlatform/src/main/java/com/apple/jingle/store/mediaapi/resource/SFMediaAPICommonResourceType.java#L52
+ validateAdamId(objectGraph, intent.id);
+ const mediaApiRequest = makeDeveloperRequest(objectGraph, intent.id);
+ const response = await fetchData(objectGraph, mediaApiRequest);
+ const page = developerPageFromResponse(objectGraph, response);
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, page);
+ injectSEOData(page, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForDeveloperPage(objectGraph, page, response));
+ }
+ return page;
+ },
+};
+//# sourceMappingURL=developer-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/editorial-pages/editorial-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/editorial-pages/editorial-page-intent-controller.js
new file mode 100644
index 0000000..8c19eb7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/editorial-pages/editorial-page-intent-controller.js
@@ -0,0 +1,55 @@
+import { isNothing } from "@jet/environment";
+import { renderPage as renderEditorialPage } from "../../common/editorial-pages/editorial-page-controller-util";
+import { makeEditorialPageRequest } from "../../common/editorial-pages/editorial-page-media-api-utils";
+import * as mediaNetwork from "../../foundation/media/network";
+import { FlowAction } from "../../api/models";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { isEditorialPageIntentByID } from "../../api/intents/editorial/editorial-page-intent";
+import { editorialPageRoutes, makeEditorialPageURL, } from "../../common/editorial-pages/editorial-page-intent-controller-utils";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { validateAdamId, validateNeedsVisionRestriction } from "../../foundation/media/util";
+export const EditorialPageIntentController = {
+ $intentKind: "EditorialPageIntent",
+ routes(objectGraph) {
+ return editorialPageRoutes(objectGraph);
+ },
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a;
+ if ("id" in intent) {
+ // See: https://github.pie.apple.com/its/Jingle/blob/ce14e21b6ac3dd4be884aa12b26d4e79c6d8aa7a/MZStorePlatform/src/main/java/com/apple/jingle/store/mediaapi/resource/SFMediaAPICommonResourceType.java#L91
+ validateAdamId(objectGraph, intent.id);
+ }
+ validateNeedsVisionRestriction(objectGraph);
+ const mediaApiRequest = makeEditorialPageRequest(objectGraph, intent);
+ const response = await mediaNetwork.fetchData(objectGraph, mediaApiRequest);
+ const editorialPage = await renderEditorialPage(objectGraph, response, {
+ isArcadePage: false,
+ });
+ if (isNothing(editorialPage)) {
+ const notFoundError = new mediaNetwork.NetworkError("Media API response lacked required data");
+ notFoundError.statusCode = 404;
+ throw notFoundError;
+ }
+ // The Media API often does not provide a URL; if that is the case, synthesize one
+ if (isNothing(editorialPage.canonicalURL) && isEditorialPageIntentByID(intent)) {
+ editorialPage.canonicalURL = makeEditorialPageURL(objectGraph, intent);
+ }
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, editorialPage, intent.platform);
+ injectSEOData(editorialPage, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForEditorialPage(objectGraph, editorialPage, response));
+ }
+ return editorialPage;
+ });
+ },
+ actionFor(intent, objectGraph) {
+ const action = new FlowAction("page");
+ action.destination = intent;
+ if (isEditorialPageIntentByID(intent)) {
+ action.pageUrl = makeEditorialPageURL(objectGraph, intent);
+ }
+ return action;
+ },
+};
+//# sourceMappingURL=editorial-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/editorial-pages/editorial-shelf-collection-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/editorial-pages/editorial-shelf-collection-page-intent-controller.js
new file mode 100644
index 0000000..a8fdf9c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/editorial-pages/editorial-shelf-collection-page-intent-controller.js
@@ -0,0 +1,35 @@
+import { isNothing } from "@jet/environment/types/optional";
+import * as mediaNetwork from "../../foundation/media/network";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { editorialShelfCollectionPageRoutes, makeEditorialShelfCollectionPageURL, makeEditorialShelfCollectionPageRequest, renderEditorialShelfCollectionPage, } from "../../common/editorial-pages/editorial-shelf-collection-page-utils";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { validateEditorialShelfCollectionId } from "../../foundation/media/util";
+export const EditorialShelfCollectionPageIntentController = {
+ $intentKind: "EditorialShelfCollectionPageIntent",
+ routes: editorialShelfCollectionPageRoutes,
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a;
+ // See: https://github.pie.apple.com/its/amp-enums/blob/f75500b44f871f35ba3ce459a5ff4c9f225e71b0/src/main/java/com/apple/jingle/store/IdSpace.java#L43
+ validateEditorialShelfCollectionId(objectGraph, intent.id);
+ const mediaApiReqest = makeEditorialShelfCollectionPageRequest(objectGraph, intent);
+ const response = await mediaNetwork.fetchData(objectGraph, mediaApiReqest);
+ const editorialShelfCollectionPage = renderEditorialShelfCollectionPage(objectGraph, response);
+ if (isNothing(editorialShelfCollectionPage)) {
+ const notFoundError = new mediaNetwork.NetworkError("Media API response lacked required data");
+ notFoundError.statusCode = 404;
+ throw notFoundError;
+ }
+ if (isNothing(editorialShelfCollectionPage.canonicalURL)) {
+ editorialShelfCollectionPage.canonicalURL = makeEditorialShelfCollectionPageURL(objectGraph, intent);
+ }
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, editorialShelfCollectionPage, intent.platform);
+ injectSEOData(editorialShelfCollectionPage, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForEditorialShelfCollectionPage(objectGraph, editorialShelfCollectionPage, response));
+ }
+ return editorialShelfCollectionPage;
+ });
+ },
+};
+//# sourceMappingURL=editorial-shelf-collection-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/grouping/grouping-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/grouping/grouping-page-intent-controller.js
new file mode 100644
index 0000000..a12827a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/grouping/grouping-page-intent-controller.js
@@ -0,0 +1,58 @@
+import { isNothing } from "@jet/environment/types/optional";
+import { fetchData } from "../../foundation/media/network";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { makeBaseGroupingPageRequest, prepareGroupingPageRequest } from "../../common/grouping/grouping-request";
+import { flattenedGroupingFromDataContainer, groupingPageFromFlattenedGrouping, groupingParseContextFromDataContainer, } from "../../common/grouping/render-grouping-page";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { setPreviewPlatform } from "../../common/preview-platform";
+import { groupingPageRoutes as routes, makeGroupingPageCanonicalURL } from "../../common/grouping/grouping-page-url";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { validateGroupingId } from "../../foundation/media/util";
+function makeMediaApiGroupingPageRequest(objectGraph, intent) {
+ const mediaApiRequest = makeBaseGroupingPageRequest(objectGraph);
+ if ("name" in intent) {
+ mediaApiRequest.addingQuery("name", intent.name);
+ }
+ else {
+ mediaApiRequest.withIdOfType(intent.id, "groupings");
+ }
+ if ("tabs" in intent) {
+ mediaApiRequest.addingQuery("tabs", intent.tabs);
+ }
+ prepareGroupingPageRequest(objectGraph, mediaApiRequest);
+ setPreviewPlatform(objectGraph, mediaApiRequest);
+ return mediaApiRequest;
+}
+export const GroupingPageIntentController = {
+ $intentKind: "GroupingPageIntent",
+ routes,
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a;
+ if ("id" in intent) {
+ // See: https://github.pie.apple.com/its/Jingle/blob/aaccec936f1feed227fd171ae66bb160cf38e497/MZStorePlatform/src/main/clojure/jingle/store/platform/api/realm/apps/apps_endpoints.clj#L1554
+ // See: https://github.pie.apple.com/its/Jingle/blob/aaccec936f1feed227fd171ae66bb160cf38e497/MZStorePlatform/src/main/clojure/jingle/store/platform/api/controller/editorial_resource.clj#L9
+ // See: https://github.pie.apple.com/its/Jingle/blob/aaccec936f1feed227fd171ae66bb160cf38e497/MZStorePlatform/src/main/java/com/apple/jingle/store/mediaapi/util/SFMediaAPIEditorialUtil.java#L585
+ // See: https://github.pie.apple.com/its/Jingle/blob/aaccec936f1feed227fd171ae66bb160cf38e497/MZStorePlatform/src/main/java/com/apple/jingle/store/mediaapi/util/SFMediaAPIEditorialUtil.java#L633-L639
+ validateGroupingId(objectGraph, intent.id);
+ }
+ const mediaApiRequest = makeMediaApiGroupingPageRequest(objectGraph, intent);
+ const response = await fetchData(objectGraph, mediaApiRequest);
+ const flattenedGrouping = flattenedGroupingFromDataContainer(objectGraph, response);
+ const groupingParseContext = groupingParseContextFromDataContainer(objectGraph, response);
+ if (isNothing(flattenedGrouping) || isNothing(groupingParseContext)) {
+ return null;
+ }
+ const page = groupingPageFromFlattenedGrouping(objectGraph, flattenedGrouping, groupingParseContext);
+ if ("id" in intent) {
+ page.canonicalURL = makeGroupingPageCanonicalURL(objectGraph, intent);
+ }
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, page, intent.platform);
+ injectSEOData(page, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForGroupingPage(objectGraph, page, response));
+ }
+ return page;
+ });
+ },
+};
+//# sourceMappingURL=grouping-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/bundle-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/bundle-page-intent-controller.js
new file mode 100644
index 0000000..382da66
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/bundle-page-intent-controller.js
@@ -0,0 +1,61 @@
+import { makeBundlePageIntent as baseMakeBundlePageIntent, } from "../../api/intents/bundle-page-intent";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { generateRoutes } from "../../common/util/generate-routes";
+import { makeBundlePageRequest } from "../../common/product-page/bundle-page-common";
+import { createProductPageFromResponse } from "../../common/product-page/shelf-based/shelf-based-product-page";
+import { inferPreviewPlatformFromDeviceFamilies } from "../../common/preview-platform";
+import * as mediaNetwork from "../../foundation/media/network";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { URL } from "../../foundation/network/urls";
+import { validateAdamId } from "../../foundation/media/util";
+function makeBundlePageIntent(opts) {
+ const { ...clone } = opts;
+ // The `{bundleName}` dynamic segment is part of the URL for SEO purposes, but is not actually part
+ // of the `Intent` since it is not necessary for data-fetching. This data must not end up as
+ // part of the `Intent` as the value cannot be consistently normalized, which can break important
+ // caching behavior within the `web` client
+ delete clone["bundleName"];
+ return baseMakeBundlePageIntent(clone);
+}
+// @ts-expect-error `generateRoutes` does not handle a typed `Platform` value correctly
+const { routes } = generateRoutes(makeBundlePageIntent, "/app-bundle/{bundleName}/{id}", [], {
+ optionalQuery: ["platform", "lic"],
+});
+export const BundlePageIntentController = {
+ $intentKind: "BundlePageIntent",
+ routes,
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a, _b;
+ // See: https://github.pie.apple.com/its/Jingle/blob/ce14e21b6ac3dd4be884aa12b26d4e79c6d8aa7a/MZStorePlatform/src/main/java/com/apple/jingle/store/mediaapi/resource/SFMediaAPICommonResourceType.java#L47
+ validateAdamId(objectGraph, intent.id);
+ const mediaApiRequest = makeBundlePageRequest(objectGraph, intent);
+ const response = await mediaNetwork.fetchData(objectGraph, mediaApiRequest);
+ const previewPlatform = (_a = intent.platform) !== null && _a !== void 0 ? _a : inferPreviewPlatformFromDeviceFamilies(objectGraph, response);
+ if (!objectGraph.activeIntent.previewPlatform) {
+ objectGraph.activeIntent.setInferredPreviewPlatform(previewPlatform);
+ }
+ const page = await createProductPageFromResponse(objectGraph, response);
+ // The page URL (`.canonicalURL` property on the view-model) from Media API does not account for the `platform`
+ // query param; we need to add that in ourselves. We can't rely on `makeCanonicalUrl` as that does not handle the
+ // `bundleName` segment; we need to take the Media API URL and append the `platform` ourselves
+ if (page.canonicalURL) {
+ const pageURL = new URL(page.canonicalURL);
+ if (intent.platform) {
+ pageURL.param("platform", intent.platform);
+ }
+ if (intent.lic) {
+ pageURL.param("lic", intent.lic);
+ }
+ page.canonicalURL = pageURL.toString();
+ }
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, page, previewPlatform);
+ injectSEOData(page, (_b = objectGraph.seo) === null || _b === void 0 ? void 0 : _b.getSEODataForBundlePage(objectGraph, page, response));
+ }
+ return page;
+ });
+ },
+};
+//# sourceMappingURL=bundle-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/eula-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/eula-page-intent-controller.js
new file mode 100644
index 0000000..af1d155
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/eula-page-intent-controller.js
@@ -0,0 +1,38 @@
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { isNothing } from "@jet/environment";
+import * as mediaNetwork from "../../foundation/media/network";
+import * as mediaDataFetching from "../../foundation/media/data-fetching";
+import * as models from "../../api/models";
+import * as serverData from "../../foundation/json-parsing/server-data";
+export const EulaPageIntentController = {
+ $intentKind: "EulaPageIntent",
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ const { resourceId, resourceType } = intent;
+ if (isNothing(resourceId) || isNothing(resourceType)) {
+ const notFoundError = new mediaNetwork.NetworkError("content not found");
+ notFoundError.statusCode = 404;
+ throw notFoundError;
+ }
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph).withIdOfType(resourceId, "eula");
+ mediaApiRequest.targetResourceType = resourceType;
+ const response = await mediaNetwork.fetchData(objectGraph, mediaApiRequest);
+ const fullText = serverData.asString(response, "results.eula.text");
+ const textParagraphs = fullText.split(/\n{1,2}/); // Split by one or two newlines. 3+ are respected.
+ const paragraphs = [];
+ for (const text of textParagraphs) {
+ const paragraph = new models.Paragraph(text);
+ paragraph.wantsCollapsedNewlines = false;
+ paragraph.suppressVerticalMargins = true;
+ paragraphs.push(paragraph);
+ }
+ const shelf = new models.Shelf("paragraph");
+ shelf.isHorizontal = false;
+ shelf.items = paragraphs;
+ const page = new models.GenericPage([shelf]);
+ page.title = objectGraph.loc.string("LICENSE_AGREEMENT");
+ return page;
+ });
+ },
+};
+//# sourceMappingURL=eula-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/product-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/product-page-intent-controller.js
new file mode 100644
index 0000000..c13c360
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/product-page-intent-controller.js
@@ -0,0 +1,63 @@
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { FlowAction } from "../../api/models";
+import { productPageRoutes, makeProductPageURLWithoutAppSlug, } from "../../common/product-page/intent-controller-routing";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { lookupURLForProductId as makeProductPageRequest } from "../../common/builders/url-mapping";
+import { createProductPageFromResponse } from "../../common/product-page/product-page-util";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import * as mediaNetwork from "../../foundation/media/network";
+import { URL } from "../../foundation/network/urls";
+import { inferPreviewPlatformFromDeviceFamilies } from "../../common/preview-platform";
+import { validateAdamId } from "../../foundation/media/util";
+export const ProductPageIntentController = {
+ $intentKind: "ProductPageIntent",
+ routes: productPageRoutes,
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a, _b;
+ // See: https://github.pie.apple.com/its/Jingle/blob/ce14e21b6ac3dd4be884aa12b26d4e79c6d8aa7a/MZStorePlatform/src/main/java/com/apple/jingle/store/mediaapi/resource/SFMediaAPICommonResourceType.java#L46
+ validateAdamId(objectGraph, intent.id);
+ const mediaApiRequest = makeProductPageRequest(objectGraph, intent.id, false, intent.ppid, false, false);
+ const response = await mediaNetwork.fetchData(objectGraph, mediaApiRequest);
+ // If the `Intent` does not specify a platform that the user requested, we need to select one of the options
+ // available in the Media API response to use instead. This will ensure we use the correct platform attributes
+ // when rendering the view-model
+ const inferredPlatform = inferPreviewPlatformFromDeviceFamilies(objectGraph, response);
+ if (!objectGraph.activeIntent.previewPlatform) {
+ const platformToSet = (_a = intent.platform) !== null && _a !== void 0 ? _a : inferredPlatform;
+ objectGraph.activeIntent.setInferredPreviewPlatform(platformToSet);
+ }
+ const previewPlatform = objectGraph.activeIntent.previewPlatform;
+ const productPage = (await createProductPageFromResponse(objectGraph, response));
+ // The page URL (`.canonicalURL` property on the view-model) from Media API does not account for
+ // the `platform` and `ppid` query param; we need to add that in ourselves.
+ // We can't rely on `makeCanonicalUrl` as that does not handle the `appName` segment;
+ // We need to take the Media API URL and append the `platform` and `ppid` ourselves
+ if (productPage.canonicalURL) {
+ const pageURL = new URL(productPage.canonicalURL);
+ if (typeof intent.platform === "string" && intent.platform !== inferredPlatform) {
+ pageURL.param("platform", intent.platform);
+ }
+ if (typeof intent.ppid === "string" && intent.ppid) {
+ pageURL.param("ppid", intent.ppid);
+ }
+ if (typeof intent.lic === "string" && intent.lic) {
+ pageURL.param("lic", intent.lic);
+ }
+ productPage.canonicalURL = pageURL.toString();
+ }
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, productPage, previewPlatform);
+ injectSEOData(productPage, (_b = objectGraph.seo) === null || _b === void 0 ? void 0 : _b.getSEODataForProductPage(objectGraph, productPage, response));
+ }
+ return productPage;
+ });
+ },
+ actionFor(intent, objectGraph) {
+ const action = new FlowAction("product");
+ action.destination = intent;
+ action.pageUrl = makeProductPageURLWithoutAppSlug(objectGraph, intent);
+ return action;
+ },
+};
+//# sourceMappingURL=product-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/see-all-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/see-all-intent-controller.js
new file mode 100644
index 0000000..618bf5d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/product-page/see-all-intent-controller.js
@@ -0,0 +1,39 @@
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import * as mediaNetwork from "../../foundation/media/network";
+import { validateAdamId } from "../../foundation/media/util";
+import { mediaApiProductSeeAllRequest } from "../../common/builders/url-mapping";
+import { createProductPageFromResponse } from "../../common/product-page/product-page-util";
+import { seeAllPageRoutes } from "../../common/product-page/intent-controller-routing";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+export const SeeAllPageIntentController = {
+ $intentKind: "SeeAllPageIntent",
+ routes: seeAllPageRoutes,
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a, _b;
+ validateAdamId(objectGraph, intent.id);
+ const seeAllRequest = mediaApiProductSeeAllRequest(objectGraph, intent.id, intent["see-all"]);
+ const seeAllResponse = await mediaNetwork.fetchData(objectGraph, seeAllRequest);
+ const page = (await createProductPageFromResponse(objectGraph, seeAllResponse));
+ if (objectGraph.client.isWeb) {
+ // Synthesize the URL for the page based on the product page URL and the expected query param
+ if (page.canonicalURL) {
+ const pageURL = new URL(page.canonicalURL);
+ const params = pageURL.searchParams;
+ params.set("see-all", intent["see-all"]);
+ params.set("platform", intent.platform);
+ if (intent.platform) {
+ params.set("platform", intent.platform);
+ }
+ page.canonicalURL = pageURL.toString();
+ }
+ page.seeAllType = intent["see-all"];
+ injectWebNavigation(objectGraph, page, intent.platform);
+ injectSEOData(page, (_b = (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForSeeAllPage) === null || _b === void 0 ? void 0 : _b.call(_a, objectGraph, page, seeAllResponse));
+ }
+ return page;
+ });
+ },
+};
+//# sourceMappingURL=see-all-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/room/room-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/room/room-page-intent-controller.js
new file mode 100644
index 0000000..a414856
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/room/room-page-intent-controller.js
@@ -0,0 +1,29 @@
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { fetchData } from "../../foundation/media/network";
+import { roomPageRoutes as routes, makeCanonicalRoomPageUrl } from "../../common/room/room-common";
+import { createRoomRequest } from "../../common/room/room-request";
+import { renderRoomPage } from "../../common/room/room-page";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { validateFcId } from "../../foundation/media/util";
+export const RoomPageIntentController = {
+ $intentKind: "RoomPageIntent",
+ routes,
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a;
+ // See: https://github.pie.apple.com/its/Jingle/blob/d2d051c9ef2891f72d5f02f5bbbf2d7748afa7b9/MZStoreComponents/src/main/java/com/apple/jingle/app/store/editorial/SFEditorialHelper.java#L293
+ validateFcId(objectGraph, intent.id);
+ const mediaApiRequest = createRoomRequest(objectGraph, intent.id);
+ const response = await fetchData(objectGraph, mediaApiRequest);
+ const page = renderRoomPage(objectGraph, response);
+ page.canonicalURL = makeCanonicalRoomPageUrl(objectGraph, intent);
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, page, intent.platform);
+ injectSEOData(page, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForRoomPage(objectGraph, page, response));
+ }
+ return page;
+ });
+ },
+};
+//# sourceMappingURL=room-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/search/search-landing-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/search/search-landing-page-intent-controller.js
new file mode 100644
index 0000000..aafca37
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/search/search-landing-page-intent-controller.js
@@ -0,0 +1,34 @@
+import { isNothing } from "@jet/environment";
+import { makeSearchLandingPageIntent, } from "../../api/intents/search/search-landing-page-intent";
+import { generateRoutes } from "../../common/util/generate-routes";
+import { fetchPage } from "../../common/search/search-landing-page-utils";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { validateNeedsVisionRestriction } from "../../foundation/media/util";
+const { routes, makeCanonicalUrl } = generateRoutes(makeSearchLandingPageIntent, "/{platform}/search");
+export const SearchLandingPageIntentController = {
+ $intentKind: "SearchLandingPageIntent",
+ routes,
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ if (objectGraphWithoutActiveIntent.client.isWeb && !intent.platform) {
+ // Search on the "web" client requires a platform; if we don't have one from the `Intent`, we should
+ // assume we want `iphone` results
+ intent.platform = "iphone";
+ }
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a;
+ validateNeedsVisionRestriction(objectGraph);
+ const searchLandingPage = await fetchPage(objectGraph);
+ if (isNothing(searchLandingPage.canonicalURL)) {
+ searchLandingPage.canonicalURL = makeCanonicalUrl(objectGraph, intent);
+ }
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, searchLandingPage, intent.platform);
+ injectSEOData(searchLandingPage, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForSearchLandingPage(objectGraph, searchLandingPage, null));
+ }
+ return searchLandingPage;
+ });
+ },
+};
+//# sourceMappingURL=search-landing-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/search/search-results-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/search/search-results-controller.js
new file mode 100644
index 0000000..d263c24
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/search/search-results-controller.js
@@ -0,0 +1,152 @@
+import { isNothing } from "@jet/environment";
+import * as models from "../../api/models";
+import { FetchTimingMetricsBuilder } from "@jet/environment/metrics/fetch-timing-metrics-builder";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { AbstractPageBuilder } from "../../common/builders/abstract-page-builder";
+import * as search from "../../common/search/search";
+import { makeCanonicalSearchResultsPageUrl, searchResultsPageRoutes } from "../../common/search/search-page-url";
+import * as searchResultsFetching from "../../common/search/search-results-fetching";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { replacePlatformSelectorDestinationWithSearchResults } from "../../common/web-navigation/search-results-platform-selection";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { validateNeedsVisionRestriction } from "../../foundation/media/util";
+/** Handles `SearchResultsIntent` to fetch the search results. This is for shelves v1. */
+export class SearchResultsIntentController {
+ constructor() {
+ this.$intentKind = "SearchResultsIntent";
+ }
+ async perform(intent, objectGraph) {
+ return await fetchSearchResults(objectGraph, intent.requestDescriptor);
+ }
+}
+/** Handles `SearchResultsMoreIntent` to fetch more of the search results. This is for shelves v1. */
+export class SearchResultsMoreIntentController {
+ constructor() {
+ this.$intentKind = "SearchResultsMoreIntent";
+ }
+ async perform(intent, objectGraph) {
+ return await fetchMoreSearchResults(objectGraph, intent.pageToken);
+ }
+}
+/** Handles `SegmentedSearchResultsPageIntent` to fetch the segmented search results. */
+export class SegmentedSearchResultsPageIntentController {
+ constructor() {
+ this.$intentKind = "SegmentedSearchResultsPageIntent";
+ }
+ async perform(intent, objectGraph) {
+ return await fetchSegmentedSearchResults(objectGraph, intent.requestDescriptor);
+ }
+}
+/** Handles `SearchResultsPageIntent` to fetch the search results. This is for shelves 2.0. */
+export class SearchResultsPageIntentController {
+ constructor() {
+ this.$intentKind = "SearchResultsPageIntent";
+ this.routes = searchResultsPageRoutes;
+ }
+ async perform(intent, objectGraphWithoutActiveIntnet) {
+ if (objectGraphWithoutActiveIntnet.client.isWeb && !intent.platform) {
+ // Search on the "web" client requires a platform; if we don't have one from the `Intent`, we should
+ // assume we want `iphone` results
+ intent.platform = "iphone";
+ }
+ return await withActiveIntent(objectGraphWithoutActiveIntnet, intent, async (objectGraph) => {
+ var _a;
+ validateNeedsVisionRestriction(objectGraph);
+ const searchResultsPage = await fetchSearchResultsPage(objectGraph, intent);
+ if (isNothing(searchResultsPage.canonicalURL)) {
+ searchResultsPage.canonicalURL = makeCanonicalSearchResultsPageUrl(objectGraph, intent);
+ }
+ if (objectGraph.client.isWeb) {
+ const webNavigation = injectWebNavigation(objectGraph, searchResultsPage, intent.platform);
+ webNavigation.searchAction.destination.term = intent.term;
+ replacePlatformSelectorDestinationWithSearchResults(objectGraph, webNavigation, intent);
+ injectSEOData(searchResultsPage, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForSearchResultsPage(objectGraph, searchResultsPage, null));
+ }
+ return searchResultsPage;
+ });
+ }
+ actionFor(intent, objectGraph) {
+ const flowAction = new models.FlowAction("search");
+ flowAction.destination = intent;
+ flowAction.pageUrl = makeCanonicalSearchResultsPageUrl(objectGraph, intent);
+ return flowAction;
+ }
+}
+/** Handles `SearchResultsPageMoreIntent` to fetch more of the search results. This is for shelves 2.0. */
+export class SearchResultsPageMoreIntentController {
+ constructor() {
+ this.$intentKind = "SearchResultsPageMoreIntent";
+ }
+ async perform(intent, objectGraph) {
+ return await fetchMoreSearchResultsPage(objectGraph, intent.pageToken);
+ }
+}
+/** Fetches search results for shelves v1. */
+async function fetchSearchResults(objectGraph, requestDescriptor) {
+ const fetchTimingMetricsBuilder = new FetchTimingMetricsBuilder();
+ const modifiedObjectGraph = objectGraph.addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder);
+ const combinedSearchData = await searchResultsFetching.fetchSequentialSearchResultsData(modifiedObjectGraph, requestDescriptor);
+ if (combinedSearchData === null) {
+ return search.emptyResults(modifiedObjectGraph, requestDescriptor.facets);
+ }
+ return await search.searchResultsFromResponse(modifiedObjectGraph, combinedSearchData);
+}
+/** Fetches search results for segmented search results on visionOS */
+async function fetchSegmentedSearchResults(objectGraph, requestDescriptor) {
+ const combinedSearchData = await searchResultsFetching.fetchSegmentedSearchResults(objectGraph, requestDescriptor);
+ if (combinedSearchData === null) {
+ return search.emptySegmentedResultsPage(objectGraph);
+ }
+ return await search.segmentedSearchResultsPageFromResponse(objectGraph, combinedSearchData);
+}
+/** Fetches more search results for shelves v1. */
+async function fetchMoreSearchResults(objectGraph, token) {
+ return await search.paginatedSearchResultsWithToken(objectGraph, token);
+}
+/** Fetches search results for shelves 2.0. */
+async function fetchSearchResultsPage(objectGraph, requestDescriptor) {
+ const fetchTimingMetricsBuilder = new FetchTimingMetricsBuilder();
+ const modifiedObjectGraph = objectGraph.addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder);
+ const combinedSearchData = await searchResultsFetching.fetchSequentialSearchResultsData(modifiedObjectGraph, requestDescriptor);
+ if (combinedSearchData === null) {
+ return search.emptyResultsPage(objectGraph, requestDescriptor.facets);
+ }
+ return await search.searchResultsPageFromResponse(modifiedObjectGraph, combinedSearchData);
+}
+/** Fetches more search results for shelves 2.0. */
+async function fetchMoreSearchResultsPage(objectGraph, token) {
+ return await search.paginatedSearchResultsPageWithToken(objectGraph, token);
+}
+export class SearchResultsBuilder extends AbstractPageBuilder {
+ constructor(objectGraph, runtime) {
+ super("SearchResultsBuilder");
+ runtime.exportingService("SearchData", {
+ /** Fetches search results for shelves v1. */
+ async fetchResults(options) {
+ return await fetchSearchResults(objectGraph, options);
+ },
+ /** Fetches more search results for shelves v1. */
+ async fetchMoreResults(options) {
+ return await fetchMoreSearchResults(objectGraph, options.pageToken);
+ },
+ /** Fetches search results for shelves 2.0. */
+ async fetchSearchResultsPage(options) {
+ return await fetchSearchResultsPage(objectGraph, options);
+ },
+ /** Fetches more search results for shelves 2.0. */
+ async fetchMoreOfSearchResultsPage(options) {
+ return await fetchMoreSearchResultsPage(objectGraph, options.pageToken);
+ },
+ });
+ }
+ async handlePage(objectGraph, url, parameters, matchedRuleIdentifier, referrerData, isIncomingURL) {
+ throw new Error("No page routes specified");
+ }
+ pageRoute(objectGraph) {
+ return [];
+ }
+ pageType() {
+ return "unknown";
+ }
+}
+//# sourceMappingURL=search-results-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/today/routable-article-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/today/routable-article-page-intent-controller.js
new file mode 100644
index 0000000..9ab32b1
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/today/routable-article-page-intent-controller.js
@@ -0,0 +1,53 @@
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { buildArticlePageRequest } from "../../common/today/article-request";
+import { fetchData } from "../../foundation/media/network";
+import { articlePageFromResponse, ArticleParseContext } from "../../common/today/article";
+import { routableArticlePageWithPlatformRoutes, routableArticlePageWithoutPlatformRoutes, makeRoutableArticlePageCanonicalUrl, } from "../../common/today/routable-article-page-url-utils";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { inferPreviewPlatformFromEditorialPlatforms } from "../../common/preview-platform";
+import { validateAdamId } from "../../foundation/media/util";
+export const RoutableArticlePageIntentController = {
+ $intentKind: "RoutableArticlePageIntent",
+ routes(objectGraph) {
+ return [
+ // Note: order here is important! Both URL patterns provide an option where there is a
+ // single dynamic segment in front of the stable `/story` segment; it could be either
+ // the platform without an explicit storefront, or a storefront without an explicit platform.
+ // We want to assume the latter, so the URLs without a platform must be defined first
+ ...routableArticlePageWithoutPlatformRoutes(objectGraph),
+ ...routableArticlePageWithPlatformRoutes(objectGraph),
+ ];
+ },
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a, _b, _c;
+ // See: https://github.pie.apple.com/its/Jingle/blob/ce14e21b6ac3dd4be884aa12b26d4e79c6d8aa7a/MZStorePlatform/src/main/java/com/apple/jingle/store/mediaapi/resource/SFMediaAPICommonResourceType.java#L89
+ validateAdamId(objectGraph, intent.id);
+ const mediaApiRequest = buildArticlePageRequest(objectGraph, intent, false);
+ const response = await fetchData(objectGraph, mediaApiRequest);
+ // If the user visits a "story" without an explicit "platform" specified, we need to infer a
+ // desirable default so that we can look up the correct platform attributes when rendering
+ // the view-model
+ const previewPlatform = (_a = intent.platform) !== null && _a !== void 0 ? _a : inferPreviewPlatformFromEditorialPlatforms(objectGraph, response);
+ if (!objectGraph.activeIntent.previewPlatform) {
+ objectGraph.activeIntent.setInferredPreviewPlatform(previewPlatform);
+ }
+ const articleParseContext = new ArticleParseContext();
+ const page = articlePageFromResponse(objectGraph, response, articleParseContext);
+ // The Media API-provided `.canonicalURL` will not contain the explicit `{platform}`
+ // segment, if required; we need to override their value to make use of the URL
+ // factory provided by our routing utils instead
+ page.canonicalURL = makeRoutableArticlePageCanonicalUrl(objectGraph, intent);
+ if (objectGraph.client.isWeb) {
+ const webNavigation = injectWebNavigation(objectGraph, page, previewPlatform);
+ if ((_b = page.card) === null || _b === void 0 ? void 0 : _b.title) {
+ webNavigation.title = page.card.title;
+ }
+ injectSEOData(page, (_c = objectGraph.seo) === null || _c === void 0 ? void 0 : _c.getSEODataForArticlePage(objectGraph, page, response));
+ }
+ return page;
+ });
+ },
+};
+//# sourceMappingURL=routable-article-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/today/routable-today-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/today/routable-today-page-intent-controller.js
new file mode 100644
index 0000000..d12e4ef
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/today/routable-today-page-intent-controller.js
@@ -0,0 +1,21 @@
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { buildTodayPageFromRequest, generateTodayPageRequest, prepareRequest as prepareTodayPageRequest, } from "../../common/today/today-controller-util";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+export const RoutableTodayPageIntentController = {
+ $intentKind: "RoutableTodayPageIntent",
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a;
+ const mediaApiRequest = generateTodayPageRequest(objectGraph);
+ prepareTodayPageRequest(objectGraph, mediaApiRequest, true);
+ const page = await buildTodayPageFromRequest(objectGraph, mediaApiRequest);
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, page, intent.platform);
+ injectSEOData(page, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForTodayPage(objectGraph, page, null));
+ }
+ return page;
+ });
+ },
+};
+//# sourceMappingURL=routable-today-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/top-charts/charts-hub-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/top-charts/charts-hub-page-intent-controller.js
new file mode 100644
index 0000000..0f1a16e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/top-charts/charts-hub-page-intent-controller.js
@@ -0,0 +1,39 @@
+import { makeChartsPageIntent } from "../../api/intents/charts-page-intent";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { renderChartsHub } from "../../common/charts/charts-hub";
+import { chartsHubPageRoutes, makeChartsHubPageURL } from "../../common/charts/charts-page-url";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { injectSEOData } from "../../api/models/web-renderable-page";
+const APPS_GENRE_ID_STATIC = "36";
+const GAMES_GENRE_ID_STATIC = "6014";
+export const ChartsHubPageIntentController = {
+ $intentKind: "ChartsHubPageIntent",
+ routes: chartsHubPageRoutes,
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a;
+ const [appsPage, gamesPage] = await Promise.all([
+ objectGraph.dispatcher.dispatch(makeChartsPageIntent({
+ storefront: intent.storefront,
+ language: intent.language,
+ platform: intent.platform,
+ genreId: APPS_GENRE_ID_STATIC,
+ }), objectGraph),
+ objectGraph.dispatcher.dispatch(makeChartsPageIntent({
+ storefront: intent.storefront,
+ language: intent.language,
+ platform: intent.platform,
+ genreId: GAMES_GENRE_ID_STATIC,
+ }), objectGraph),
+ ]);
+ const hubPage = renderChartsHub(objectGraph, appsPage, gamesPage);
+ hubPage.canonicalURL = makeChartsHubPageURL(objectGraph, intent);
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, hubPage, intent.platform);
+ injectSEOData(hubPage, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForChartsHubPage(objectGraph, hubPage, null));
+ }
+ return hubPage;
+ });
+ },
+};
+//# sourceMappingURL=charts-hub-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/top-charts/charts-page-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/top-charts/charts-page-intent-controller.js
new file mode 100644
index 0000000..79a0667
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/top-charts/charts-page-intent-controller.js
@@ -0,0 +1,25 @@
+import { injectSEOData } from "../../api/models/web-renderable-page";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { chartsPageRoutes, makeChartsPageURL } from "../../common/charts/charts-page-url";
+import { injectWebNavigation } from "../../common/web-navigation/inject-web-navigation";
+import { fetchAndRenderTopChartPage } from "../../common/top-charts/top-charts-page";
+import { validateGenreId } from "../../foundation/media/util";
+export const ChartsPageIntentController = {
+ $intentKind: "ChartsPageIntent",
+ routes: chartsPageRoutes,
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ var _a;
+ // See: https://github.pie.apple.com/its/Jingle/blob/aaccec936f1feed227fd171ae66bb160cf38e497/MZStorePlatform/src/main/clojure/jingle/store/platform/api/controller/categories.clj#L135
+ validateGenreId(objectGraph, intent.genreId);
+ const page = await fetchAndRenderTopChartPage(objectGraph, intent.genreId, null, intent.chart, intent.ageBandId);
+ if (objectGraph.client.isWeb) {
+ injectWebNavigation(objectGraph, page, intent.platform);
+ injectSEOData(page, (_a = objectGraph.seo) === null || _a === void 0 ? void 0 : _a.getSEODataForChartsPage(objectGraph, page, null));
+ }
+ page.canonicalURL = makeChartsPageURL(objectGraph, intent);
+ return page;
+ });
+ },
+};
+//# sourceMappingURL=charts-page-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/controllers/web-navigation/category-tabs-intent-controller.js b/node_modules/@jet-app/app-store/tmp/src/controllers/web-navigation/category-tabs-intent-controller.js
new file mode 100644
index 0000000..02487d2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/controllers/web-navigation/category-tabs-intent-controller.js
@@ -0,0 +1,62 @@
+import { isSome } from "@jet/environment/types/optional";
+import { dataCollectionFromDataContainer } from "../../foundation/media/data-structure";
+import { withActiveIntent } from "../../foundation/dependencies/active-intent";
+import { asDictionary, isDefinedNonNullNonEmpty } from "../../foundation/json-parsing/server-data";
+import { attributeAsDictionary } from "../../foundation/media/attributes";
+import { Request } from "../../foundation/media/data-fetching";
+import { fetchData } from "../../foundation/media/network";
+import { relationshipData } from "../../foundation/media/relationships";
+import { artworkFromApiArtwork } from "../../common/content/content";
+import { actionFromData } from "../../common/lockups/lockups";
+import { newLocationTracker } from "../../common/metrics/helpers/location";
+export class CategoryTabsIntentController {
+ constructor() {
+ this.$intentKind = "CategoryTabsIntent";
+ }
+ async perform(intent, objectGraphWithoutActiveIntent) {
+ return await withActiveIntent(objectGraphWithoutActiveIntent, intent, async (objectGraph) => {
+ const editorialItemId = objectGraph.bag.webNavigationCategoryTabsEditorialItemId;
+ if (!isSome(editorialItemId)) {
+ return [];
+ }
+ const attributes = ["editorialArtwork"];
+ const request = new Request(objectGraph)
+ .withIdOfType(editorialItemId, "collections")
+ .includingScopedAttributes("editorial-items", attributes)
+ .includingScopedAttributes("editorial-pages", attributes)
+ .addingQuery("sparseLimit[card-contents]", "0")
+ .addingQuery("platform", "web")
+ .addingQuery("previewPlatform", objectGraph.activeIntent.platform);
+ const response = await fetchData(objectGraph, request);
+ const contents = dataCollectionFromDataContainer(response);
+ return contents.map((item) => {
+ const primaryContent = relationshipData(objectGraph, item, "primary-content");
+ const dataSource = primaryContent || item;
+ const metricsBase = {
+ pageInformation: null,
+ locationTracker: newLocationTracker(),
+ id: item.id,
+ };
+ const formattedArtwork = getFormattedArtwork(objectGraph, item);
+ return {
+ action: actionFromData(objectGraph, dataSource, metricsBase, objectGraph.host.clientIdentifier),
+ isActive: intent.id === dataSource.id,
+ artwork: formattedArtwork,
+ };
+ });
+ });
+ }
+}
+function getFormattedArtwork(objectGraph, data) {
+ const artwork = attributeAsDictionary(data, "artwork", null) || attributeAsDictionary(data, "editorialArtwork");
+ if (!isDefinedNonNullNonEmpty(artwork)) {
+ return undefined;
+ }
+ const editorialArtwork = attributeAsDictionary(data, "editorialArtwork");
+ const icon = asDictionary(editorialArtwork, "brandLogo", null);
+ return artworkFromApiArtwork(objectGraph, icon, {
+ allowingTransparency: true,
+ useCase: 20 /* ArtworkUseCase.CategoryIcon */,
+ });
+}
+//# sourceMappingURL=category-tabs-intent-controller.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/amp-localization/amp-localization.js b/node_modules/@jet-app/app-store/tmp/src/foundation/amp-localization/amp-localization.js
new file mode 100644
index 0000000..df04983
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/amp-localization/amp-localization.js
@@ -0,0 +1,371 @@
+/**
+ * A type encapsulating localization logic for strings
+ * returned by localization server.
+ *
+ * Replacement for 'its' namespace from @onyx/localization package.
+ */
+export class AmpLocalization {
+ constructor() {
+ // region Properties
+ /**
+ * Localization JSON dictionary loaded from localization file.
+ */
+ this.locData = {};
+ /**
+ * The 2-letter code for current device language.
+ */
+ this.language = "en";
+ // endregion
+ }
+ // endregion
+ // region API
+ /**
+ * Updates the localization data for the device.
+ * @param localizations A JSON dictionary representing the localized strings.
+ * @param language Language code for current device language.
+ */
+ updateLocalizationData(localizations, language) {
+ this.locData = localizations;
+ this.language = language;
+ }
+ /**
+ * Localizes a string replacing placehoders in key with values in the parameters dictionary
+ * @param key The loc key to look up
+ * @param params Parameters to replace in the loc string
+ * @return The localized string
+ */
+ localize(key, params) {
+ let value = this.locData[key];
+ if (value === undefined || typeof value !== "string") {
+ value = key;
+ }
+ if (params) {
+ value = this.replaceTokens(value, params);
+ }
+ value = this.replaceMarkupTokens(value, params);
+ return value;
+ }
+ /**
+ * Localize with appropriate plural form based on the count.
+ *
+ * Some languages have more plural forms than others.
+ * The full set of categories is "zero", "one", "two", "few", "many", and "other".
+ *
+ * The base loc key is used for "other" (the default).
+ * Otherwise the category is appended to the base loc key with a "." separator.
+ *
+ * http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+ * http://cldr.unicode.org/index/cldr-spec/plural-rules#TOC-Determining-Plural-Categories
+ * http://unicode.org/repos/cldr/trunk/specs/ldml/tr35-numbers.html#Language_Plural_Rules
+ *
+ * English loc keys
+ * key = "@@count@@ dogs"; // default, aka "plural"
+ * key.zero = "no dogs"; // used when count is 0
+ * key.one = "@@count@@ dog"; // used when count is 1, aka "singular"
+ *
+ * @param key base loc key
+ * @param count number used to determine the plural form
+ * @param params (optional) substitution keys and values, "count" will be added if it's not already present
+ * @return localized string
+ */
+ localizeWithCount(objectGraph, key, count, params) {
+ let keyToUse = null;
+ let pluralFormKey;
+ let category;
+ if (count === 0) {
+ // Special case zero for all languages so we can have strings like
+ // "you have no messages" instead of "you have 0 messages".
+ pluralFormKey = `${key}.zero`;
+ if (this.isLocalized(objectGraph, pluralFormKey)) {
+ keyToUse = pluralFormKey;
+ }
+ }
+ if (keyToUse === null) {
+ keyToUse = key;
+ category = this.pluralCategory(objectGraph, count);
+ if (category !== "other") {
+ pluralFormKey = `${key}.${category}`;
+ if (this.isLocalized(objectGraph, pluralFormKey)) {
+ keyToUse = pluralFormKey;
+ }
+ }
+ }
+ if (!params) {
+ params = {};
+ }
+ // @@count@@ is the standard, but @@number@@ is frequently used too.
+ if (params.count === undefined) {
+ params.count = this.formatNumber(count.toString());
+ }
+ if (params.number === undefined) {
+ params.number = this.formatNumber(count.toString());
+ }
+ return this.localize(keyToUse !== null && keyToUse !== void 0 ? keyToUse : key, params);
+ }
+ // endregion
+ // region Private Methods
+ replaceTokens(text, values) {
+ Object.entries(values).forEach(([key, value]) => {
+ const subKey = "@@" + key + "@@";
+ text = this.replaceSubstring(text, subKey, value);
+ });
+ return text;
+ }
+ replaceMarkupTokens(text, values) {
+ if (text.indexOf("##") <= -1) {
+ return text;
+ }
+ // Resolve token properties.
+ let markupParams;
+ if (values) {
+ // Create a copy to avoid mutating defaults.
+ markupParams = { ...AmpLocalization.MARKUP_PARAMS };
+ Object.entries(values).forEach(([key, value]) => {
+ markupParams[key] = value;
+ });
+ }
+ else {
+ markupParams = AmpLocalization.MARKUP_PARAMS;
+ }
+ Object.entries(markupParams).forEach(([key, value]) => {
+ const token = "##" + key + "##";
+ text = text.replace(new RegExp(token, "gi"), value);
+ });
+ // Replace any remaining standard markup tags like <br> etc.
+ text = text.replace(/##([^##]+)##/gi, "<$1>");
+ return text;
+ }
+ /**
+ * Searches "str" for "substr" and replaces each occurrence with "replacement"
+ * @param str input string
+ * @param substr the string to search for in the input string
+ * @param replacement value to use for the replacement
+ *
+ * JavaScript String.replace has a misfeature where "$" in the replacement is always interpreted as a meta char,
+ * i.e. you have to use "$$" in the replacement to get a single "$" in the resulting string
+ * which lead to <rdar://problem/16848063> tv episode title containing "$&" not displaying correctly
+ * Using split and join is faster than substituting $$ for $ in the replacement.
+ */
+ replaceSubstring(str, substr, replacement) {
+ return str.split(substr).join(replacement);
+ }
+ formatNumber(value) {
+ let decimalSeparator = this.locData["_decimalSeparator"];
+ if (decimalSeparator === undefined || typeof decimalSeparator !== "string") {
+ decimalSeparator = ".";
+ }
+ let thousandsSeparator = this.locData["_thousandsSeparator"];
+ if (thousandsSeparator === undefined || typeof thousandsSeparator !== "string") {
+ thousandsSeparator = ".";
+ }
+ const parts = parseFloat(value).toString().split(".");
+ const chars = parts[0].split("");
+ for (let i = chars.length - 3; i > 0; i -= 3) {
+ chars.splice(i, 0, thousandsSeparator);
+ }
+ parts[0] = chars.join("");
+ return parts.join(decimalSeparator);
+ }
+ /**
+ * Check whether the given key is localized or not.
+ * @param key The key to check.
+ */
+ isLocalized(objectGraph, key) {
+ const value = this.locData[key];
+ if (value === undefined || typeof value !== "string") {
+ return false;
+ }
+ else if (key.indexOf(".") === -1) {
+ // Simple localization keys such as "OK".
+ return true;
+ }
+ else if (value === key || (value.indexOf("**") === 0 && value.lastIndexOf("**") === value.length - 2)) {
+ objectGraph.console.error("Unlocalized key in keys dictionary", key);
+ return false;
+ }
+ return true;
+ }
+ /**
+ * Returns localization plural category for given number.
+ * @param num The number to return plural category for.
+ * @returns Plural category for specified number or "other"
+ * if there's no plural category function available for current language.
+ *
+ * @see:
+ * - http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+ * - http://cldr.unicode.org/index/cldr-spec/plural-rules#TOC-Determining-Plural-Categories
+ * - http://unicode.org/repos/cldr/trunk/specs/ldml/tr35-numbers.html#Language_Plural_Rules
+ */
+ pluralCategory(objectGraph, num) {
+ const categoryFn = AmpLocalization.pluralCategoryFnByLanguage[this.language];
+ if (categoryFn !== undefined) {
+ return categoryFn(num);
+ }
+ else {
+ objectGraph.console.warn("Missing plural category function for: " + this.language);
+ return "other";
+ }
+ }
+}
+/**
+ * Markup parameters used for replacing markup tokens in text.
+ */
+AmpLocalization.MARKUP_PARAMS = { nbsp: "&nbsp;", gt: "&gt;", lt: "&lt;", copy: "\u00a9" };
+// endregion
+// regions Plurals
+AmpLocalization.pluralCategoryDefault = function (num) {
+ return "other";
+};
+AmpLocalization.pluralCategoryOne = function (num) {
+ if (num === 1) {
+ return "one";
+ }
+ return "other";
+};
+AmpLocalization.pluralCategoryArabic = function (num) {
+ const n = num >> 0;
+ if (n !== num) {
+ // non integer
+ return "other";
+ }
+ if (n === 0) {
+ return "zero";
+ }
+ if (n === 1) {
+ return "one";
+ }
+ if (n === 2) {
+ return "two";
+ }
+ const m100 = n % 100;
+ if (m100 >= 11) {
+ // n mod 100 in 11..99
+ return "many";
+ }
+ if (m100 >= 3) {
+ // n mod 100 in 3..10
+ return "few";
+ }
+ return "other";
+};
+AmpLocalization.pluralCategoryFrench = function (num) {
+ // Do not check for integer, French includes fractional values!
+ if (num < 2 && num >= 0) {
+ return "one";
+ }
+ return "other";
+};
+AmpLocalization.pluralCategoryHebrew = function (num) {
+ const n = num >> 0;
+ if (n !== num) {
+ // non integer
+ return "other";
+ }
+ if (n === 1) {
+ return "one";
+ }
+ if (n === 2) {
+ return "two";
+ }
+ const m10 = n % 10;
+ if (m10 === 0 && n > 10) {
+ // n mod 10 is 0 and n != 0..10
+ return "many";
+ }
+ return "other";
+};
+AmpLocalization.pluralCategoryPolish = function (num) {
+ const n = num >> 0;
+ if (n !== num) {
+ // non integer
+ return "other";
+ }
+ if (n === 1) {
+ return "one";
+ }
+ const m10 = n % 10;
+ if (m10 <= 4 && m10 >= 2) {
+ const m100 = n % 100;
+ if (m100 > 14 || m100 < 12) {
+ // n mod 10 in 2..4 and n mod 100 not in 12..14
+ return "few";
+ }
+ }
+ return "many";
+};
+AmpLocalization.pluralCategoryRomanian = function (num) {
+ const n = num >> 0;
+ if (n !== num) {
+ // non integer
+ return "few";
+ }
+ if (n === 0) {
+ return "few";
+ }
+ if (n === 1) {
+ return "one";
+ }
+ const m100 = num % 100;
+ if (m100 <= 19 && m100 >= 1) {
+ // n mod 100 in 1..19
+ return "few";
+ }
+ return "other";
+};
+AmpLocalization.pluralCategoryRussian = function (num) {
+ const n = num >> 0;
+ if (n !== num) {
+ // non integer
+ return "other";
+ }
+ const m10 = n % 10;
+ if (m10 >= 5 || m10 === 0) {
+ // n mod 10 is 0 or n mod 10 in 5..9
+ return "many";
+ }
+ const m100 = n % 100;
+ if (m100 <= 14 && m100 >= 11) {
+ // n mod 100 in 11..14
+ return "many";
+ }
+ if (m10 === 1) {
+ // n mod 10 is 1 and n mod 100 is not 11
+ return "one";
+ }
+ // n mod 10 in 2..4 and n mod 100 not in 12..14
+ return "few";
+};
+/**
+ * Mapping language code to plural category function.
+ */
+AmpLocalization.pluralCategoryFnByLanguage = {
+ zh: AmpLocalization.pluralCategoryDefault,
+ id: AmpLocalization.pluralCategoryDefault,
+ ja: AmpLocalization.pluralCategoryDefault,
+ ko: AmpLocalization.pluralCategoryDefault,
+ ms: AmpLocalization.pluralCategoryDefault,
+ th: AmpLocalization.pluralCategoryDefault,
+ vi: AmpLocalization.pluralCategoryDefault,
+ en: AmpLocalization.pluralCategoryOne,
+ ca: AmpLocalization.pluralCategoryOne,
+ da: AmpLocalization.pluralCategoryOne,
+ nl: AmpLocalization.pluralCategoryOne,
+ de: AmpLocalization.pluralCategoryOne,
+ el: AmpLocalization.pluralCategoryOne,
+ fi: AmpLocalization.pluralCategoryOne,
+ hu: AmpLocalization.pluralCategoryOne,
+ it: AmpLocalization.pluralCategoryOne,
+ nb: AmpLocalization.pluralCategoryOne,
+ no: AmpLocalization.pluralCategoryOne,
+ pt: AmpLocalization.pluralCategoryOne,
+ es: AmpLocalization.pluralCategoryOne,
+ sv: AmpLocalization.pluralCategoryOne,
+ tr: AmpLocalization.pluralCategoryOne,
+ ar: AmpLocalization.pluralCategoryArabic,
+ fr: AmpLocalization.pluralCategoryFrench,
+ iw: AmpLocalization.pluralCategoryHebrew,
+ pl: AmpLocalization.pluralCategoryPolish,
+ ro: AmpLocalization.pluralCategoryRomanian,
+ ru: AmpLocalization.pluralCategoryRussian, // Russian
+};
+//# sourceMappingURL=amp-localization.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/active-intent.js b/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/active-intent.js
new file mode 100644
index 0000000..5270dd4
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/active-intent.js
@@ -0,0 +1,121 @@
+import { isSome } from "@jet/environment/types/optional";
+import { makeMetatype } from "@jet/environment/util/metatype";
+import { unreachable } from "../util/errors";
+export const ActiveIntentMetaType = makeMetatype("app-store:active-intent");
+/**
+ * Executes {@linkcode callback} with an {@linkcode AppStoreObjectGraph} that has been prepared with an
+ * {@linkcode ActiveIntent} dependency
+ *
+ * This will allow code running within {@linkcode callback} to access meta-data about the active
+ * {@linkcode Intent} without needing that information to be passed through every layer of the code
+ */
+export function withActiveIntent(objectGraph, intent, callback) {
+ const objectGraphWithActiveIntent = objectGraph.addingActiveIntent({
+ previewPlatform: intent.platform,
+ });
+ return callback(objectGraphWithActiveIntent);
+}
+/**
+ * Ephemeral storage of `Intent`-specific details that need to be accessible
+ * throughout the process of building that `Intent`'s result
+ */
+export class ActiveIntent {
+ constructor(implementation) {
+ this.implementation = implementation;
+ this.inferredPreviewPlatform = undefined;
+ }
+ /**
+ * Explicitly set a {@linkcode PreviewPlatform} for the active intent
+ *
+ * This might be a value derrived from an API response, in cases where the original `Intent`
+ * was not specific about which platform to display
+ */
+ setInferredPreviewPlatform(platform) {
+ this.inferredPreviewPlatform = platform;
+ }
+ /**
+ * The {@linkcode PreviewPlatform} value of the current {@linkcode Intent}
+ */
+ get previewPlatform() {
+ var _a;
+ return (_a = this.inferredPreviewPlatform) !== null && _a !== void 0 ? _a : this.implementation.previewPlatform;
+ }
+ /**
+ * The {@linkcode Platform} equivalent of the active {@linkcode PreviewPlatform}
+ *
+ * This translates the user-facing {@linkcode PreviewPlatform} into the "internal"
+ * Media API {@linkcode Platform}
+ */
+ get platform() {
+ if (isSome(this.previewPlatform)) {
+ switch (this.previewPlatform) {
+ case "ipad":
+ case "iphone":
+ case "mac":
+ case "watch":
+ return this.previewPlatform;
+ case "tv":
+ return "appletv";
+ case "vision":
+ return "realityDevice";
+ default:
+ unreachable(this.previewPlatform);
+ }
+ }
+ return undefined;
+ }
+ /**
+ * The {@linkcode AttributePlatform} equivalent of the active {@linkcode PreviewPlatform}
+ *
+ * This translates the user-facing {@linkcode PreviewPlatform} into the "internal"
+ * {@linkcode AttributePlatform}
+ */
+ get attributePlatform() {
+ if (isSome(this.previewPlatform)) {
+ switch (this.previewPlatform) {
+ case "iphone":
+ case "ipad":
+ return "ios";
+ case "mac":
+ return "osx";
+ case "tv":
+ return "appletvos";
+ case "vision":
+ return "xros";
+ case "watch":
+ return "watch";
+ default:
+ unreachable(this.previewPlatform);
+ }
+ }
+ return undefined;
+ }
+ /**
+ * The {@linkcode AppPlatform} equivalent of the active {@linkcode PreviewPlatform}
+ *
+ * This translates the user-facing {@linkcode PreviewPlatform} into the "internal"
+ * {@linkcode AppPlatform}
+ */
+ get appPlatform() {
+ if (isSome(this.previewPlatform)) {
+ switch (this.previewPlatform) {
+ case "ipad":
+ return "pad";
+ case "iphone":
+ return "phone";
+ case "mac":
+ return "mac";
+ case "tv":
+ return "tv";
+ case "vision":
+ return "vision";
+ case "watch":
+ return "watch";
+ default:
+ unreachable(this.previewPlatform);
+ }
+ }
+ return undefined;
+ }
+}
+//# sourceMappingURL=active-intent.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/locale/locale-from-bag.js b/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/locale/locale-from-bag.js
new file mode 100644
index 0000000..7790008
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/locale/locale-from-bag.js
@@ -0,0 +1,41 @@
+export class LocaleFromBag {
+ constructor(objectGraph) {
+ this.objectGraph = objectGraph;
+ }
+ /**
+ * The current {@link NormalizedStorefront | storefront} for the client
+ *
+ * This reads directly from the `Bag`, as that is the source of truth
+ * for locale information in the "native" clients
+ */
+ get activeStorefront() {
+ // The type-cast here reflects the implicit assumption that any values
+ // belonging to the `Bag` have already been normalized
+ return this.objectGraph.bag.mediaCountryCode;
+ }
+ /**
+ * The current {@link NormalizedLanguage | language} for the client
+ *
+ * This reads directly from the `Bag`, as that is the source of truth
+ * for locale information in the "native" clients
+ */
+ get activeLanguage() {
+ // The type-cast here reflects the implicit assumption that any values
+ // belonging to the `Bag` have already been normalized
+ return this.objectGraph.bag.language;
+ }
+ setActiveLocale(_locale) {
+ // Intentional no-op; "native" clients that use the `Bag` as the source
+ // of "locale" information do not allow for mutation of the "locale"
+ }
+ normalize(_unnormalizedLocale) {
+ return {
+ storefront: this.activeStorefront,
+ language: this.activeLanguage,
+ };
+ }
+ deriveLocaleForUrl(locale) {
+ return locale;
+ }
+}
+//# sourceMappingURL=locale-from-bag.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/locale/locale.js b/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/locale/locale.js
new file mode 100644
index 0000000..d00bacc
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/locale/locale.js
@@ -0,0 +1,3 @@
+import { makeMetatype } from "@jet/environment/util/metatype";
+export const LocaleMetaType = makeMetatype("app-store:locale");
+//# sourceMappingURL=locale.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/seo.js b/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/seo.js
new file mode 100644
index 0000000..9def945
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/dependencies/seo.js
@@ -0,0 +1,3 @@
+import { makeMetatype } from "@jet/environment/util/metatype";
+export const SEOMetaType = makeMetatype("app-store:seo");
+//# sourceMappingURL=seo.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/app-store-experiments.js b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/app-store-experiments.js
new file mode 100644
index 0000000..e073481
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/app-store-experiments.js
@@ -0,0 +1,7 @@
+import { ExperimentCache } from "./experiment-cache";
+export function currentTreatmentIdForArea(objectGraph, experimentAreaId) {
+ var _a;
+ const experimentCache = objectGraph.optional(ExperimentCache.metatype);
+ return (_a = experimentCache === null || experimentCache === void 0 ? void 0 : experimentCache.currentTreatmentForExperiment(experimentAreaId)) === null || _a === void 0 ? void 0 : _a.identifier;
+}
+//# sourceMappingURL=app-store-experiments.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/experiment-area-id.js b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/experiment-area-id.js
new file mode 100644
index 0000000..435c196
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/experiment-area-id.js
@@ -0,0 +1,10 @@
+export var ExperimentAreaId;
+(function (ExperimentAreaId) {
+ ExperimentAreaId["ArcadeDownloadPackOnboarding"] = "Tf5Kjqz";
+ ExperimentAreaId["CondensedTodayAds"] = "tBc9hUt";
+ ExperimentAreaId["ProductPagePreloading"] = "m0henFo";
+ ExperimentAreaId["ProductPageVariants"] = "fNPb5Km";
+ ExperimentAreaId["ProductPageYMALRowCount"] = "isj11bm";
+ ExperimentAreaId["SearchLandingPage"] = "WqjkRLH";
+})(ExperimentAreaId || (ExperimentAreaId = {}));
+//# sourceMappingURL=experiment-area-id.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/experiment-cache.js b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/experiment-cache.js
new file mode 100644
index 0000000..c0384ea
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/experiment-cache.js
@@ -0,0 +1,63 @@
+import { makeMetatype } from "@jet/environment/util/metatype";
+import { ExperimentAreaId } from "./experiment-area-id";
+export class ExperimentCache {
+ constructor() {
+ this.cachedTreatments = {};
+ this.cachedRawTreatments = {};
+ }
+ async loadTreatments(objectGraph) {
+ const experimentAreas = this.experimentAreasForPlatform(objectGraph);
+ if (experimentAreas.length > 0) {
+ try {
+ this.cachedRawTreatments = await objectGraph.treatmentStore.treatmentsForAreas(experimentAreas);
+ for (const [experimentAreaId, treatment] of Object.entries(this.cachedRawTreatments)) {
+ /// AMS Adds some debug metadata after the treatment identifier in internal builds we do not want this
+ /// for app store usage
+ const rawIdentifier = treatment.identifier;
+ this.cachedTreatments[experimentAreaId] = {
+ ...treatment,
+ identifier: rawIdentifier.split(":")[0],
+ };
+ }
+ }
+ catch (error) {
+ objectGraph.console.error("Failed to load treatments", error);
+ }
+ }
+ }
+ currentTreatmentForExperiment(experimentAreaId) {
+ return this.cachedTreatments[experimentAreaId];
+ }
+ createAb2Data() {
+ const ab2Data = [];
+ for (const [experimentAreaId, treatment] of Object.entries(this.cachedRawTreatments)) {
+ ab2Data.push({
+ areaId: experimentAreaId,
+ bucket: -2,
+ treatmentId: treatment.identifier,
+ });
+ }
+ return ab2Data;
+ }
+ experimentAreasForPlatform(objectGraph) {
+ const experimentAreas = [];
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ case "pad":
+ experimentAreas.push(...[
+ ExperimentAreaId.ArcadeDownloadPackOnboarding,
+ ExperimentAreaId.CondensedTodayAds,
+ ExperimentAreaId.ProductPagePreloading,
+ ExperimentAreaId.ProductPageVariants,
+ ExperimentAreaId.ProductPageYMALRowCount,
+ ExperimentAreaId.SearchLandingPage,
+ ]);
+ break;
+ default:
+ break;
+ }
+ return experimentAreas;
+ }
+}
+ExperimentCache.metatype = makeMetatype("app-store:experimentCache");
+//# sourceMappingURL=experiment-cache.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/product-page-experiments.js b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/product-page-experiments.js
new file mode 100644
index 0000000..7af0b43
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/product-page-experiments.js
@@ -0,0 +1,21 @@
+import * as appStoreExperiments from "./app-store-experiments";
+import { ExperimentAreaId } from "./experiment-area-id";
+export function shouldSkipProductPagePreload(objectGraph) {
+ const treatmentId = appStoreExperiments.currentTreatmentIdForArea(objectGraph, ExperimentAreaId.ProductPagePreloading);
+ return treatmentId === "3NepyQj01" /* ProductPagePreloadingExperimentTypes.SkipPreload */;
+}
+// --- Product Page Variants --
+/// Returns the treatment group id based on xp_ab value as string.
+export function productVariantTreatmentId(objectGraph) {
+ return appStoreExperiments.currentTreatmentIdForArea(objectGraph, ExperimentAreaId.ProductPageVariants);
+}
+/**
+ * The number of rows that the YMAL shelf should use.
+ * @param objectGraph The object graph.
+ * @returns A number representing the number of rows in the YMAL shelf.
+ */
+export function ymalShelfNumberOfRows(objectGraph) {
+ const treatmentId = appStoreExperiments.currentTreatmentIdForArea(objectGraph, ExperimentAreaId.ProductPageYMALRowCount);
+ return treatmentId === "2S6U3Dq01" /* ProductPageYMALShelfRowNumberExperimentTypes.ThreeRow */ ? 3 : 2;
+}
+//# sourceMappingURL=product-page-experiments.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/search-results-experiments.js b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/search-results-experiments.js
new file mode 100644
index 0000000..54654ee
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/search-results-experiments.js
@@ -0,0 +1,10 @@
+import * as serverData from "../../foundation/json-parsing/server-data";
+/**
+ * Returns the current treatment for editorial collections based on the meta blob.
+ * @param meta Meta blob in initial response.
+ */
+export function currentEditorialCollectionTreatment(objectGraph, meta) {
+ const showGridCard = serverData.asBooleanOrFalse(meta, "experiments.showGridCard");
+ return showGridCard ? 0 /* EditorialCollectionExperimentType.Card */ : 1 /* EditorialCollectionExperimentType.Swoosh */;
+}
+//# sourceMappingURL=search-results-experiments.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/today-ad-experiments.js b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/today-ad-experiments.js
new file mode 100644
index 0000000..52468f9
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/experimentation/today-ad-experiments.js
@@ -0,0 +1,15 @@
+import * as appStoreExperiments from "./app-store-experiments";
+import { ExperimentAreaId } from "./experiment-area-id";
+/**
+ * Check whether the condensed treatment should be applied to the Today ad.
+ * @param objectGraph The object graph.
+ * @returns A boolean indicating if the treatment should be applied.
+ */
+export function shouldTodayAdBeCondensed(objectGraph) {
+ if (!objectGraph.client.isPhone) {
+ return false;
+ }
+ const treatmentId = appStoreExperiments.currentTreatmentIdForArea(objectGraph, ExperimentAreaId.CondensedTodayAds);
+ return treatmentId === "5pdfhju01" /* TodayAdCondensedDisplayExperimentTypes.Condensed */;
+}
+//# sourceMappingURL=today-ad-experiments.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/json-parsing/derived-data.js b/node_modules/@jet-app/app-store/tmp/src/foundation/json-parsing/derived-data.js
new file mode 100644
index 0000000..fdf1f4e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/json-parsing/derived-data.js
@@ -0,0 +1,37 @@
+import * as validation from "@jet/environment/json/validation";
+import * as serverData from "./server-data";
+const DERIVED_CACHE_KEY = "_jet-internal:derived-data";
+export function value(data, key, generator) {
+ if (!data) {
+ return null;
+ }
+ return validation.context(key, () => {
+ let computedDataStore = data[DERIVED_CACHE_KEY];
+ let returnValue = null;
+ if (!computedDataStore) {
+ computedDataStore = {};
+ data[DERIVED_CACHE_KEY] = computedDataStore;
+ returnValue = generateValue(computedDataStore, key, generator);
+ }
+ else {
+ returnValue = serverData.traverse(computedDataStore, key);
+ if (!returnValue) {
+ returnValue = generateValue(computedDataStore, key, generator);
+ }
+ }
+ return returnValue;
+ });
+}
+/**
+ * Computes an unknown value and saves it back into computedDataStore
+ * @param {JSONData} computedDataStore The dictionary in which to store the data
+ * @param {string} key The key to store it with
+ * @param {() => T} generator A function that will generate the value
+ * @returns {T} The value that was generated
+ */
+function generateValue(computedDataStore, key, generator) {
+ const generatedValue = generator();
+ computedDataStore[key] = generatedValue;
+ return generatedValue;
+}
+//# sourceMappingURL=derived-data.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/json-parsing/server-data.js b/node_modules/@jet-app/app-store/tmp/src/foundation/json-parsing/server-data.js
new file mode 100644
index 0000000..2ff6394
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/json-parsing/server-data.js
@@ -0,0 +1,464 @@
+//
+// server-data.ts
+// AppStoreKit
+//
+// Created by Kevin MacWhinnie on 8/17/16.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+import * as validation from "@jet/environment/json/validation";
+import { isNothing } from "@jet/environment/types/optional";
+/**
+ * Returns the string representation of a given object path.
+ * @param path The object path to coerce to a string.
+ * @returns A string representation of `path`.
+ */
+export function objectPathToString(path) {
+ if (isNull(path)) {
+ return null;
+ }
+ else if (Array.isArray(path)) {
+ return path.join(".");
+ }
+ else {
+ return path;
+ }
+}
+const PARSED_PATH_CACHE = {};
+/**
+ * Traverse a nested JSON object structure, short-circuiting
+ * when finding `undefined` or `null` values. Usage:
+ *
+ * const object = {x: {y: {z: 42}}};
+ * const meaningOfLife = serverData.traverse(object, 'x.y.z');
+ *
+ * @param object The JSON object to traverse.
+ * @param path The path to search. If falsy, `object` will be returned without being traversed.
+ * @param defaultValue The object to return if the path search fails.
+ * @return The value at `path` if found; default value otherwise.
+ */
+export function traverse(object, path, defaultValue) {
+ if (object === undefined || object === null) {
+ return defaultValue;
+ }
+ if (!path) {
+ return object;
+ }
+ let components;
+ if (typeof path === "string") {
+ components = PARSED_PATH_CACHE[path];
+ if (!components) {
+ // Fast Path: If the path contains only a single component, we can skip
+ // all of the work below here and speed up storefronts that
+ // don't have JIT compilation enabled.
+ if (!path.includes(".")) {
+ const value = object[path];
+ if (value !== undefined && value !== null) {
+ return value;
+ }
+ else {
+ return defaultValue;
+ }
+ }
+ components = path.split(".");
+ PARSED_PATH_CACHE[path] = components;
+ }
+ }
+ else {
+ components = path;
+ }
+ let current = object;
+ for (const component of components) {
+ current = current[component];
+ if (current === undefined || current === null) {
+ return defaultValue;
+ }
+ }
+ return current;
+}
+// endregion
+// region Nullability
+/**
+ * Returns a bool indicating whether or not a given object null or undefined.
+ * @param object The object to test.
+ * @return true if the object is null or undefined; false otherwise.
+ */
+export function isNull(object) {
+ return object === null || object === undefined;
+}
+/**
+ * Returns a bool indicating whether or not a given object is null or empty.
+ * @param object The object to test
+ * @return true if object is null or empty; false otherwise.
+ */
+export function isNullOrEmpty(object) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return isNull(object) || Object.keys(object).length === 0;
+}
+/**
+ * Returns a bool indicating whether or not a given object is non-null.
+ * @param object The object to test.
+ * @return true if the object is not null or undefined; false otherwise.
+ */
+export function isDefinedNonNull(object) {
+ return typeof object !== "undefined" && object !== null;
+}
+/**
+ * Returns a bool indicating whether or not a given object is non-null or empty.
+ * @param object The object to test.
+ * @return true if the object is not null or undefined and not empty; false otherwise.
+ */
+export function isDefinedNonNullNonEmpty(object) {
+ if (isNothing(object)) {
+ return false;
+ }
+ if (typeof object === "string") {
+ return object.length > 0;
+ }
+ else if (Array.isArray(object)) {
+ return object.length > 0;
+ }
+ else {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return Object.keys(object).length !== 0;
+ }
+}
+/**
+ * Returns a bool indicating whether or not a given object is non-null or empty.
+ * @param object The object to test.
+ * @return true if the object is not null or undefined and not empty; false otherwise.
+ */
+export function isSetDefinedNonNullNonEmpty(object) {
+ return isDefinedNonNull(object) && object.size > 0;
+}
+/**
+ * Checks if the passed string or number is a number
+ *
+ * @param value The value to check
+ * @return True if the value is an number, false if not
+ */
+export function isNumber(value) {
+ if (isNull(value)) {
+ return false;
+ }
+ let valueToCheck;
+ if (typeof value === "string") {
+ valueToCheck = parseInt(value);
+ }
+ else {
+ valueToCheck = value;
+ }
+ return !Number.isNaN(valueToCheck);
+}
+/**
+ * Checks if the value is a string
+ *
+ * @param value The value to check
+ * @return True if the value is a string, false if not
+ */
+export function isString(value) {
+ if (isNothing(value)) {
+ return false;
+ }
+ return typeof value === "string";
+}
+/**
+ * Returns a bool indicating whether or not a given object is defined but empty.
+ * @param object The object to test.
+ * @return true if the object is not null and empty; false otherwise.
+ */
+export function isArrayDefinedNonNullAndEmpty(object) {
+ return isDefinedNonNull(object) && object.length === 0;
+}
+// endregion
+// region Defaulting Casts
+/**
+ * Check that a given object is an array, substituting an empty array if not.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find an array.
+ * Omit this parameter if `object` is itself an array.
+ * @returns An untyped array.
+ */
+export function asArrayOrEmpty(object, path) {
+ var _a;
+ return (_a = asArray(object, path, [])) !== null && _a !== void 0 ? _a : [];
+}
+/**
+ * Check that a given object is a boolean, substituting the value `false` if not.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a boolean.
+ * Omit this parameter if `object` is itself a boolean.
+ * @returns A boolean from `object`, or defaults to `false`.
+ */
+export function asBooleanOrFalse(object, path) {
+ return asBooleanWithDefault(object, false, path);
+}
+/**
+ * Check that a given object is a boolean, substituting the value `false` if not.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a boolean.
+ * Omit this parameter if `object` is itself a boolean.
+ * @returns A boolean from `object`, or the provided default.
+ */
+export function asBooleanWithDefault(object, defaultValue, path) {
+ const target = traverse(object, path, null);
+ if (typeof target === "boolean") {
+ return target;
+ }
+ else {
+ if (!isNull(target)) {
+ validation.context("asBooleanWithDefault", () => {
+ validation.unexpectedType("defaultValue", "boolean", target, objectPathToString(path));
+ });
+ }
+ return defaultValue;
+ }
+}
+/**
+ * Safely coerce an object into a string.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a string.
+ * Omit this parameter if `object` is itself a string.
+ * @param policy The validation policy to use when resolving this value
+ * @returns A string from `object`, or `null` if `object` is null.
+ */
+export function asString(object, path, policy = "coercible") {
+ const target = traverse(object, path, null);
+ if (isNull(target)) {
+ return target;
+ }
+ else if (typeof target === "string") {
+ return target;
+ }
+ else {
+ // We don't consider arbitrary objects as convertable to strings even through they will result in some value
+ const coercedValue = typeof target === "object" ? null : String(target);
+ switch (policy) {
+ case "strict": {
+ validation.context("asString", () => {
+ validation.unexpectedType("coercedValue", "string", target, objectPathToString(path));
+ });
+ break;
+ }
+ case "coercible": {
+ if (isNull(coercedValue)) {
+ validation.context("asString", () => {
+ validation.unexpectedType("coercedValue", "string", target, objectPathToString(path));
+ });
+ }
+ break;
+ }
+ case "none":
+ default: {
+ break;
+ }
+ }
+ return coercedValue;
+ }
+}
+/**
+ * Safely coerce an object into a number.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a number.
+ * Omit this parameter if `object` is itself a number.
+ * @param policy The validation policy to use when resolving this value
+ * @returns A number from `object`, or `null` if `object` is null.
+ */
+export function asNumber(object, path, policy = "coercible") {
+ const target = traverse(object, path, null);
+ if (isNull(target) || typeof target === "number") {
+ return target;
+ }
+ else {
+ const coercedValue = Number(target);
+ switch (policy) {
+ case "strict": {
+ validation.context("asNumber", () => {
+ validation.unexpectedType("coercedValue", "number", target, objectPathToString(path));
+ });
+ break;
+ }
+ case "coercible": {
+ if (isNaN(coercedValue)) {
+ validation.context("asNumber", () => {
+ validation.unexpectedType("coercedValue", "number", target, objectPathToString(path));
+ });
+ return null;
+ }
+ break;
+ }
+ case "none":
+ default: {
+ break;
+ }
+ }
+ return coercedValue;
+ }
+}
+/**
+ * Safely coerce an object into a dictionary.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find the dictionary.
+ * Omit this parameter if `object` is itself a dictionary.
+ * @param defaultValue The object to return if the path search fails.
+ * @returns A sub-dictionary from `object`, or `null` if `object` is null.
+ */
+export function asDictionary(object, path, defaultValue) {
+ const target = traverse(object, path, null);
+ if (target instanceof Object && !Array.isArray(target)) {
+ // Note: It's too expensive to actually validate this is a dictionary of { string : Type } at run time
+ return target;
+ }
+ else {
+ if (!isNull(target)) {
+ validation.context("asDictionary", () => {
+ validation.unexpectedType("defaultValue", "object", target, objectPathToString(path));
+ });
+ }
+ if (isDefinedNonNull(defaultValue)) {
+ return defaultValue;
+ }
+ return null;
+ }
+}
+/**
+ * Coerce an object into an array
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find an array.
+ * Omit this parameter if `object` is itself an array.
+ * @returns An untyped array.
+ */
+export function asArray(object, path, defaultValue) {
+ const target = traverse(object, path, null);
+ if (Array.isArray(target)) {
+ // Note: This is kind of a nasty cast, but I don't think we want to validate that everything is of type T
+ return target;
+ }
+ else {
+ if (!isNull(target)) {
+ validation.context("asArray", () => {
+ validation.unexpectedType("defaultValue", "array", target, objectPathToString(path));
+ });
+ }
+ if (isDefinedNonNull(defaultValue)) {
+ return defaultValue;
+ }
+ return null;
+ }
+}
+/**
+ * Safely coerce an object into a given interface.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a string.
+ * Omit this parameter if `object` is itself a string.
+ * @param defaultValue The object to return if the path search fails.
+ * @returns A sub-dictionary from `object`, or `null` if `object` is null.
+ */
+export function asInterface(object, path, defaultValue) {
+ return asDictionary(object, path, defaultValue);
+}
+/**
+ * Coerce an object into a boolean.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a boolean.
+ * Omit this parameter if `object` is itself a boolean.
+ * @param policy The validation policy to use when resolving this value
+ * @returns A boolean from `object`, or `null` if `object` is null.
+ * @note This is distinct from `asBooleanOrFalse` in that it doesn't default to false,
+ * and it tries to convert string boolean values into actual boolean types
+ */
+export function asBoolean(object, path, policy = "coercible") {
+ const target = traverse(object, path, null);
+ // Value was null
+ if (isNull(target)) {
+ return null;
+ }
+ // Value was boolean.
+ if (typeof target === "boolean") {
+ return target;
+ }
+ // Value was string.
+ if (typeof target === "string") {
+ if (target === "true") {
+ return true;
+ }
+ else if (target === "false") {
+ return false;
+ }
+ }
+ // Else coerce.
+ const coercedValue = Boolean(target);
+ switch (policy) {
+ case "strict": {
+ validation.context("asBoolean", () => {
+ validation.unexpectedType("coercedValue", "number", target, objectPathToString(path));
+ });
+ break;
+ }
+ case "coercible": {
+ if (isNull(coercedValue)) {
+ validation.context("asBoolean", () => {
+ validation.unexpectedType("coercedValue", "number", target, objectPathToString(path));
+ });
+ return null;
+ }
+ break;
+ }
+ case "none":
+ default: {
+ break;
+ }
+ }
+ return coercedValue;
+}
+/**
+ * Attempts to coerce the passed value to a JSONValue
+ *
+ * Note: due to performance concerns this does not perform a deep inspection of Objects or Arrays.
+ *
+ * @param value The value to coerce
+ * @return A JSONValue or null if value is not a valid JSONValue type
+ */
+export function asJSONValue(value) {
+ if (value === null || value === undefined) {
+ return null;
+ }
+ switch (typeof value) {
+ case "string":
+ case "number":
+ case "boolean":
+ return value;
+ case "object":
+ // Note: It's too expensive to actually validate this is an array of JSONValues at run time
+ if (Array.isArray(value)) {
+ return value;
+ }
+ // Note: It's too expensive to actually validate this is a dictionary of { string : JSONValue } at run time
+ return value;
+ default:
+ validation.context("asJSONValue", () => {
+ validation.unexpectedType("defaultValue", "JSONValue", typeof value);
+ });
+ return null;
+ }
+}
+/**
+ * Attempts to coerce the passed value to JSONData
+ *
+ * @param value The value to coerce
+ * @return A JSONData or null if the value is not a valid JSONData object
+ */
+export function asJSONData(value) {
+ if (value === null || value === undefined) {
+ return null;
+ }
+ if (value instanceof Object && !Array.isArray(value)) {
+ // Note: It's too expensive to actually validate this is a dictionary of { string : Type } at run time
+ return value;
+ }
+ validation.context("asJSONValue", () => {
+ validation.unexpectedType("defaultValue", "object", typeof value);
+ });
+ return null;
+}
+// endregion
+//# sourceMappingURL=server-data.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/associations.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/associations.js
new file mode 100644
index 0000000..7e2b088
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/associations.js
@@ -0,0 +1,17 @@
+import * as serverData from "../json-parsing/server-data";
+/**
+ * @param data The media api data to find teh card in
+ * @returns The editorial-card data for this media api item, this will return the first associated card
+ */
+export function editorialCardsFromData(data) {
+ const editorialCards = serverData.asArrayOrEmpty(data, "meta.associations.editorial-cards.data");
+ return editorialCards;
+}
+/**
+ * @param data The media api data to find teh card in
+ * @returns The editorial-card data for this media api item, this will return the first associated card
+ */
+export function editorialCardFromData(data) {
+ return serverData.asInterface(editorialCardsFromData(data)[0]);
+}
+//# sourceMappingURL=associations.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/attributes.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/attributes.js
new file mode 100644
index 0000000..992a56e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/attributes.js
@@ -0,0 +1,149 @@
+import { isNothing } from "@jet/environment/types/optional";
+import * as serverData from "../json-parsing/server-data";
+// region Generic Attribute retrieval
+// region Attribute retrieval
+/**
+ * Retrieve the specified attribute from the data, coercing it to a JSONData dictionary
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param defaultValue The object to return if the path search fails.
+ * @returns The dictionary of data
+ */
+export function attributeAsDictionary(data, attributePath, defaultValue) {
+ if (isNothing(data)) {
+ return null;
+ }
+ return serverData.asDictionary(data.attributes, attributePath, defaultValue);
+}
+/**
+ * Retrieve the specified attribute from the data, coercing it to an Interface
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param defaultValue The object to return if the path search fails.
+ * @returns The dictionary of data as an interface
+ */
+export function attributeAsInterface(data, attributePath, defaultValue) {
+ return attributeAsDictionary(data, attributePath, defaultValue);
+}
+/**
+ * Retrieve the specified attribute from the data as an array, coercing to a JSONValue array
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @returns {any[]} The attribute value as an array.
+ */
+export function attributeAsArray(data, attributePath) {
+ if (serverData.isNull(data)) {
+ return [];
+ }
+ return serverData.asArray(data.attributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as an array, coercing to an empty array if the object is not an array.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @returns {any[]} The attribute value as an array.
+ */
+export function attributeAsArrayOrEmpty(data, attributePath) {
+ var _a;
+ return (_a = attributeAsArray(data, attributePath)) !== null && _a !== void 0 ? _a : [];
+}
+/**
+ * Retrieve the specified attribute from the data as a string.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The object path for the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {string} The attribute value as a string.
+ */
+export function attributeAsString(data, attributePath, policy = "coercible") {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ return serverData.asString(data.attributes, attributePath, policy);
+}
+/**
+ * Retrieve the specified attribute from the data as a boolean.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a boolean.
+ */
+export function attributeAsBoolean(data, attributePath, policy = "coercible") {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ return serverData.asBoolean(data.attributes, attributePath, policy);
+}
+/**
+ * Retrieve the specified attribute from the data as a boolean, which will be `false` if the attribute does not exist.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @returns {boolean} The attribute value as a boolean, coercing to `false` if the value is not present..
+ */
+export function attributeAsBooleanOrFalse(data, attributePath) {
+ if (serverData.isNull(data)) {
+ return false;
+ }
+ return serverData.asBooleanOrFalse(data.attributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as a number.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a number.
+ */
+export function attributeAsNumber(data, attributePath, policy = "coercible") {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ return serverData.asNumber(data.attributes, attributePath, policy);
+}
+export function hasAttributes(data) {
+ return !serverData.isNull(serverData.asDictionary(data, "attributes"));
+}
+/**
+ * The canonical way to detect if an item from Media API is hydrated or not.
+ *
+ * @param data The data from which to retrieve the attributes.
+ */
+export function isNotHydrated(data) {
+ return !hasAttributes(data);
+}
+// region Custom Attributes
+/**
+ * Performs conversion for a custom variant of given attribute, if any are available.
+ * @param attribute Attribute to get custom attribute key for, if any.
+ */
+export function attributeKeyAsCustomAttributeKey(attribute) {
+ return customAttributeMapping[attribute];
+}
+/**
+ * Whether or not given custom attributes key allows fallback to default page with AB testing treatment within a nondefault page.
+ * This is to allow AB testing to affect only icons within custom product pages.
+ */
+export function attributeAllowsNonDefaultTreatmentInNonDefaultPage(customAttribute) {
+ return customAttribute === "customArtwork" || customAttribute === "customIconArtwork"; // Only the icon artwork.
+}
+/**
+ * Defines mapping of attribute to custom attribute.
+ */
+const customAttributeMapping = {
+ artwork: "customArtwork",
+ iconArtwork: "customIconArtwork",
+ screenshotsByType: "customScreenshotsByType",
+ promotionalText: "customPromotionalText",
+ videoPreviewsByType: "customVideoPreviewsByType",
+ customScreenshotsByTypeForAd: "customScreenshotsByTypeForAd",
+ customVideoPreviewsByTypeForAd: "customVideoPreviewsByTypeForAd",
+ customDeepLink: "customDeepLink",
+};
+// endregion
+//# sourceMappingURL=attributes.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js
new file mode 100644
index 0000000..445f42f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-fetching.js
@@ -0,0 +1,631 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as serverData from "../json-parsing/server-data";
+import * as client from "../wrappers/client";
+export function allPlatforms(objectGraph, platformsToExclude) {
+ const platforms = new Set();
+ platforms.add("iphone");
+ platforms.add("ipad");
+ platforms.add("appletv");
+ platforms.add("mac");
+ platforms.add("watch");
+ if (objectGraph.client.isVision || objectGraph.bag.enableVisionPlatform) {
+ platforms.add("realityDevice");
+ }
+ if (isSome(platformsToExclude)) {
+ for (const platform of platformsToExclude) {
+ platforms.delete(platform);
+ }
+ }
+ return Array.from(platforms);
+}
+export function defaultPlatformForClient(objectGraph) {
+ if (objectGraph.client.isCompanionVisionApp) {
+ // The Vision companion app always prefers visionOS assets
+ return "realityDevice";
+ }
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return "iphone";
+ case "pad":
+ return "ipad";
+ case "tv":
+ return "appletv";
+ case "mac":
+ return "mac";
+ case "watch":
+ return "watch";
+ case "vision":
+ return "realityDevice";
+ case "web":
+ return "web";
+ default:
+ return null;
+ }
+}
+/**
+ * Returns the layout size controlling the number of rows to display.
+ * @param {AppStoreObjectGraph} objectGraph Object graph to get the client device type.
+ * @returns {number} Layout size.
+ */
+export function defaultLayoutSize(objectGraph) {
+ return objectGraph.client.isPhone ? 2 : 1;
+}
+/**
+ * Returns the sparse count controlling the number of shelves to hydrate.
+ * @param {AppStoreObjectGraph} objectGraph Object graph to get the client device type.
+ * @returns {number} Sparse count.
+ */
+export function defaultSparseCountForClient(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return 4;
+ case "pad":
+ return 5;
+ case "tv":
+ return 6;
+ case "mac":
+ return 5;
+ case "watch":
+ return 10;
+ default:
+ return null;
+ }
+}
+/**
+ * Returns the sparse limit controlling the number of items to hydrate per shelve.
+ * @param {AppStoreObjectGraph} objectGraph Object graph to get the client device type.
+ * @returns {number} Sparse limit.
+ */
+export function defaultSparseLimitForClient(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return 9;
+ case "pad":
+ return 12;
+ case "tv":
+ return 15;
+ case "mac":
+ return 15;
+ case "watch":
+ return 3;
+ case "web":
+ return 12;
+ default:
+ return null;
+ }
+}
+/**
+ * Get the list of all platforms excluding the current platform
+ * @returns {Platform[]}
+ */
+export function defaultAdditionalPlatformsForClient(objectGraph) {
+ switch (objectGraph.host.clientIdentifier) {
+ case "com.apple.TVAppStore.AppStoreTopShelfExtension":
+ case "com.apple.Arcade.ArcadeTopShelfExtension":
+ case "com.apple.AppStore.Widgets":
+ // Skip additional platforms for top shelf extension due to memory constraint.
+ return [];
+ default: {
+ const currentPlatform = defaultPlatformForClient(objectGraph);
+ return allPlatforms(objectGraph, isSome(currentPlatform) ? new Set([currentPlatform]) : undefined);
+ }
+ }
+}
+/**
+ * Get the product page reviews limit for a client.
+ * @returns {number} The limit to use.
+ */
+export function defaultProductPageReviewsLimitForClient(objectGraph) {
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ return 6;
+ case "pad":
+ return 10;
+ case "mac":
+ return 12;
+ case "vision":
+ return 10;
+ default:
+ return 8;
+ }
+}
+/// Returns the context param to use for a given grouping request.
+/// This is leveraged for switching grouping tab root in specific configurations.
+export function defaultGroupingContextForClient(objectGraph) {
+ let context = null;
+ if (objectGraph.host.clientIdentifier === client.messagesIdentifier) {
+ // Messages Grouping (iOS)
+ context = "messages";
+ }
+ else if (objectGraph.host.clientIdentifier === client.watchIdentifier) {
+ // Bridge App Store (iOS)
+ context = "watch";
+ }
+ else if (objectGraph.client.isWatch && objectGraph.client.isTinkerWatch) {
+ // Tinker App Store (watchOS)
+ context = "tinker";
+ }
+ return context;
+}
+// Nothing on the App Store is rated 1000+, so a value this high essentially
+// indicates that content restrictions are disabled.
+export const ageRestrictionsDisabledThreshold = 1000;
+export class Request {
+ constructor(objectGraph, param, enableMixedCatalog, supplementaryMetadataAssociations) {
+ var _a;
+ /// The resource types used in a mixed contents call
+ this.contentsResourceTypes = new Set();
+ /// The ID(s) associated with the resource type
+ this.ids = new Set();
+ /// IDs associated with specific resource types, used for mixed catalog requests
+ this.idsByResourceType = new Map();
+ /// The original ordering of the ids in this reqeust if there is a mixed catalog request
+ this.originalOrdering = [];
+ this.relationshipIncludes = new Set();
+ // Contents of `extend` param
+ this.attributeIncludes = new Set();
+ this.platform = null;
+ /// The paths to additional metadata that need to be fetched for mixed media requests
+ this.supplementaryMetadataAssociations = [];
+ this.additionalPlatforms = new Set();
+ this.additionalQuery = {};
+ this.relationshipLimits = {};
+ this.searchTerm = null;
+ this.searchTypes = [];
+ /// Whether we are searching watch or messages store.
+ this.context = null;
+ /// Whether or not to use custom extend attributes, instead of using `attributeIncludes` as-is.
+ this.useCustomAttributes = false;
+ /// An optional country code override for constructing the URL. Used in very specific regulatory scenarios where the bag country code cannot be relied upon.
+ this.countryCodeOverride = undefined;
+ this.objectGraph = objectGraph;
+ this.platform = defaultPlatformForClient(objectGraph);
+ this.isMixedMediaRequest = enableMixedCatalog !== null && enableMixedCatalog !== void 0 ? enableMixedCatalog : false;
+ this.supplementaryMetadataAssociations = supplementaryMetadataAssociations !== null && supplementaryMetadataAssociations !== void 0 ? supplementaryMetadataAssociations : [];
+ this.includeAppBinaryTraitsAttribute = objectGraph.client.isiOS;
+ // By default, the `platform` for web defaults to whatever the `activeIntent` is (e.g. "mac"),
+ // but this can be overridden when calling `setPreviewPlatform` with the request, which
+ // will set the `platform` to `web` and `previewPlatform` will be the `activeIntent` platform.
+ if ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.platform) {
+ this.platform = objectGraph.activeIntent.platform;
+ }
+ if (serverData.isNullOrEmpty(param)) {
+ return;
+ }
+ if (typeof param === "string") {
+ this.href = param;
+ }
+ else if (Array.isArray(param)) {
+ this.withDataItems(param, supplementaryMetadataAssociations, enableMixedCatalog);
+ }
+ }
+ /**
+ * Adds a data object to an idsByResourceType mapping.
+ * @param data The data object
+ * @returns An updated {resource-type: IDs} map, which includes the data object
+ */
+ addDataToIDsByResourceType(data) {
+ const resourceType = data.type;
+ const id = data.id;
+ let ids = this.idsByResourceType.get(resourceType);
+ if (isNothing(ids)) {
+ ids = new Set();
+ }
+ ids.add(id);
+ this.idsByResourceType.set(resourceType, ids);
+ }
+ forType(type) {
+ this.resourceType = type;
+ return this;
+ }
+ /**
+ * This method is used to add data items to the request. It is used to build the request for the mixed catalog / catalog
+ *
+ * @param dataItems The data items to add to the request
+ * @param supplementaryMetadataAssociations The metadata paths to add to the request
+ * @param enableMixedCatalog Whether to allow mixed catalog requests
+ * @returns The modified Request
+ */
+ withDataItems(dataItems, supplementaryMetadataAssociations, enableMixedCatalog) {
+ if (dataItems.length === 0) {
+ return this;
+ }
+ this.isMixedMediaRequest = this.isMixedMediaRequest || (enableMixedCatalog !== null && enableMixedCatalog !== void 0 ? enableMixedCatalog : false);
+ for (const data of dataItems) {
+ // Track all IDs in a single set, for the case where we use the contents endpoint.
+ this.ids.add(data.id);
+ // Track all IDs by resource type, for either the case where we ultimately have just
+ // one resource type, or where we used the mixed catalog endpoint.
+ this.addDataToIDsByResourceType(data);
+ // If we have any additional metadata paths that need fetching, we now add them
+ // as additional resource types for the mixed catalog endpoint.
+ if (isSome(enableMixedCatalog) &&
+ enableMixedCatalog &&
+ isSome(supplementaryMetadataAssociations) &&
+ supplementaryMetadataAssociations.length > 0) {
+ for (const association of supplementaryMetadataAssociations) {
+ const metadataItems = extractMetaAssociationFromData(association, data);
+ if (isSome(metadataItems) && metadataItems.length > 0) {
+ metadataItems.forEach((metadataItem) => {
+ this.addDataToIDsByResourceType(metadataItem);
+ });
+ }
+ }
+ }
+ }
+ // Now that we have collated all IDs and resource types, we can assign them to different
+ // properties depending on the number of resource types we need.
+ if (this.idsByResourceType.size === 1) {
+ // We only have one list of IDs for a single resource type, so we just take the first one
+ this.resourceType = this.idsByResourceType.keys().next().value;
+ this.isMixedMediaRequest = false;
+ }
+ else if (this.idsByResourceType.size > 1 && !this.isMixedMediaRequest) {
+ this.resourceType = "contents";
+ this.contentsResourceTypes = new Set(Array.from(this.idsByResourceType.keys()));
+ }
+ this.originalOrdering.push(...[...dataItems]);
+ return this;
+ }
+ /**
+ * Add a single id to this request, this should only be used when fetching a single resource
+ * @param id The single ID to add to the request
+ * @param type The type of the resource
+ * @returns The modified Request
+ */
+ withIdOfType(id, type) {
+ return this.withDataItems([{ id, type }]);
+ }
+ /**
+ * Add a list of ids to this request, this will add these ids to the mapping of resource types
+ * @param ids The IDs to add to the request
+ * @param type The type of the resource
+ * @returns The modified Request
+ */
+ withIdsOfType(ids, type) {
+ return this.withDataItems(ids.map((id) => ({ id, type })));
+ }
+ includingRelationships(relationships) {
+ for (const relationship of relationships) {
+ this.relationshipIncludes.add(relationship);
+ }
+ return this;
+ }
+ includingScopedRelationships(scopedDataType, relationshipsToAdd) {
+ // Lazy init top-level property
+ if (!this.scopedRelationshipIncludes) {
+ this.scopedRelationshipIncludes = new Map();
+ }
+ // Retrieve existing scoped relationship. Lazy init if needed.
+ let scopedRelationship = this.scopedRelationshipIncludes.get(scopedDataType);
+ if (!scopedRelationship) {
+ scopedRelationship = new Set();
+ }
+ // Update
+ for (const newRelationship of relationshipsToAdd) {
+ scopedRelationship.add(newRelationship);
+ }
+ this.scopedRelationshipIncludes.set(scopedDataType, scopedRelationship);
+ return this;
+ }
+ includingMetaKeys(scopedDataType, keysToAdd) {
+ // Lazy init top-level property
+ if (!this.metaIncludes) {
+ this.metaIncludes = new Map();
+ }
+ // Retrieve existing inclusion. Lazy init if needed.
+ let scopedMeta = this.metaIncludes.get(scopedDataType);
+ if (!scopedMeta) {
+ scopedMeta = new Set();
+ }
+ // Update
+ for (const newKey of keysToAdd) {
+ scopedMeta.add(newKey);
+ }
+ this.metaIncludes.set(scopedDataType, scopedMeta);
+ return this;
+ }
+ includingViews(viewsToAdd) {
+ // Lazy init top-level property
+ if (!this.viewsIncludes) {
+ this.viewsIncludes = new Set();
+ }
+ for (const view of viewsToAdd) {
+ this.viewsIncludes.add(view);
+ }
+ return this;
+ }
+ includingKindsKeys(scopedDataType, keysToAdd) {
+ // Lazy init top-level property
+ if (!this.kindIncludes) {
+ this.kindIncludes = new Map();
+ }
+ // Retrieve existing inclusion. Lazy init if needed.
+ let scopedMeta = this.kindIncludes.get(scopedDataType);
+ if (!scopedMeta) {
+ scopedMeta = new Set();
+ }
+ // Update
+ for (const newKey of keysToAdd) {
+ scopedMeta.add(newKey);
+ }
+ this.kindIncludes.set(scopedDataType, scopedMeta);
+ return this;
+ }
+ includingAssociateKeys(scopedDataType, keysToAdd) {
+ // Lazy init top-level property
+ if (!this.associateIncludes) {
+ this.associateIncludes = new Map();
+ }
+ // Retrieve existing inclusion. Lazy init if needed.
+ let scopedAssociate = this.associateIncludes.get(scopedDataType);
+ if (!scopedAssociate) {
+ scopedAssociate = new Set();
+ }
+ // Update
+ for (const newKey of keysToAdd) {
+ scopedAssociate.add(newKey);
+ }
+ this.associateIncludes.set(scopedDataType, scopedAssociate);
+ return this;
+ }
+ /**
+ * Include the relationships needed for an upsell.
+ * @param requiresScopedInclude A flag indicating whether the include should be "scoped". This will need to be the
+ * case when the relationships we are including is on a request for a separate primary resource type. For example,
+ * if we are looking to have the upsell relationship included in an EI (Today card, for example), this needs to be
+ * scoped. However, if we are fetching a grouping directly and need the upsell included, this should *not* be scoped.
+ */
+ includingRelationshipsForUpsell(requiresScopedRelationshipInclude) {
+ const relationship = "marketing-items";
+ if (requiresScopedRelationshipInclude) {
+ // Lazy init top-level property
+ if (!this.scopedRelationshipIncludes) {
+ this.scopedRelationshipIncludes = new Map();
+ }
+ // Retrieve existing scoped relationship. Lazy init if needed.
+ let scopedRelationship = this.scopedRelationshipIncludes.get("editorial-items");
+ if (!scopedRelationship) {
+ scopedRelationship = new Set();
+ }
+ // Update
+ scopedRelationship.add(relationship);
+ this.scopedRelationshipIncludes.set("editorial-items", scopedRelationship);
+ }
+ else {
+ this.relationshipIncludes.add(relationship);
+ }
+ // In order to get metrics metadata stiched in to the marketing item relationship, we have to include it in our
+ // request.
+ if (relationship === "marketing-items") {
+ if (!this.metaIncludes) {
+ this.metaIncludes = new Map();
+ }
+ // Retrieve existing inclusion. Lazy init if needed.
+ let scopedMeta = this.metaIncludes.get("marketing-items");
+ if (!scopedMeta) {
+ scopedMeta = new Set();
+ }
+ scopedMeta.add("metrics");
+ this.metaIncludes.set("marketing-items", scopedMeta);
+ }
+ return this;
+ }
+ includingAttributes(attributes) {
+ for (const attribute of attributes) {
+ this.attributeIncludes.add(attribute);
+ }
+ return this;
+ }
+ includingScopedAttributes(resourceType, attributesToAdd) {
+ // Lazy init top-level property
+ if (!this.scopedAttributeIncludes) {
+ this.scopedAttributeIncludes = new Map();
+ }
+ // Retrieve existing scoped relationship. Lazy init if needed.
+ let attributesForResourceType = this.scopedAttributeIncludes.get(resourceType);
+ if (!attributesForResourceType) {
+ attributesForResourceType = new Set();
+ }
+ // Update
+ for (const attribute of attributesToAdd) {
+ attributesForResourceType.add(attribute);
+ }
+ this.scopedAttributeIncludes.set(resourceType, attributesForResourceType);
+ return this;
+ }
+ /**
+ * Adds an age restriction to the request. ManagedConfiguration provides
+ * this value on the client, which maps to JRPurpleRating.clientIdentifier.
+ *
+ * @returns {Request} The updated request
+ */
+ includingAgeRestrictions() {
+ const maxAppContentRating = this.objectGraph.client.maxAppContentRating;
+ if (maxAppContentRating < ageRestrictionsDisabledThreshold) {
+ this.ageRestriction = maxAppContentRating;
+ }
+ return this;
+ }
+ includingAdditionalPlatforms(additionalPlatforms) {
+ for (const platform of additionalPlatforms) {
+ this.additionalPlatforms.add(platform);
+ }
+ return this;
+ }
+ includingScopedAvailableIn(resourceType, valuesToAdd) {
+ // Lazy init top-level property
+ if (!this.scopedAvailableInIncludes) {
+ this.scopedAvailableInIncludes = new Map();
+ }
+ // Retrieve existing scoped relationship. Lazy init if needed.
+ let valuesForResourceType = this.scopedAvailableInIncludes.get(resourceType);
+ if (!valuesForResourceType) {
+ valuesForResourceType = new Set();
+ }
+ // Update
+ for (const value of valuesToAdd) {
+ valuesForResourceType.add(value);
+ }
+ this.scopedAvailableInIncludes.set(resourceType, valuesForResourceType);
+ return this;
+ }
+ /**
+ * Include the sparse limit needed for a specific resource type.
+ * @param {media.Type} resourceType Resource type to use for the sparse limit.
+ * @param {number} value Value to set as the sparse limit.
+ * @returns {Request} Updated request.
+ */
+ includingScopedSparseLimit(resourceType, value) {
+ // Lazy init top-level property
+ if (!this.scopedSparseLimit) {
+ this.scopedSparseLimit = new Map();
+ }
+ this.scopedSparseLimit.set(resourceType, value);
+ return this;
+ }
+ addingQuery(key, value) {
+ if (isSome(value)) {
+ this.additionalQuery[key] = value;
+ }
+ else {
+ delete this.addingQuery[key];
+ }
+ return this;
+ }
+ /**
+ * @param query The additional query values to add
+ * @returns The modified Reqeust
+ */
+ addingQueryValues(query) {
+ this.additionalQuery = {
+ ...this.addingQuery,
+ ...query,
+ };
+ return this;
+ }
+ addingRelationshipLimit(relationship, limit) {
+ this.relationshipLimits[relationship] = limit;
+ return this;
+ }
+ withSearchTerm(term) {
+ this.searchTerm = term;
+ return this;
+ }
+ searchingOverTypes(types) {
+ for (const type of types) {
+ this.searchTypes.push(type);
+ }
+ return this;
+ }
+ addingContext(context) {
+ this.context = context;
+ return this;
+ }
+ includingMacOSCompatibleIOSAppsWhenSupported(verifiedBadgeOnly = false) {
+ if (this.objectGraph.appleSilicon.isSupportEnabled) {
+ if (!verifiedBadgeOnly) {
+ this.enablingFeature("macOSCompatibleIOSApps");
+ }
+ this.includingScopedAttributes("apps", ["isVerifiedForAppleSiliconMac"]);
+ }
+ return this;
+ }
+ /// Mark a request to include (or exclude) appBinaryTraits attributes.
+ includingAppBinaryTraitsAttribute(includeAppBinaryTraitsAttribute = true) {
+ this.includeAppBinaryTraitsAttribute = includeAppBinaryTraitsAttribute;
+ return this;
+ }
+ /**
+ * Mark a request to use custom attributes.
+ * This triggers usage of `customXYZ` extend attributes in url-builder for specific extend params
+ * e.g. `artwork`, `customArtwork`.
+ */
+ usingCustomAttributes(useCustomAttributes) {
+ this.useCustomAttributes = useCustomAttributes;
+ return this;
+ }
+ alwaysUseIdsAsQueryParam(value) {
+ this.useIdsAsQueryParam = value;
+ return this;
+ }
+ attributingTo(canonicalUrl) {
+ this.canonicalUrl = canonicalUrl;
+ return this;
+ }
+ withFilter(type, value) {
+ this.filterType = type;
+ this.filterValue = value;
+ return this;
+ }
+ withLimit(limit) {
+ this.limit = limit;
+ return this;
+ }
+ withSparseLimit(sparseLimit) {
+ if (sparseLimit !== null) {
+ this.sparseLimit = sparseLimit;
+ }
+ return this;
+ }
+ withSparseCount(sparseCount) {
+ if (sparseCount !== null) {
+ this.sparseCount = sparseCount;
+ }
+ return this;
+ }
+ enablingFeature(feature) {
+ if (!this.enabledFeatures) {
+ this.enabledFeatures = [];
+ }
+ this.enabledFeatures.push(feature);
+ return this;
+ }
+ enablingFeatures(features) {
+ if (!this.enabledFeatures) {
+ this.enabledFeatures = [];
+ }
+ this.enabledFeatures.push(...features);
+ return this;
+ }
+ /**
+ * Limit the request to include only certain fields.
+ * This should only be used in very select use-cases, where:
+ * - There is no possibility of data being used for other purposes, e.g. sidpack
+ * - All consumer of this data use it in the same narrow scope, e.g. requesting a set of icons for displaying without metadata only.
+ * @param fields Set of fields to limit to
+ */
+ asPartialResponseLimitedToFields(fields) {
+ this.fields = fields;
+ return this;
+ }
+ includesResourceType(resourceType) {
+ if (this.resourceType === resourceType) {
+ return true;
+ }
+ if (serverData.isDefinedNonNull(this.contentsResourceTypes)) {
+ return this.contentsResourceTypes.has(resourceType);
+ }
+ return false;
+ }
+ withCountryCodeOverride(countryCodeOverride) {
+ this.countryCodeOverride = countryCodeOverride;
+ return this;
+ }
+}
+/**
+ * Extracts the metadata association from the data.
+ * @param association The association to extract
+ * @param data The data to extract from
+ */
+export function extractMetaAssociationFromData(association, data) {
+ if (serverData.isNullOrEmpty(data)) {
+ return null;
+ }
+ const associationData = serverData.asArrayOrEmpty(data, `meta.associations.${association}.data`);
+ if (isNothing(associationData)) {
+ return null;
+ }
+ return [...associationData];
+}
+//# sourceMappingURL=data-fetching.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-structure.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-structure.js
new file mode 100644
index 0000000..cafcce8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/data-structure.js
@@ -0,0 +1,84 @@
+/**
+ * Created by joel on 1/25/18.
+ */
+import * as serverData from "../json-parsing/server-data";
+import { ResponseMetadata } from "../network/network";
+export function dataFromDataContainer(objectGraph, dataContainer) {
+ const dataArray = serverData.asArrayOrEmpty(dataContainer, "data");
+ if (dataArray.length > 1) {
+ objectGraph.console.warn("tried to extract data from container but more than one member present");
+ }
+ if (dataArray.length !== 1) {
+ return null;
+ }
+ return dataArray[0];
+}
+export function dataCollectionFromDataContainer(dataContainer) {
+ return serverData.asArrayOrEmpty(dataContainer, "data");
+}
+/**
+ * Check whether or not a server vended `Data` object is hydrated or not.
+ * @param {Data} data to check if hydrated.
+ */
+export function isDataHydrated(data) {
+ return serverData.isDefinedNonNull(data.attributes);
+}
+/**
+ * Check whether or not a entire data collection contains elements that are fully hydrated.
+ * @param {Data[]} dataArray Data array to check.
+ */
+export function isDataCollectionHydrated(dataCollection) {
+ // Iterate from the back to determine if fully hydrated faster - unhydrated elements tend to be latter elements.
+ const lastIndex = dataCollection.length - 1;
+ for (let index = lastIndex; index >= 0; index--) {
+ const data = dataCollection[index];
+ if (!isDataHydrated(data)) {
+ return false;
+ }
+ }
+ return true;
+}
+/**
+ * Check whether or not a entire data collection at least has 1 element that is fully hydrated.
+ * @param {Data[]} dataArray Data array to check.
+ */
+export function isDataCollectionPartiallyHydrated(dataCollection) {
+ for (const data of dataCollection) {
+ if (isDataHydrated(data)) {
+ return true;
+ }
+ }
+ return false;
+}
+/**
+ * Check whether or not a today data module represents a today page on the heuristic that:
+ * - The "Today" modules have labels with value 'TodayForApps' as opposed to `WhatYouMissed`
+ * - Date marker "Today" modules have a 'date' field.
+ */
+export function isModuleTodayForApps(todayModule) {
+ const todayModuleId = "TodayForApps";
+ const date = serverData.traverse(todayModule, "date");
+ return todayModule.label === todayModuleId || serverData.isDefinedNonNull(date);
+}
+/**
+ * Get chart results from a server response. Always returns chart segments with valid data
+ */
+export function chartResultsFromServerResponse(response) {
+ const resultsArray = serverData.asArrayOrEmpty(response, "results.apps");
+ return resultsArray.filter((segment) => {
+ return !serverData.isNull(segment.data);
+ });
+}
+export function dataCollectionFromResultsListContainer(resultsListContainer) {
+ return serverData.asArrayOrEmpty(resultsListContainer, "results.contents");
+}
+/**
+ * Get the metrics dictionary included in the meta data of a mediaApi Data object.
+ * @param {MetaDataProviding} metaDataProvidingObject
+ * @returns {MapLike<string> | null}
+ */
+export function metricsFromMediaApiObject(metaDataProvidingObject) {
+ return serverData.asDictionary(metaDataProvidingObject, "meta.metrics");
+}
+// endregion
+//# sourceMappingURL=data-structure.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/network.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/network.js
new file mode 100644
index 0000000..2b01d40
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/network.js
@@ -0,0 +1,304 @@
+/**
+ * Created by joel on 20/2/2018.
+ *
+ * This `network.ts` is the Media API arm of the netwok fetch requests.
+ * It is built on `Network` object and provides standard functionality specific for MAPI.
+ *
+ * @see `src/network.ts` for fetching from non-Media API endpoints
+ */
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as serverData from "../json-parsing/server-data";
+import { MetricsIdentifierType } from "../metrics/metrics-identifiers-cache";
+import { ResponseMetadata } from "../network/network";
+import * as urls from "../network/urls";
+import { extractMetaAssociationFromData } from "./data-fetching";
+import * as urlBuilder from "./url-builder";
+const secondaryUserIdHeaderName = "X-Apple-AppStore-UserId-Secondary";
+/**
+ * Implements the MAPI fetch, building URL from MAPI Request and opaquely managing initial token request and refreshes.
+ *
+ * @param {Request} request MAPI Request to fetch with.
+ * @param {FetchOptions} options? FetchOptions for the MAPI request.
+ * @returns {Promise<Type>} Promise resolving to some type for given MAPI request.
+ */
+export async function fetchData(objectGraph, request, options) {
+ var _a;
+ const startTime = Date.now();
+ const token = await objectGraph.mediaToken.refreshToken();
+ const fetchTimingMetricsBuilder = objectGraph.fetchTimingMetricsBuilder;
+ const requestOptions = options !== null && options !== void 0 ? options : {};
+ const personalizationIdentifier = (_a = objectGraph.personalizationMetricsIdentifiersCache) === null || _a === void 0 ? void 0 : _a.getMetricsIdForType(MetricsIdentifierType.user);
+ if (isSome(personalizationIdentifier)) {
+ if (isSome(requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.headers)) {
+ requestOptions.headers[secondaryUserIdHeaderName] = personalizationIdentifier;
+ }
+ else {
+ requestOptions.headers = {
+ [secondaryUserIdHeaderName]: personalizationIdentifier,
+ };
+ }
+ }
+ const response = await fetchWithToken(objectGraph, request, token, requestOptions, false, fetchTimingMetricsBuilder);
+ const endTime = Date.now();
+ if (request.canonicalUrl) {
+ response[ResponseMetadata.requestedUrl] = request.canonicalUrl;
+ }
+ const roundTripTimeIncludingWaiting = endTime - startTime;
+ if (roundTripTimeIncludingWaiting > 500) {
+ const longFetchUrl = urlBuilder.buildURLFromRequest(objectGraph, request).toString();
+ objectGraph.console.warn("Fetch took too long (" + roundTripTimeIncludingWaiting.toString() + "ms) " + longFetchUrl);
+ }
+ return response;
+}
+export function redirectParametersInUrl(objectGraph, url) {
+ const redirectURLParams = objectGraph.bag.redirectUrlWhitelistedQueryParams;
+ return redirectURLParams.filter((param) => { var _a; return serverData.isDefinedNonNull((_a = url.query) === null || _a === void 0 ? void 0 : _a[param]); });
+}
+/**
+ * Given a built URL, token, and options, calls into native networking APIs to fetch content.
+ *
+ * @param {string} url URL to fetch data from.
+ * @param {string} request The original request used to build this url.
+ * @param {string} token MAPI token key.
+ * @param {FetchOptions} options Fetch options for MAPI requests.
+ * @param {boolean} isRetry flag indicating whether this is a fetch retry following a 401 request, and media token was refreshed.
+ * @returns {Promise<Type>} Promise resolving to some type for given MAPI request.
+ */
+async function fetchWithToken(objectGraph, request, token, options = {}, isRetry = false, fetchTimingMetricsBuilder) {
+ var _a, _b;
+ const originalUrl = urlBuilder.buildURLFromRequest(objectGraph, request).toString();
+ // Removes all affiliate/redirect params for caching (https://connectme.apple.com/docs/DOC-577671)
+ const filteredURL = new urls.URL(originalUrl);
+ const redirectParameters = redirectParametersInUrl(objectGraph, filteredURL);
+ for (const param of redirectParameters) {
+ filteredURL.removeParam(param);
+ }
+ const filteredUrlString = filteredURL.toString();
+ let headers = options.headers;
+ if (!headers) {
+ headers = {};
+ }
+ headers["Authorization"] = "Bearer " + token;
+ const response = await objectGraph.network.fetch({
+ url: filteredUrlString,
+ headers: headers,
+ method: options.method,
+ body: options.requestBodyString,
+ timeout: options.timeout,
+ });
+ try {
+ if (response.status === 401 || response.status === 403) {
+ if (isRetry) {
+ throw Error("We refreshed the token but we still get 401 from the API");
+ }
+ objectGraph.mediaToken.resetToken();
+ return await objectGraph.mediaToken.refreshToken().then(async (newToken) => {
+ // Explicitly re-fetch with the original request so logging and metrics are correct
+ return await fetchWithToken(objectGraph, request, newToken, options, true, fetchTimingMetricsBuilder);
+ });
+ }
+ else if (response.status === 404) {
+ // item is not available in this storefront or perhaps not at all
+ throw noContentError();
+ }
+ else if (!response.ok) {
+ const error = new NetworkError(`Bad Status code ${response.status} for ${filteredUrlString}, original ${originalUrl}`);
+ error.statusCode = response.status;
+ throw error;
+ }
+ const parser = (resp) => {
+ var _a;
+ const parseStartTime = Date.now();
+ let result;
+ if (serverData.isNull(resp.body) || resp.body === "") {
+ if (resp.status === 204) {
+ // 204 indicates a success, but the response will typically be empty
+ // Create a fake result so that we don't throw an error when JSON parsing
+ const emptyData = {};
+ result = emptyData;
+ }
+ else {
+ throw noContentError();
+ }
+ }
+ else {
+ try {
+ result = JSON.parse(resp.body);
+ }
+ catch (e) {
+ let errorMessage = e.message;
+ if (["debug", "internal"].includes(objectGraph.client.buildType)) {
+ errorMessage = `${e.message}, body: ${resp.body}`;
+ }
+ throw new JSONParseError(errorMessage);
+ }
+ }
+ const parseEndTime = Date.now();
+ if (result) {
+ result[ResponseMetadata.pageInformation] = serverData.asJSONData(getPageInformationFromResponse(objectGraph, resp));
+ if (resp.metrics.length > 0) {
+ const metrics = {
+ ...resp.metrics[0],
+ parseStartTime: parseStartTime,
+ parseEndTime: parseEndTime,
+ };
+ result[ResponseMetadata.timingValues] = metrics;
+ }
+ else {
+ const fallbackMetrics = {
+ pageURL: resp.url,
+ parseStartTime,
+ parseEndTime,
+ };
+ result[ResponseMetadata.timingValues] = fallbackMetrics;
+ }
+ result[ResponseMetadata.contentMaxAge] = getContentTimeToLiveFromResponse(objectGraph, resp);
+ // If we have an empty data object, throw a 204 (No Content).
+ if (Array.isArray(result.data) &&
+ serverData.isArrayDefinedNonNullAndEmpty(result.data) &&
+ !serverData.asBooleanOrFalse(options.allowEmptyDataResponse)) {
+ throw noContentError();
+ }
+ if (Array.isArray(result.data) && request.originalOrdering.length > 1) {
+ result.data = buildHydratedDataListResponse(objectGraph, request.originalOrdering, (_a = result.data) !== null && _a !== void 0 ? _a : [], request.supplementaryMetadataAssociations);
+ }
+ result[ResponseMetadata.requestedUrl] = originalUrl;
+ }
+ return result;
+ };
+ if (isSome(fetchTimingMetricsBuilder)) {
+ return fetchTimingMetricsBuilder.measureParsing(response, parser);
+ }
+ else {
+ return parser(response);
+ }
+ }
+ catch (e) {
+ if (e instanceof NetworkError) {
+ throw e;
+ }
+ const correlationKey = (_a = response.headers["x-apple-jingle-correlation-key"]) !== null && _a !== void 0 ? _a : (_b = response.metrics[0]) === null || _b === void 0 ? void 0 : _b.clientCorrelationKey;
+ throw new Error(`Error Fetching - filtered: ${filteredUrlString}, original: ${originalUrl}, correlationKey: ${correlationKey !== null && correlationKey !== void 0 ? correlationKey : "N/A"}, ${e.name}, ${e.message}`);
+ }
+}
+export class NetworkError extends Error {
+}
+class JSONParseError extends Error {
+}
+export function noContentError() {
+ const error = new NetworkError(`No content`);
+ error.statusCode = 204;
+ return error;
+}
+export function notFoundError() {
+ const error = new NetworkError("Not found");
+ error.statusCode = 404;
+ return error;
+}
+const serverInstanceHeader = "x-apple-application-instance";
+const environmentDataCenterHeader = "x-apple-application-site";
+function getPageInformationFromResponse(objectGraph, response) {
+ const storeFrontHeader = objectGraph.client.storefrontIdentifier;
+ let storeFront = null;
+ if ((storeFrontHeader === null || storeFrontHeader === void 0 ? void 0 : storeFrontHeader.length) > 0) {
+ const storeFrontHeaderComponents = storeFrontHeader.split("-");
+ if (serverData.isDefinedNonNullNonEmpty(storeFrontHeaderComponents)) {
+ storeFront = storeFrontHeaderComponents[0];
+ }
+ }
+ return {
+ serverInstance: response.headers[serverInstanceHeader],
+ storeFrontHeader: storeFrontHeader,
+ language: objectGraph.bag.language,
+ storeFront: storeFront,
+ environmentDataCenter: response.headers[environmentDataCenterHeader],
+ };
+}
+function getContentTimeToLiveFromResponse(objectGraph, response) {
+ const cacheControlHeaderKey = Object.keys(response.headers).find((key) => key.toLowerCase() === "cache-control");
+ if (serverData.isNull(cacheControlHeaderKey) || cacheControlHeaderKey === "") {
+ return null;
+ }
+ const headerValue = response.headers[cacheControlHeaderKey];
+ if (serverData.isNullOrEmpty(headerValue)) {
+ return null;
+ }
+ const matches = headerValue.match(/max-age=(\d+)/);
+ if (serverData.isNull(matches) || matches.length < 2) {
+ return null;
+ }
+ return serverData.asNumber(matches[1]);
+}
+/**
+ * Builds the hydrated list of shelf items, based on the data in the response, and the original unhydated items.
+ */
+function buildHydratedDataListResponse(objectGraph, unhydratedData, hydratedDataCollection, associations = []) {
+ // Create a map of all the hydrated data items from the response, using the resource type and ID as the key
+ const hydratedDataMap = {};
+ for (const dataItem of hydratedDataCollection) {
+ const key = dataMapKey(objectGraph, dataItem.type, dataItem.id);
+ hydratedDataMap[key] = dataItem;
+ }
+ // Next iterate through the unhyrated items in their original order, and swap in the hydrated item.
+ const hydratedItems = [];
+ for (const unhydratedItem of unhydratedData) {
+ const key = dataMapKey(objectGraph, unhydratedItem.type, unhydratedItem.id);
+ const hydratedItem = hydratedDataMap[key];
+ if (isSome(hydratedItem)) {
+ /// Start with a base of what was there already, this is to ensure we don't lose any existing meta data
+ /// if some of the requested associations are not present in the response.
+ if (serverData.isDefinedNonNullNonEmpty(associations)) {
+ hydratedItem.meta = {
+ ...unhydratedItem.meta,
+ };
+ for (const association of associations) {
+ hydrateMetaAssociationIfNecessary(objectGraph, association, hydratedItem, unhydratedItem, hydratedDataMap);
+ }
+ }
+ hydratedItems.push(hydratedItem);
+ }
+ }
+ return hydratedItems;
+}
+/**
+ * Creates a key to use in a mapping of data items.
+ * @param objectGraph Current object graph
+ * @param data The data item
+ * @returns A string composed from the data item ID and resource type
+ */
+function dataMapKey(objectGraph, resourceType, id) {
+ return `${resourceType}_${id}`;
+}
+/**
+ * @param objectGraph The current object graph
+ * @param association The association we'd like to hydrate in the meta object
+ * @param unhydratedItem The original MAPI data item to look for editorial card, and fill in with a fetched card if there is one
+ * @param hydratedDataMap The data map of all the fetched items keyed by id / type
+ */
+function hydrateMetaAssociationIfNecessary(objectGraph, association, hydratedItem, unhydratedItem, hydratedDataMap) {
+ var _a;
+ if (isNothing(hydratedItem.meta)) {
+ hydratedItem.meta = {
+ associations: {},
+ };
+ }
+ else if (isNothing(hydratedItem.meta.associations)) {
+ hydratedItem.meta.associations = {};
+ }
+ const unhydratedAssociationsData = extractMetaAssociationFromData(association, unhydratedItem);
+ if (serverData.isDefinedNonNullNonEmpty(unhydratedAssociationsData)) {
+ const hydratedAssociationData = [];
+ for (const unhydratedAssociation of unhydratedAssociationsData) {
+ const associationDataKey = dataMapKey(objectGraph, unhydratedAssociation.type, unhydratedAssociation.id);
+ const associationData = hydratedDataMap[associationDataKey];
+ if (isSome(associationData)) {
+ hydratedAssociationData.push(associationData);
+ }
+ }
+ const hydratedAssociations = (_a = serverData.asDictionary(hydratedItem.meta.associations)) !== null && _a !== void 0 ? _a : {};
+ hydratedAssociations[association] = {
+ data: hydratedAssociationData,
+ };
+ }
+}
+//# sourceMappingURL=network.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/platform-attributes.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/platform-attributes.js
new file mode 100644
index 0000000..3849624
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/platform-attributes.js
@@ -0,0 +1,143 @@
+import { isNothing } from "@jet/environment/types/optional";
+import * as serverData from "../json-parsing/server-data";
+import * as attributes from "./attributes";
+/**
+ * There are nested attributes that are platform-specific. If provided with a platform for which the response data has
+ * platform-specific attributes, this function will return those attributes.
+ * @param {Data} data The data for which to determine attributes.
+ * @param {AttributePlatform} platform The platform to fetch the attributes for
+ * @returns {any} If a platform is provided, returns `true` exactly when platform-specific attributes exist for that
+ * platform. If no platform is provided, it simply returns the data's top-level attributes.
+ */
+function attributesForPlatform(data, platform) {
+ const allPlatformAttributes = attributes.attributeAsDictionary(data, "platformAttributes");
+ return serverData.traverse(allPlatformAttributes, platform !== null && platform !== void 0 ? platform : undefined);
+}
+/**
+ * Determines if attributes exist for a given platform
+ * @param {Data} data The data to check
+ * @param {AttributePlatform} platform The platform to check
+ * @returns {boolean} True if the platform exists in the data's platform attributes. False if not.
+ */
+export function hasPlatformAttribute(data, platform) {
+ const platformAttributes = attributesForPlatform(data, platform);
+ return serverData.isDefinedNonNullNonEmpty(platformAttributes);
+}
+/**
+ * Retrieve the specified attribute from the data as a dictionary
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @returns The value for the requested attribute
+ */
+export function platformAttributeAsDictionary(data, platform, attributePath) {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return null;
+ }
+ return serverData.asDictionary(platformAttributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as an array.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @returns {any[]} The attribute value as an array.
+ */
+export function platformAttributeAsArray(data, platform, attributePath) {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (isNothing(platformAttributes)) {
+ return null;
+ }
+ return serverData.asArray(platformAttributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as an array, coercing to an empty array if the object is not an array.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @returns {any[]} The attribute value as an array.
+ */
+export function platformAttributeAsArrayOrEmpty(data, platform, attributePath) {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return [];
+ }
+ return serverData.asArrayOrEmpty(platformAttributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as a string.
+ *
+ * If the attribute lives under the platform-specific attributes, then a platform may be provided to properly call in to
+ * the nested structure.
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The object path for the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {string} The attribute value as a string.
+ */
+export function platformAttributeAsString(data, platform, attributePath, policy = "coercible") {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return null;
+ }
+ return serverData.asString(platformAttributes, attributePath, policy);
+}
+/**
+ * Retrieve the specified attribute from the data as a boolean.
+ *
+ * If the attribute lives under the platform-specific attributes, then a platform may be provided to properly call in to
+ * the nested structure.
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a boolean.
+ */
+export function platformAttributeAsBoolean(data, platform, attributePath, policy = "coercible") {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return null;
+ }
+ return serverData.asBoolean(platformAttributes, attributePath, policy);
+}
+/**
+ * Retrieve the specified attribute from the data as a boolean, which will be `false` if the attribute does not exist.
+ *
+ * If the attribute lives under the platform-specific attributes, then a platform may be provided to properly call in to
+ * the nested structure.
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @returns {boolean} The attribute value as a boolean, coercing to `false` if the value is not present..
+ */
+export function platformAttributeAsBooleanOrFalse(data, platform, attributePath) {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return false;
+ }
+ return serverData.asBooleanOrFalse(platformAttributes, attributePath);
+}
+/**
+ * Retrieve the specified attribute from the data as a number.
+ *
+ * If the attribute lives under the platform-specific attributes, then a platform may be provided to properly call in to
+ * the nested structure.
+ * @param data The data from which to retrieve the attribute.
+ * @param platform The platform to look up
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a number.
+ */
+export function platformAttributeAsNumber(data, platform, attributePath, policy = "coercible") {
+ const platformAttributes = attributesForPlatform(data, platform);
+ if (serverData.isNull(platformAttributes)) {
+ return null;
+ }
+ return serverData.asNumber(platformAttributes, attributePath, policy);
+}
+// endregion
+//# sourceMappingURL=platform-attributes.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/relationships.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/relationships.js
new file mode 100644
index 0000000..0f96574
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/relationships.js
@@ -0,0 +1,43 @@
+import * as serverData from "../json-parsing/server-data";
+export function hasRelationship(data, relationshipType, checkForContent = true) {
+ const relationshipDataContainer = relationship(data, relationshipType);
+ if (!relationshipDataContainer) {
+ return false;
+ }
+ if (!relationshipDataContainer.data || (checkForContent && relationshipDataContainer.data.length === 0)) {
+ return false;
+ }
+ return true;
+}
+export function relationship(data, relationshipType) {
+ if (serverData.isDefinedNonNull(data)) {
+ return serverData.asInterface(data.relationships, relationshipType);
+ }
+ return null;
+}
+export function relationshipViewsContainer(data, relationshipType) {
+ return serverData.asInterface(data.views, relationshipType);
+}
+export function relationshipData(objectGraph, data, relationshipType) {
+ const relationshipDataArray = serverData.asArrayOrEmpty(data.relationships, [
+ relationshipType,
+ "data",
+ ]);
+ if (relationshipDataArray.length === 0) {
+ return null;
+ }
+ if (relationshipDataArray.length > 1) {
+ objectGraph.console.warn(`there was an array of relationships when only the first was asked for in relationship ${relationshipType}`);
+ }
+ return relationshipDataArray[0];
+}
+export function relationshipCollection(data, relationshipType, allowNulls = false) {
+ if (!hasRelationship(data, relationshipType, false) && allowNulls) {
+ return null;
+ }
+ return serverData.asArrayOrEmpty(data.relationships, [relationshipType, "data"]);
+}
+export function relationshipViewsCollection(data, relationshipType) {
+ return serverData.asArrayOrEmpty(data.views, [relationshipType, "data"]);
+}
+//# sourceMappingURL=relationships.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/url-builder.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/url-builder.js
new file mode 100644
index 0000000..955529c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/url-builder.js
@@ -0,0 +1,381 @@
+/**
+ * Created by joel on 11/4/2018.
+ */
+import { isNothing, isSome } from "@jet/environment/types/optional";
+import * as serverData from "../json-parsing/server-data";
+import * as urls from "../network/urls";
+import * as attributes from "./attributes";
+/// this is exposed for compatibility. If you find yourself needing to use this outside of the media api module you
+/// probably have code smell. DO NOT USE.
+export function buildURLFromRequest(objectGraph, request) {
+ var _a, _b;
+ const baseURL = request.href && request.href.length > 0
+ ? baseURLForHref(request.href)
+ : baseURLForResourceType(objectGraph, request.isMixedMediaRequest, request.resourceType, request.countryCodeOverride);
+ const mediaApiURL = new urls.URL(baseURL);
+ if (serverData.isDefinedNonNullNonEmpty(request.resourceType)) {
+ for (const pathComponent of pathComponentsForRequest(request.resourceType, request.targetResourceType)) {
+ mediaApiURL.append("pathname", pathComponent);
+ }
+ }
+ if (request.isMixedMediaRequest) {
+ for (const [resourceType, ids] of request.idsByResourceType.entries()) {
+ mediaApiURL.param(`ids[${resourceType}]`, Array.from(ids).sort().join(","));
+ }
+ }
+ else if (request.ids.size > 1 || request.useIdsAsQueryParam) {
+ mediaApiURL.param("ids", Array.from(request.ids).sort().join(","));
+ }
+ else if (request.ids.size === 1) {
+ const id = request.ids.values().next().value;
+ mediaApiURL.append("pathname", id);
+ }
+ if (request.resourceType !== undefined) {
+ const trailingPathComponent = trailingPathComponentForResourceType(request.resourceType);
+ if (serverData.isDefinedNonNullNonEmpty(trailingPathComponent)) {
+ mediaApiURL.append("pathname", trailingPathComponent);
+ }
+ }
+ mediaApiURL.param("platform", (_a = request.platform) !== null && _a !== void 0 ? _a : undefined);
+ if (request.additionalPlatforms.size > 0) {
+ mediaApiURL.param("additionalPlatforms", Array.from(request.additionalPlatforms).sort().join(","));
+ }
+ /**
+ * Add `extend` attributes.
+ * Note that when `useCustomAttributes` is true, there is `customArtwork` param even when `attributeIncludes` is initially empty.
+ * This due MAPI auto-extend for `artwork`, and lack of auto-extend for `customArtwork`
+ */
+ if (request.attributeIncludes.size > 0 || request.useCustomAttributes) {
+ let extendAttributes = Array.from(request.attributeIncludes);
+ if (request.useCustomAttributes) {
+ extendAttributes = convertRequestAttributesToCustomAttributes(objectGraph, extendAttributes);
+ }
+ extendAttributes.sort();
+ mediaApiURL.param("extend", extendAttributes.join(","));
+ }
+ // Add age restriction if present.
+ if (serverData.isDefinedNonNull(request.ageRestriction) && objectGraph.bag.enableAgeRatingFilter) {
+ mediaApiURL.param("restrict[ageRestriction]", request.ageRestriction.toString());
+ }
+ // Automatically extend iOS catalog requests for apps to include appBinaryTraits.
+ if (request.includeAppBinaryTraitsAttribute) {
+ request.includingScopedAttributes("apps", ["appBinaryTraits"]);
+ }
+ if (serverData.isDefinedNonNull(request.scopedAttributeIncludes)) {
+ for (const [dataType, scopedIncludes] of request.scopedAttributeIncludes.entries()) {
+ mediaApiURL.param(`extend[${dataType}]`, Array.from(scopedIncludes).sort().join(","));
+ }
+ }
+ if (request.relationshipIncludes.size > 0) {
+ mediaApiURL.param("include", Array.from(request.relationshipIncludes).sort().join(","));
+ }
+ if (serverData.isDefinedNonNull(request.scopedRelationshipIncludes)) {
+ for (const [dataType, scopedIncludes] of request.scopedRelationshipIncludes.entries()) {
+ mediaApiURL.param(`include[${dataType}]`, Array.from(scopedIncludes).sort().join(","));
+ }
+ }
+ if (serverData.isDefinedNonNull(request.metaIncludes)) {
+ for (const [dataType, scopedMeta] of request.metaIncludes.entries()) {
+ mediaApiURL.param(`meta[${dataType}]`, Array.from(scopedMeta).sort().join(","));
+ }
+ }
+ if (serverData.isSetDefinedNonNullNonEmpty(request.viewsIncludes)) {
+ mediaApiURL.param("views", Array.from(request.viewsIncludes).sort().join(","));
+ }
+ if (serverData.isDefinedNonNull(request.kindIncludes)) {
+ for (const [dataType, scopedMeta] of request.kindIncludes.entries()) {
+ mediaApiURL.param(`kinds[${dataType}]`, Array.from(scopedMeta).sort().join(","));
+ }
+ }
+ if (serverData.isDefinedNonNull(request.associateIncludes)) {
+ for (const [dataType, scopedAssociate] of request.associateIncludes.entries()) {
+ mediaApiURL.param(`associate[${dataType}]`, Array.from(scopedAssociate).sort().join(","));
+ }
+ }
+ if (serverData.isDefinedNonNull(request.scopedAvailableInIncludes)) {
+ for (const [dataType, scopedAvailableIn] of request.scopedAvailableInIncludes.entries()) {
+ mediaApiURL.param(`availableIn[${dataType}]`, Array.from(scopedAvailableIn).sort().join(","));
+ }
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.fields)) {
+ let extendedFields = Array.from(request.fields);
+ if (request.useCustomAttributes) {
+ extendedFields = convertRequestFieldsToCustomFields(extendedFields);
+ }
+ request.fields.sort();
+ mediaApiURL.param("fields", extendedFields.join(","));
+ }
+ if (serverData.isDefinedNonNull(request.limit) && request.limit > 0) {
+ mediaApiURL.param(`limit`, `${request.limit}`);
+ }
+ if (serverData.isDefinedNonNull(request.sparseLimit)) {
+ mediaApiURL.param(`sparseLimit`, `${request.sparseLimit}`);
+ }
+ if (serverData.isDefinedNonNull(request.scopedSparseLimit)) {
+ for (const [dataType, scopedLimit] of request.scopedSparseLimit.entries()) {
+ mediaApiURL.param(`sparseLimit[${dataType}]`, String(scopedLimit));
+ }
+ }
+ if (serverData.isDefinedNonNull(request.sparseCount)) {
+ mediaApiURL.param(`sparseCount`, `${request.sparseCount}`);
+ }
+ for (const relationshipID of Object.keys(request.relationshipLimits).sort()) {
+ const limit = request.relationshipLimits[relationshipID];
+ mediaApiURL.param(`limit[${relationshipID}]`, `${limit}`);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.additionalQuery)) {
+ mediaApiURL.append("query", request.additionalQuery);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.searchTerm)) {
+ // Search hints shouldn't add `search` to the end of the path name as the correct final path
+ // is `v1/catalog/us/search/suggestions`, which is handled by `trailingPathComponentForResourceType()`
+ // Search hints also shouldn't have the bubble param
+ if (isNothing(request.resourceType) || request.resourceType !== "search-hints") {
+ mediaApiURL.append("pathname", "search");
+ mediaApiURL.param("bubble[search]", request.searchTypes.join(","));
+ }
+ mediaApiURL.param("term", request.searchTerm);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.enabledFeatures)) {
+ mediaApiURL.param("with", request.enabledFeatures.join(","));
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.context)) {
+ mediaApiURL.param("contexts", request.context);
+ }
+ if (serverData.isDefinedNonNullNonEmpty(request.filterType) &&
+ serverData.isDefinedNonNullNonEmpty(request.filterValue)) {
+ mediaApiURL.param(`filter[${request.filterType}]`, request.filterValue);
+ }
+ const language = objectGraph.bag.mediaApiLanguage;
+ // Only attach the language query param if:
+ // - there is a language available in the bag, and
+ // - a language has not been manually attached to the request. This is used for special situations to override the language for particular content,
+ // so it should take precedence over the default.
+ if (serverData.isDefinedNonNull(language) && serverData.isNull(request.additionalQuery["l"])) {
+ mediaApiURL.param("l", language);
+ }
+ mediaApiURL.host = (_b = hostForUrl(objectGraph, mediaApiURL, request)) !== null && _b !== void 0 ? _b : undefined;
+ mediaApiURL.protocol = "https";
+ return mediaApiURL;
+}
+/**
+ * Get the media api base url for all requests.
+ * @param objectGraph Current object graph
+ * @param isMixedCatalogRequest Whether the request intends to use mixed catalog
+ * @param type The request resource type
+ * @param overrideCountryCode A country code to override the bag value.
+ * @returns A built base URL string
+ */
+function baseURLForResourceType(objectGraph, isMixedCatalogRequest, type, overrideCountryCode) {
+ switch (type) {
+ case "personalization-data":
+ case "reviews":
+ case "app-distribution":
+ return `/v1/${endpointTypeForResourceType(type)}/`;
+ default:
+ const countryCode = isSome(overrideCountryCode) && overrideCountryCode.length > 0
+ ? overrideCountryCode
+ : objectGraph.bag.mediaCountryCode;
+ const baseURL = `/v1/${endpointTypeForResourceType(type)}/${countryCode}`;
+ return isMixedCatalogRequest ? baseURL : `${baseURL}/`;
+ }
+}
+/**
+ * Get the media api base url for all requests that already have an href.
+ * @return {string}
+ */
+function baseURLForHref(href) {
+ return href;
+}
+function endpointTypeForResourceType(type) {
+ switch (type) {
+ case "apps":
+ case "app-events":
+ case "arcade-apps":
+ case "app-bundles":
+ case "charts":
+ case "contents":
+ case "developers":
+ case "eula":
+ case "in-apps":
+ case "multiple-system-operators":
+ case "user-reviews":
+ case "customers-also-bought-apps-with-download-intent":
+ return "catalog";
+ case "categories":
+ case "editorial-pages":
+ case "editorial-items":
+ case "editorial-item-groups":
+ case "editorial-elements":
+ case "groupings":
+ case "multiplex":
+ case "multirooms":
+ case "rooms":
+ case "today":
+ case "collections":
+ return "editorial";
+ case "ratings":
+ return "ratings";
+ case "personalization-data":
+ case "reviews":
+ return "me";
+ case "upsellMarketingItem":
+ case "landing":
+ return "engagement";
+ case "landing:new-protocol":
+ return "recommendations";
+ case "personal-recommendations":
+ return "recommendations";
+ case "engagement-data":
+ return "engagement";
+ case "app-distribution":
+ return "listing";
+ default:
+ return "catalog";
+ }
+}
+/**
+ * The path component to add for the given resource
+ */
+function pathComponentsForRequest(resourceType, targetResourceType) {
+ switch (resourceType) {
+ case "eula":
+ if (targetResourceType === undefined) {
+ return [resourceType]; // Might be modelled better as an error.
+ }
+ else {
+ return [resourceType, targetResourceType];
+ }
+ case "landing:new-protocol":
+ return [];
+ case "landing":
+ if (targetResourceType === undefined) {
+ return ["search", resourceType]; // Might be modelled better as an error.
+ }
+ else {
+ return ["search", resourceType, targetResourceType];
+ }
+ case "user-reviews":
+ return ["apps"];
+ case "reviews":
+ return ["reviews", "apps"];
+ case "multiplex":
+ return ["multiplex"];
+ case "upsellMarketingItem":
+ return ["upsell", "marketing-items"];
+ case "trending-contents":
+ return ["search", resourceType];
+ case "customers-also-bought-apps-with-download-intent":
+ return ["apps"];
+ case "searchLanding:see-all":
+ return [];
+ case "search-hints":
+ return [];
+ case "app-distribution":
+ return ["apps"];
+ default:
+ return [resourceType];
+ }
+}
+/**
+ * Add a component to the end of the path for the given resource
+ */
+function trailingPathComponentForResourceType(type) {
+ switch (type) {
+ case "user-reviews":
+ return "reviews";
+ case "customers-also-bought-apps-with-download-intent":
+ return "view/customers-also-bought-apps-with-download-intent";
+ case "collections":
+ return "contents";
+ case "searchLanding:see-all":
+ return "view/see-all";
+ case "search-hints":
+ return "search/suggestions";
+ default:
+ return null;
+ }
+}
+function hostForUrl(objectGraph, url, request) {
+ var _a;
+ const path = (_a = url.pathname) !== null && _a !== void 0 ? _a : "";
+ let host = null;
+ if (request.isStorePreviewRequest) {
+ host = objectGraph.bag.mediaPreviewHost;
+ }
+ else if (request.isMediaRealmRequest) {
+ host = objectGraph.bag.mediaRealmHost;
+ }
+ else if (path.includes("search/landing")) {
+ // Special case <rdar://problem/50185140> RFW3: Use bag key "apps-media-api-edge-end-points" for "search/landing" end-point
+ // until we figure out a better way to test the paths
+ const useEdgeForSearchLanding = objectGraph.bag.edgeEndpoints.indexOf("landing") !== -1;
+ host = useEdgeForSearchLanding ? objectGraph.bag.mediaEdgeHost(objectGraph) : objectGraph.bag.mediaHost;
+ }
+ else if (request.resourceType === "app-distribution" && isSome(objectGraph.bag.appDistributionMediaAPIHost)) {
+ host = objectGraph.bag.appDistributionMediaAPIHost;
+ }
+ else if (request.isMixedMediaRequest && objectGraph.bag.mediaAPICatalogMixedShouldUseEdge) {
+ // CatalogMixed endpoint should be routed to edge when the bag is enabled.
+ host = objectGraph.bag.mediaEdgeHost(objectGraph);
+ }
+ else if (objectGraph.bag.edgeEndpoints.map((endpoint) => path.includes(endpoint)).reduce(truthReducer, false)) {
+ if (path.includes("search") && !path.includes("view/see-all")) {
+ host = objectGraph.bag.mediaEdgeSearchHost;
+ }
+ else {
+ host = objectGraph.bag.mediaEdgeHost(objectGraph);
+ }
+ }
+ else {
+ host = objectGraph.bag.mediaHost;
+ }
+ if (serverData.isNull(host)) {
+ host = "api.apps.apple.com";
+ }
+ return host;
+}
+const truthReducer = (accumulator, current) => accumulator || current;
+/**
+ * Performs a conversion for given attribute to fetch the customAttribute variant of it.
+ * @param objectGraph The object graph
+ * @param attribute Attribute to convert if needed, e.g. `artwork`
+ * @returns `string` attribute that is custom equivalent of `attribute`, or `attribute` unmodified.
+ */
+function convertRequestAttributesToCustomAttributes(objectGraph, requestAttributes) {
+ const convertedAttributes = requestAttributes.map((attribute) => {
+ var _a;
+ return (_a = attributes.attributeKeyAsCustomAttributeKey(attribute)) !== null && _a !== void 0 ? _a : attribute;
+ });
+ /**
+ * `artwork` is an autoincluded resources, so `attributes` usually doesn't contain this explicitly :(
+ * Per MAPI contract, we "autoinclude" `customArtwork` explicitly for requests with custon attributes.
+ */
+ convertedAttributes.push("customArtwork");
+ /**
+ * `iconArtwork` is not autoincluded, but we need to ensure it is always requested even alongside its
+ * custom counterpart, `customIconArtwork`. This is because we might be viewing a macOS only app on iOS,
+ * where custom attributes are supported, but not available for macOS apps.
+ */
+ if (requestAttributes.includes("iconArtwork")) {
+ convertedAttributes.push("iconArtwork");
+ }
+ /**
+ * `customDeepLink` is always desired as an included resource in case an app decides to use a custom tap destination.
+ * Per MAPI contract, we "autoinclude" `customDeepLink` explicitly for all requests with custom attributes.
+ */
+ convertedAttributes.push("customDeepLink");
+ return convertedAttributes;
+}
+/**
+ * Performs the conversion for given field value (which may specify `attributes` keys) to customAttribute variant of it.
+ */
+function convertRequestFieldsToCustomFields(requestFields) {
+ const convertedFields = requestFields.map((fieldName) => {
+ var _a;
+ return (_a = attributes.attributeKeyAsCustomAttributeKey(fieldName)) !== null && _a !== void 0 ? _a : fieldName;
+ });
+ // DON'T include `customArtwork` for request `field` conversion. Only specify if `artwork` was initially in `requestFields`.
+ return convertedFields;
+}
+//# sourceMappingURL=url-builder.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/media/util.js b/node_modules/@jet-app/app-store/tmp/src/foundation/media/util.js
new file mode 100644
index 0000000..77ed5c2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/media/util.js
@@ -0,0 +1,185 @@
+import { noContentError, notFoundError } from "./network";
+/**
+ * Validate an untrusted adam ID without contacting the server.
+ *
+ * This allows avoiding the work of calling the server with completely bogus
+ * IDs. It's scoped to web since ID formats can change and web can be updated
+ * easily (whereas backporting to older native clients is trickier).
+ *
+ * @param {AppStoreObjectGraph} objectGraph
+ * @param {string} id - the Adam ID to validate
+ * @returns {void} undefined if valid, throws a `NetworkError` (204) otherwise
+ */
+export function validateAdamId(objectGraph, id) {
+ if (objectGraph.client.isWeb && !isAdamId(id)) {
+ throw noContentError();
+ }
+}
+/**
+ * Check if an ID is a valid Adam ID.
+ *
+ * @param {string} id - string to validate
+ * @return {boolean} true if valid, false otherwise
+ */
+function isAdamId(id) {
+ // Media API actually validates if the number <= 2^63-1. But doubles in
+ // JavaScript cannot precisely represent this (the closest number is >2^63)
+ // so checking this requires BigInt, which might not be available. At the
+ // end of the day, checking this is likely not worth the complexity. 2^63-1
+ // is 9223372036854775807 so we just restrict the number of digits.
+ //
+ // See: https://github.pie.apple.com/its/amp-enums/blob/f75500b44f871f35ba3ce459a5ff4c9f225e71b0/src/main/java/com/apple/jingle/store/IdSpace.java#L131-L140
+ // See: https://github.com/google/guava/blob/869a75a1e3ff85d36672a3cd154772dc90c7b3d2/guava/src/com/google/common/primitives/Longs.java#L400-L440
+ // See: https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/Long.html#MAX_VALUE
+ return /^\d{1,19}$/.test(id);
+}
+/**
+ * Validate an untrusted Featured Content ID without contacting the server.
+ *
+ * This allows avoiding the work of calling the server with completely bogus
+ * IDs. It's scoped to web since ID formats can change and web can be updated
+ * easily (whereas backporting to older native clients is trickier).
+ *
+ * @param {AppStoreObjectGraph} objectGraph
+ * @param {string} id the FC ID to validate
+ * @returns {void} undefined if valid, throws a `NetworkError` (204) otherwise
+ */
+export function validateFcId(objectGraph, id) {
+ if (objectGraph.client.isWeb && !isFcId(id)) {
+ throw noContentError();
+ }
+}
+/**
+ * Check if an ID is a valid Featured Content (FC) ID.
+ *
+ * @param {string} id - string to validate
+ * @return {boolean} true if valid, false otherwise
+ */
+function isFcId(id) {
+ // FcIds are actually Adam IDs under the hood.
+ // See: https://github.pie.apple.com/its/Jingle/blob/d2d051c9ef2891f72d5f02f5bbbf2d7748afa7b9/MZStoreComponents/src/main/java/com/apple/jingle/app/store/editorial/SFEditorialHelper.java#L293
+ return isAdamId(id);
+}
+/**
+ * Validate an untrusted (Chart) Genre ID without contacting the server.
+ *
+ * This allows avoiding the work of calling the server with completely bogus
+ * IDs. It's scoped to web since ID formats can change and web can be updated
+ * easily (whereas backporting to older native clients is trickier).
+ *
+ * @param {AppStoreObjectGraph} objectGraph
+ * @param {string} id the genre ID to validate
+ * @returns {void} undefined if valid, throws a `NetworkError` (204) otherwise
+ */
+export function validateGenreId(objectGraph, id) {
+ if (objectGraph.client.isWeb && !isGenreId(id)) {
+ throw noContentError();
+ }
+}
+/**
+ * Check if an ID is a valid (Chart) Genre ID.
+ *
+ * @param {string} id - string to validate
+ * @return {boolean} true if valid, false otherwise
+ */
+function isGenreId(id) {
+ // Genre IDs are Java int (signed 32-bit).
+ //
+ // Technically negative is not excluded, but all charts are positive, so
+ // filter out negative.
+ //
+ // See: https://github.pie.apple.com/its/Jingle/blob/main/shared/reference-data-logic/MZReferenceDataLogic/src/main/java/com/apple/jingle/eo/MZGenreService.java#L555
+ return isPositiveJavaInt(id);
+}
+/**
+ * Validate an untrusted Grouping ID without contacting the server.
+ *
+ * This allows avoiding the work of calling the server with completely bogus
+ * IDs. It's scoped to web since ID formats can change and web can be updated
+ * easily (whereas backporting to older native clients is trickier).
+ *
+ * @param {AppStoreObjectGraph} objectGraph
+ * @param {string} id the grouping ID to validate
+ * @returns {void} undefined if valid, throws a `NetworkError` (204) otherwise
+ */
+export function validateGroupingId(objectGraph, id) {
+ if (objectGraph.client.isWeb && !isGroupingId(id)) {
+ throw noContentError();
+ }
+}
+/**
+ * Check if an ID is a valid Grouping ID.
+ *
+ * @param {string} id - string to validate
+ * @return {boolean} true if valid, false otherwise
+ */
+function isGroupingId(id) {
+ // Grouping IDs are Java int (signed 32-bit)
+ //
+ // Technically negative does not fail to parse, but they're all positive in
+ // practice.
+ //
+ // See: https://github.pie.apple.com/its/Jingle/blob/aaccec936f1feed227fd171ae66bb160cf38e497/MZStorePlatform/src/main/java/com/apple/jingle/store/mediaapi/util/SFMediaAPIEditorialUtil.java#L634
+ return isPositiveJavaInt(id);
+}
+/**
+ * Test if a string contains a Java int.
+ *
+ * @param {string} s - the string to test
+ * @returns {boolean} true if the string is a stringified Java int, false otherwise
+ */
+function isPositiveJavaInt(s) {
+ // Java int is 32-bit signed. We can check bounds since signed integers are
+ // exactly representable as doubles (actually up to 2^53 is representable
+ // exactly).
+ //
+ // See: https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/Integer.html#MAX_VALUE
+ return /^\d+$/.test(s) && parseInt(s, 10) <= 2147483647;
+}
+/**
+ * Validate an untrusted Editorial Shelf Collection ID without contacting the server.
+ *
+ * This allows avoiding the work of calling the server with completely bogus
+ * IDs. It's scoped to web since ID formats can change and web can be updated
+ * easily (whereas backporting to older native clients is trickier).
+ *
+ * @param {AppStoreObjectGraph} objectGraph
+ * @param {string} id the editorial shelf collection ID to validate
+ * @returns {void} undefined if valid, throws a `NetworkError` (204) otherwise
+ */
+export function validateEditorialShelfCollectionId(objectGraph, id) {
+ if (objectGraph.client.isWeb && !isEditorialShelfCollectionId(id)) {
+ throw noContentError();
+ }
+}
+/**
+ * Check if an ID is a valid editorial shelf collection ID.
+ *
+ * @param {string} id - string to validate
+ * @return {boolean} true if valid, false otherwise
+ */
+function isEditorialShelfCollectionId(id) {
+ // Ids are prefixed with "eds.". Beyond that they do have a UUID-type
+ // identifier that follows, but that seems more liable to change.
+ //
+ // See: https://github.pie.apple.com/its/amp-enums/blob/f75500b44f871f35ba3ce459a5ff4c9f225e71b0/src/main/java/com/apple/jingle/store/IdSpace.java#L43C5-L43C20
+ // See: https://github.pie.apple.com/its/amp-enums/blob/f75500b44f871f35ba3ce459a5ff4c9f225e71b0/src/main/java/com/apple/jingle/store/IdSpace.java#L250-L252
+ return id.startsWith("eds.");
+}
+/**
+ * Validates if a request can be performed for `vision` platform content, based on
+ * the feature flag is disabled.
+ *
+ * @param {AppStoreObjectGraph} objectGraph The application's state graph.
+ * @throws {NetworkError} Throws a 404 error if access is restricted.
+ * @returns {void}
+ */
+export function validateNeedsVisionRestriction(objectGraph) {
+ var _a;
+ if (objectGraph.client.isWeb &&
+ ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.previewPlatform) === "vision" &&
+ !objectGraph.bag.enableVisionPlatform) {
+ throw notFoundError();
+ }
+}
+//# sourceMappingURL=util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/metrics/buy-parameters.js b/node_modules/@jet-app/app-store/tmp/src/foundation/metrics/buy-parameters.js
new file mode 100644
index 0000000..a48e886
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/metrics/buy-parameters.js
@@ -0,0 +1,112 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+/**
+ * An object which allows buy parameters to be operated on like a hash map.
+ */
+export class BuyParameters {
+ /**
+ * Create a buy parameters object with an optional string representation.
+ *
+ * @param buyParams A string containing buy parameters.
+ */
+ constructor(buyParams) {
+ this._values = {};
+ if (isSome(buyParams) && buyParams.length > 0) {
+ const pairs = buyParams.split("&");
+ for (const pair of pairs) {
+ const [encodedKey, encodedValue] = pair.split("=");
+ const key = decodeURIComponent(encodedKey);
+ const value = isNothing(encodedValue) ? "" : decodeURIComponent(encodedValue);
+ this._values[key] = value;
+ }
+ }
+ }
+ /**
+ * Determine the search key to use for the given parameters.
+ *
+ * @param key The key to determine the search value for.
+ * @param prefix An optional prefix to prepend to `key`.
+ * @returns A key which can be used to store and retrieve
+ * values in this buy parameters object.
+ */
+ _searchKey(key, prefix) {
+ if (key.length === 0) {
+ throw new Error("key may not be zero length");
+ }
+ if (isNothing(prefix) || prefix.length === 0) {
+ return key;
+ }
+ else {
+ return `${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
+ }
+ }
+ /**
+ * Returns the value associated with a given key.
+ *
+ * @param key The key to retrieve the associated value for.
+ * @param keyPrefix The prefix to add to the key. Defaults to `mt`.
+ * @returns The value associated with `key`, or `undefined` is none is found.
+ */
+ get(key, keyPrefix = "mt") {
+ const searchKey = this._searchKey(key, keyPrefix);
+ return this._values[searchKey];
+ }
+ /**
+ * Add a given key-value pair to this buy parameters.
+ *
+ * If a value was previously associated with `key`,
+ * it will be replaced with `value`.
+ *
+ * @param key The key to associate `value` with in these buy parameters.
+ * @param value The value to add to the buy parameters. If value is `undefined`
+ * or `null`, any values previously associated with `key` will be removed.
+ * @param keyPrefix The prefix to add to the key. Defaults to `mt`.
+ * @returns `this`
+ */
+ set(key, value, keyPrefix = "mt") {
+ const searchKey = this._searchKey(key, keyPrefix);
+ if (isNothing(value)) {
+ delete this._values[searchKey];
+ }
+ else {
+ this._values[searchKey] = value;
+ }
+ return this;
+ }
+ /**
+ * Convert this buy parameters to its string representation.
+ */
+ toString() {
+ let buyParams = "";
+ for (const key of Object.keys(this._values)) {
+ const value = this._values[key];
+ if (buyParams.length > 0) {
+ buyParams += "&";
+ }
+ buyParams += encodeURIComponent(key);
+ buyParams += "=";
+ buyParams += encodeURIComponent(value);
+ }
+ return buyParams;
+ }
+ /**
+ * Convert the buy params into a map representation, in which
+ * the keys and values are encoded.
+ */
+ toEncodedMap() {
+ const map = {};
+ for (const key of Object.keys(this._values)) {
+ const value = this._values[key];
+ const encodedKey = encodeURIComponent(key);
+ const encodedValue = encodeURIComponent(value);
+ map[encodedKey] = encodedValue;
+ }
+ return map;
+ }
+ /**
+ * Buy params in a raw (non url encoded) map representation.
+ */
+ toRawMap() {
+ return { ...this._values };
+ }
+}
+//# sourceMappingURL=buy-parameters.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/metrics/cookies.js b/node_modules/@jet-app/app-store/tmp/src/foundation/metrics/cookies.js
new file mode 100644
index 0000000..5005b72
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/metrics/cookies.js
@@ -0,0 +1,27 @@
+import { isNothing } from "@jet/environment/types/optional";
+/**
+ * Iterate the cookies contained in a string.
+ *
+ * @param cookie A string containing zero or more cookies.
+ */
+export function* cookiesOf(cookie) {
+ if (isNothing(cookie)) {
+ return;
+ }
+ const rawEntries = cookie.split(";");
+ for (const rawEntry of rawEntries) {
+ const keyEndIndex = rawEntry.indexOf("=");
+ if (keyEndIndex === -1) {
+ // If there's no splitter, treat the whole raw
+ // entry as the key and provide an empty value.
+ const key = decodeURIComponent(rawEntry).trim();
+ yield { key, value: "" };
+ }
+ else {
+ const key = decodeURIComponent(rawEntry.substring(0, keyEndIndex)).trim();
+ const value = decodeURIComponent(rawEntry.substring(keyEndIndex + 1)).trim();
+ yield { key, value };
+ }
+ }
+}
+//# sourceMappingURL=cookies.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/metrics/metrics-identifiers-cache.js b/node_modules/@jet-app/app-store/tmp/src/foundation/metrics/metrics-identifiers-cache.js
new file mode 100644
index 0000000..96a487f
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/metrics/metrics-identifiers-cache.js
@@ -0,0 +1,245 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import { makeMetatype } from "@jet/environment/util/metatype";
+/**
+ * @public
+ * Type to define which type of metrics identifier key to use to fetch identifiers, fields, etc.
+ */
+export var MetricsIdentifierType;
+(function (MetricsIdentifierType) {
+ MetricsIdentifierType["client"] = "clientId";
+ MetricsIdentifierType["user"] = "userId";
+ MetricsIdentifierType["canonical"] = "canonicalAccountIdentifierOverride";
+})(MetricsIdentifierType || (MetricsIdentifierType = {}));
+/**
+ * @public
+ * The default namespaces to provide to the native `MetricsIdStore` for fetching and caching
+ */
+export var MetricsIdentifiersDefaultBagNamespaces;
+(function (MetricsIdentifiersDefaultBagNamespaces) {
+ MetricsIdentifiersDefaultBagNamespaces["client"] = "APPSTORE_ENGAGEMENT_CLIENT";
+ MetricsIdentifiersDefaultBagNamespaces["user"] = "APPSTORE_ENGAGEMENT";
+})(MetricsIdentifiersDefaultBagNamespaces || (MetricsIdentifiersDefaultBagNamespaces = {}));
+export var MetricsIdentifiersPaymentBagNamespaces;
+(function (MetricsIdentifiersPaymentBagNamespaces) {
+ MetricsIdentifiersPaymentBagNamespaces["client"] = "APPSTORE_PAYMENTS_ENGAGEMENT_CLIENT";
+ MetricsIdentifiersPaymentBagNamespaces["user"] = "APPSTORE_PAYMENTS_ENGAGEMENT";
+})(MetricsIdentifiersPaymentBagNamespaces || (MetricsIdentifiersPaymentBagNamespaces = {}));
+export var MetricsIdentifiersPersonalizationBagNamespaces;
+(function (MetricsIdentifiersPersonalizationBagNamespaces) {
+ MetricsIdentifiersPersonalizationBagNamespaces["user"] = "APPSTORE_PERSONALIZATION";
+})(MetricsIdentifiersPersonalizationBagNamespaces || (MetricsIdentifiersPersonalizationBagNamespaces = {}));
+/**
+ * The payment context mappings for a metrics identifier key.
+ */
+export const paymentIdentifierContextMapping = {
+ [MetricsIdentifierType.client]: {
+ keyType: MetricsIdentifierType.client,
+ bagNamespace: MetricsIdentifiersPaymentBagNamespaces.client,
+ crossSyncDevice: false,
+ },
+ [MetricsIdentifierType.user]: {
+ keyType: MetricsIdentifierType.user,
+ bagNamespace: MetricsIdentifiersPaymentBagNamespaces.user,
+ crossSyncDevice: true,
+ },
+};
+/**
+ * The default context mappings for a metrics identifier key.
+ */
+const defaultIdentifierContextMapping = {
+ [MetricsIdentifierType.client]: {
+ keyType: MetricsIdentifierType.client,
+ bagNamespace: MetricsIdentifiersDefaultBagNamespaces.client,
+ crossSyncDevice: false,
+ },
+ [MetricsIdentifierType.user]: {
+ keyType: MetricsIdentifierType.user,
+ bagNamespace: MetricsIdentifiersDefaultBagNamespaces.user,
+ crossSyncDevice: true,
+ },
+};
+/**
+ * The personalization context mappings for a metrics identifier key.
+ */
+export const personalizationIdentifierContextMapping = {
+ [MetricsIdentifierType.user]: {
+ keyType: MetricsIdentifierType.user,
+ bagNamespace: MetricsIdentifiersPersonalizationBagNamespaces.user,
+ crossSyncDevice: true,
+ },
+};
+/**
+ * Represents a cache for metrics identifiers and fields.
+ */
+export class MetricsIdentifiersCache {
+ /**
+ * Constructs a new instance of the MetricsIdentifiersCache class.
+ * @param identifierContextMapping - The mapping of identifier types to key contexts.
+ */
+ constructor(identifierContextMapping = defaultIdentifierContextMapping) {
+ this.cachedMetricsIds = {};
+ this.cachedMetricsFields = {};
+ /**
+ * A flag indicating whether a DSID fallback field should be added to the event fields.
+ */
+ this.shouldAddDsIdFallbackField = false;
+ /**
+ * The DSID of the currently logged in user on this JS request.
+ */
+ this.userDsId = null;
+ this.identifierContextMapping = identifierContextMapping;
+ }
+ /**
+ * Loads the values for the specified identifier types.
+ * @param objectGraph - The object graph.
+ * @param idTypes - The identifier types.
+ * @returns A promise that resolves to an array of loaded values.
+ */
+ async loadValues(objectGraph, idTypes) {
+ if (objectGraph.bag.isMetricsUserIdFallbackEnabled && !objectGraph.user.isUnderThirteen) {
+ this.shouldAddDsIdFallbackField = true;
+ this.userDsId = objectGraph.user.dsid;
+ }
+ else {
+ this.shouldAddDsIdFallbackField = false;
+ this.userDsId = null;
+ }
+ const loadedValues = [];
+ for (const idType of idTypes) {
+ const idTypeLoadedValues = await this.loadValuesForIdType(objectGraph, idType);
+ loadedValues.push(idTypeLoadedValues);
+ }
+ for (const { idType, id, fields } of loadedValues) {
+ if (id) {
+ this.cachedMetricsIds[idType] = id;
+ }
+ if (fields) {
+ this.cachedMetricsFields[idType] = fields;
+ }
+ }
+ }
+ /**
+ * @param objectGraph The app store object graph for all our native dependencies
+ * @param idType The type of id we're loading values for
+ * @returns The MetricsIdStore values for this id type
+ */
+ async loadValuesForIdType(objectGraph, idType) {
+ const returnValues = {
+ idType,
+ };
+ const context = this.identifierContextMapping[idType];
+ if (context) {
+ try {
+ const fields = await validation.context("MetricsIdentifiersCache:loadValues:metricsFields", async () => {
+ return await objectGraph.metricsIdentifiers.getMetricsFieldsForContexts([context]);
+ });
+ if (isSome(fields)) {
+ returnValues["fields"] = fields;
+ const extractedIdValue = fields[idType];
+ if (isSome(extractedIdValue) &&
+ typeof extractedIdValue === "string" &&
+ extractedIdValue.length > 0) {
+ returnValues["id"] = extractedIdValue;
+ }
+ }
+ }
+ catch (error) {
+ objectGraph.console.error(`Unable to fetch metrics fields for idType ${idType}`, error);
+ }
+ if (isNothing(returnValues["id"])) {
+ try {
+ const id = await validation.context("MetricsIdentifiersCache:loadValues:metricsIdentifier", async () => {
+ return await objectGraph.metricsIdentifiers.getIdentifierForContext(context);
+ });
+ if (isSome(id)) {
+ returnValues["id"] = id;
+ }
+ }
+ catch (error) {
+ objectGraph.console.error(`Unable to fetch metrics identifier for idType ${idType}`, error);
+ }
+ }
+ }
+ return returnValues;
+ }
+ /**
+ * Gets the metrics identifier for the specified identifier type.
+ * @param idType - The identifier type.
+ * @returns The metrics identifier, or undefined if not found.
+ */
+ getMetricsIdForType(idType) {
+ return this.cachedMetricsIds[idType];
+ }
+ /**
+ * Gets the metrics fields for the specified identifier types.
+ * @param idTypes - The identifier types.
+ * @returns The metrics fields, or undefined if not found.
+ */
+ getMetricsFieldsForTypes(idTypes) {
+ const fieldsForTypes = idTypes.map((idType) => { var _a; return (_a = this.cachedMetricsFields[idType]) !== null && _a !== void 0 ? _a : {}; });
+ const eventFields = Object.assign({}, ...fieldsForTypes);
+ if (this.shouldAddDsIdFallbackField && idTypes.indexOf(MetricsIdentifierType.user) !== -1) {
+ this.addDsIdFallbackFieldIfNecessary(eventFields);
+ }
+ return eventFields;
+ }
+ /**
+ * Adds a DSID fallback field to the event fields if necessary.
+ * @param eventFields - The event fields.
+ */
+ addDsIdFallbackFieldIfNecessary(eventFields) {
+ const existingUserId = eventFields[MetricsIdentifierType.user];
+ const isExistingUserIdInvalid = isNothing(existingUserId) ||
+ typeof existingUserId !== "string" ||
+ existingUserId.length === 0 ||
+ existingUserId.length === MetricsIdentifiersCache.clientGeneratedUserIdLength;
+ if (isExistingUserIdInvalid && isSome(this.userDsId) && this.userDsId.length > 0) {
+ eventFields["dsId"] = this.userDsId;
+ }
+ }
+ /**
+ * Allows the setting of an app-level canonical account override for downstream metrics computations
+ * @param canonicalAccountIdentifier - The identifier to use when operated with canonical account overides
+ */
+ setCanonicalAccountIdentifierOverride(canonicalAccountIdentifier) {
+ if (canonicalAccountIdentifier.length >= 0) {
+ this.cachedMetricsFields[MetricsIdentifierType.canonical] = {
+ [MetricsIdentifierType.canonical]: canonicalAccountIdentifier,
+ };
+ }
+ }
+}
+/**
+ * The metatype of the MetricsIdentifiersCache class.
+ */
+MetricsIdentifiersCache.defaultMetatype = makeMetatype("app-store:metricsIdentifiersCache");
+MetricsIdentifiersCache.paymentMetatype = makeMetatype("app-store:paymentMetricsIdentifiersCache");
+MetricsIdentifiersCache.personalizationMetatype = makeMetatype("app-store:personalizationMetricsIdentifiersCache");
+/**
+ * The length of a client generated user ID.
+ */
+MetricsIdentifiersCache.clientGeneratedUserIdLength = 24;
+export class MockMetricsIdentifiersCache extends MetricsIdentifiersCache {
+ constructor() {
+ super();
+ this.cachedMetricsIds = {
+ [MetricsIdentifierType.client]: "1a2b3c4d5e",
+ [MetricsIdentifierType.user]: "1a2b3c4d5e",
+ };
+ this.cachedMetricsFields = {
+ [MetricsIdentifierType.client]: {
+ clientId: "1a2b3c4d5e",
+ },
+ [MetricsIdentifierType.user]: {
+ userNs: "mockuserNs",
+ userId: "1a2b3c4d5e",
+ metricsId: 2,
+ },
+ [MetricsIdentifierType.canonical]: {
+ canonicalAccountIdentifierOverride: "mockCanonicalAccountIdentifier",
+ },
+ };
+ }
+}
+//# sourceMappingURL=metrics-identifiers-cache.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/network/http.js b/node_modules/@jet-app/app-store/tmp/src/foundation/network/http.js
new file mode 100644
index 0000000..34c6a9a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/network/http.js
@@ -0,0 +1,42 @@
+/**
+ * Created by keithpk on 12/3/16.
+ */
+/**
+ * The `FormBuilder` class can be used to construct HTTP
+ * the form parameters for use with an HTTP post operation.
+ */
+export class FormBuilder {
+ /**
+ * The content type to use for form parameters.
+ */
+ static get contentType() {
+ return "application/x-www-form-urlencoded";
+ }
+ /**
+ * Construct an empty form builder.
+ */
+ constructor() {
+ this._params = "";
+ }
+ /**
+ * Append a parameter to the builder's form parameters.
+ * @param key The key of the parameter.
+ * @param value The value of the parameter.
+ * @return The builder.
+ */
+ param(key, value) {
+ if (key && value) {
+ const separator = this._params.length > 0 ? "&" : "";
+ this._params += `${separator}${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
+ }
+ return this;
+ }
+ /**
+ * Create and return a form parameters string based on the contents of the builder.
+ * @return The form parameters string.
+ */
+ build() {
+ return this._params;
+ }
+}
+//# sourceMappingURL=http.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/network/network.js b/node_modules/@jet-app/app-store/tmp/src/foundation/network/network.js
new file mode 100644
index 0000000..db302e7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/network/network.js
@@ -0,0 +1,123 @@
+/**
+ * Created by ls on 9/7/2018.
+ *
+ * This `network.ts` is the NON-MEDIA API arm of network fetch requests.
+ * It is built on `Network` object and provides standard functionality, such as:
+ * 1. Parsing the body into specific format.
+ * 2. Adding timing metrics onto blob.
+ *
+ * This should *only* be used for objects that should have timing metrics, i.e. requests to Non-MediaAPI endpoints
+ * that will ultimately render some whole page. Otherwise, use `objectGraph.network.fetch` directly.
+ *
+ * @see `src/media/network.ts` for fetching from Media API endpoints
+ */
+import { isSome } from "@jet/environment/types/optional";
+import * as serverData from "../json-parsing/server-data";
+/** @public */
+// eslint-disable-next-line @typescript-eslint/no-namespace
+export var ResponseMetadata;
+(function (ResponseMetadata) {
+ ResponseMetadata.requestedUrl = "_jet-internal:metricsHelpers_requestedUrl";
+ /**
+ * Symbol used to place timing metrics values onto fetch responses
+ * without interfering with the data returned by the server.
+ */
+ ResponseMetadata.timingValues = "_jet-internal:metricsHelpers_timingValues";
+ /**
+ * Key used to access the page information gathered from a response's headers
+ */
+ ResponseMetadata.pageInformation = "_jet-internal:metricsHelpers_pageInformation";
+ /**
+ * Key used to access the content max-age gathered from a response's headers.
+ */
+ ResponseMetadata.contentMaxAge = "_jet-internal:responseMetadata_contentMaxAge";
+})(ResponseMetadata || (ResponseMetadata = {}));
+/**
+ * Module's private fetch implementation built off `net` global.
+ *
+ * @param {FetchRequest} request describes fetch request.
+ * @param {(value: string) => Type} parser Some function parsing response body `string` into specific type.
+ * @returns {Promise<Type>} Promise resolving to specific object.
+ * @throws {Error} Throws error if status code of request is not 200.
+ *
+ * @note Similar to `fetchWithToken` in `media` module, but excludes media token specific functionality.
+ * Top level data fetches to endpoints that don't do redirects, and can benefit from metrics should
+ * call methods that build off of this instead of calling `objectGraph.network.fetch(...)` directly.
+ */
+async function fetch(objectGraph, request, parser) {
+ const response = await objectGraph.network.fetch(request);
+ if (!response.ok) {
+ throw Error(`Bad Status code ${response.status} for ${request.url}`);
+ }
+ const parseStartTime = Date.now();
+ const result = parser(response.body);
+ const parseEndTime = Date.now();
+ if (result) {
+ // Build full network timing metrics.
+ const completeTimingMetrics = networkTimingMetricsWithParseTime(response.metrics, parseStartTime, parseEndTime);
+ if (serverData.isDefinedNonNull(completeTimingMetrics)) {
+ result[ResponseMetadata.timingValues] = completeTimingMetrics;
+ }
+ }
+ result[ResponseMetadata.requestedUrl] = request.url.toString();
+ return result;
+}
+/**
+ * Fetch from an endpoint with JSON response body.
+ *
+ * @param {FetchRequest} request to fetch from endpoint with JSON response..
+ * @returns {Promise<Type>} Promise resolving to body of response parsed as `Type`.
+ * @throws {Error} Throws error if status code of request is not 200.
+ */
+export async function fetchJSON(objectGraph, request) {
+ return await fetch(objectGraph, request, (body) => {
+ if (isSome(body)) {
+ return JSON.parse(body);
+ }
+ else {
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+ return {};
+ }
+ });
+}
+/**
+ * Fetch from an endpoint with XML response body.
+ *
+ * @param {FetchRequest} request to fetch from endpoint with XML response.
+ * @returns {Promise<Type>} Promise resolving to body of response parsed as `Type`.
+ * @throws {Error} Throws error if status code of request is not 200.
+ */
+export async function fetchPlist(objectGraph, request) {
+ return await fetch(objectGraph, request, (body) => {
+ if (isSome(body)) {
+ return objectGraph.plist.parse(body);
+ }
+ else {
+ throw new Error(`Could not fetch Plist, response body was not defined for ${request.url}`);
+ }
+ });
+}
+/**
+ * With network requests now being created and parsed in JS, different timing metrics are measured in both Native and JS.
+ * This function populates the missing values from `HTTPTimingMetrics`'s native counterpart, `JSNetworkPerformanceMetrics`.
+ *
+ * @param {HTTPTimingMetrics[] | null} responseMetrics Array of response metrics provided by native.
+ * @param {number} parseStartTime Time at which response body string parse began in JS.
+ * @param {number} parseEndTime Time at which response body string parse ended in JS.
+ * @returns {HTTPTimingMetrics | null} Fully populated timing metrics, or `null` if native response provided no metrics events to build off of.
+ */
+function networkTimingMetricsWithParseTime(responseMetrics, parseStartTime, parseEndTime) {
+ // No metrics events to build from.
+ if (serverData.isNull(responseMetrics) || responseMetrics.length === 0) {
+ return null;
+ }
+ // Append parse times to first partial timing metrics from native.
+ const firstPartialTimingMetrics = {
+ ...responseMetrics[0],
+ parseStartTime: parseStartTime,
+ parseEndTime: parseEndTime,
+ };
+ // Timing metrics with all properties populated.
+ return firstPartialTimingMetrics;
+}
+//# sourceMappingURL=network.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/network/url-constants.js b/node_modules/@jet-app/app-store/tmp/src/foundation/network/url-constants.js
new file mode 100644
index 0000000..f25d9cb
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/network/url-constants.js
@@ -0,0 +1,452 @@
+/**
+ * Copied from SKUIUrl these action types come in urls like scheme:/?action=[UrlAction]
+ */
+/* tslint:disable:variable-name */
+export const Protocol = {
+ /// Protocol used for internal app store routing
+ internal: "x-as3-internal",
+ /// Protocol used for routing HTTP URLs
+ http: "http",
+ /// Protocol used for routing standard HTTPS URLs
+ https: "https",
+ /// The protocol used for iTunes app urls
+ itms: "itms",
+ /// The protocol used for secure iTunes app urls
+ itmss: "itmss",
+ /// Protocol used to target the app store
+ itmsAppss: "itms-appss",
+ /// Protocol used to target the app store
+ itmsApps: "itms-apps",
+ /// Protocol used to target the messages app store
+ itmsMessagess: "itms-messagess",
+ /// Protocol used to target the messages app store
+ itmsMessages: "itms-messages",
+ /// Protocol used to target the watch app store
+ itmsWatchs: "itms-watchs",
+ /// Protocol used to target the watch app store
+ itmsWatch: "itms-watch",
+ /// Protocol used for internal file routing
+ file: "file",
+ /// Protocol used for referencing resources in the host app's bundles.
+ resource: "resource",
+ /// Protocol used to target the mac app store
+ macappstore: "macappstore",
+ /// Protocol used to target the mac app store
+ macappstores: "macappstores",
+ /// Legacy protocol used to target the tv app store,
+ /// kept to maintain backwards compatibility.
+ tvappstoreLegacy: "com.apple.tvappstore",
+ /// Protocol used to target the tv app store
+ tvappstore: "com.apple.TVAppStore",
+ /// Protocol used to target Arcade
+ arcade: "com.apple.Arcade",
+ /// Protocol used to target StoreKitUI App Store service
+ storeKitUIServiceAppStore: "appstore-ui",
+};
+export const Path = {
+ // Path for loading a shelf
+ shelf: "shelf",
+ // Path for MZStore requests
+ store: "WebObjects/MZStore.woa/wa",
+ // Path for MZStoreElements requests
+ storeElements: "WebObjects/MZStoreElements.woa/wa",
+ // Path for performing a lookup
+ lookup: "lookup",
+ // Path for performing a docTypeLookup
+ docTypeLookup: "docTypeLookup",
+ // Path for performing an install sheet
+ install: "install",
+ // Path to the grouping page
+ grouping: "viewGrouping",
+ // Path to the artist page
+ viewArtist: "viewArtist",
+ // Path to the product page
+ viewSoftware: "viewSoftware",
+ // Path to the today page
+ today: "today",
+ // Path to the arcade main page
+ arcade: "arcade",
+ arcadeUpsellPreview: "arcadeUpsellPreview",
+ // Path to Arcade See All
+ arcadeSeeAllGames: "arcadeSeeAllGames",
+ // Pagination path for Arcade See All
+ arcadeSeeAllGamesLoadMore: "arcadeSeeAllGamesLoadMore",
+ // Path to the genre page (which is a grouping page)
+ genre: "genre",
+ // Path to the genre page (which is a grouping page)
+ viewGenre: "viewGenre",
+ recommendationsSeeAll: "recommendationsSeeAll",
+ // Path for a screenshots lookup.
+ screenshots: "screenshots",
+ // Path to a single room
+ room: "viewRoom",
+ // Path to a programmed multi-room
+ multiRoom: "viewMultiRoom",
+ // Path to the main top charts page
+ charts: "charts",
+ // Path to the product page
+ product: "app",
+ // Path to the game page
+ game: "game",
+ // Path to siri product page deep links on tvOS App Store.
+ siri: "siri",
+ // Path to the product page for an app bundle.
+ productBundle: "app-bundle",
+ // Path component for the developer page
+ developer: "developer",
+ artist: "artist",
+ // Path component for the ratings shelf
+ ratings: "ratings",
+ // Path component for the reviews shelf
+ reviews: "reviews",
+ // Path to view reviews for MZStore
+ viewReviews: "viewContentsUserReviews",
+ // Path component for the personalized review shelf
+ personalizedReviews: "personalizedReviews",
+ // Path to editorial items (articles)
+ article: "article",
+ story: "story",
+ editorialItem: "editorialItem",
+ viewEditorialItem: "viewEditorialItem",
+ todayCardPreview: "todayCardPreview",
+ // Path component for MSO rooms
+ mso: "mso",
+ // Path for resetting the storefront
+ resetAndRedirect: "resetAndRedirect",
+ // Account
+ account: "account",
+ personalizationTransparency: "personalizationTransparency",
+ // Path component for an href-driven route.
+ href: "href",
+ // Path component for the EULA.
+ eula: "eula",
+ // Path component for the EULA on tvOS.
+ tvEula: "tv-eula",
+ // Path component for the Privacy Policy on tvOS.
+ privacyPolicy: "privacyPolicy",
+ // Path component for the Safety and Compliance Page on tvOS.
+ safetyCompliance: "safety-compliance",
+ // Path component for recommendations
+ appsForYou: "apps-for-you",
+ // Path for the storeFront
+ storeFront: "storeFront",
+ // Path for the tab
+ tab: "tab",
+ // Path component for the search landing page.
+ searchLandingPage: "searchLandingPage",
+ // Path component for search trending content page.
+ searchTrendingApps: "searchTrendingApps",
+ // Path for showing product privacy detail page
+ privacyDetail: "privacyDetail",
+ // Path for showing product privacy definitions page
+ privacyDefinitions: "privacyDefinitions",
+ // Path for the report a problem link
+ reportAProblem: "reportaproblem.apple.com/store",
+ // Path for showing a game center player profile
+ gameCenterProfile: "gameCenterProfile",
+ // Path for showing the ODP page
+ onDeviceRecommendations: "onDeviceRecommendations",
+ // Path for editorial pages
+ editorialPage: "editorialPage",
+ // Path to acessibility details
+ accessibilityDetails: "accessibilityDetails",
+ // ----------------------
+ // Debug
+ // ----------------------
+ test: "test",
+ shelfTypes: "shelfTypes",
+ groupingTest: "grouping",
+ builtIn: "builtIn",
+ shelfTextTest: "shelfText",
+ shelfLockupsTest: "shelfLockups",
+ shelfMiscTest: "shelfMisc",
+ lockupTest: "lockupTest",
+ articleTestArtwork: "articleArtworkTest",
+ articleTestSingleAppIcon: "articleSingleAppIconTest",
+ articleTestSingleAppIconHeroArt: "articleSingleAppIconHeroArtTest",
+ articleTestBrandedApp: "articleBrandedAppTest",
+ articleTestMultiAppTwo: "articleMultiAppTwoTest",
+ articleTestMultiAppThree: "articleMultiAppThreeTest",
+ articleTestGrid: "articleGridTest",
+ articleTestInAppPurchase: "articleInAppPurchaseTest",
+};
+export const Host = {
+ // Legacy Mac Software Update URL
+ showUpdatesPage: "showUpdatesPage",
+ // Legacy Deep Link Actions
+ showAccountPage: "showAccountPage",
+ showPurchasesPage: "showPurchasesPage",
+ showSubscriptionsPage: "showSubscriptionsPage",
+ // Legacy MAS Safari Extension Search
+ searchExtensions: "searchExtensions",
+ // Legacy search.itunes.apple.com redirected urls
+ searchItunes: "search.itunes.apple.com",
+ // Legacy itunes.apple.com redirected urls
+ iTunes: "itunes.apple.com",
+ // Urls allowing programming to preview programmed groupings
+ storePreview: "storepreview.apple.com",
+ // Urls allowing programming to preview programmed stories
+ appsPreview: "preview.apps.apple.com",
+ // Legacy product url for tvOS App Store
+ product: "product",
+ // Host for arcade subscribe page
+ arcadeSubscribePage: "arcadeSubscribePage",
+ // Host for an upsell that must necessarily go through our own Arcade subscribe page,
+ // rather than through an AMS marketing item page.
+ arcadeSubscribePageCustomContext: "arcadeSubscribePageCustomContext",
+ // Host for arcade welcome page
+ arcadeWelcomePage: "arcadeWelcomePage",
+ // Host for Arcade See All page for standalone case.
+ arcadeSeeAllPage: "arcadeSeeAllGames",
+ // Host for the deep link from an Arcade app clip.
+ appClipSubscribe: "appClipSubscribe",
+ familyCircle: "familyCircle",
+ // Host for showing spam blocking extensions
+ spamBlockingExtensions: "spamBlockingExtensions",
+ // Host for showing Safari extensions
+ safariExtensions: "safariExtensions",
+ // Host for app launch trampoline
+ launchApp: "launchApp",
+ // Host for SharePlay more deeplink
+ sharePlayApps: "sharePlayApps",
+ // Host for opening a deeplink at the end of Buddy
+ buddyOnboarding: "buddyOnboarding",
+ // Host for Arcade download (starter) pack page
+ arcadeDownloadPackPage: "arcadeDownloadPack",
+ // Host for showing settings page
+ showSettingsPage: "showSettingsPage",
+ // Host for showing settings page
+ showHiddenPurchasesPage: "showHiddenPurchases",
+};
+/// The different preview hosts that require the media api preview endpoint
+export const previewHosts = new Set([Host.storePreview, Host.appsPreview]);
+export const Parameters = {
+ // Generic ID
+ id: "id",
+ // Generic IDs
+ ids: "ids",
+ // Product variant ID for given `id`
+ productVariantID: "ppid",
+ // Country Code
+ countryCode: "cc",
+ // Language override
+ language: "l",
+ // Featured Content ID
+ featuredContentId: "fcId",
+ fetchData: "fetchData",
+ isTodaySection: "isTodaySection",
+ isTodayFeedPreview: "isTodayFeedPreview",
+ // Genre
+ genre: "genre",
+ // Bundle Identifier
+ bundleIdentifier: "bundleIdentifier",
+ // BundleID
+ bundleId: "bundleId",
+ // Spotlight In-App Purchase Identifier
+ offerName: "offerName",
+ // Top Charts
+ charts: "charts",
+ ages: "ages",
+ chart: "chart",
+ types: "types",
+ // v0 urls (used e.g. when redirected from MZContentLink URLs).
+ v0: "v0",
+ // Action urls
+ action: "action",
+ // Type parameter
+ type: "type",
+ context: "context",
+ isArcade: "isArcade",
+ isSubscribed: "isSubscribed",
+ isTrialAvailable: "isTrialAvailable",
+ isTrialEnrolled: "isTrialEnrolled",
+ groupingFeaturedContentId: "groupingFeaturedContentId",
+ editorialPageShelfType: "editorialPageShelfType",
+ nativeGroupingShelfId: "nativeGroupingShelfId",
+ isArcadeSeeAllGamesShelf: "isArcadeSeeAllGamesShelf",
+ isGameCenterActivityFeedShelf: "isGameCenterActivityFeedShelf",
+ isGameCenterPlayerShelf: "isGameCenterPlayerShelf",
+ isGameCenterPlayerRibbonItem: "isGameCenterPlayerRibbonItem",
+ isGameCenterAchievementsShelf: "isGameCenterAchievementsShelf",
+ isGameCenterContinuePlayingShelf: "isGameCenterContinuePlayingShelf",
+ isGameCenterPopularWithYourFriendsShelf: "isGameCenterPopularWithYourFriendsShelf",
+ isGameCenterSuggestedFriendsShelf: "isGameCenterSuggestedFriendsShelf",
+ isGameCenterReengagementShelf: "isGameCenterReengagementShelf",
+ isOnDeviceRecommendationsShelf: "isOnDeviceRecommendationsShelf",
+ isOnDeviceSearchHistoryShelf: "isOnDeviceSearchHistoryShelf",
+ isSearchFocusHeaderShelf: "isSearchFocusHeaderShelf",
+ isArcadeDownloadPackShelfPlaceholder: "isArcadeDownloadPackShelfPlaceholder",
+ onDeviceRecommendationsUseCase: "onDeviceRecommendationsUseCase",
+ onDevicePersonalizationUseCase: "onDevicePersonalizationUseCase",
+ // Denotes that this url is coming from the purchases page.
+ isPurchasesApp: "isPurchasesApp",
+ // Used for single sign on, the parameter is passed from store kit
+ isViewOnly: "isViewOnly",
+ // Determines whether unlisted apps should be returned in the response.
+ // Note that for direct lookups, MAPI allows the App Store to view unlisted
+ // apps without this parameter, although they would like to eventually gate
+ // the behavior behind this parameter.
+ includeUnlistedApps: "includeUnlistedApps",
+ enabled: "enabled",
+ href: "href",
+ recoMetrics: "recoMetrics",
+ // Used on article urls when the TodayCard is showing fallback media
+ showingFallbackMedia: "showingFallbackMedia",
+ path: "path",
+ useReleaseId: "useReleaseId",
+ // The client identifier that should be used to display the item, regardless of host device.
+ clientIdentifierOverride: "clientIdentifierOverride",
+ // The message attached to the subscribe page, for ATB.
+ subscribePageMessage: "message",
+ // The ID for a specific editorial item, in the case of a subscribe page.
+ editorialItem: "editorialItem",
+ // The identifer for the ATB request.
+ askToBuyId: "askToBuyId",
+ // Indicates an ID, when the 'app' disambiguation is necessary.
+ appId: "appId",
+ // Whether the URL has been launched from a PPT (Purple Performance Test).
+ isPPT: "isPPT",
+ // How the shelf is sorted, represented by a value of `ArcadeSeeAllGamesPageSort`
+ sort: "sort",
+ // Featured Content ID
+ grouping: "grouping",
+ // Code Parameter, e.g. for Redeem code
+ code: "code",
+ // Parameter to include attribution on offer action for post subcribge
+ includePostSubscribeAttribution: "includePostSubscribeAttribution",
+ // Campaign Token
+ campaignToken: "ct",
+ // Provider Token
+ providerToken: "pt",
+ // Q Token
+ qToken: "its_qt",
+ // Advertisement ID
+ advertisementId: "adId",
+ // The token used to pass arbitrary data in a url
+ token: "token",
+ // The current parse context for the a page
+ parseContext: "parseContext",
+ // An ID for a privacy type
+ privacyTypeId: "privacyTypeId",
+ // The token used to pass arbitrary data in a url
+ requestDescriptor: "requestDescriptor",
+ // Page Facet
+ ageRating: "ageRating",
+ // Page Facet
+ controllerSupport: "controllerSupport",
+ // Page Facet
+ multiplayerSupport: "multiplayerSupport",
+ // Page Facet
+ comingSoon: "comingSoon",
+ // Page Facet
+ binCompatGames: "binCompatGames",
+ // Page Facet
+ gamePreviews: "gamePreviews",
+ // Upsell (marketing item) marketing hint
+ offerHints: "offerHints",
+ // Indicates that a 204 network error should invalidate the App Store's widgets.
+ invalidateWidgetsOnFailure: "invalidateWidgetsOnFailure",
+ // Metrics
+ metrics: "metrics",
+ // App event ID
+ appEventId: "eventid",
+ // Offer Item ID
+ offerItemId: "offerItemId",
+ // App event deep link
+ appEventDeepLink: "deepLink",
+ // The use case param for collection deeplinking
+ useCaseShort: "uc",
+ // The collection id param for collection deeplinking
+ collectionId: "collection-id",
+ // The seed id param for collection deeplinking
+ seedId: "seed-id",
+ // Whether the shelf is the product page similar items shelf.
+ isShelfWithAd: "isShelfWithAd",
+ // The ad placement type for the shelf. Only used if `isShelfWithAd` is true.
+ shelfWithAdPlacementType: "shelfWithAdPlacementType",
+ // Provides a refresh type to route a refresh request for a shelf.
+ // See `refreshUrl` on `Shelf`.
+ shelfRefreshType: "refreshType",
+ // Param to denote that this url originated for preloading purposes.
+ isPreloading: "isPreloading",
+ // The name parameter for editorial pages with known names
+ name: "name",
+ // The editorial page shelf type
+ shelfType: "shelfType",
+ // The id of the shelf in a url
+ shelfId: "shelfId",
+ // Parameter for the onboarding cards for the today tab
+ onbaordingCardIds: "onboardingCardIds",
+ // Parameter for the today card previews to provide a future date and time
+ preview: "preview",
+ // The card config for used for the card on a today page
+ todayCardConfig: "todayCardConfig",
+ // The parameter that is expanded into the header used for today previews.
+ experimentId: "experimentId",
+ shortEditorialNotes: "shortEditorialNotes",
+ // User subscription status defined by Mercury and passed to Arcade download pack onboarding.
+ // It is used to select the right copy variant.
+ arcadeSubscriptionStatus: "subscriptionStatus",
+ // Used to indicate a web browser is being displayed in the web browser context via App Store Components.
+ webBrowser: "webBrowser",
+ // The id of the editorial-page that was used to create a url
+ editorialPageId: "editorialPageId",
+ // A filter ID to add to a editorial item request in order to fetch a certain version of the story.
+ editorialCardId: "filter[canvas:cardId]",
+ // A filter to add to the hydration call for the Today tab recommended candidates to fetch only recommendable apps.
+ filterRecommendable: "filter[recommendable]",
+ // A device family, used for accessibility labels
+ deviceFamily: "deviceFamily",
+};
+// Install Page-specific parameters
+export const InAppPurchaseInstallPageParameters = {
+ // The adamId for the IAP.
+ inAppPurchaseId: "inAppPurchaseId",
+};
+// Product Page-specific parameters
+export const ProductPageParameters = {
+ // Product URL
+ url: "productUrl",
+ // Whether an iAP is a subscription.
+ isSubscription: "isSubscription",
+ // The minimum version of the app to display
+ // Typically used to prevent older cached versions being displayed
+ // when deep-linking from an app clip.
+ minExternalVersionId: "minExternalVersionId",
+};
+// Review Page-specific parameters
+export const ReviewsPageParameters = {
+ // Product adamId
+ adamId: "adamId",
+ // Sort index for the reviews request
+ sort: "sort",
+};
+export const MSOPageParameters = {
+ // Ids of available apps.
+ availableAdamIds: "availableAdamIds",
+};
+export const ShelfParameters = {
+ // Shelf Title
+ title: "shelfTitle",
+ // Whether the shelf should auto-configure a see-all on fetch
+ shouldInferSeeAllFromFetchedItems: "shelfShouldInferSeeAllFromFetchedItems",
+ // Shelf content type
+ contentType: "shelfContentType",
+ // Shelf offer them
+ offerTheme: "shelfOfferTheme",
+ // metrics page information
+ metricsPageInformation: "metricsPage",
+};
+export const ShareURLParameters = {
+ // Which client should be the link be opened with
+ clientSpecifier: "app",
+};
+export const Hashes = {
+ // Reviews action anchor (#reviews)
+ reviews: "reviews",
+};
+export const ShelfRefreshType = {
+ productPageSimilarItems: "productPageSimilarItems",
+};
+/* tslint:enable:variable-name */
+//# sourceMappingURL=url-constants.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/network/urls.js b/node_modules/@jet-app/app-store/tmp/src/foundation/network/urls.js
new file mode 100644
index 0000000..6abbd37
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/network/urls.js
@@ -0,0 +1,382 @@
+/**
+ * Created by keithpk on 12/2/16.
+ */
+import { isNothing } from "@jet/environment/types/optional";
+const protocolRegex = /^([a-z][a-z0-9.+-]*:)(\/\/)?([\S\s]*)/i;
+const queryParamRegex = /([^=?&]+)=?([^&]*)/g;
+const componentOrder = ["hash", "query", "pathname", "host"];
+function splitUrlComponent(input, marker, style) {
+ const index = input.indexOf(marker);
+ let result;
+ let remainder = input;
+ if (index !== -1) {
+ const prefix = input.slice(0, index);
+ const suffix = input.slice(index + marker.length, input.length);
+ if (style === "prefix") {
+ result = prefix;
+ remainder = suffix;
+ }
+ else {
+ result = suffix;
+ remainder = prefix;
+ }
+ }
+ // log("Token: " + marker + " String: " + input, " Result: " + result + " Remainder: " + remainder)
+ return {
+ result: result,
+ remainder: remainder,
+ };
+}
+export class URL {
+ constructor(url) {
+ this.query = {};
+ if (!url) {
+ return;
+ }
+ // Split the protocol from the rest of the urls
+ let remainder = url;
+ const match = protocolRegex.exec(url);
+ if (match) {
+ // Pull out the protocol
+ let protocol = match[1];
+ if (protocol) {
+ protocol = protocol.split(":")[0];
+ }
+ this.protocol = protocol;
+ // Save the remainder
+ remainder = match[3];
+ }
+ // Then match each component in a specific order
+ let parse = { remainder: remainder, result: undefined };
+ for (const component of componentOrder) {
+ if (!parse.remainder) {
+ break;
+ }
+ switch (component) {
+ case "hash": {
+ parse = splitUrlComponent(parse.remainder, "#", "suffix");
+ this.hash = parse.result;
+ break;
+ }
+ case "query": {
+ parse = splitUrlComponent(parse.remainder, "?", "suffix");
+ if (parse.result) {
+ this.query = URL.queryFromString(parse.result);
+ }
+ break;
+ }
+ case "pathname": {
+ parse = splitUrlComponent(parse.remainder, "/", "suffix");
+ if (parse.result) {
+ // Replace the initial /, since paths require it
+ this.pathname = "/" + parse.result;
+ }
+ break;
+ }
+ case "host": {
+ if (parse.remainder) {
+ const authorityParse = splitUrlComponent(parse.remainder, "@", "prefix");
+ const userInfo = authorityParse.result;
+ const hostPort = authorityParse.remainder;
+ if (userInfo) {
+ const userInfoSplit = userInfo.split(":");
+ this.username = decodeURIComponent(userInfoSplit[0]);
+ this.password = decodeURIComponent(userInfoSplit[1]);
+ }
+ if (hostPort) {
+ const hostPortSplit = hostPort.split(":");
+ this.host = hostPortSplit[0];
+ this.port = hostPortSplit[1];
+ }
+ }
+ break;
+ }
+ default: {
+ throw new Error("Unhandled case!");
+ }
+ }
+ }
+ }
+ set(component, value) {
+ if (!value) {
+ return this;
+ }
+ if (component === "query") {
+ if (typeof value === "string") {
+ value = URL.queryFromString(value);
+ }
+ }
+ switch (component) {
+ // Exhaustive match to make sure TS property minifiers and other
+ // transformer plugins do not break this code.
+ case "protocol":
+ this.protocol = value;
+ break;
+ case "username":
+ this.username = value;
+ break;
+ case "password":
+ this.password = value;
+ break;
+ case "port":
+ this.port = value;
+ break;
+ case "pathname":
+ this.pathname = value;
+ break;
+ case "query":
+ this.query = value;
+ break;
+ case "hash":
+ this.hash = value;
+ break;
+ default:
+ // The fallback for component which is not a property of URL object.
+ this[component] = value;
+ break;
+ }
+ return this;
+ }
+ get(component) {
+ switch (component) {
+ // Exhaustive match to make sure TS property minifiers and other
+ // transformer plugins do not break this code.
+ case "protocol":
+ return this.protocol;
+ case "username":
+ return this.username;
+ case "password":
+ return this.password;
+ case "port":
+ return this.port;
+ case "pathname":
+ return this.pathname;
+ case "query":
+ return this.query;
+ case "hash":
+ return this.hash;
+ default:
+ // The fallback for component which is not a property of URL object.
+ return this[component];
+ }
+ }
+ append(component, value) {
+ const existingValue = this.get(component);
+ let newValue;
+ if (component === "query") {
+ if (typeof value === "string") {
+ value = URL.queryFromString(value);
+ }
+ if (typeof existingValue === "string") {
+ newValue = { existingValue, ...value };
+ }
+ else {
+ newValue = { ...existingValue, ...value };
+ }
+ }
+ else {
+ let existingValueString = existingValue;
+ if (!existingValueString) {
+ existingValueString = "";
+ }
+ let newValueString = existingValueString;
+ if (component === "pathname") {
+ const pathLength = existingValueString.length;
+ if (!pathLength || existingValueString[pathLength - 1] !== "/") {
+ newValueString += "/";
+ }
+ }
+ // eslint-disable-next-line @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-base-to-string
+ newValueString += value;
+ newValue = newValueString;
+ }
+ return this.set(component, newValue);
+ }
+ param(key, value) {
+ if (!key) {
+ return this;
+ }
+ if (!this.query) {
+ this.query = {};
+ }
+ this.query[key] = value;
+ return this;
+ }
+ removeParam(key) {
+ if (!key || !this.query) {
+ return this;
+ }
+ if (this.query[key] !== undefined) {
+ delete this.query[key];
+ }
+ return this;
+ }
+ /**
+ * Push a new string value onto the path for this url
+ * @returns URL this object with the updated path.
+ */
+ path(value) {
+ return this.append("pathname", value);
+ }
+ pathExtension() {
+ var _a;
+ // Extract path extension if one exists
+ if (isNothing(this.pathname)) {
+ return null;
+ }
+ const lastFilenameComponents = (_a = this.pathname
+ .split("/")
+ .filter((item) => item.length > 0) // Remove any double or trailing slashes
+ .pop()) === null || _a === void 0 ? void 0 : _a.split(".");
+ if (lastFilenameComponents === undefined) {
+ return null;
+ }
+ if (lastFilenameComponents.filter((part) => {
+ return part !== "";
+ }).length < 2 // Remove any empty parts (e.g. .ssh_config -> ["ssh_config"])
+ ) {
+ return null;
+ }
+ return lastFilenameComponents.pop();
+ }
+ /**
+ * Returns the path components of the URL
+ * @returns An array of non-empty path components from `urls`.
+ */
+ pathComponents() {
+ if (!this.pathname) {
+ return [];
+ }
+ return this.pathname.split("/").filter((component) => component.length > 0);
+ }
+ /**
+ * Returns the last path component from this url, updating the url to not include this path component
+ * @returns String the last path component from this url.
+ */
+ popPathComponent() {
+ if (!this.pathname) {
+ return null;
+ }
+ const lastPathComponent = this.pathname.slice(this.pathname.lastIndexOf("/") + 1);
+ if (lastPathComponent.length === 0) {
+ return null;
+ }
+ this.pathname = this.pathname.slice(0, this.pathname.lastIndexOf("/"));
+ return lastPathComponent;
+ }
+ /**
+ * Same as toString
+ *
+ * @returns {string} A string representation of the URL
+ */
+ build() {
+ return this.toString();
+ }
+ /**
+ * Converts the URL to a string
+ *
+ * @returns {string} A string representation of the URL
+ */
+ toString() {
+ let url = "";
+ if (this.protocol) {
+ url += this.protocol + "://";
+ }
+ if (this.username) {
+ url += encodeURIComponent(this.username);
+ if (this.password) {
+ url += ":" + encodeURIComponent(this.password);
+ }
+ url += "@";
+ }
+ if (this.host) {
+ url += this.host;
+ if (this.port) {
+ url += ":" + this.port;
+ }
+ }
+ if (this.pathname) {
+ url += this.pathname;
+ /// Trim off trailing path separators when we have a valid path
+ /// We don't do this unless pathname has elements otherwise we will trim the `://`
+ if (url.endsWith("/") && this.pathname.length > 0) {
+ url = url.slice(0, -1);
+ }
+ }
+ if (this.query && Object.keys(this.query).length) {
+ url += "?" + URL.toQueryString(this.query);
+ }
+ if (this.hash) {
+ url += "#" + this.hash;
+ }
+ return url;
+ }
+ // ----------------
+ // Static API
+ // ----------------
+ /**
+ * Converts a string into a query dictionary
+ * @param query The string to parse
+ * @returns The query dictionary containing the key-value pairs in the query string
+ */
+ static queryFromString(query) {
+ const result = {};
+ let parseResult = queryParamRegex.exec(query);
+ while (parseResult) {
+ const key = decodeURIComponent(parseResult[1]);
+ const value = decodeURIComponent(parseResult[2]);
+ result[key] = value;
+ parseResult = queryParamRegex.exec(query);
+ }
+ return result;
+ }
+ /**
+ * Converts a query dictionary into a query string
+ *
+ * @param query The query dictionary
+ * @returns {string} The string representation of the query dictionary
+ */
+ static toQueryString(query) {
+ let queryString = "";
+ let first = true;
+ for (const key of Object.keys(query)) {
+ if (!first) {
+ queryString += "&";
+ }
+ first = false;
+ queryString += encodeURIComponent(key);
+ const value = query[key];
+ if (value && value.length) {
+ queryString += "=" + encodeURIComponent(value);
+ }
+ }
+ return queryString;
+ }
+ /**
+ * Convenience method to instantiate a URL from a string
+ * @param url The URL string to parse
+ * @returns {URL} The new URL object representing the URL
+ */
+ static from(url) {
+ return new URL(url);
+ }
+ /**
+ * Convenience method to instantiate a URL from numerous (optional) components
+ * @param protocol The protocol type
+ * @param host The host name
+ * @param path The path
+ * @param query The query
+ * @param hash The hash
+ * @returns {URL} The new URL object representing the URL
+ */
+ static fromComponents(protocol, host, path, query, hash) {
+ const url = new URL();
+ url.protocol = protocol;
+ url.host = host;
+ url.pathname = path;
+ url.query = query;
+ url.hash = hash;
+ return url;
+ }
+}
+//# sourceMappingURL=urls.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/routing/routing-components.js b/node_modules/@jet-app/app-store/tmp/src/foundation/routing/routing-components.js
new file mode 100644
index 0000000..28fdc48
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/routing/routing-components.js
@@ -0,0 +1,318 @@
+/**
+ * Created by km on 11/16/16.
+ */
+import * as urls from "../network/urls";
+import * as urlUtil from "./url-util";
+// endregion
+// region private URLRule helpers.
+/**
+ * Checks whether or not a given _pathComponents component contains a parameter.
+ * @param pathComponent The _pathComponents component to check.
+ * @returns true if the _pathComponents component is surrounded by curly braces; false otherwise.
+ */
+function isPathComponentParameter(pathComponent) {
+ const parameterStartIndex = pathComponent.indexOf("{");
+ const parameterEndIndex = pathComponent.indexOf("}");
+ return parameterStartIndex >= 0 && parameterEndIndex > parameterStartIndex + 1;
+}
+/**
+ * Extracts the parameter contained in a _pathComponents component.
+ * @param pathComponent A _pathComponents component surrounded by curly braces.
+ * @returns The parameter contained in the component.
+ */
+function getPathComponentParameter(pathComponent) {
+ const parameterStartIndex = pathComponent.indexOf("{");
+ const parameterEndIndex = pathComponent.indexOf("}");
+ const pathHasRequiredCurlyBraces = parameterStartIndex >= 0 && parameterEndIndex > parameterStartIndex;
+ return pathHasRequiredCurlyBraces
+ ? pathComponent.substring(parameterStartIndex + 1, parameterEndIndex)
+ : pathComponent;
+}
+/**
+ * Extracts the value from a path component, for a given internal key example:
+ * Path Component: "id123456"
+ * Internal Key: "id{id}"
+ * Return Value: "123456"
+ * @param pathComponent A _pathComponents component surrounded by curly braces.
+ * @returns The parameter contained in the component.
+ */
+export function getPathComponentParameterValueUsingInternalKey(pathComponent, internalKey) {
+ const valueStartIndex = internalKey.indexOf("{");
+ const valueEndIndex = pathComponent.length - (internalKey.length - (internalKey.indexOf("}") + 1));
+ const pathHasRequiredCurlyBraces = valueStartIndex >= 0 && valueEndIndex > valueStartIndex;
+ return pathHasRequiredCurlyBraces ? pathComponent.substring(valueStartIndex, valueEndIndex) : pathComponent;
+}
+/**
+ * Creates a mapping from key to _pathComponents component index
+ * for efficiently extracting parameters from a _pathComponents.
+ * @param rulePath The _pathComponents to create a mapping for.
+ * @returns A map of keys to _pathComponents component indexes.
+ */
+function makePathParameterMapping(rulePath) {
+ const mapping = {};
+ rulePath.forEach((ruleComponent, index) => {
+ if (isPathComponentParameter(ruleComponent)) {
+ mapping[ruleComponent] = index;
+ }
+ });
+ return mapping;
+}
+/**
+ * Normalizes a given protocol string for matching.
+ * @param protocol The protocol to match against.
+ * @returns The `protocol` with colon added if needed.
+ */
+function normalizeProtocol(protocol) {
+ // An empty string is falsy.
+ if (protocol === null || protocol === undefined) {
+ return null;
+ }
+ return protocol;
+}
+/**
+ * Creates `UrlRouteQuery` objects from substring of url.
+ * ? = optional
+ * -caseInsensitive = case insensitive
+ * @param parameters strings of form `<key>[?][-i]=<value>`.
+ * @returns Array of `UrlRouteQuery` objects.
+ */
+function parseQuery(parameters) {
+ const parsedQuery = [];
+ if (!parameters) {
+ return parsedQuery;
+ }
+ for (const param of parameters) {
+ const parts = param.split("=");
+ let key = parts[0];
+ const optional = key.indexOf("?") !== -1;
+ key = key.replace("?", "");
+ const caseInsensitive = key.indexOf("-caseInsensitive") !== -1;
+ key = key.replace("-caseInsensitive", "");
+ let value = null;
+ if (parts.length > 1) {
+ value = decodeURIComponent(parts[1]);
+ }
+ parsedQuery.push({
+ key,
+ value,
+ optional,
+ caseInsensitive,
+ });
+ }
+ return parsedQuery;
+}
+// endregion
+// region Url Rule
+/**
+ * The `UrlRule` class extracts the pattern format from `UrlRuleDefinition`s, and encapsulates
+ * the information needed to match against a candidate URL and extract parameters from it.
+ *
+ * The terminology here is:
+ * - rule: A specific url pattern.
+ * - route: A group of rules that together form a single route, i.e. UrlRule[].
+ */
+export class UrlRule {
+ /**
+ * Construct the route with all required properties.
+ * @param rule The rule to match.
+ */
+ constructor(rule) {
+ this.identifier = rule.identifier;
+ this._protocol = normalizeProtocol(rule.protocol);
+ this._hostName = rule.hostName;
+ if (rule.path) {
+ this._pathComponents = rule.path.split("/").filter((component) => component.length > 0);
+ this._pathParameterMap = makePathParameterMapping(this._pathComponents);
+ }
+ else {
+ this._pathComponents = null;
+ this._pathParameterMap = null;
+ }
+ this._pathExtension = rule.pathExtension;
+ this._query = parseQuery(rule.query);
+ this._hash = rule.hash;
+ this._regex = rule.regex;
+ if (rule.exclusions) {
+ this._exclusions = rule.exclusions.map(function (ex) {
+ return new UrlRule(ex);
+ });
+ }
+ else {
+ this._exclusions = null;
+ }
+ }
+ /**
+ * Checks whether or not the route matches a given URL.
+ * @param url The URL to check against.
+ * @returns true if the route matches `urls`; false otherwise.
+ */
+ matches(url) {
+ var _a, _b;
+ if (this._regex) {
+ if (!this._regex.length) {
+ // If the rule specifies regex but does not supply patterns, we need to return false. Otherwise, we will
+ // risk matching against everything. This is because an empty regex with no other rule parameters will
+ // cause us to fallthrough to the end and match against all URLs.
+ return false;
+ }
+ let didMatchRegex = false;
+ for (const regexPattern of this._regex) {
+ if (regexPattern.test(url.toString())) {
+ // If we match against any of regex patterns, then we should proceed.
+ // If no matches are found, then this rule is not matched.
+ didMatchRegex = true;
+ break;
+ }
+ }
+ if (!didMatchRegex) {
+ return false;
+ }
+ }
+ if (this._protocol && url.protocol !== this._protocol) {
+ return false;
+ }
+ if (this._hostName && url.host !== this._hostName) {
+ return false;
+ }
+ if (this._pathComponents) {
+ const rulePathComponents = this._pathComponents;
+ const urlPathComponents = url.pathComponents();
+ if (rulePathComponents.length !== urlPathComponents.length) {
+ return false;
+ }
+ // We're iterating two arrays here, an old style for-loop is appropriate
+ const length = rulePathComponents.length;
+ for (let i = 0; i < length; i++) {
+ const ruleComponent = rulePathComponents[i];
+ if (isPathComponentParameter(ruleComponent)) {
+ // component parameters always match
+ continue;
+ }
+ const urlComponent = urlPathComponents[i];
+ if (ruleComponent !== urlComponent) {
+ return false;
+ }
+ }
+ }
+ if (this._pathExtension) {
+ if (url.pathExtension() !== this._pathExtension) {
+ return false;
+ }
+ }
+ if (this._query) {
+ for (const param of this._query) {
+ let value;
+ if (param.caseInsensitive) {
+ for (const [queryKey, queryValue] of Object.entries((_a = url.query) !== null && _a !== void 0 ? _a : {})) {
+ if (param.key.toLocaleLowerCase() === queryKey.toLocaleLowerCase()) {
+ value = queryValue;
+ }
+ }
+ }
+ else {
+ value = (_b = url.query) === null || _b === void 0 ? void 0 : _b[param.key];
+ }
+ if (!value && !param.optional) {
+ return false;
+ }
+ if (param.value && param.value !== value) {
+ return false;
+ }
+ }
+ }
+ if (this._hash && url.hash !== this._hash) {
+ return false;
+ }
+ if (this._exclusions) {
+ for (const exclusionRule of this._exclusions) {
+ if (exclusionRule._exclusions) {
+ throw Error("Matching exclusion rules with further exclusion rules may introduce significant code-complexity and/or reduce the ease with which developers are able to reason about your desired goals. Are there any simpler options?");
+ }
+ if (exclusionRule.matches(url)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ /**
+ * Extract information from a matching url.
+ * @param matchingUrl The url to extract parameters from.
+ * @returns `Parameters` extracted from `matchingUrl`
+ * @note This function is only valid when `this.matches(matchingUrl) === true`.
+ */
+ extractParameters(matchingUrl) {
+ var _a, _b;
+ const parameters = {};
+ if (this._pathComponents !== null && this._pathParameterMap !== null) {
+ const urlPathComponents = matchingUrl.pathComponents();
+ for (const internalKey of Object.keys(this._pathParameterMap)) {
+ const externalKey = getPathComponentParameter(internalKey);
+ const index = this._pathParameterMap[internalKey];
+ const parameterValue = getPathComponentParameterValueUsingInternalKey(urlPathComponents[index], internalKey);
+ parameters[externalKey] = decodeURIComponent(parameterValue);
+ }
+ }
+ if (this._query) {
+ for (const param of this._query) {
+ parameters[param.key] = (_b = (_a = matchingUrl.query) === null || _a === void 0 ? void 0 : _a[param.key]) !== null && _b !== void 0 ? _b : undefined;
+ }
+ }
+ return parameters;
+ }
+}
+/**
+ * `UrlRouter` manages a set of url rule templates to allow `urls` to serve as keys for different associated objects (like Builders).
+ *
+ * @note This is replaces old `UrlRouter` as a synchronous way match route URLs to handlers. In contrast to the previous implementation,
+ * it maps entire objects (containing related async handlers and properties) to urls.
+ */
+export class UrlRouter {
+ /// Constructs an empty URL router object.
+ constructor() {
+ this._routeMappings = [];
+ }
+ /**
+ * Register a new route defined by a set of definitions and object on the router.
+ * @param routeDefinitions The definitions of rules to register.
+ * @param object The object for the rule.
+ */
+ associate(routeDefinitions, object) {
+ const route = [];
+ for (const definition of routeDefinitions) {
+ route.push(new UrlRule(definition));
+ }
+ this._routeMappings.push({ route: route, object: object });
+ }
+ /**
+ * Resolve given url to associated object, if any exist.
+ * @param urlOrString URL or string representation of url to resolve objects for.
+ * @returns `UrlRouterResult` containing url, extracted parameters, and associated object, or `null` if no match was found.
+ */
+ routedObjectForUrl(urlOrString) {
+ let url = typeof urlOrString === "string" ? new urls.URL(urlOrString) : urlOrString;
+ url = urlUtil.normalizedAppStoreUrl(url);
+ url = urlUtil.normalizedActionUrl(url);
+ for (const mapping of this._routeMappings) {
+ for (const rule of mapping.route) {
+ if (rule.matches(url)) {
+ return {
+ normalizedUrl: url,
+ parameters: rule.extractParameters(url),
+ object: mapping.object,
+ matchedRuleIdentifier: rule.identifier,
+ };
+ }
+ }
+ }
+ // No match. Still return a result with normalized url.
+ return {
+ normalizedUrl: url,
+ parameters: null,
+ object: null,
+ matchedRuleIdentifier: null,
+ };
+ }
+}
+// endregion
+//# sourceMappingURL=routing-components.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/action-provider.js b/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/action-provider.js
new file mode 100644
index 0000000..e0038da
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/action-provider.js
@@ -0,0 +1,13 @@
+import { isBaseActionProvider } from "@jet/environment/dispatching";
+/**
+ * Retrieve an {@linkcode ActionModel} for the given {@linkcode intent} through
+ * any registered `IntentController`s that implement `ActionProvider`
+ */
+export function actionFor(intent, objectGraph, options = {}) {
+ const resolvedController = objectGraph.dispatcher.controller(intent);
+ if (isBaseActionProvider(resolvedController)) {
+ return resolvedController.actionFor(intent, objectGraph, options);
+ }
+ return null;
+}
+//# sourceMappingURL=action-provider.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/app-store-intent-dispatcher.js b/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/app-store-intent-dispatcher.js
new file mode 100644
index 0000000..0a88ada
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/app-store-intent-dispatcher.js
@@ -0,0 +1,62 @@
+import { IntentDispatcher } from "@jet/environment/dispatching";
+import { ExperimentCache } from "../experimentation/experiment-cache";
+import { MetricsIdentifiersCache, MetricsIdentifierType, paymentIdentifierContextMapping, personalizationIdentifierContextMapping, } from "../metrics/metrics-identifiers-cache";
+import { AppStoreObjectGraph } from "./app-store-object-graph";
+export class AppStoreIntentDispatcher {
+ constructor() {
+ this.dispatcher = new IntentDispatcher();
+ }
+ // MARK: - IBaseIntentDispatcher
+ register(controller) {
+ this.dispatcher.register(controller);
+ }
+ async dispatch(intent, objectGraph) {
+ const intentObjectGraph = await this.createIntentObjectGraphWithAsyncValues(objectGraph);
+ return await this.dispatcher.dispatch(intent, intentObjectGraph);
+ }
+ controller(intent) {
+ return this.dispatcher.controller(intent);
+ }
+ get registeredControllers() {
+ return this.dispatcher.registeredControllers;
+ }
+ async createIntentObjectGraphWithAsyncValues(objectGraph) {
+ const experimentCache = new ExperimentCache();
+ const metricsIdentifiersCache = new MetricsIdentifiersCache();
+ const personalizationMetricsIdentifierCache = new MetricsIdentifiersCache(personalizationIdentifierContextMapping);
+ let paymentMetricsIdentifiersCache;
+ // Doing these in sequence to help minimize the liklihood of hitting the treatment store locking timeout
+ // rdar://147518835 (AppStore Ad Regression Performance - Metrics Identifier blocking network requests)
+ if (objectGraph instanceof AppStoreObjectGraph) {
+ let appStoreObjectGraph = objectGraph;
+ // reassigning the bag creates a fresh cache
+ appStoreObjectGraph = appStoreObjectGraph.addingBag(appStoreObjectGraph.bag.underlyingBag);
+ await experimentCache.loadTreatments(appStoreObjectGraph);
+ await metricsIdentifiersCache.loadValues(appStoreObjectGraph, [
+ MetricsIdentifierType.client,
+ MetricsIdentifierType.user,
+ ]);
+ // Only load the values if the namespace is in the bag
+ if (objectGraph.bag.personalizationUserIdEnabled) {
+ await personalizationMetricsIdentifierCache.loadValues(objectGraph, [MetricsIdentifierType.user]);
+ }
+ // The payment topic uses a different metrics identifier cache as it has a separate namespace.
+ if (appStoreObjectGraph.props.enabled("paymentTopicFromBag") &&
+ appStoreObjectGraph.bag.metricsPaymentNamespaceEnabled) {
+ paymentMetricsIdentifiersCache = new MetricsIdentifiersCache(paymentIdentifierContextMapping);
+ // Only add this promise if paymentMetricsIdentifiersCache is actually created
+ await paymentMetricsIdentifiersCache.loadValues(appStoreObjectGraph, [
+ MetricsIdentifierType.client,
+ MetricsIdentifierType.user,
+ ]);
+ }
+ objectGraph = appStoreObjectGraph;
+ }
+ return objectGraph
+ .adding(ExperimentCache.metatype, experimentCache)
+ .adding(MetricsIdentifiersCache.defaultMetatype, metricsIdentifiersCache)
+ .adding(MetricsIdentifiersCache.paymentMetatype, paymentMetricsIdentifiersCache || metricsIdentifiersCache)
+ .adding(MetricsIdentifiersCache.personalizationMetatype, personalizationMetricsIdentifierCache);
+ }
+}
+//# sourceMappingURL=app-store-intent-dispatcher.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/app-store-object-graph.js b/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/app-store-object-graph.js
new file mode 100644
index 0000000..a5102b2
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/app-store-object-graph.js
@@ -0,0 +1,380 @@
+import { ObjectGraph } from "@jet/environment/dependencies";
+import { fetchTimingMetricsBuilderType, } from "@jet/environment/metrics/fetch-timing-metrics-builder";
+import * as jetTypes from "@jet/environment/types/globals/types";
+import * as askTypes from "../../api/typings/constants";
+import { isDefinedNonNull } from "../json-parsing/server-data";
+import { AppleSiliconWrapper } from "../wrappers/apple-silicon";
+import { BagWrapper } from "../wrappers/bag";
+import { ClientWrapper } from "../wrappers/client";
+import { ClientOrderingWrapper } from "../wrappers/client-ordering";
+import { ConsoleWrapper } from "../wrappers/console";
+import { HostWrapper } from "../wrappers/host";
+import { LocalizationWrapper } from "../wrappers/localization";
+import { PropertiesWrapper } from "../wrappers/properties";
+import { StorageWrapper } from "../wrappers/storage";
+import { ExperimentCache } from "../experimentation/experiment-cache";
+import { MetricsIdentifiersCache } from "../metrics/metrics-identifiers-cache";
+import { LocaleMetaType } from "../dependencies/locale/locale";
+import { SEOMetaType } from "../dependencies/seo";
+import { ActiveIntentMetaType, ActiveIntent } from "../dependencies/active-intent";
+import { LocaleFromBag } from "../dependencies/locale/locale-from-bag";
+import { ObjectGraphType } from "../../gameservicesui/src/foundation/object-graph-types";
+export class AppStoreObjectGraph extends ObjectGraph {
+ configureDefaults(
+ // Jet Types
+ aBag, aCryptography, aHost, aNetwork, aPlatform, aPlist, aRandom, aServices, aCookieProvider, aConsole,
+ // Ask Types
+ aStoreMetrics, aAMSEngagement, aLocalization, aAdsLocalizer, aDevice, aClient, aProperties, aUser, aPlayer, aMetricsIdentifiers, aClientOrdering, aArcade, aGameCenter, aResilientDeepLinks, aAppleSilicon, aStorage, aAds, aOnDeviceRecommendationsManager, aOnDeviceSearchHistoryManager, aFeatureFlags, aMediaToken, aAppDistribution, timeoutManager, treatmentStore, userDefaults) {
+ let objectGraph = this
+ // Jet Types
+ .addingCryptography(aCryptography)
+ .addingHost(aHost)
+ .addingNetwork(aNetwork)
+ .addingPlatform(aPlatform)
+ .addingPlist(aPlist)
+ .addingRandom(aRandom)
+ .addingServices(aServices)
+ .addingCookieProvider(aCookieProvider)
+ .addingBag(aBag) // Bag depends on having Host
+ .addingConsole(aConsole)
+ // ASK types
+ .addingStoreMetrics(aStoreMetrics)
+ .addingAMSEngagement(aAMSEngagement)
+ .addingLoc(aLocalization)
+ .addingAdsLoc(aAdsLocalizer)
+ .addingDevice(aDevice)
+ .addingClient(aClient)
+ .addingProperties(aProperties)
+ .addingUser(aUser)
+ .addingPlayer(aPlayer)
+ .addingMetricsIdentifiers(aMetricsIdentifiers)
+ .addingClientOrdering(aClientOrdering)
+ .addingArcade(aArcade)
+ .addingGameCenter(aGameCenter)
+ .addingDeepLinks(aResilientDeepLinks)
+ .addingAppleSilicon(aAppleSilicon)
+ .addingStorage(aStorage)
+ .addingAds(aAds)
+ .addingOnDeviceRecommendationsManager(aOnDeviceRecommendationsManager)
+ .addingOnDeviceSearchHistoryManager(aOnDeviceSearchHistoryManager)
+ .addingFeatureFlags(aFeatureFlags)
+ .addingMediaToken(aMediaToken)
+ .addingAppDistribution(aAppDistribution)
+ .addingTimeoutManager(timeoutManager)
+ .addingAdsLoc(aAdsLocalizer)
+ .addingTreatmentStore(treatmentStore)
+ .addingUserDefaults(userDefaults);
+ objectGraph.loc.load(objectGraph);
+ // `LocaleFromBag` requires that the `bag` is already present on the Object Graph
+ objectGraph = objectGraph.addingLocale(new LocaleFromBag(objectGraph));
+ return objectGraph;
+ }
+ // Jet Types
+ get bag() {
+ return this.required(BagWrapper.type);
+ }
+ addingBag(bag) {
+ return this.addingBagWrapper(new BagWrapper(bag, this.host)).adding(jetTypes.bag, bag);
+ }
+ addingBagWrapper(bag) {
+ return this.adding(BagWrapper.type, bag);
+ }
+ get console() {
+ return this.required(ConsoleWrapper.type);
+ }
+ addingConsole(console) {
+ return this.addingConsoleWrapper(new ConsoleWrapper(console));
+ }
+ addingConsoleWrapper(console) {
+ return this.adding(ConsoleWrapper.type, console);
+ }
+ get cryptography() {
+ return this.required(jetTypes.cryptography);
+ }
+ addingCryptography(cryptography) {
+ return this.adding(jetTypes.cryptography, cryptography);
+ }
+ get host() {
+ return this.required(HostWrapper.type);
+ }
+ addingHost(host) {
+ return this.addingHostWrapper(new HostWrapper(host));
+ }
+ addingHostWrapper(host) {
+ return this.adding(HostWrapper.type, host);
+ }
+ get locale() {
+ return this.required(LocaleMetaType);
+ }
+ addingLocale(locale) {
+ return this.adding(LocaleMetaType, locale);
+ }
+ get network() {
+ return this.required(jetTypes.net);
+ }
+ addingNetwork(network) {
+ return this.adding(jetTypes.net, network);
+ }
+ get platform() {
+ return this.required(jetTypes.platform);
+ }
+ addingPlatform(platform) {
+ return this.adding(jetTypes.platform, platform);
+ }
+ get plist() {
+ return this.required(jetTypes.plist);
+ }
+ addingPlist(plist) {
+ return this.adding(jetTypes.plist, plist);
+ }
+ get random() {
+ return this.required(jetTypes.random);
+ }
+ addingRandom(random) {
+ return this.adding(jetTypes.random, random);
+ }
+ get services() {
+ return this.required(jetTypes.services);
+ }
+ addingServices(services) {
+ return this.adding(jetTypes.services, services);
+ }
+ get cookieProvider() {
+ return this.required(jetTypes.cookieProvider);
+ }
+ addingCookieProvider(cookieProvider) {
+ return this.adding(jetTypes.cookieProvider, cookieProvider);
+ }
+ get fetchTimingMetricsBuilder() {
+ return this.optional(fetchTimingMetricsBuilderType);
+ }
+ addingFetchTimingMetricsBuilder(fetchTimingMetricsBuilder) {
+ return this.adding(fetchTimingMetricsBuilderType, fetchTimingMetricsBuilder);
+ }
+ // Ask Types
+ get storeMetrics() {
+ return this.required(askTypes.storeMetrics);
+ }
+ addingStoreMetrics(storeMetrics) {
+ return this.adding(askTypes.storeMetrics, storeMetrics);
+ }
+ get amsEngagement() {
+ return this.optional(askTypes.amsEngagement);
+ }
+ addingAMSEngagement(amsEngagement) {
+ return this.adding(askTypes.amsEngagement, amsEngagement);
+ }
+ get loc() {
+ return this.required(LocalizationWrapper.type);
+ }
+ addingLoc(loc) {
+ return this.addingLocWrapper(new LocalizationWrapper(loc, this));
+ }
+ addingLocWrapper(loc) {
+ return this.adding(LocalizationWrapper.type, loc);
+ }
+ get adsLoc() {
+ return this.required(askTypes.adsLocalizer);
+ }
+ addingAdsLoc(loc) {
+ return this.adding(askTypes.adsLocalizer, loc);
+ }
+ get device() {
+ return this.required(askTypes.device);
+ }
+ addingDevice(device) {
+ return this.adding(askTypes.device, device);
+ }
+ get client() {
+ return this.required(ClientWrapper.type);
+ }
+ addingClient(client) {
+ return this.addingClientWrapper(new ClientWrapper(client));
+ }
+ addingClientWrapper(client) {
+ return this.adding(ClientWrapper.type, client);
+ }
+ get props() {
+ return this.required(PropertiesWrapper.type);
+ }
+ addingProperties(properties) {
+ return this.addingPropertiesWrapper(new PropertiesWrapper(properties));
+ }
+ addingPropertiesWrapper(properties) {
+ return this.adding(PropertiesWrapper.type, properties);
+ }
+ get user() {
+ return this.required(askTypes.user);
+ }
+ addingUser(user) {
+ return this.adding(askTypes.user, user);
+ }
+ /// Returns the current GameCenter player, only available for GAMES_TARGET
+ get player() {
+ if (preprocessor.GAMES_TARGET && this.props.enabled("157263806-add-playerBridge-askGlobal")) {
+ return this.required(askTypes.player);
+ }
+ else {
+ return undefined;
+ }
+ }
+ addingPlayer(player) {
+ return this.adding(askTypes.player, player);
+ }
+ get metricsIdentifiers() {
+ return this.required(askTypes.metricsIdentifiers);
+ }
+ addingMetricsIdentifiers(metricsIdentifiers) {
+ return this.adding(askTypes.metricsIdentifiers, metricsIdentifiers);
+ }
+ get clientOrdering() {
+ return this.required(ClientOrderingWrapper.type);
+ }
+ addingClientOrdering(clientOrdering) {
+ return this.addingClientOrderingWrapper(new ClientOrderingWrapper(clientOrdering));
+ }
+ addingClientOrderingWrapper(clientOrdering) {
+ return this.adding(ClientOrderingWrapper.type, clientOrdering);
+ }
+ get arcade() {
+ return this.required(askTypes.arcade);
+ }
+ addingArcade(arcade) {
+ return this.adding(askTypes.arcade, arcade);
+ }
+ get gameCenter() {
+ return this.required(askTypes.gameCenter);
+ }
+ addingGameCenter(gameCenter) {
+ return this.adding(askTypes.gameCenter, gameCenter);
+ }
+ get deepLinks() {
+ return this.required(askTypes.resilientDeepLinks);
+ }
+ addingDeepLinks(resilientDeepLinks) {
+ return this.adding(askTypes.resilientDeepLinks, resilientDeepLinks);
+ }
+ get appleSilicon() {
+ return this.required(AppleSiliconWrapper.type);
+ }
+ addingAppleSilicon(appleSilicon) {
+ return this.addingAppleSiliconWrapper(new AppleSiliconWrapper(appleSilicon));
+ }
+ addingAppleSiliconWrapper(appleSilicon) {
+ return this.adding(AppleSiliconWrapper.type, appleSilicon);
+ }
+ get storage() {
+ return this.required(StorageWrapper.type);
+ }
+ addingStorage(storage) {
+ return this.addingStorageWrapper(new StorageWrapper(storage));
+ }
+ addingStorageWrapper(storage) {
+ return this.adding(StorageWrapper.type, storage);
+ }
+ get ads() {
+ return this.required(askTypes.ads);
+ }
+ addingAds(ads) {
+ return this.adding(askTypes.ads, ads);
+ }
+ get onDeviceRecommendationsManager() {
+ return this.required(askTypes.onDeviceRecommendationsManager);
+ }
+ addingOnDeviceRecommendationsManager(onDeviceRecommendationsManager) {
+ return this.adding(askTypes.onDeviceRecommendationsManager, onDeviceRecommendationsManager);
+ }
+ get onDeviceSearchHistoryManager() {
+ return this.required(askTypes.onDeviceSearchHistoryManager);
+ }
+ addingOnDeviceSearchHistoryManager(onDeviceSearchHistoryManager) {
+ return this.adding(askTypes.onDeviceSearchHistoryManager, onDeviceSearchHistoryManager);
+ }
+ get featureFlags() {
+ return this.required(askTypes.featureFlags);
+ }
+ addingFeatureFlags(featureFlags) {
+ return this.adding(askTypes.featureFlags, featureFlags);
+ }
+ get mediaToken() {
+ return this.required(askTypes.mediaToken);
+ }
+ addingMediaToken(mediaToken) {
+ return this.adding(askTypes.mediaToken, mediaToken);
+ }
+ get appDistribution() {
+ return this.required(askTypes.appDistribution);
+ }
+ addingAppDistribution(appDistribution) {
+ return this.adding(askTypes.appDistribution, appDistribution);
+ }
+ get timeoutManager() {
+ return this.required(askTypes.timeoutManager);
+ }
+ addingTimeoutManager(timeoutManager) {
+ return this.adding(askTypes.timeoutManager, timeoutManager);
+ }
+ get treatmentStore() {
+ return this.required(askTypes.treatmentStore);
+ }
+ addingTreatmentStore(treatmentStore) {
+ return this.adding(askTypes.treatmentStore, treatmentStore);
+ }
+ get experimentCache() {
+ return this.optional(ExperimentCache.metatype);
+ }
+ get metricsIdentifiersCache() {
+ return this.optional(MetricsIdentifiersCache.defaultMetatype);
+ }
+ get paymentMetricsIdentifiersCache() {
+ return this.optional(MetricsIdentifiersCache.paymentMetatype);
+ }
+ get personalizationMetricsIdentifiersCache() {
+ return this.optional(MetricsIdentifiersCache.personalizationMetatype);
+ }
+ get userDefaults() {
+ if (!this.client.isiOS) {
+ return undefined;
+ }
+ return this.required(askTypes.userDefaults);
+ }
+ addingUserDefaults(userDefaults) {
+ return this.adding(askTypes.userDefaults, userDefaults);
+ }
+ isAvailable(type) {
+ return isDefinedNonNull(this.optional(type));
+ }
+ // "Web" client dependencies
+ get activeIntent() {
+ return this.optional(ActiveIntentMetaType);
+ }
+ addingActiveIntent(implementation) {
+ return this.adding(ActiveIntentMetaType, new ActiveIntent(implementation));
+ }
+ get seo() {
+ return this.optional(SEOMetaType);
+ }
+ addingSEO(implementation) {
+ return this.adding(SEOMetaType, implementation);
+ }
+ // GameStoreKit dependencies
+ get dispatcher() {
+ return this.required(ObjectGraphType.dispatcher);
+ }
+ get nativeIntentDispatcher() {
+ return this.required(ObjectGraphType.nativeIntentDispatcher);
+ }
+ get debugSettings() {
+ return this.required(ObjectGraphType.debugSettings);
+ }
+ get router() {
+ return this.required(ObjectGraphType.router);
+ }
+ get localizer() {
+ return this.required(ObjectGraphType.localizer);
+ }
+ get personNameComponentsFormatter() {
+ return this.required(ObjectGraphType.personNameComponentsFormatter);
+ }
+}
+//# sourceMappingURL=app-store-object-graph.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/runtime.js b/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/runtime.js
new file mode 100644
index 0000000..d3b5904
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/runtime/runtime.js
@@ -0,0 +1,52 @@
+import * as validation from "@jet/environment/json/validation";
+import { LegacyRuntime } from "@jet/environment/runtime";
+export class AppStoreRuntime extends LegacyRuntime {
+ constructor(dispatcher, objectGraph) {
+ super(dispatcher, objectGraph, {});
+ }
+ exportingService(name, service) {
+ this.wrapServiceInValidation(service);
+ const existingService = this.serviceWithName(name) || {};
+ const newService = {
+ ...existingService,
+ ...service,
+ };
+ return super.exportingService(name, newService);
+ }
+ // eslint-disable-next-line @typescript-eslint/ban-types
+ exportingServiceName(name, functionName, implementation) {
+ const service = {};
+ service[functionName] = implementation;
+ this.exportingService(name, service);
+ }
+ wrapServiceInValidation(service) {
+ for (const memberName of Object.keys(service)) {
+ const serviceFunction = service[memberName];
+ // For every service route function that we find, we're going
+ // to wrap it in a thunk so that we can attach validation
+ // incidents to it before it's returned back to native code.
+ if (serviceFunction instanceof Function) {
+ // Using a regular function here because we want to forward
+ // `this` as well as our arguments to the wrapped function.
+ service[memberName] = function validationThunk(...args) {
+ // Execute the function
+ const returnValue = serviceFunction.apply(this, args);
+ // Ensure we record validation incidents after the fact
+ if (returnValue instanceof Promise) {
+ return returnValue.then((value) => {
+ validation.recordValidationIncidents(value);
+ return value;
+ });
+ }
+ else {
+ // This would be a violation of the calling convention,
+ // but I guess we might as well consume the incidents.
+ validation.recordValidationIncidents(returnValue);
+ return returnValue;
+ }
+ };
+ }
+ }
+ }
+}
+//# sourceMappingURL=runtime.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/util/array-util.js b/node_modules/@jet-app/app-store/tmp/src/foundation/util/array-util.js
new file mode 100644
index 0000000..57c8e84
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/util/array-util.js
@@ -0,0 +1,32 @@
+/**
+ * Creates an array containing multiple copies of the elements of another array.
+ *
+ * For example, if A = [Q, W, E, R, T, Y], then calling repeating(A, 3) would return
+ * [Q, W, E, R, T, Y, Q, W, E, R, T, Y, Q, W, E, R, T, Y]
+ *
+ * @param elements The elements that will be repeated
+ * @param times The number of times all of the elements should appear in the new array.
+ */
+export function arrayByRepeating(elements, times) {
+ if (times <= 0) {
+ return [];
+ }
+ let newArray = [];
+ for (let i = 0; i < times; i++) {
+ newArray = newArray.concat(elements);
+ }
+ return newArray;
+}
+export function partition(array, predicate) {
+ const part1 = [];
+ const part2 = [];
+ array.forEach((item) => (predicate(item) ? part1.push(item) : part2.push(item)));
+ return [part1, part2];
+}
+export function isEmpty(elements) {
+ return elements.length === 0;
+}
+export function isNotEmpty(elements) {
+ return !isEmpty(elements);
+}
+//# sourceMappingURL=array-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/util/color-util.js b/node_modules/@jet-app/app-store/tmp/src/foundation/util/color-util.js
new file mode 100644
index 0000000..84e3815
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/util/color-util.js
@@ -0,0 +1,231 @@
+import { isNothing, isSome } from "@jet/environment/types/optional";
+const HEX_PARSER = new RegExp("#?([0-9,a-f,A-F][0-9,a-f,A-F])([0-9,a-f,A-F][0-9,a-f,A-F])([0-9,a-f,A-F][0-9,a-f,A-F])([0-9,a-f,A-F][0-9,a-f,A-F])?");
+export function fromHex(hexString) {
+ if (isNothing(hexString) || hexString === "") {
+ return null;
+ }
+ const results = HEX_PARSER.exec(hexString);
+ if (results === null || !(results.length === 4 || results.length === 5)) {
+ return null;
+ }
+ const red = parseInt(results[1], 16) / 255;
+ const green = parseInt(results[2], 16) / 255;
+ const blue = parseInt(results[3], 16) / 255;
+ const alpha = isSome(results[4]) ? parseInt(results[4], 16) / 255 : undefined;
+ const newColor = {
+ red: red,
+ green: green,
+ blue: blue,
+ alpha: alpha,
+ type: "rgb",
+ };
+ return newColor;
+}
+export const black = rgbWith(0, 0, 0);
+export const white = rgbWith(1, 1, 1);
+export function rgbWith(red, green, blue, alpha) {
+ const newColor = {
+ red: red,
+ green: green,
+ blue: blue,
+ alpha: alpha,
+ type: "rgb",
+ };
+ return newColor;
+}
+export function luminanceFrom(rgbColor) {
+ // Note: This is lifted from UIColor_Private
+ // Using RGB color components, calculates and returns (0.2126 * r) + (0.7152 * g) + (0.0722 * b).
+ return rgbColor.red * 0.2126 + rgbColor.green * 0.7152 + rgbColor.blue * 0.0722;
+}
+/**
+ * Determines whether the given color is considered dark.
+ * @param color The color to test.
+ * @param darkColorCutoff The cutoff value, which ought to be between 0 and 100.
+ */
+export function isDarkColor(color, darkColorCutoff = 84) {
+ if (isNothing(color)) {
+ return false;
+ }
+ switch (color.type) {
+ case "rgb":
+ // Multiplying 100 because the rest of the codebase now works off darkColorCutoff 0-100.
+ // Before this change it used to be darkColorCutoff 0.0-1.0
+ return luminanceFrom(color) * 100 < darkColorCutoff;
+ case "named":
+ return isNamedColorDarkColor(color);
+ default:
+ return false;
+ }
+}
+function isNamedColorDarkColor(namedColor) {
+ switch (namedColor.name) {
+ case "black":
+ return true;
+ default:
+ return false;
+ }
+}
+export function named(name) {
+ const newColor = {
+ name: name,
+ type: "named",
+ };
+ return newColor;
+}
+export function dynamicWith(lightColor, darkColor) {
+ const newColor = {
+ lightColor: lightColor,
+ darkColor: darkColor,
+ type: "dynamic",
+ };
+ return newColor;
+}
+/**
+ * A convenience for comparing two colors to see if they are equal.
+ * @param firstColor The first color to compare
+ * @param secondColor The second color to compare
+ * @returns True if the colors are considered equal
+ */
+export function isColorEqualToColor(firstColor, secondColor) {
+ if (isNothing(firstColor) || isNothing(secondColor)) {
+ return false;
+ }
+ if (firstColor.type === "rgb" && secondColor.type === "rgb") {
+ return (firstColor.red === secondColor.red &&
+ firstColor.green === secondColor.green &&
+ firstColor.blue === secondColor.blue &&
+ firstColor.alpha === secondColor.alpha);
+ }
+ else if (firstColor.type === "named" && secondColor.type === "named") {
+ return firstColor.name === secondColor.name;
+ }
+ else if (firstColor.type === "dynamic" && secondColor.type === "dynamic") {
+ return (isColorEqualToColor(firstColor.lightColor, secondColor.lightColor) &&
+ isColorEqualToColor(firstColor.darkColor, secondColor.darkColor));
+ }
+ return false;
+}
+/**
+ * Type guard to check if a value is a ColorBucketSaturationBrightnessCriteria.
+ * @param value The value to check.
+ * @returns True if the value is a ColorBucketSaturationBrightnessCriteria, false otherwise.
+ */
+function isSaturationBrightnessCriteria(value) {
+ if (typeof value !== "object" || value === null) {
+ return false;
+ }
+ const obj = value; // Cast for easier property access
+ // Check if all required properties exist and have the correct types
+ return ("colorHex" in obj &&
+ typeof obj.colorHex === "string" &&
+ "maxSaturation" in obj &&
+ typeof obj.maxSaturation === "number" &&
+ "maxBrightness" in obj &&
+ typeof obj.maxBrightness === "number");
+}
+/**
+ * Finds the closest matching color from a list of hex colors using LAB color space
+ * for more perceptually accurate matching
+ */
+export function findColorBucketForColor(targetRGB, saturationBrightnessBasedBuckets, hueBasedBuckets) {
+ if (isNothing(targetRGB) || (saturationBrightnessBasedBuckets.length === 0 && hueBasedBuckets.length === 0)) {
+ return null;
+ }
+ const targetColor = targetRGB;
+ if (isNothing(targetColor)) {
+ return null;
+ }
+ const targetHsb = rgbToHsb(targetColor);
+ for (const saturationBrightnessBasedBucket of saturationBrightnessBasedBuckets) {
+ if (doesColorMeetCriteria(targetHsb, saturationBrightnessBasedBucket)) {
+ return fromHex(saturationBrightnessBasedBucket.colorHex);
+ }
+ }
+ for (const hueBasedBucket of hueBasedBuckets) {
+ if (doesColorMeetCriteria(targetHsb, hueBasedBucket)) {
+ return fromHex(hueBasedBucket.colorHex);
+ }
+ }
+ return null;
+}
+/**
+ * Determines whether a color meets the specified criteria.
+ *
+ * For saturation/brightness criteria, the color passes if either its saturation or brightness
+ * is less than or equal to the maximum allowed values.
+ *
+ * For hue criteria, the color passes if its hue falls within the specified range (inclusive).
+ *
+ * @param color The RGB color to evaluate
+ * @param criteria The criteria to check against (either hue-based or saturation/brightness-based)
+ * @returns True if the color passes the criteria, false otherwise or if color is null/undefined
+ */
+export function doesColorMeetCriteria(color, criteria) {
+ if (isNothing(color)) {
+ return false;
+ }
+ const hsbColor = isHSBColor(color) ? color : rgbToHsb(color);
+ // Check saturation / brightness buckets first, using <= for inclusive range checks
+ if (isSaturationBrightnessCriteria(criteria)) {
+ return hsbColor.saturation <= criteria.maxSaturation || hsbColor.brightness <= criteria.maxBrightness;
+ }
+ else {
+ // Check if the hue falls within the min/max bounds (inclusive)
+ // Special handling for the Pink range potentially including 360 if needed,
+ // but mapping 360->0 covers the Coral case correctly.
+ return hsbColor.hue >= criteria.minHue && hsbColor.hue <= criteria.maxHue;
+ }
+}
+function isHSBColor(value) {
+ if (typeof value !== "object" || value === null) {
+ return false;
+ }
+ const obj = value; // Cast for easier property access
+ // Check if the type property exists and has the correct value
+ return "type" in obj && obj.type === "hsb";
+}
+/**
+ * Converts RGB color to HSB color space.
+ * HSL is particularly good at representing relationships between colors
+ * as humans perceive them, especially for determining similar hues
+ * across different brightness levels.
+ */
+function rgbToHsb(rgb) {
+ const r = rgb.red;
+ const g = rgb.green;
+ const b = rgb.blue;
+ const max = Math.max(r, g, b);
+ const min = Math.min(r, g, b);
+ const delta = max - min;
+ // Calculate brightness (value in HSV)
+ const brightness = max * 100;
+ // Calculate saturation
+ const saturation = max === 0 ? 0 : (delta / max) * 100;
+ // Calculate hue
+ let hue = 0;
+ if (delta !== 0) {
+ switch (max) {
+ case r:
+ hue = ((g - b) / delta + (g < b ? 6 : 0)) * 60;
+ break;
+ case g:
+ hue = ((b - r) / delta + 2) * 60;
+ break;
+ case b:
+ hue = ((r - g) / delta + 4) * 60;
+ break;
+ default:
+ break;
+ }
+ }
+ // Ensure hue is in [0, 360)
+ hue = (hue + 360) % 360;
+ return {
+ type: "hsb",
+ hue: hue,
+ saturation: saturation,
+ brightness: brightness,
+ };
+}
+//# sourceMappingURL=color-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/util/constants.js b/node_modules/@jet-app/app-store/tmp/src/foundation/util/constants.js
new file mode 100644
index 0000000..86522d5
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/util/constants.js
@@ -0,0 +1,26 @@
+/**
+ * Created by keithpk on 2/9/17.
+ */
+/**
+ * These are the developer IDs for apps that Apple works on internally
+ * The IDs in the list below are CPIDs (content provider IDs), which are not exposed
+ * to the client. So, we use the developer.id field that we have available instead.
+ * Note that Dark Sky both the same developer ID as Apple Inc.
+ * 2003 Apple Inc
+ * 120076162 Apple Health Research
+ * 244848 The Dark Sky Company LLC
+ * 115216 Claris International Inc.
+ * 14275 Shazam Entertainment Limited
+ * 118988565 AC Wellness Network LLC
+ */
+export const appleOwnedDeveloperIds = [
+ "284417353",
+ "1464590764",
+ "314638464",
+ "284993479",
+ "1351056256", // AC Wellness Network LLC
+];
+export const iAdRequestDataHeaderName = "X-Apple-iAd-Request-Data";
+export const appStoreClientRequestIdName = "X-Apple-App-Store-Client-Request-Id";
+export const iAdRoutingInfoHeaderName = "X-Apple-iAd-Env-Name";
+//# sourceMappingURL=constants.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/util/date-util.js b/node_modules/@jet-app/app-store/tmp/src/foundation/util/date-util.js
new file mode 100644
index 0000000..182dd63
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/util/date-util.js
@@ -0,0 +1,127 @@
+//
+// date.ts
+// AppStoreKit
+//
+// Created by Sam Vafaee on 10/25/17.
+// Copyright (c) 2017 Apple Inc. All rights reserved.
+//
+import * as serverData from "../json-parsing/server-data";
+/**
+ * Returns the date representation for a string ignoring the time. For preorders, it
+ * is vital that any date string be passed through this before being used.
+ * @param dateString The date string to parse
+ * @returns A date in local time zone.
+ */
+export function parseDateOmittingTimeFromString(dateString) {
+ // Sanity check
+ if (serverData.isNull(dateString) || dateString === "") {
+ return null;
+ }
+ // Only consider date
+ if (dateString.indexOf("T") !== -1) {
+ dateString = dateString.split("T")[0];
+ }
+ // This string replacement is needed to ensure midnight in the local time zone
+ // is used, rather than UTC-midnight.
+ return new Date(dateString.replace(/-/g, "/"));
+}
+/**
+ * Strips time from the given date and returns it as midnight UTC of the same day.
+ * @param date Local date.
+ * @returns UTC date
+ */
+export function convertLocalDateToUTCMidnight(date) {
+ // Sanity check
+ if (serverData.isNull(date)) {
+ return null;
+ }
+ return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
+}
+/**
+ * Returns the number of milliseconds from epoch to UTC midnight of the given date.
+ * @param date Local date.
+ * @returns Milliseconds from epoch.
+ */
+export function millisecondsToUTCMidnightFromLocalDate(date) {
+ // Sanity check
+ if (serverData.isNull(date)) {
+ return null;
+ }
+ const utcDate = convertLocalDateToUTCMidnight(date);
+ if (serverData.isNull(utcDate)) {
+ return null;
+ }
+ return utcDate.getTime();
+}
+/**
+ * Removes the time portion from the given date and returns it as midnight of the same day.
+ * @param date Local date
+ * @returns Local date
+ */
+export function convertLocalDateToLocalMidnight(date) {
+ // Sanity check
+ if (serverData.isNull(date)) {
+ return null;
+ }
+ const midnight = new Date(date);
+ midnight.setHours(0, 0, 0, 0);
+ return midnight;
+}
+/**
+ * Sets the minutes, seconds and milliseconds of the given date to 0.
+ * @param date Local date
+ * @returns Local date
+ */
+export function convertLocalDateByFlooringToHour(date) {
+ // Sanity check
+ if (serverData.isNull(date)) {
+ return null;
+ }
+ return dateFlooredToHour(date);
+}
+/**
+ * Creates a new date with the minutes, seconds and milliseconds of the given date set to 0.
+ * @param date The date to remove minutes, seconds, and milliseconds from.
+ * @returns The new date with minutes, seconds, and milliseconds removed using local device time zone.
+ */
+export function dateFlooredToHour(date) {
+ const dateCopy = new Date(date.getTime());
+ dateCopy.setMinutes(0);
+ dateCopy.setSeconds(0);
+ dateCopy.setMilliseconds(0);
+ return dateCopy;
+}
+/**
+ * Returns the number of hours between two dates, or null if either date is null.
+ * @param fromDate The date to calculate from.
+ * @param toDate The date to calculate to.
+ */
+export function numberOfHoursBetween(fromDate, toDate) {
+ // Sanity check
+ if (serverData.isNull(fromDate) || serverData.isNull(toDate)) {
+ return null;
+ }
+ return Math.ceil((toDate.getTime() - dateFlooredToHour(fromDate).getTime()) / (60 * 60 * 1000));
+}
+/**
+ * Returns the number of days between two dates, or null if either date is null.
+ * @param fromDate The date to calculate from.
+ * @param toDate The date to calculate to.
+ */
+export function numberOfDaysBetween(fromDate, toDate) {
+ // Sanity check
+ if (serverData.isNull(fromDate) || serverData.isNull(toDate)) {
+ return null;
+ }
+ return Math.floor((toDate.getTime() - fromDate.getTime()) / (60 * 60 * 1000 * 24));
+}
+/**
+ * Whether or not two dates occur on the same day.
+ * @param date A date that will be compared to the second input.
+ * @param otherDate A date that will be compared to the first input.
+ */
+export function areLocalDatesSameDay(date, otherDate) {
+ var _a, _b;
+ return ((_a = convertLocalDateToLocalMidnight(date)) === null || _a === void 0 ? void 0 : _a.getTime()) === ((_b = convertLocalDateToLocalMidnight(otherDate)) === null || _b === void 0 ? void 0 : _b.getTime());
+}
+//# sourceMappingURL=date-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/util/errors.js b/node_modules/@jet-app/app-store/tmp/src/foundation/util/errors.js
new file mode 100644
index 0000000..d1a3482
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/util/errors.js
@@ -0,0 +1,13 @@
+/**
+ * Created by Ben Williams on 3/13/19.
+ */
+/**
+ * Enables compile time detection for code that should be unreachable
+ * For example, calling this function in the default case of a switch statement
+ * will provide compile time checking that the switch is exhaustive
+ * @return never
+ */
+export function unreachable(value) {
+ throw new Error(`This method should never be called with value: ${value}`);
+}
+//# sourceMappingURL=errors.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/util/math-util.js b/node_modules/@jet-app/app-store/tmp/src/foundation/util/math-util.js
new file mode 100644
index 0000000..92b4992
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/util/math-util.js
@@ -0,0 +1,32 @@
+/**
+ * Given an integer m > 1, called a modulus, two integers are said to be congruent modulo m,
+ * if m is a divisor of their difference (i.e., if there is an integer k such that a \u2212 b = km).
+ *
+ * Unlike JavaScript's remainder operator (%), this mod function never returns negative values.
+ *
+ * @param n An integer
+ * @param m The modulous
+ */
+export function mod(n, m) {
+ return ((n % m) + m) % m;
+}
+/**
+ * Reduce sig fig of `value` to `significantDigits`.
+ * @param value Value to reduce precision of.
+ * @param significantDigits Significant digits to keep.
+ */
+export function reduceSignificantDigits(value, significantDigits) {
+ const roundFactor = Math.pow(10.0, significantDigits);
+ const roundingFunction = value > 0.0 ? Math.floor : Math.ceil;
+ return roundingFunction(value / roundFactor) * roundFactor;
+}
+/**
+ * Clamps a value between an upper and lower bound.
+ * @param value The preferred value.
+ * @param min The minimum allowed value.
+ * @param max The maximum allowed value.
+ */
+export function clamp(value, min, max) {
+ return Math.max(min, Math.min(max, value));
+}
+//# sourceMappingURL=math-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/util/objects.js b/node_modules/@jet-app/app-store/tmp/src/foundation/util/objects.js
new file mode 100644
index 0000000..6740572
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/util/objects.js
@@ -0,0 +1,33 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/**
+ * Created by km on 3/6/17.
+ */
+/**
+ * Creates and returns a shallow copy of a given object.
+ * @param object The object to create a shallow copy of.
+ * @returns The new copy of object.
+ */
+export function shallowCopyOf(object) {
+ if (object === null || object === undefined) {
+ return object;
+ }
+ const copy = Object.create(Object.getPrototypeOf(object));
+ Object.assign(copy, object);
+ return copy;
+}
+/**
+ * Returns whether or not a given object is an instance of
+ * a specified type, modifying the type of the object in
+ * TypeScript's type system.
+ *
+ * Prefer this function to using `instanceof` directly as it
+ * will retype the passed in object if the check succeeds.
+ *
+ * @param object The object whose nominal type will be checked.
+ * @param type The metatype that `object` is expected to be an instance of.
+ * @returns Whether object is an instance of type, updating the type of object accordingly.
+ */
+export function isTypeOf(object, type) {
+ return object instanceof type;
+}
+//# sourceMappingURL=objects.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/util/promise-util.js b/node_modules/@jet-app/app-store/tmp/src/foundation/util/promise-util.js
new file mode 100644
index 0000000..d661559
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/util/promise-util.js
@@ -0,0 +1,215 @@
+/**
+ * This module provides enhanced promise handling capabilities beyond native JavaScript Promises.
+ * It focuses on distinguishing between required and optional promises, standardizing results,
+ * and simplifying error handling for complex async operations.
+ */
+/**
+ * Symbol used to uniquely identify PromiseResult objects.
+ * This allows for reliable type checking.
+ */
+export const PROMISE_RESULT_SYMBOL = Symbol("PromiseResult");
+/**
+ * Type guard to check if a value is a PromiseResult.
+ *
+ * @param value - The value to check
+ * @returns True if the value is a PromiseResult, false otherwise
+ */
+function isPromiseResult(value) {
+ return value !== null && typeof value === "object" && PROMISE_RESULT_SYMBOL in value;
+}
+/**
+ * Custom error class that aggregates multiple promise failures.
+ * Provides access to all underlying errors while presenting a combined error message.
+ */
+export class MultiPromiseError extends Error {
+ constructor(reasons) {
+ super(errorMessageForReasons(reasons));
+ this.reasons = reasons;
+ this.reasons = reasons;
+ }
+}
+/**
+ * Helper function to create a standardized fulfilled result object.
+ * Ensures the result has the PROMISE_RESULT_SYMBOL for type checking.
+ *
+ * @param value - The value to wrap in a fulfilled result
+ * @returns A standardized fulfilled result object
+ */
+export function makeFulfilledResult(value) {
+ return {
+ success: true,
+ value,
+ error: null,
+ [PROMISE_RESULT_SYMBOL]: true,
+ };
+}
+/**
+ * Helper function to create a standardized rejected result object.
+ * Ensures the result has the PROMISE_RESULT_SYMBOL for type checking.
+ *
+ * @param error - The error to wrap in a rejected result
+ * @returns A standardized rejected result object
+ */
+export function makeRejectedResult(error) {
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
+ return {
+ success: false,
+ value: null,
+ error: normalizedError,
+ [PROMISE_RESULT_SYMBOL]: true,
+ };
+}
+/**
+ * Custom error class that indicates that a required promise failed.
+ */
+export class RequiredPromiseError extends Error {
+ constructor(reason) {
+ super(reason.message);
+ this.reason = reason;
+ this.reason = reason;
+ }
+}
+/**
+ * Transforms a promise into one that never throws, instead returning a standardized result.
+ * Use this for optional operations that shouldn't cause the overall process to fail.
+ * This is a convenience alias for tryAwait with a more semantic name.
+ *
+ * @param promise - The promise to make optional
+ * @returns A promise that resolves to a PromiseResult instead of throwing
+ */
+export async function optional(promise) {
+ return await tryAwait(promise);
+}
+/**
+ * Marks a promise as required (will throw on rejection).
+ * This is mainly for code clarity as regular promises are already required by default.
+ *
+ * @param promise - The promise to mark as required
+ * @returns The original promise (identity function)
+ */
+export async function required(promise) {
+ return await promise;
+}
+/**
+ * Executes a mix of required and optional promises, returning standardized results.
+ * Regular promises are treated as required and will cause this function to throw if they fail.
+ * Promises wrapped with optional() will never cause this function to throw.
+ *
+ * This implementation handles both regular promises and promises that resolve to PromiseResult.
+ *
+ * @param promises - Array of promises (both required and optional)
+ * @returns Promise resolving to an array of standardized results
+ * @throws Original error if a single required promise fails
+ * @throws MultiPromiseError if multiple required promises fail
+ * @note For homogeneous promise arrays, prefer using more specific functions:
+ * - Use allRequired() when all promises should be treated as required
+ * - Use allOptional() when all promises should be treated as optional
+ * - Only use allMixed() when you need to combine both required and optional promises
+ */
+export async function allMixed(promises) {
+ // Process each promise
+ const results = await Promise.all(promises.map(async (promise) => {
+ try {
+ const value = await promise;
+ // If it's a PromiseResult (from optional()/tryAwait), use it directly
+ if (isPromiseResult(value)) {
+ // Handle the case where T is already a PromiseResult
+ // This fixes the double-wrapping issue when all promises are optional
+ return value;
+ }
+ else {
+ // It's a regular promise that succeeded
+ return makeFulfilledResult(value);
+ }
+ }
+ catch (error) {
+ // This was a required promise that failed
+ return makeRejectedResult(error instanceof Error ? error : new Error(String(error)));
+ }
+ }));
+ const requiredFailures = [];
+ results.forEach((result) => {
+ if (!result.success) {
+ requiredFailures.push(result.error);
+ }
+ });
+ // If any required promises failed, throw an error
+ if (requiredFailures.length > 0) {
+ if (requiredFailures.length === 1) {
+ throw requiredFailures[0];
+ }
+ else {
+ throw new MultiPromiseError(requiredFailures);
+ }
+ }
+ return results;
+}
+/**
+ * Executes all promises and returns their values.
+ * All promises are treated as required and will cause this function to throw if any fail.
+ *
+ * @param promises - Array of promises
+ * @returns Promise resolving to an array of values
+ * @throws Original error if a single promise fails
+ * @throws MultiPromiseError if multiple promises fail
+ */
+export async function allRequired(promises) {
+ return await Promise.all(promises);
+}
+/**
+ * Makes all promises optional and then executes them, returning standardized results.
+ * This function is specifically designed for cases where you want all promises to be treated as optional.
+ * It avoids the double-wrapping issue that can occur when using allMixed with arrays of optional promises.
+ *
+ * @param promises - Array of promises to make optional
+ * @returns Promise resolving to an array of standardized results
+ * @example
+ * ```
+ * const promises = urls.map(fetchData);
+ * const results = await allOptional(promises);
+ * // Each result is a PromiseResult<T> that won't cause the overall operation to fail
+ * ```
+ */
+export async function allOptional(promises) {
+ // Use Promise.all with optional() for each promise
+ return await Promise.all(promises.map(optional));
+}
+/**
+ * Creates an error message from multiple rejection reasons.
+ * Formats multiple error reasons into a single error message string,
+ * handling both Error objects and other rejection values.
+ *
+ * @param reasons - Array of error reasons that caused promise rejections
+ * @returns Concatenated error message string
+ */
+function errorMessageForReasons(reasons) {
+ return reasons
+ .map((reason) => {
+ if (reason instanceof Error) {
+ return reason.message;
+ }
+ else {
+ return JSON.stringify(reason);
+ }
+ })
+ .join("");
+}
+/**
+ * Safely awaits a promise and returns a standardized result object.
+ * Eliminates the need for repetitive try/catch blocks throughout the codebase.
+ *
+ * @param promiseOrFn - Either a promise or a function that returns a promise
+ * @returns A standardized PromiseResult object indicating success or failure
+ */
+export async function tryAwait(promiseOrFn) {
+ try {
+ // Handle both promise and function that returns a promise
+ const promise = typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn;
+ const value = await promise;
+ return makeFulfilledResult(value);
+ }
+ catch (error) {
+ return makeRejectedResult(error);
+ }
+}
+//# sourceMappingURL=promise-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/util/string-util.js b/node_modules/@jet-app/app-store/tmp/src/foundation/util/string-util.js
new file mode 100644
index 0000000..affeba3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/util/string-util.js
@@ -0,0 +1,127 @@
+/**
+ * Created by dabolfathi on 4/26/17.
+ */
+/**
+ * Returns a single string, which is the result of joining together all
+ * the strings in the list with the provided separator.
+ * @param strings The list of strings to be joined.
+ * @param separator The separator to use when joining them together.
+ * @returns {string} The joined string.
+ */
+export function join(strings, separator) {
+ if (strings == null || separator == null) {
+ return null;
+ }
+ if (strings.length === 0) {
+ return "";
+ }
+ let stringCount = strings.length;
+ let joinedString = "";
+ strings.forEach((element, index) => {
+ if (element === null) {
+ stringCount -= 1;
+ }
+ else {
+ joinedString += element;
+ if (index < stringCount - 1) {
+ joinedString += separator;
+ }
+ }
+ });
+ return joinedString;
+}
+/**
+ * Generate a normalized string for robust multilingual search that properly handles
+ * CJK (Chinese, Japanese, Korean), Arabic, Cyrillic, and Latin-based scripts.
+ * Preserves Unicode characters while normalizing diacritics and case appropriately.
+ *
+ * @param input The input string to normalize
+ * @returns A normalized string suitable for search matching
+ */
+export function normalizeForSearch(input) {
+ if (!input) {
+ return "";
+ }
+ try {
+ // Remove special characters
+ // \u2122 -> (Trade Mark Sign)
+ // \u2120 -> (Service Mark)
+ // \u03a9 -> (Greek Capital Letter Omega)
+ // \u00a9 -> (Copyright Sign)
+ // \u00ae -> (Registered Sign)
+ // \u30fc -> (Katakana-Hiragana Prolonged Sound Mark)
+ // \u03c9 -> (Greek Small Letter Omega)
+ const removedSpecialUnicodesRegex = /[\u2122\u2120\u03a9\u00a9\u00ae\u30fc\u03c9]/g;
+ return (input
+ .toLowerCase() // Case insensitivity
+ .replace(removedSpecialUnicodesRegex, "")
+ // Apply normalization only to Latin characters to preserve CJK integrity
+ // Handle Latin characters safely with basic ranges A-Za-z
+ // \u00C0-\u00FF - Latin-1 Supplement (Upper Half)
+ // \u0100-\u017F - Latin Extended-A (includes dotless i \u0131)
+ // \u0180-\u024F - Latin Extended-B
+ // \u1E00-\u1EFF - Latin Extended Additional
+ .replace(/[A-Za-z\u00A0-\u00FF\u0100-\u017F\u0180-\u024F\u1E00-\u1EFF\p{Diacritic}]+/gu, (latinText) => {
+ return (latinText
+ .normalize("NFKD")
+ .replace(/\p{Diacritic}/gu, "")
+ // Convert specific characters that don't match basic Latin
+ .replace(/\u0131/g, "i") // Convert dotless i to regular i
+ // Keep only actual Latin characters and numbers after normalization
+ .replace(/[^A-Za-z0-9]/g, ""));
+ })
+ // Remove punctuation, symbols, and control characters but preserve:
+ // - Letters from all writing systems (\p{L})
+ // - Numbers (\p{N})
+ // - Whitespace (\s)
+ // - Underscores (_)
+ .replace(/[^\p{L}\p{N}\s_]/gu, "")
+ // Normalize multiple whitespace to single spaces
+ .replace(/\s+/g, " ")
+ // Trim leading/trailing whitespace
+ .trim());
+ }
+ catch (error) {
+ // Fallback: use basic character classes
+ return (input
+ .toLowerCase()
+ // Remove punctuation, symbols, and control characters but preserve:
+ // - Letters from all writing systems (\p{L})
+ // - Numbers (\p{N})
+ // - Whitespace (\s)
+ // - Underscores (_)
+ .replace(/[^\p{L}\p{N}\s_]/gu, "")
+ // Normalize multiple whitespace to single spaces
+ .replace(/\s+/g, " ")
+ // Trim leading/trailing whitespace
+ .trim());
+ }
+}
+/**
+ * Whether or not the input string is contains search term using normalized string which comparing using case insensitive, locale insensitive and ignore all special characters.
+ *
+ * @param input
+ * @param normalizedTerm
+ * @returns
+ */
+export function containsSearchTerm(input, normalizedTerm) {
+ const normalizedInput = normalizeForSearch(input);
+ return normalizedInput.includes(normalizedTerm);
+}
+/**
+ * Wraps a string with bidirectional isolate characters to ensure proper text direction handling.
+ * This is particularly useful for user-generated content like display names that may contain
+ * mixed left-to-right and right-to-left text.
+ *
+ * @param text The string to wrap with bidi isolates
+ * @returns The string wrapped with Left-to-Right Isolate (U+2066) and Pop Directional Isolate (U+2069)
+ */
+export function withBidiIsolates(text) {
+ if (!text) {
+ return text;
+ }
+ // U+2068: First Strong Isolate
+ // U+2069: Pop Directional Isolate
+ return "\u2068" + text + "\u2069";
+}
+//# sourceMappingURL=string-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/util/validation-util.js b/node_modules/@jet-app/app-store/tmp/src/foundation/util/validation-util.js
new file mode 100644
index 0000000..fe9bd93
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/util/validation-util.js
@@ -0,0 +1,30 @@
+import { isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import { tryAwait } from "./promise-util";
+/**
+ * Creates a validation context for async operations and ensures proper cleanup.
+ *
+ * Wraps an async operation in a validation context, automatically handling
+ * context cleanup even when errors occur. Optionally processes errors through
+ * a provided error handler.
+ *
+ * @param name - The name for the validation context
+ * @param producer - Async function that produces the result
+ * @param errorHandler - Optional function to handle errors from the producer
+ * @returns The result of the async operation
+ */
+export async function withAsyncValidationContext(name, producer, errorHandler) {
+ validation.beginContext(name);
+ let result = await tryAwait(producer());
+ if (!result.success && isSome(errorHandler)) {
+ result = await tryAwait(errorHandler(result.error));
+ }
+ validation.endContext();
+ if (!result.success) {
+ throw result.error;
+ }
+ else {
+ return result.value;
+ }
+}
+//# sourceMappingURL=validation-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/apple-silicon.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/apple-silicon.js
new file mode 100644
index 0000000..4371e77
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/apple-silicon.js
@@ -0,0 +1,13 @@
+import { makeMetatype } from "@jet/environment/util/metatype";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import { Wrapper } from "./wrapper";
+export class AppleSiliconWrapper extends Wrapper {
+ get isSupportEnabled() {
+ return serverData.isDefinedNonNull(this.implementation) && this.implementation.isSupportEnabled;
+ }
+ get isRosettaAvailable() {
+ return serverData.isDefinedNonNull(this.implementation) && this.implementation.isRosettaAvailable;
+ }
+}
+AppleSiliconWrapper.type = makeMetatype("app-store:as-wrapper");
+//# sourceMappingURL=apple-silicon.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/bag.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/bag.js
new file mode 100644
index 0000000..ad76b76
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/bag.js
@@ -0,0 +1,1126 @@
+/**
+ * Created by km on 1/9/17.
+ */
+import { isSome } from "@jet/environment";
+import { makeMetatype } from "@jet/environment/util/metatype";
+import * as serverData from "../json-parsing/server-data";
+import { Wrapper } from "./wrapper";
+import { CachedBag } from "./cached-bag";
+export class BagWrapper extends Wrapper {
+ constructor(bag, host) {
+ super(new CachedBag(bag));
+ this.underlyingBag = bag;
+ }
+ /// The boolean for whether today ad medium lockup screenshots are enabled
+ get todayAdMediumLockupScreenshotEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("today-ad-medium-lockup-screenshots-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ get todayAdMediumLockupScreenshotAnimationEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("today-ad-medium-lockup-screenshots-animation-enabled")) !== null && _a !== void 0 ? _a : true;
+ }
+ /// The URL for the trending searches endpoint.
+ get trendingSearchesURL() {
+ return this.implementation.url("trending-searches");
+ }
+ /// The URL for the search hints endpoint.
+ get searchHintsURL() {
+ return this.implementation.url("searchHints");
+ }
+ /// The URL for fetching a personalized review
+ get personalizedUserReviewURL() {
+ return this.implementation.url("personalizedUserReviewUrl");
+ }
+ /// The boolean for whether personalized reviews are enabled
+ get personalizedUserReviewEnabled() {
+ return this.implementation.boolean("personalizedUserReviewEnabled");
+ }
+ /// The URL for posting tap-to-rate requests.
+ get userRateURL() {
+ return this.implementation.url("p2-application-user-rate-content");
+ }
+ /// The URL for posting write review requests.
+ get writeReviewURL() {
+ return this.implementation.url("p2-application-user-write-review");
+ }
+ /// The base URL for accessory rooms
+ get accessoryRoomURL() {
+ return this.implementation.url("p2-accessory-room");
+ }
+ /// The URL for passbook main room
+ get passbookMainURL() {
+ return this.implementation.url("passbook");
+ }
+ /// The URL for library-link room
+ get libraryLinkURL() {
+ return this.implementation.url("library-link");
+ }
+ /// The metrics configuration for mt-metrics kit integration
+ get metricsConfiguration() {
+ return serverData.asJSONData(this.implementation.dictionary("metrics"));
+ }
+ // When the metrics payment topic is enabled the clientId and userId are on a separate namespace.
+ get metricsPaymentNamespaceEnabled() {
+ if (serverData.isNullOrEmpty(this.metricsPaymentTopic)) {
+ return false;
+ }
+ const identifiers = serverData.asJSONData(this.implementation.dictionary("metrics-identifiers"));
+ const metricsNameSpace = serverData.asDictionary(identifiers, "APPSTORE_PAYMENTS_ENGAGEMENT");
+ const metricsClientIdSpace = serverData.asDictionary(identifiers, "APPSTORE_PAYMENTS_ENGAGEMENT_CLIENT");
+ return (isSome(metricsNameSpace) &&
+ metricsNameSpace.length !== 0 &&
+ isSome(metricsClientIdSpace) &&
+ metricsClientIdSpace.length !== 0);
+ }
+ // The Payment topic if one is provided by the bag. Otherwise the default one will be used
+ get metricsPaymentTopic() {
+ var _a, _b;
+ if (preprocessor.GAMES_TARGET) {
+ return (_a = serverData.asString(this.metricsConfiguration, "topics.GAMES_PAYMENTS_ENGAGEMENT_TOPIC")) !== null && _a !== void 0 ? _a : null;
+ }
+ else {
+ return (_b = serverData.asString(this.metricsConfiguration, "topics.APPSTORE_PAYMENTS_ENGAGEMENT_TOPIC")) !== null && _b !== void 0 ? _b : null;
+ }
+ }
+ // Whether the personalization user id has been inlcuded in the metrics identifiers bag entry
+ get personalizationUserIdEnabled() {
+ const identifiers = serverData.asJSONData(this.implementation.dictionary("metrics-identifiers"));
+ const personalizationIdNameSpace = serverData.asDictionary(identifiers, "APPSTORE_PERSONALIZATION");
+ return isSome(personalizationIdNameSpace) && Object.keys(personalizationIdNameSpace).length !== 0;
+ }
+ /// The language for the users storefront
+ get language() {
+ return this.implementation.string("language");
+ }
+ /// The language for media API
+ get mediaApiLanguage() {
+ const languageTag = this.implementation.string("language-tag");
+ if (languageTag) {
+ return languageTag;
+ }
+ return this.implementation.string("language");
+ }
+ /// The URL for terms and conditions page
+ get termsAndConditionsURL() {
+ return this.implementation.url("p2-service-terms-url");
+ }
+ /// whether we should send iAd data as a post request
+ get usePostForAppStoreSearch() {
+ return this.implementation.boolean("usePostForAppStoreSearch");
+ }
+ /// Whether or not monetary gifting is enabled.
+ get isMonetaryGiftingEnabled() {
+ return this.implementation.boolean("isBuyingScheduledGiftCertificateEnabled");
+ }
+ /// The URL for Account Top-Up in the finance sheet.
+ get accountTopUpURL() {
+ return this.implementation.url("AddFundsUrl");
+ }
+ /// The title for Account Top-Up in the finance sheet.
+ get accountTopUpTitle() {
+ return this.implementation.string("account-top-up-title");
+ }
+ /// Whether or not content gifting is enabled.
+ get isContentGiftingEnabled() {
+ return this.implementation.boolean("isScheduledGiftingEnabled");
+ }
+ /// The URL for buy button metadata.
+ get buyButtonMetadataURL() {
+ return this.implementation.url("personalized-buy-buttons/software");
+ }
+ /// Whether or not the current storefront supports the TV App.
+ get isTVAppEnabled() {
+ return this.implementation.boolean("uvSearch/nowplaying-enabled");
+ }
+ /// the url to send people to to email support
+ get emailSupportLinkURL() {
+ return this.implementation.url("supportLinkUrl");
+ }
+ /// The URL to report a review item helpful or not.
+ get voteUrl() {
+ return this.implementation.url("voteUrl");
+ }
+ /// Enables the Review Summary module
+ get enableReviewSummarization() {
+ return this.implementation.boolean("enable-review-summarization");
+ }
+ /// Provides configuration for Review Summary Report A Concern
+ get reviewSummaryReportConcernData() {
+ return serverData.asJSONData(this.implementation.dictionary("review-summarization-report-concern"));
+ }
+ /// The URL to report a concern about a review item.
+ get reportConcernUrl() {
+ return this.implementation.url("reportConcernUrl");
+ }
+ /// The explanation for the report a concern screen for a review.
+ get reportConcernExplanation() {
+ return this.implementation.string("reportConcernExplanation");
+ }
+ /// An array of reasons for reporting a concern for a review.
+ get reportConcernReasons() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("reportConcernReasons")));
+ }
+ /// The boolean that enables report a problem.
+ get reportProblemEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("product-page-report-problem-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// The bag key that stores the report a problem URL.
+ get productPageReportProblemURL() {
+ return this.implementation.string("product-page-report-problem-url");
+ }
+ /// The string array of adamIDs of SAD apps with subscriptions.
+ get productPageReportProblemSADSubscriptionArray() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("product-page-report-problem-sad-subscriptions")));
+ }
+ /// The string array of second party apps to not show the report a problem link for.
+ /// We have added the array of IDs while we wait for bag key support. Removal is tracked by rdar://82606581 (Remove array of second party app ids from JS bag.)
+ get productPageReportProblemSecondPartyAppArray() {
+ const secondPartyAppArray = serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("product-page-report-problem-second-party-apps")));
+ const defaultSecondPartyAppArray = [
+ "1473505534",
+ "1416238567",
+ "640199958",
+ "1529498570",
+ "915061776",
+ "1130498044",
+ "1070072560",
+ ];
+ if (serverData.isNullOrEmpty(secondPartyAppArray)) {
+ return defaultSecondPartyAppArray;
+ }
+ return secondPartyAppArray;
+ }
+ /// The URL to create a new account.
+ get createAccountUrl() {
+ var _a;
+ return ((_a = this.implementation.url("createAccountUrl")) !== null && _a !== void 0 ? _a : "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/signupWizard");
+ }
+ get mediaCountryCode() {
+ return this.implementation.string("countryCode");
+ }
+ get mediaHost() {
+ return this.implementation.url("apps-media-api-host");
+ }
+ mediaEdgeHost(objectGraph) {
+ // The key has different type for AppStoreComponents
+ // see <rdar://71290056>.
+ if (objectGraph.host.clientIdentifier === "com.apple.appstorecomponentsd") {
+ return this.implementation.url("apps-media-api-edge-host");
+ }
+ else {
+ return this.implementation.string("apps-media-api-edge-host");
+ }
+ }
+ get mediaAPICatalogMixedShouldUseEdge() {
+ var _a;
+ return (_a = this.implementation.boolean("apps-media-api-catalog-mixed-should-use-edge")) !== null && _a !== void 0 ? _a : false;
+ }
+ get mediaEdgeSearchHost() {
+ return this.implementation.string("apps-media-api-search-edge-host");
+ }
+ get mediaPreviewHost() {
+ return this.implementation.string("apps-media-api-preview-host");
+ }
+ get mediaRealmHost() {
+ return this.implementation.string("notification-settings-media-api-host");
+ }
+ get edgeEndpoints() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("apps-media-api-edge-end-points")));
+ }
+ get mediaAdvertRequestLimit() {
+ var _a;
+ return (_a = this.implementation.double("apps-media-api-search-ads-limit")) !== null && _a !== void 0 ? _a : 4;
+ }
+ get searchSortOptions() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("searchSortOptions")));
+ }
+ get ageBands() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("ageBands")));
+ }
+ get redirectUrlWhitelistedQueryParams() {
+ let params = serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("processRedirectUrl/whitelistedQueryParams")));
+ if (serverData.isNullOrEmpty(params)) {
+ params = [
+ "affC",
+ "adId",
+ "advp",
+ "at",
+ "ct",
+ "itsct",
+ "itscg",
+ "itscc",
+ "itcCt",
+ "its_qt",
+ "ls",
+ "partnerId",
+ "pt",
+ "qtkid",
+ "uo",
+ ];
+ }
+ return params;
+ }
+ get redirectUrlEndpoint() {
+ var _a;
+ return ((_a = this.implementation.string("processRedirectUrl/endpoint")) !== null && _a !== void 0 ? _a : "https://itunes.apple.com/WebObjects/MZStoreServices.woa/wa/processRedirectUrl");
+ }
+ get aristotleParentAppAdamId() {
+ var _a;
+ return (_a = this.implementation.string("aristotle-app-id")) !== null && _a !== void 0 ? _a : "383941000";
+ }
+ /// The AdamId of the App Store app which owns the IAP for Arcade subscriptions
+ get arcadeAppAdamId() {
+ return this.implementation.string("app-store-app-id");
+ }
+ /// The product family of the Arcade subscription IAP. This is for multiple IAPs for the same service not family sharing.
+ get arcadeProductFamilyId() {
+ var _a;
+ return (_a = this.implementation.string("arcade-iap-family-id")) !== null && _a !== void 0 ? _a : this.implementation.string("ocelot-iap-family-id");
+ }
+ /// The Identifier (developer supplied reverse dns style name) of the IAP for Arcade subscriptions
+ get arcadeProductId() {
+ var _a;
+ return ((_a = this.implementation.string("arcade-iap-offer-name")) !== null && _a !== void 0 ? _a : this.implementation.string("ocelot-iap-offer-name"));
+ }
+ /// The percentage of users that will see the Arcade category bar See All Games uplift.
+ get arcadeCategoryBarSAGUpliftDisplayRate() {
+ var _a;
+ return (_a = this.implementation.double("arcade-category-bar-see-all-games-display-rate")) !== null && _a !== void 0 ? _a : 0.0;
+ }
+ get isArcadeEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("arcade-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ get isAppsGroupingTagsEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("apps-groupings-tags-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ get isAppsProductPageTagsEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("apps-product-page-tags-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ get isAppsSlpTagsEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("apps-slp-tags-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ get searchResultsLearnMoreEditorialId() {
+ return this.implementation.string("transparencyLawEditorialItemId");
+ }
+ /// An array containing a mapping of system app bundle IDs and adam IDs.
+ get systemApps() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("hideableSystemApps")));
+ }
+ /// An array containing a mapping of system app bundle IDs and adam IDs
+ /// Should only be used for watchOS as a temporary fix for rdar://100234873
+ get nonDeletableSystemApps() {
+ const apps = serverData.asJSONValue(this.implementation.array("nonDeletableSystemApps"));
+ if (serverData.isDefinedNonNullNonEmpty(apps)) {
+ return serverData.asArrayOrEmpty(apps);
+ }
+ else {
+ return [
+ { "id": 1635387927, "bundle-id": "com.apple.Depth" },
+ { "id": 1635862301, "bundle-id": "com.apple.Mandrake" },
+ { "id": 1584216343, "bundle-id": "com.apple.findmy.finddevices" },
+ { "id": 1584215960, "bundle-id": "com.apple.NanoWorldClock" },
+ { "id": 1584215812, "bundle-id": "com.apple.HeartRate" },
+ { "id": 1584215851, "bundle-id": "com.apple.SessionTrackerApp" },
+ { "id": 1146562108, "bundle-id": "com.apple.NanoPhone" },
+ { "id": 1146560473, "bundle-id": "com.apple.MobileSMS" },
+ { "id": 1584215428, "bundle-id": "com.apple.NanoPhotos" },
+ { "id": 1459455352, "bundle-id": "com.apple.DeepBreathing" },
+ { "id": 1067456176, "bundle-id": "com.apple.NanoCompass.watchkitapp" }, // Compass
+ ];
+ }
+ }
+ /// The standard tabs for the current storefront.
+ get tabsStandard() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("tabs/standard")));
+ }
+ // URL to sub-in for deep links specifying the watch category. Note that this can be a multiplexing url.
+ get watchAppsGroupingURL() {
+ var _a;
+ return ((_a = this.implementation.url("watchAppsGrouping")) !== null && _a !== void 0 ? _a : "https://apps.apple.com/WebObjects/MZStore.woa/wa/viewFeature?id=1472048385");
+ }
+ get requireAgeVerification() {
+ return this.implementation.boolean("requireAgeVerification");
+ }
+ get ageRatingLearnMoreEditorialItemId() {
+ return this.implementation.string("ageRatingLearnMoreEditorialItemId");
+ }
+ get appleSiliconMacUnverifiedBadgeEditorialItemId() {
+ return this.implementation.string("appleSiliconMacUnverifiedBadgeEditorialItemId");
+ }
+ get safariExtensionsGroupingURL() {
+ return this.implementation.url("safariExtensionsGrouping");
+ }
+ get familySubscriptionsLearnMoreEditorialItemId() {
+ return this.implementation.string("familySubscriptionsLearnMoreEditorialItemId");
+ }
+ get dynamicUIRegexStrings() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("commerce-ui-urls/dynamic-url-patterns")));
+ }
+ get financeUIRegexStrings() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("commerce-ui-urls/url-patterns")));
+ }
+ get webViewRegexStrings() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("commerce-ui-urls/v2-url-patterns")));
+ }
+ get arcadePreOrderUpsellLimitSeconds() {
+ var _a;
+ return (_a = this.implementation.double("arcadePreOrderUpsellLimitSeconds")) !== null && _a !== void 0 ? _a : 86400;
+ }
+ get recentlyPlayedGamesWindowInSeconds() {
+ var _a;
+ return (_a = this.implementation.double("recentlyPlayedGamesWindowInSeconds")) !== null && _a !== void 0 ? _a : 7776000; // 90 days
+ }
+ get gamesFriendsPlayedWindowInSeconds() {
+ var _a;
+ return (_a = this.implementation.integer("games-friends-played-window-in-seconds")) !== null && _a !== void 0 ? _a : 15778800; // Half a year
+ }
+ get enableComingSoonToggle() {
+ return this.implementation.boolean("enableComingSoonToggle");
+ }
+ /// Whether or not to show the app acessibility labels
+ get enableAppAccessibilityLabels() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-app-accessibility-labels")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Whether or not to show the app privacy labels
+ get enablePrivacyNutritionLabels() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-privacy-nutrition-labels")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Whether or not to show the seller info
+ get enableSellerInfo() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-seller-info")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Whether or not to show the seller ICP annotation
+ get enableSellerICPAnnotation() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-seller-icp")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Whether to display enhanced category features (bar, breakout, and swoosh) on apps and games tabs
+ get enableFeaturedCategoriesOnGroupings() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-featured-categories-on-groupings")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Whether to display enhanced category bricks on apps and games tabs
+ get enableCategoryBricksOnGroupings() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-category-bricks-on-groupings")) !== null && _a !== void 0 ? _a : false;
+ }
+ get arcadeOfferEditorialItemId() {
+ return this.implementation.string("arcadeOfferEditorialItemId");
+ }
+ get sponsoredSearchODMLTimeout() {
+ var _a;
+ return (_a = this.implementation.double("sponsored-search-odml-timeout")) !== null && _a !== void 0 ? _a : 3;
+ }
+ get isSearchLandingAdsEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("isSearchLandingAdsEnabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ get isLLMSearchTagsEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("apps-search-tags-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ get searchLandingAdFetchTimeout() {
+ var _a;
+ return (_a = this.implementation.double("search-landing-ad-fetch-timeout")) !== null && _a !== void 0 ? _a : 0.175;
+ }
+ // Time that SLP needs to be offscreen for it to be refreshed in seconds.
+ get searchLandingPageOffscreenRefreshInterval() {
+ var _a;
+ return (_a = this.implementation.double("search-landing-offscreen-refresh-interval-in-seconds")) !== null && _a !== void 0 ? _a : 60; // default is minute
+ }
+ // Time to delay the data fetch for SLP when refreshing
+ get searchLandingPageRefreshUpdateDelayInterval() {
+ var _a;
+ return (_a = this.implementation.double("search-landing-page-update-delay-interval-in-seconds")) !== null && _a !== void 0 ? _a : 0.3; // default is 0.3s
+ }
+ get appPrivacyLearnMoreEditorialItemId() {
+ return this.implementation.string("appPrivacyLearnMoreEditorialItemId");
+ }
+ // The editorial id for the article page showing the transparency info for ratings and reviews
+ get ratingsAndReviewsLearnMoreEditorialId() {
+ return this.implementation.string("ratings-and-reviews-learn-more-editorial-item-id");
+ }
+ // The editorial id for the article page showing info about the review summary
+ get reviewSummarizationLearnMoreEditorialItemId() {
+ return this.implementation.string("review-summarization-learn-more-editorial-item-id");
+ }
+ /// An array containing a list of app bundle IDs and adam IDs for apps that should not display
+ /// any app privacy shelves
+ get suppressedPrivacyAppIds() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("suppressedPrivacyLabels")));
+ }
+ /// An array containing a list of app bundle IDs and adam IDs for apps that should not display
+ /// any app accessibility shelves
+ get suppressedAccessibilityAppIds() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("suppressed-accessibility-labels")));
+ }
+ get appPrivacyDefinitionsEditorialItemId() {
+ return this.implementation.string("appPrivacyDefinitionsEditorialItemId");
+ }
+ // The EI ID for the web's category tabs shown in the sidebar
+ get webNavigationCategoryTabsEditorialItemId() {
+ return this.implementation.string("web-navigation-category-tabs-editorial-item-id");
+ }
+ /// The percentage of users that will see a live preview of the widget in the Widget Gallery
+ get todayWidgetLivePreviewRolloutRate() {
+ var _a;
+ return (_a = this.implementation.double("todayWidgetLivePreviewRolloutRate")) !== null && _a !== void 0 ? _a : 1.0;
+ }
+ /// The percentage of users that will see the new carousel item styles under Hero 3.0
+ get hero3RolloutRate() {
+ var _a;
+ return (_a = this.implementation.double("arcade-hero-shelf-tagline-style-rollout-rate")) !== null && _a !== void 0 ? _a : 1.0;
+ }
+ /// The percentage of users that will see the trial enrolled (subscriber) tab
+ get arcadeTrialEnrolledStateRate() {
+ var _a;
+ return (_a = this.implementation.double("arcade-trial-enrolled-state-rate")) !== null && _a !== void 0 ? _a : 0.0;
+ }
+ get marketingItemSelectionTimeout() {
+ var _a;
+ return (_a = this.implementation.double("marketing-item-selection-timeout")) !== null && _a !== void 0 ? _a : 1.0;
+ }
+ /// Whether or not to enable app events
+ get enableAppEvents() {
+ var _a;
+ return (_a = this.implementation.boolean("enableAppEvents")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Whether or not product page variants is enabled (feature-gate)
+ get enableProductPageVariants() {
+ var _a;
+ return (_a = this.implementation.boolean("enableProductPageVariants")) !== null && _a !== void 0 ? _a : false;
+ }
+ get enableArcadeTrialEligibleBadging() {
+ return this.implementation.boolean("enable-arcade-trial-eligible-badging");
+ }
+ /// The duration we should use for the hero carousel auto scrolling
+ get heroCarouselAutoScrollDuration() {
+ var _a;
+ return (_a = this.implementation.double("heroCarouselAutoScrollDuration")) !== null && _a !== void 0 ? _a : 7.0;
+ }
+ /// Enable additional JS logging for Product Page Variants
+ get enableAdditionalLoggingForPPV() {
+ var _a;
+ return (_a = this.implementation.boolean("enableAdditionalLoggingForPPV")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Whether or not to enable on device personalization
+ get enableOnDevicePersonalization() {
+ const enableOnDevicePersonalization = this.implementation.boolean("enable-on-device-personalization");
+ if (serverData.isNull(enableOnDevicePersonalization)) {
+ return true;
+ }
+ return enableOnDevicePersonalization;
+ }
+ /// Whether or not automatic page refreshing is enabled
+ get enableAutomaticPageRefresh() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-automatic-page-refresh")) !== null && _a !== void 0 ? _a : true;
+ }
+ /// Rollout rate for smart stack suggestions for the today widget.
+ get widgetSuggestionsFromTodayTabRolloutRate() {
+ var _a;
+ return (_a = this.implementation.double("today-widget-suggestions-from-today-tab-rollout-rate")) !== null && _a !== void 0 ? _a : 1.0;
+ }
+ /// The upper bound for how many extra minutes we delay the smart stack from suggesting the Today Widget (to protect MAPI).
+ get todayWidgetSmartStackJitterMinutes() {
+ var _a;
+ return (_a = this.implementation.double("today-widget-smart-stack-jitter-minutes")) !== null && _a !== void 0 ? _a : 45;
+ }
+ get enableSystemAppReviews() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-system-app-reviews")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Whether or not CPPs are enabled for Search Ads.
+ get enableCPPInSearchAds() {
+ return this.implementation.boolean("enableCPPsInSearchAds") || false;
+ }
+ get cancelPreorderItemSrv() {
+ return (this.implementation.url("cancelPreorderItemSrv") || "https://buy.itunes.apple.com/commerce/preorders/cancel");
+ }
+ get getCancellablePreorderItemsSrv() {
+ return (this.implementation.url("getCancellablePreorderItemsSrv") ||
+ "https://buy.itunes.apple.com/commerce/preorders/cancellable");
+ }
+ /// An array of the enabled ad placements.
+ get enabledAdPlacements() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("enabled-ad-placements")));
+ }
+ /// A dictionary of timeout values for the ad placements.
+ get adPlacementTimeouts() {
+ return serverData.asDictionary(serverData.asJSONData(this.implementation.dictionary("ad-placement-timeouts")));
+ }
+ /// The editorial story ID for In App Purchases
+ get inAppPurchasesLearnMoreEditorialItemId() {
+ return this.implementation.string("in-app-purchases-learn-more-editorial-item-id");
+ }
+ /// Determines whether external purchases are enabled.
+ get enableExternalPurchases() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-external-purchase")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// An array of the enabled external purchases placements
+ get enabledExternalPurchasesPlacements() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("enabled-external-purchase-placements")));
+ }
+ /// The editorial item ID for the external purchases story
+ get externalPurchasesLearnMoreEditorialItemId() {
+ return this.implementation.string("external-purchase-learn-more-editorial-item-id");
+ }
+ /// The editorial item ID for the external browser story
+ get externalBrowserLearnMoreEditorialItemId() {
+ return this.implementation.string("external-browser-learn-more-editorial-item-id");
+ }
+ /// The SharePlay groupings url
+ get sharePlayAppsEditorialItemId() {
+ return this.implementation.string("share-play-apps-editorial-item-id");
+ }
+ /// Determines whether to show the icon on the external purchases product page banner
+ get externalPurchasesIncludeProductPageBannerIcon() {
+ var _a;
+ return (_a = this.implementation.boolean("external-purchase-product-page-banner-include-icon")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Determines whether to use a variant of the external purchases product page annotation copy
+ get externalPurchasesProductPageAnnotationVariant() {
+ return this.implementation.string("external-purchase-product-page-annotation-variant");
+ }
+ /// Tells us whether we want new events for ODJ to be enabled or not.
+ get newEventsForODJAreEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("new-events-for-odj-are-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ get defaultChart() {
+ return serverData.asJSONData(this.implementation.dictionary("default-chart"));
+ }
+ // The url to display the user account
+ get accountUrl() {
+ var _a;
+ return ((_a = this.implementation.url("accountUrl")) !== null && _a !== void 0 ? _a : "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/accountSummary");
+ }
+ // The url to display the user account
+ get redeemUrl() {
+ var _a;
+ return ((_a = this.implementation.url("redeemUrl")) !== null && _a !== void 0 ? _a : "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemLandingPage");
+ }
+ // The url to display the user account
+ get charityUrl() {
+ var _a;
+ return ((_a = this.implementation.url("charityUrl")) !== null && _a !== void 0 ? _a : "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/buyCharityGiftWizard");
+ }
+ // The url to display the manage subs page
+ get manageSubscriptionsUrl() {
+ var _a;
+ return ((_a = this.implementation.url("manageSubscriptionsUrl")) !== null && _a !== void 0 ? _a : "https://finance-app.itunes.apple.com/subscriptions/manage?context=deeplink");
+ }
+ // The url to display the manage subs page
+ get manageSubscriptionsV2Url() {
+ var _a;
+ return ((_a = this.implementation.url("manageSubscriptionsV2Url")) !== null && _a !== void 0 ? _a : "https://apps.mzstatic.com/content/54a1317a0ad442d3965d64ef6bfaae1c/");
+ }
+ /// The language value to use for ad language content filtering.
+ /// This may not necessarily match the user's language setting - in some regions ads are required
+ /// to be localized to the country's primary language, regardless of the user's own preferences.
+ get adsOverrideLanguage() {
+ return this.implementation.string("ads-override-language");
+ }
+ /// The percentage of users that will see the controller recommended or required treatment
+ /// (as opposed to supported) on supported platforms.
+ get gameControllerRecommendedRolloutRate() {
+ var _a;
+ return (_a = this.implementation.double("game-controller-recommended-rollout-rate")) !== null && _a !== void 0 ? _a : 0;
+ }
+ /// The ID of the editorial item that provides information about game controllers.
+ get gameControllerLearnMoreEditorialItemId() {
+ return this.implementation.string("game-controller-learn-more-editorial-item-id");
+ }
+ /// The ID of the editorial item that provides information about spatial controllers.
+ get spatialControlsLearnMoreEditorialItemId() {
+ return this.implementation.string("spatial-controllers-learn-more-editorial-item-id");
+ }
+ // Whether the user can use MAPI based search focus
+ get mediaAPISearchFocusEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("apps-search-focus-suggestions-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Indicates whether the Search Landing Page supports the V2 protocol
+ get supportsSearchLandingPageV2() {
+ var _a;
+ return (_a = this.implementation.boolean("supports-apps-slp-v2")) !== null && _a !== void 0 ? _a : false;
+ }
+ get enableSearchLandingPageV2ByTreatment() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-apps-slp-v2-by-treatment")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// The percentage of users that will see the new Search Landing Page V2 protocol
+ get searchLandingPageV2RolloutRate() {
+ var _a;
+ return (_a = this.implementation.double("apps-slp-v2-rollout-rate")) !== null && _a !== void 0 ? _a : 0;
+ }
+ /// The percentage of users that will see the Today tab Arcade personalization
+ get todayTabArcadePersonalizationRate() {
+ var _a;
+ return (_a = this.implementation.double("today-tab-arcade-personalization-rate")) !== null && _a !== void 0 ? _a : 0;
+ }
+ /// Whether to enable extending supported Game Center features.
+ get gameCenterExtendSupportedFeatures() {
+ var _a;
+ return (_a = this.implementation.boolean("game-center-extend-supported-features")) !== null && _a !== void 0 ? _a : false;
+ }
+ get adPlacementEligibleSlotPositions() {
+ const eligibleSlotPositions = serverData.asJSONData(this.implementation.dictionary("ad-placement-eligible-slot-positions"));
+ if (serverData.isDefinedNonNullNonEmpty(eligibleSlotPositions)) {
+ return eligibleSlotPositions;
+ }
+ return {
+ "today": [
+ {
+ shelfIdentifier: "0",
+ slot: 0,
+ },
+ {
+ shelfIdentifier: "0",
+ slot: 1,
+ },
+ ],
+ "product-page-ymal": [
+ {
+ shelfIdentifier: "customers-also-bought-apps",
+ slot: 0,
+ },
+ ],
+ };
+ }
+ // The url to display the manage preorders page
+ get managePreordersUrl() {
+ var _a;
+ return (_a = this.implementation.url("preordersUrl")) !== null && _a !== void 0 ? _a : "https://finance-app.itunes.apple.com/preorders";
+ }
+ // A url to modify the user's account.
+ get modifyAccount() {
+ var _a;
+ return ((_a = this.implementation.url("modifyAccount")) !== null && _a !== void 0 ? _a : "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/accountSummary");
+ }
+ // The url to display the purchase history page
+ get purchaseHistoryUrl() {
+ return this.implementation.url("purchaseHistoryUrl");
+ }
+ // The url to display the ratings and reviews page
+ get ratingsReviewsUrl() {
+ var _a;
+ return ((_a = this.implementation.url("ratingsReviewsUrl")) !== null && _a !== void 0 ? _a : "https://apps.mzstatic.com/content/54a1317a0ad442d3965d64ef6bfaae1c/ratings-reviews");
+ }
+ // A url to sign up for a new account.
+ get signup() {
+ var _a;
+ return ((_a = this.implementation.url("signup")) !== null && _a !== void 0 ? _a : "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/signupWizard");
+ }
+ // The landing page to redeem a code.
+ get redeemCodeLanding() {
+ var _a;
+ return ((_a = this.implementation.url("redeemCodeLanding")) !== null && _a !== void 0 ? _a : "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemLandingPage");
+ }
+ /// The bag key that stores the generic report a problem URL.
+ get reportProblemUrl() {
+ return this.implementation.string("reportProblemUrl");
+ }
+ // Whether the app store should allow unrestricted arcade tab badging by server side marketing journeys
+ get unrestrictedServerSideTabBadging() {
+ var _a;
+ return (_a = this.implementation.boolean("unrestricted-server-side-tab-badging")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Whether all users should receive the "condensed" today ad.
+ get todayAdCondensedEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("today-ad-condensed-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Whether to enable bin-compat checks for the visionOS App Store
+ get enableVisionAppStoreBinCompatChecks() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-vision-app-store-bincompat-checks")) !== null && _a !== void 0 ? _a : false;
+ }
+ // The ID for an editorial page containing Safari Extensions
+ get safariExtensionsEditorialPageId() {
+ return this.implementation.url("safari-extensions-editorial-page-id");
+ }
+ /// The ID of the editorial item to show when deeplinking to buddy onboarding
+ get buddyOnboardingEditorialItemId() {
+ return this.implementation.string("buddy-onboarding-editorial-item-id");
+ }
+ // Whether click events should be sent for the friends playing shelf on the product page
+ get productPageFriendsPlayingClickEventsEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("product-page-friends-playing-click-events-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ // ID of the editorial item to show for more information about the "High Motion" annotation.
+ get highMotionLearnMoreEditorialItemId() {
+ return this.implementation.string("high-motion-learn-more-editorial-item-id");
+ }
+ // The speed of the river in the medium lockup with screenshots ad.
+ // This is temporary, and only for testing purposes.
+ get todayAdMediumLockupScreenshotsRiverSpeed() {
+ return this.implementation.double("today-ad-medium-lockup-screenshots-river-speed");
+ }
+ // Collection ID of the game categories for Arcade download/starter pack onboarding.
+ get arcadeDownloadPackCategoriesCollectionId() {
+ return this.implementation.string("arcade-download-packs-onboarding-collection-id");
+ }
+ // Time interval in seconds where Arcade download pack shelf is visible after user has gone through the onboarding flow.
+ get arcadeDownloadPackShelfTTLInSeconds() {
+ var _a;
+ return (_a = this.implementation.integer("arcade-starter-pack-ttl-in-seconds")) !== null && _a !== void 0 ? _a : 0;
+ }
+ // The feature flag as whether or not we should display IAP offers (eg winback offers).
+ get enableOfferItems() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-winback-offers")) !== null && _a !== void 0 ? _a : false;
+ }
+ // The host for App Distribution Media API requests.
+ get appDistributionMediaAPIHost() {
+ return this.implementation.string("app-distribution-media-api-host");
+ }
+ // The language tag to use for App Distribution Media API requests.
+ get appDistributionLanguageTag() {
+ return this.implementation.string("app-distribution-language-tag");
+ }
+ // Whether App Distribution is supported.
+ get supportsAppDistribution() {
+ var _a;
+ return (_a = this.implementation.boolean("supports-app-distribution")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Whether Arcade download/starter pack onboarding should be triggered after Arcade purchase from the page offer button.
+ // When Mercury supports this flow the flag should be disabled.
+ get arcadeDownloadPackPostSubscribeTrigger() {
+ var _a;
+ return (_a = this.implementation.boolean("arcade-download-packs-post-subscribe-trigger")) !== null && _a !== void 0 ? _a : true;
+ }
+ // The feature flag as whether or not we should display contingent offers.
+ get enableContingentOffers() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-contingent-offers")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// The percentage of users that will see Arcade packs onboarding experience.
+ get arcadeDownloadPackRolloutRate() {
+ var _a;
+ return (_a = this.implementation.double("arcade-download-packs-rollout-rate")) !== null && _a !== void 0 ? _a : 0;
+ }
+ // Whether to enable including the Vision platform in fetch requests
+ get enableVisionPlatform() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-vision-platform")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Dictionary containing the use cases that are supported for mixed media requests.
+ get supportedMixedMediaRequestUsecases() {
+ var _a;
+ return ((_a = serverData.asDictionary(serverData.asJSONData(this.implementation.dictionary("supported-mixed-media-request-usecases")))) !== null && _a !== void 0 ? _a : {});
+ }
+ // Whether the page and click events are enabled for Arcade download pack.
+ get arcadeDownloadPacksMetricsEventsEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("arcade-download-packs-metrics-events-enabled")) !== null && _a !== void 0 ? _a : true;
+ }
+ // Whether the impression events are enabled for Arcade download pack.
+ get arcadeDownloadPacksImpressionEventsEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("arcade-download-packs-impression-events-enabled")) !== null && _a !== void 0 ? _a : true;
+ }
+ /// The learn more EI ID for when purchasing a visionOS only app on iOS
+ get visionOnlyAppLearnMoreEditorialItemId() {
+ return this.implementation.string("vision-only-app-learn-more-editorial-item-id");
+ }
+ // Whether Arcade download pack onboarding will be shown after successful CIP carrier link.
+ get arcadeDownloadPacksCIPDeeplinkIntegrationEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("arcade-download-packs-cip-deeplink-trigger")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Whether Arcade download pack onboarding will be shown after Hardware upsell successful purchase.
+ // Only for tab badge upsell.
+ get arcadeDownloadPacksHardwareTabBadgeUpsellIntegrationEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("arcade-download-packs-hw-tabbadge-trigger")) !== null && _a !== void 0 ? _a : false;
+ }
+ // A URL that links to the About the App Store website
+ get aboutAppStoreUrl() {
+ return this.implementation.string("about-app-store-url");
+ }
+ // The EI ID for the About In App Purchases article
+ get aboutInAppPurchasesEditorialItemId() {
+ return this.implementation.string("about-in-app-purchases-editorial-item-id");
+ }
+ // A URL that links to the Request A Refund website
+ get requestARefundUrl() {
+ return this.implementation.string("request-a-refund-url");
+ }
+ // Whether the personalized recommendations toggle should be displayed in the personalized
+ // recommendations screen.
+ get personalizedRecommendationsToggleEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-personalized-recommendations-toggle")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Whether we should be using metricsId exclusively and remove DSID in events
+ get metricsIdMigrationEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("metrics-id-migration-enabled")) !== null && _a !== void 0 ? _a : true;
+ }
+ // Whether natural language search is enabled for all features, e.g. What's New, Bubble Tip, Hints, and Results.
+ get isNaturalLanguageSearchEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("apps-natural-language-search-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Whether natural language search is partially enabled, e.g. Bubble Tip and Results only, excludes What's New and Hints.
+ get isNaturalLanguageSearchResultsEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("apps-natural-language-search-results-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Whether we should be using metricsId exclusively and remove DSID in events
+ get metricsIdentifiersShouldCache() {
+ var _a;
+ return (_a = this.implementation.boolean("metrics-identifiers-should-cache")) !== null && _a !== void 0 ? _a : true;
+ }
+ // A URL that links to the Change Your Payment Method support article
+ get changePaymentMethodUrl() {
+ return this.implementation.string("change-payment-method-url");
+ }
+ // The EI ID for the Information about the French App Store article
+ get aboutFrenchAppStoreEditorialItemId() {
+ return this.implementation.string("about-app-store-editorial-item-id");
+ }
+ // A bag override which can disable product page on demand shelf fetching if necessary, without
+ // a JS release
+ get isOnDemandShelfFetchingEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("on-demand-product-shelf-fetching-enabled")) !== null && _a !== void 0 ? _a : true;
+ }
+ // A bag override which can enable the addition of the dsId field to metrics events in the case of
+ // missing userId
+ get isMetricsUserIdFallbackEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("metrics-user-id-fallback-enabled")) !== null && _a !== void 0 ? _a : false;
+ }
+ // A bag override which can enable the addition of the alt_ab2_data field to metrics events in the case of
+ // missing ab2_data
+ get isMetricsAb2DataFallbackEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("metrics-ab2data-fallback-enabled")) !== null && _a !== void 0 ? _a : !preprocessor.GAMES_TARGET;
+ }
+ /// Whether or not to enable on device reco reordering. Default to true while in dev.
+ get enableRecoOnDeviceReordering() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-on-device-reco-reordering")) !== null && _a !== void 0 ? _a : false;
+ }
+ // The EI IDs for any Vision Pro ribbon bar items
+ get ribbonBarVisionEditorialItemIds() {
+ return serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("ribbon-bar-vision-editorial-item-ids")));
+ }
+ // The IDs for any EIs that should be filtered from search results
+ get searchFilterEditorialItemIds() {
+ const array = serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("search-filter-editorial-item-ids")));
+ return new Set(array);
+ }
+ // The ID for the accessibility learn more EI on the product page
+ get accessibilityLearnMoreEditorialItemId() {
+ return this.implementation.string("accessibility-learn-more-editorial-item-id");
+ }
+ /// Whether discovery content based on ownership of a Vision Pro device is enabled
+ get enableDeviceDrivenDiscoveryContent() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-device-driven-discovery-content")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Whether to show install size for apps on the product page
+ get enableProductPageInstallSize() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-product-page-install-size")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Whether we show the prerendered icon artwork
+ get enableIconArtwork() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-icon-artwork")) !== null && _a !== void 0 ? _a : false;
+ }
+ // The rollout rate associated with `enable-icon-artwork`
+ get iconArtworkRolloutRate() {
+ var _a;
+ return (_a = this.implementation.double("icon-artwork-rollout-rate")) !== null && _a !== void 0 ? _a : 0.0;
+ }
+ // Whether the new age rating system should be used
+ get enableUpdatedAgeRatings() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-app-store-age-ratings")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Whether age rating should be sent to MAPI to filter returned content
+ get enableAgeRatingFilter() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-age-rating-filter")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Whether two-phase buy is enabled. This is expected to always be false, and only exists
+ // as an escape hatch.
+ get enableTwoPhaseOfferConfirmation() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-two-phase-offer-confirmation")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// Determines whether to use a variant of the external purchases product page banner text copy
+ get externalPurchasesProductPageBannerTextVariant() {
+ return this.implementation.string("external-purchase-product-page-banner-text-variant");
+ }
+ /// Describes the variant of the external purchases product page banner icon to use
+ get externalPurchasesProductPageBannerIconVariant() {
+ return this.implementation.string("external-purchase-product-page-banner-icon-variant");
+ }
+ /** ******* GameStoreKit *********/
+ // A default maximum number of games used for query events & updates.
+ get maxGamesForFetchingEvents() {
+ var _a;
+ return (_a = this.implementation.integer("max-games-for-fetching-events")) !== null && _a !== void 0 ? _a : 30;
+ }
+ // The mock home feed url for testing
+ get mockHomeFeedURL() {
+ return this.implementation.string("mock-home-feed-url");
+ }
+ // Disable play-together endpoint
+ get disablePlayTogetherEndpoint() {
+ var _a;
+ return (_a = this.implementation.boolean("disable-play-together-endpoint")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Metrics topic for the current bundle.
+ get metricsTopic() {
+ var _a, _b;
+ if (preprocessor.GAMES_TARGET) {
+ const defaultTopic = "xp_amp_gc_cs";
+ const metricsTopic = (_a = serverData.asString(this.metricsConfiguration, "topics.GAMES_CLICKSTREAM_TOPIC")) !== null && _a !== void 0 ? _a : null;
+ return metricsTopic !== null && metricsTopic !== void 0 ? metricsTopic : defaultTopic;
+ }
+ else {
+ const defaultTopic = "xp_ase_appstore_ue";
+ return (_b = this.implementation.string("metrics_topic")) !== null && _b !== void 0 ? _b : defaultTopic;
+ }
+ }
+ // A default collectionID of the game recommendations for arcade subscriber.
+ get playTogetherGameRecommendationsArcade() {
+ var _a;
+ return (_a = this.implementation.string("play-together-arcade-game-recommendations")) !== null && _a !== void 0 ? _a : "1803255513";
+ }
+ // A default collectionID of the game recommendations for users with no arcade subscription.
+ get playTogetherGameRecommendationsNonArcade() {
+ var _a;
+ return (_a = this.implementation.string("play-together-non-arcade-game-recommendations")) !== null && _a !== void 0 ? _a : "1804480915";
+ }
+ /// A collectionID of arcade game recommendations for multiplayer activity
+ get multiplayerActivityGameRecommendationsArcade() {
+ var _a;
+ return (_a = this.implementation.string("multiplayer-activity-arcade-game-recommendations")) !== null && _a !== void 0 ? _a : "1821553042";
+ }
+ /// A collectionID of non-arcade game recommendations for multiplayer activity
+ get multiplayerActivityGameRecommendationsNonArcade() {
+ var _a;
+ return (_a = this.implementation.string("multiplayer-activity-non-arcade-game-recommendations")) !== null && _a !== void 0 ? _a : "1821553152";
+ }
+ // Whether to show the Subscriber Access Arcade Badge
+ get showArcadeSubscriberAccessBadge() {
+ var _a;
+ return (_a = this.implementation.boolean("show-arcade-subscriber-access-badge")) !== null && _a !== void 0 ? _a : false;
+ }
+ // Whether to show the Non-Subscriber Arcade Badge
+ get showArcadeNonSubscriberBadge() {
+ var _a;
+ return (_a = this.implementation.boolean("show-arcade-non-subscriber-badge")) !== null && _a !== void 0 ? _a : false;
+ }
+ /// The URL for the learn more link on the Cross Use Consent screen.
+ get gamesCrossUseConsentLearnMoreURL() {
+ return this.implementation.string("games-crossuse-consent-learn-more-url");
+ }
+ /// The duration for the auto advance interval for the Play Now feed hero carousel
+ get gamesPlayNowHeroCarouselAutoAdvanceInterval() {
+ var _a;
+ return (_a = this.implementation.double("games-play-now-hero-carousel-auto-advance-interval")) !== null && _a !== void 0 ? _a : 10.0;
+ }
+ /// The max duration for the auto advance interval for the Play Now feed hero carousel
+ get gamesPlayNowHeroCarouselAutoAdvanceMaxInterval() {
+ var _a;
+ return (_a = this.implementation.double("games-play-now-hero-carousel-auto-advance-max-interval")) !== null && _a !== void 0 ? _a : 30.0;
+ }
+ /// The duration for the auto advance interval for the Editorial hero carousel
+ get gamesEditorialHeroCarouselAutoAdvanceInterval() {
+ var _a;
+ return (_a = this.implementation.double("games-editorial-hero-carousel-auto-advance-interval")) !== null && _a !== void 0 ? _a : 10.0;
+ }
+ /// The max duration for the auto advance interval for the Editorial hero carousel
+ get gamesEditorialHeroCarouselAutoAdvanceMaxInterval() {
+ var _a;
+ return (_a = this.implementation.double("games-editorial-hero-carousel-auto-advance-max-interval")) !== null && _a !== void 0 ? _a : 30.0;
+ }
+ get enablePreviewPlatformForWeb() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-preview-platform-for-web")) !== null && _a !== void 0 ? _a : false;
+ }
+ // How long to look back for completed challenges to show in active challenges shelf
+ get completedChallengesInActiveShelfTimeThreshold() {
+ var _a;
+ return (_a = this.implementation.integer("completed-challenges-in-active-shelf-time-threshold")) !== null && _a !== void 0 ? _a : 86400; // default to 1 day
+ }
+ // Whether we should request for a review when we have a chance.
+ get requestReviewEnabled() {
+ var _a;
+ return (_a = this.implementation.boolean("request-review-enabled")) !== null && _a !== void 0 ? _a : true;
+ }
+ // The minimum number of app launches before we consider requesting for a review.
+ get requestReviewMinAppLaunchCount() {
+ var _a;
+ return (_a = this.implementation.integer("request-review-min-app-launch-count")) !== null && _a !== void 0 ? _a : 15;
+ }
+ // The minimum number of game launches from the app before we consider requesting a review.
+ get requestReviewMinGameLaunchCount() {
+ var _a;
+ return (_a = this.implementation.integer("request-review-min-game-launch-count")) !== null && _a !== void 0 ? _a : 5;
+ }
+ // Whether we should request licenses in product information
+ get enableLicenses() {
+ var _a;
+ return (_a = this.implementation.boolean("enable-licenses")) !== null && _a !== void 0 ? _a : false;
+ }
+ // The number of apps to display in the chart detail screen.
+ get chartDetailPageItemCount() {
+ var _a;
+ return (_a = this.implementation.integer("chart-detail-page-item-count")) !== null && _a !== void 0 ? _a : 25;
+ }
+ /// https://quip-apple.com/G5EGABrSh1YO#temp:C:UdDa344becf5aac473db8085f35a
+ /// URL query parameters that are permitted to be included in the pageUrl field of page metrics events.
+ get metricsAllowedListURLParams() {
+ let params = serverData.asArrayOrEmpty(serverData.asJSONValue(this.implementation.array("metrics-allowed-list-url-params")));
+ if (serverData.isNullOrEmpty(params)) {
+ params = [
+ "itsct",
+ "itscg",
+ "itcCt",
+ "ct",
+ "pt",
+ "advp",
+ "mttn3pid",
+ "mttnagencyid",
+ "mttncc",
+ "mttnpid",
+ "mttnsiteid",
+ "mttnsub1",
+ "mttnsub2",
+ "mttnsubad",
+ "mttnsubkw",
+ "mttnsubplmnt",
+ "id",
+ "term",
+ "salableAdamId",
+ "ign-itscg",
+ "ign-itsct",
+ "utm_campaign",
+ "clusterId",
+ ];
+ }
+ return params;
+ }
+}
+BagWrapper.type = makeMetatype("app-store:bag-wrapper");
+//# sourceMappingURL=bag.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/cached-bag.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/cached-bag.js
new file mode 100644
index 0000000..e7ac2db
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/cached-bag.js
@@ -0,0 +1,82 @@
+import { isSome } from "@jet/environment";
+import { Wrapper } from "./wrapper";
+/**
+ * A bag wrapper that implements caching. All accessed values are cached until this object is reinitialized via rebootstrap or recreated.
+ *
+ */
+export class CachedBag extends Wrapper {
+ constructor() {
+ super(...arguments);
+ /**
+ * A cache of entries
+ *
+ * Note that `Opt<T>` values which are nil are stored, representing that the bag has no value for a key, which
+ * is coalesced in the JS. This prevents infinite cache misses when the bag has no value for a key, resulting in
+ * unnecessary calls to the native client
+ */
+ this.cache = {};
+ }
+ registerBagKeys(keys) {
+ this.implementation.registerBagKeys(keys);
+ }
+ string(key, forceDisableCache = false) {
+ return this.fromCache(key, forceDisableCache, () => {
+ return this.implementation.string(key);
+ });
+ }
+ double(key, forceDisableCache = false) {
+ return this.fromCache(key, forceDisableCache, () => {
+ return this.implementation.double(key);
+ });
+ }
+ integer(key, forceDisableCache = false) {
+ return this.fromCache(key, forceDisableCache, () => {
+ return this.implementation.integer(key);
+ });
+ }
+ boolean(key, forceDisableCache = false) {
+ return this.fromCache(key, forceDisableCache, () => {
+ return this.implementation.boolean(key);
+ });
+ }
+ array(key, forceDisableCache = false) {
+ return this.fromCache(key, forceDisableCache, () => {
+ return this.implementation.array(key);
+ });
+ }
+ dictionary(key, forceDisableCache = false) {
+ return this.fromCache(key, forceDisableCache, () => {
+ return this.implementation.dictionary(key);
+ });
+ }
+ url(key, forceDisableCache = false) {
+ return this.fromCache(key, forceDisableCache, () => {
+ return this.implementation.url(key);
+ });
+ }
+ /**
+ * Returns a cached value if possible to do so, otherwise uses the closure to fetch a new value which is cached and returned
+ *
+ * @param key The key on which to cache
+ * @param forceDisableCache Disables the cache to ensure a fresh value from the bag
+ * @param fetchValue A closure to fetch new values with
+ * @returns A previously or newly cached value
+ */
+ fromCache(key, forceDisableCache = false, fetchValue) {
+ if (forceDisableCache) {
+ return fetchValue();
+ }
+ else {
+ const cacheEntry = this.cache[key];
+ if (isSome(cacheEntry)) {
+ return cacheEntry.value;
+ }
+ else {
+ const newValue = fetchValue();
+ this.cache[key] = { value: newValue };
+ return newValue;
+ }
+ }
+ }
+}
+//# sourceMappingURL=cached-bag.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/client-ordering.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/client-ordering.js
new file mode 100644
index 0000000..683d81c
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/client-ordering.js
@@ -0,0 +1,43 @@
+/**
+ * Created by dersu on 6/23/17.
+ */
+import { makeMetatype } from "@jet/environment/util/metatype";
+import { Wrapper } from "./wrapper";
+export class ClientOrderingWrapper extends Wrapper {
+ async orderedVisibleIAPs(appBundleId, defaultOrdering, defaultVisibleIdentifiers, spotlightIdentifier) {
+ return await new Promise((resolve, reject) => {
+ // Collections that are parameters over the bridge cannot have `nil` constituents on the other side, so we
+ // should make sure we don't give such inputs or else we'll crash the app.
+ const cleanedDefaultOrdering = defaultOrdering.filter((item) => {
+ return item !== null && item !== undefined;
+ });
+ const cleanedDefaultVisibleIdentifiers = defaultVisibleIdentifiers.filter((item) => {
+ return item !== null && item !== undefined;
+ });
+ this.implementation.orderedVisibleIAPs(appBundleId, cleanedDefaultOrdering, cleanedDefaultVisibleIdentifiers, spotlightIdentifier, (ordering, error) => {
+ if (error) {
+ reject(error);
+ }
+ else {
+ resolve(ordering);
+ }
+ });
+ });
+ }
+ async visibilityForIAPs(productMap) {
+ return await new Promise((resolve, reject) => {
+ this.implementation.visibilityForIAPs(productMap, (visibilities, error) => {
+ if (error) {
+ // Do not reject; we should gracefully fail, since this ordering
+ // data isn't strictly necessary to render search results.
+ resolve({});
+ }
+ else {
+ resolve(visibilities);
+ }
+ });
+ });
+ }
+}
+ClientOrderingWrapper.type = makeMetatype("app-store:client-ordering-wrapper");
+//# sourceMappingURL=client-ordering.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/client.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/client.js
new file mode 100644
index 0000000..5a53dac
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/client.js
@@ -0,0 +1,204 @@
+/**
+ * Created by Pete Hare on 2/7/17.
+ */
+import { makeMetatype } from "@jet/environment/util/metatype";
+import { Size } from "../../api/models/base";
+import * as serverData from "../json-parsing/server-data";
+import { Wrapper } from "./wrapper";
+// region API
+/**
+ * Client identifier for the App Store.
+ */
+export const appStoreIdentifier = "com.apple.AppStore" /* ClientIdentifier.AppStore */;
+/**
+ * Client identifier for the Bridge (Watch) store.
+ */
+export const watchIdentifier = "com.apple.AppStore.BridgeStoreExtension" /* ClientIdentifier.AppStore_BridgeStoreExtension */;
+/**
+ * Client identifier for the iMessages store.
+ */
+export const messagesIdentifier = "com.apple.MobileSMS" /* ClientIdentifier.MobileSMS */;
+/**
+ * Client identifier for the arcade app.
+ */
+export const arcadeIdentifier = "com.apple.Arcade" /* ClientIdentifier.Arcade */;
+/**
+ * Client identifier indicating ATV.
+ */
+export const tvIdentifier = "com.apple.TVAppStore" /* ClientIdentifier.TVAppStore */;
+/**
+ * Client identifier for the Arcade launch repair SubscribePageExtension (iOS).
+ */
+export const productPageExtensionIdentifier = "com.apple.AppStore.ProductPageExtension" /* ClientIdentifier.AppStore_ProductPageExtension */;
+/**
+ * Client identifier for the Arcade launch repair SubscribePageExtension (iOS).
+ */
+export const subscribePageExtensionIdentifier = "com.apple.AppStore.SubscribePageExtension" /* ClientIdentifier.AppStore_SubscribePageExtension */;
+export class ClientWrapper extends Wrapper {
+ get buildType() {
+ return this.implementation.buildType;
+ }
+ // This is only supported in Luckier client builds from AppStoreJet 11.1.7 & GamesUI 2.1.5 onwards. Any previous
+ // clients will return undefined.
+ get buildVersion() {
+ return this.implementation.buildVersion;
+ }
+ get deviceType() {
+ return this.implementation.deviceType;
+ }
+ get guid() {
+ return this.implementation.guid;
+ }
+ get isActivityAvailable() {
+ return this.implementation.isActivityAvailable;
+ }
+ get isElectrocardiogramInstallationAllowed() {
+ return this.implementation.isElectrocardiogramInstallationAllowed;
+ }
+ get isScandiumInstallationAllowed() {
+ return this.implementation.isScandiumInstallationAllowed;
+ }
+ get isSidepackingEnabled() {
+ return this.implementation.isSidepackingEnabled;
+ }
+ get isTinkerWatch() {
+ return this.implementation.isTinkerWatch;
+ }
+ get screenCornerRadius() {
+ return this.implementation.screenCornerRadius;
+ }
+ get screenSize() {
+ return Size.fromNativeSize(this.implementation.screenSize);
+ }
+ get storefrontIdentifier() {
+ return this.implementation.storefrontIdentifier;
+ }
+ get supportsHEIF() {
+ return this.implementation.supportsHEIF;
+ }
+ get thinnedApplicationVariantIdentifier() {
+ return this.implementation.thinnedApplicationVariantIdentifier;
+ }
+ get isMandrakeSupported() {
+ return this.implementation.isMandrakeSupported;
+ }
+ get isCharonSupported() {
+ return this.implementation.isCharonSupported;
+ }
+ get isIconArtworkCapable() {
+ return this.implementation.isIconArtworkCapable;
+ }
+ get maxAppContentRating() {
+ return this.implementation.maxAppContentRating;
+ }
+ get hostBundleId() {
+ return this.implementation.hostBundleId;
+ }
+ isPairedSystemVersionAtLeast(version) {
+ var _a, _b, _c;
+ return (_c = (_b = (_a = this.implementation).isPairedSystemVersionAtLeast) === null || _b === void 0 ? void 0 : _b.call(_a, version)) !== null && _c !== void 0 ? _c : false;
+ }
+ deletableSystemAppCanBeInstalledOnWatchWithBundleID(bundleId) {
+ return this.implementation.deletableSystemAppCanBeInstalledOnWatchWithBundleID(bundleId);
+ }
+ deviceHasCapabilities(capabilities) {
+ return this.implementation.deviceHasCapabilities(capabilities);
+ }
+ deviceHasCapabilitiesIncludingCompatibilityCheckIsVisionOSCompatibleIOSApp(capabilities, supportsVisionOSCompatibleIOSBinary) {
+ if (this.isPad && capabilities.includes("healthkit")) {
+ // Workaround for: rdar://116905381 (J517/21C16: Unable to download AC Wellness even though App Store page says its compatible with my iPad)
+ return false;
+ }
+ return this.implementation.deviceHasCapabilitiesIncludingCompatibilityCheckIsVisionOSCompatibleIOSApp(capabilities, supportsVisionOSCompatibleIOSBinary);
+ }
+ isActivePairedWatchSystemVersionAtLeastMajorVersionMinorVersionPatchVersion(majorVersion, minorVersion, patchVersion) {
+ return this.implementation.isActivePairedWatchSystemVersionAtLeastMajorVersionMinorVersionPatchVersion(majorVersion, minorVersion, patchVersion);
+ }
+ canDevicePerformAppActionWithAppCapabilities(appAction, appCapabilities) {
+ return this.implementation.canDevicePerformAppActionWithAppCapabilities(appAction, appCapabilities);
+ }
+ isAutomaticDownloadingEnabled() {
+ return this.implementation.isAutomaticDownloadingEnabled();
+ }
+ isAuthorizedForUserNotifications() {
+ return this.implementation.isAuthorizedForUserNotifications();
+ }
+ /**
+ * Check whether the active paired device's OS is below a given version.
+ */
+ isActivePairedWatchSystemVersionBelow(version) {
+ // We want to use isActivePairedWatchSystemVersionAtLeastMajorVersionMinorVersionPatchVersion
+ // rather than isPairedSystemVersionAtLeast here, as the latter is only available from YukonB onwards
+ const versionComponents = version.split(".");
+ const majorVersion = serverData.asNumber(versionComponents[0]) || 0;
+ const minorVersion = serverData.asNumber(versionComponents[1]) || 0;
+ const patchVersion = serverData.asNumber(versionComponents[2]) || 0;
+ return !this.implementation.isActivePairedWatchSystemVersionAtLeastMajorVersionMinorVersionPatchVersion(majorVersion, minorVersion, patchVersion);
+ }
+ /** Returns `true` for phone-factor iOS devices. */
+ get isPhone() {
+ return this.deviceType === "phone";
+ }
+ /** Returns `true` for pad-factor iOS devices. */
+ get isPad() {
+ return this.deviceType === "pad";
+ }
+ /**
+ * Returns `true` for iOS devices.
+ *
+ * Note: this property might be replaced with a build-time macro in "production"
+ */
+ get isiOS() {
+ return this.isPhone || this.isPad;
+ }
+ /**
+ * Returns `true` for Mac devices
+
+ * Note: this property might be replaced with a build-time macro in "production"
+ */
+ get isMac() {
+ return this.deviceType === "mac";
+ }
+ /**
+ * Returns `true` for TV devices.
+
+ * Note: this property might be replaced with a build-time macro in "production"
+ */
+ get isTV() {
+ return this.deviceType === "tv";
+ }
+ /**
+ * Returns `true` for Watch devices.
+
+ * Note: this property might be replaced with a build-time macro in "production"
+ */
+ get isWatch() {
+ return this.deviceType === "watch";
+ }
+ /**
+ * Returns `true` for Vision devices.
+ *
+ * Note: this property might be replaced with a build-time macro in "production"
+ */
+ get isVision() {
+ return this.deviceType === "vision";
+ }
+ /**
+ * Returns `true` for the Web
+ *
+ * Note: this property might be replaced with a build-time macro in "production"
+ */
+ get isWeb() {
+ return this.deviceType === "web";
+ }
+ get isCompanionVisionApp() {
+ return this.hostBundleId === "com.apple.visionproapp";
+ }
+ /** Returns the list of remote identifiers to download any purchases to. */
+ get remoteDownloadIdentifiers() {
+ return this.implementation.remoteDownloadIdentifiers;
+ }
+}
+ClientWrapper.type = makeMetatype("app-store:client-wrapper");
+// endregion
+//# sourceMappingURL=client.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/console.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/console.js
new file mode 100644
index 0000000..1bbfac0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/console.js
@@ -0,0 +1,18 @@
+import { makeMetatype } from "@jet/environment/util/metatype";
+import { Wrapper } from "./wrapper";
+export class ConsoleWrapper extends Wrapper {
+ info(...args) {
+ return this.implementation.info(...args);
+ }
+ error(...args) {
+ return this.implementation.error(...args);
+ }
+ log(...args) {
+ return this.implementation.log(...args);
+ }
+ warn(...args) {
+ return this.implementation.warn(...args);
+ }
+}
+ConsoleWrapper.type = makeMetatype("app-store:console-wrapper");
+//# sourceMappingURL=console.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/host.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/host.js
new file mode 100644
index 0000000..001a1e3
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/host.js
@@ -0,0 +1,64 @@
+import { makeMetatype } from "@jet/environment/util/metatype";
+import { Wrapper } from "./wrapper";
+export class HostWrapper extends Wrapper {
+ get clientIdentifier() {
+ return this.implementation.clientIdentifier;
+ }
+ get clientVersion() {
+ return this.implementation.clientVersion;
+ }
+ get deviceLocalizedModel() {
+ return this.implementation.deviceLocalizedModel;
+ }
+ get deviceModel() {
+ return this.implementation.deviceModel;
+ }
+ get deviceModelFamily() {
+ return this.implementation.deviceModelFamily;
+ }
+ get devicePhysicalModel() {
+ return this.implementation.devicePhysicalModel;
+ }
+ get deviceMarketingFamilyName() {
+ return this.implementation.deviceMarketingFamilyName;
+ }
+ get osBuild() {
+ return this.implementation.osBuild;
+ }
+ get platform() {
+ return this.implementation.platform;
+ }
+ isOSAtLeast(majorVersion, minorVersion, patchVersion) {
+ return this.implementation.isOSAtLeast(majorVersion, minorVersion, patchVersion);
+ }
+ /** Returns `true` for iOS host. */
+ get isiOS() {
+ return this.platform === "iOS";
+ }
+ /** Returns `true` for macOS host. */
+ get isMac() {
+ return this.platform === "macOS";
+ }
+ /** Returns `true` for tvOS host. */
+ get isTV() {
+ return this.platform === "tvOS";
+ }
+ /** Returns `true` for watchOS host. */
+ get isWatch() {
+ return this.platform === "watchOS";
+ }
+ /** Returns `true` for web host. */
+ get isWeb() {
+ return this.platform === "web";
+ }
+ /** Returns `true` for Windows host. */
+ get isWindows() {
+ return this.platform === "Windows";
+ }
+ /** Returns `true` for visionOS host. */
+ get isVision() {
+ return this.platform === "xrOS";
+ }
+}
+HostWrapper.type = makeMetatype("app-store:host-wrapper");
+//# sourceMappingURL=host.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/localization.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/localization.js
new file mode 100644
index 0000000..d7e0ee8
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/localization.js
@@ -0,0 +1,437 @@
+import { makeMetatype } from "@jet/environment/util/metatype";
+import { AmpLocalization } from "../amp-localization/amp-localization";
+import { isNullOrEmpty } from "../json-parsing/server-data";
+import { Wrapper } from "./wrapper";
+import { isSome } from "@jet/environment/types/optional";
+import * as validation from "@jet/environment/json/validation";
+/**
+ * Wrapper around an object or `Localization` type.
+ * The wrapped object is implemented as part of a native app.
+ */
+export class LocalizationWrapper extends Wrapper {
+ constructor(loc, objectGraph) {
+ super(loc);
+ /**
+ * Path to localization file.
+ * The path is set when the localization file is loaded
+ * for the first time.
+ */
+ this.locFile = null;
+ /**
+ * Instance of `AMPLocalization` providing server-side
+ * values for localized stirngs.
+ */
+ this.ampLoc = new AmpLocalization();
+ /**
+ * Localization strings cache.
+ * Cache and reuse values returned by
+ * wrapped `Localization` implementation.
+ */
+ this.LOC_STRING_CACHE = {};
+ this.objectGraph = objectGraph;
+ }
+ /**
+ * Returns the wrapped localization's identifier.
+ */
+ get identifier() {
+ return this.implementation.identifier;
+ }
+ /**
+ * Returns the wrapped localization's safe identifier.
+ */
+ get safeIdentifier() {
+ return this.implementation.identifier.split("_")[0];
+ }
+ // endregion
+ // region Localization
+ /**
+ * Localizes a string replacing placehoders in key with values in the parameters dictionary
+ * @param key The loc key to look up
+ * @param params Parameters to replace in the loc string
+ * @return The localized string
+ */
+ string(key, defaultValue) {
+ return this.implementation.string(key);
+ }
+ /**
+ * Localizes a string, and logs & throws an error if the key is a screamer.
+ * @param key The loc key to look up
+ * @return The localized string
+ */
+ tryString(key) {
+ const value = this.implementation.string(key);
+ if (value === key || value === `**${key}**`) {
+ validation.context("tryString", () => {
+ validation.unexpectedType("coercedValue", "Localization key", key, null);
+ });
+ throw new Error(`No value exists for localization key '${key}'`);
+ }
+ return value;
+ }
+ /**
+ * Localizes a string, and logs an error if the key is a screamer.
+ * @param key The loc key to look up
+ * @param fallback The fallback value for unlocalized keys, e.g. English value
+ * @return The localized string
+ */
+ stringWithFallback(key, fallback) {
+ const value = this.implementation.string(key);
+ return value === `**AppStore.${key}**` ? fallback : value;
+ }
+ /**
+ * Localizes a string using a preferred locale.
+ *
+ * The implementation of this function is similar to `string(key:)` above, but with the option
+ * to prefer a particular locale. If one is provided, we augment the lookup key in the cache
+ * with the `locale` value. If one is not provided, we fallback to `string(key:)`. This makes it
+ * much easier to use this function directly, in place of `string(key:)` where necessary.
+
+ * @param objectGraph The object graph, used to forward this call on if the required native function is unavailable.
+ * Can be removed for 2023.
+ * @param key The loc key to look up.
+ * @param locale The preferred locale to use for look up. Falls back to default, if unavailable.
+ * @param defaultValue A default value to use if nothing is found.
+ */
+ stringForPreferredLocale(objectGraph, key, locale, defaultValue) {
+ if (isNullOrEmpty(locale)) {
+ return this.string(key, defaultValue);
+ }
+ const cacheKey = `${key}_${locale}`;
+ let value = this.LOC_STRING_CACHE[cacheKey];
+ if (!value) {
+ value = this.implementation.stringForPreferredLocale(key, locale);
+ if (value && value !== key) {
+ this.LOC_STRING_CACHE[cacheKey] = value;
+ }
+ else {
+ const serverValue = this.ampLoc.localize(key);
+ if (serverValue !== key) {
+ value = serverValue;
+ }
+ else if (defaultValue) {
+ value = defaultValue;
+ }
+ else {
+ value = key;
+ }
+ }
+ }
+ return value;
+ }
+ /**
+ * Localize with appropriate plural form based on the count.
+ *
+ * Some languages have more plural forms than others.
+ * The full set of categories is "zero", "one", "two", "few", "many", and "other".
+ *
+ * The base loc key is used for "other" (the default).
+ * Otherwise the category is appended to the base loc key with a "." separator.
+ *
+ * http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+ * http://cldr.unicode.org/index/cldr-spec/plural-rules#TOC-Determining-Plural-Categories
+ * http://unicode.org/repos/cldr/trunk/specs/ldml/tr35-numbers.html#Language_Plural_Rules
+ *
+ * English loc keys
+ * key = "@@count@@ dogs"; // default, aka "plural"
+ * key.zero = "no dogs"; // used when count is 0
+ * key.one = "@@count@@ dog"; // used when count is 1, aka "singular"
+ *
+ * @param key base loc key
+ * @param count number used to determine the plural form
+ * @param params (optional) substitution keys and values, "count" will be added if it's not already present
+ * @return localized string
+ */
+ stringWithCount(key, count, params) {
+ let value = this.implementation.stringWithCount(key, count);
+ if (!value || value === key) {
+ const serverValue = this.ampLoc.localizeWithCount(this.objectGraph, key, count, params);
+ if (serverValue) {
+ value = serverValue;
+ }
+ }
+ return value;
+ }
+ /**
+ * A variation of `stringWithCount` that supports multiple plural forms.
+ * @param key base loc key
+ * @param counts numbers used to determine the plural forms
+ * @param params (optional) substitution keys and values, "count" will be added if it's not already present
+ * @return localized string
+ */
+ stringWithCounts(key, counts, params) {
+ return this.implementation.stringWithCounts(key, counts);
+ }
+ /**
+ * Converts a string into its uppercased form.
+ * @param value The string to apply the uppercase to.
+ * @returns {string} The loacle-specific, uppercased form of the string. If for whatever reason the upper-casing cannot
+ * be applied, this function simply returns the input value.
+ */
+ uppercased(value) {
+ if (!value) {
+ return null;
+ }
+ return value.toLocaleUpperCase(this.safeIdentifier);
+ }
+ /**
+ * Converts a number into a localized string
+ *
+ * @param n The number to convert
+ * @param decimalPlaces The number of decimal places to include.
+ * @return {string} The localized version of the number
+ */
+ decimal(n, decimalPlaces) {
+ let value = this.implementation.decimal(n, decimalPlaces);
+ if (!value) {
+ if (typeof n === "number") {
+ value = `* ${n.toString()} *`;
+ }
+ else {
+ value = this.nullString();
+ }
+ }
+ return value;
+ }
+ /**
+ * Converts a number of bytes into a localized file size string
+ *
+ * @param bytes The number of bytes to convert
+ * @return The localized file size string
+ */
+ fileSize(bytes) {
+ let value = this.implementation.fileSize(bytes);
+ if (!value) {
+ value = this.nullString();
+ }
+ return value;
+ }
+ /**
+ * Converts a number of bytes into a localized file size string
+ *
+ * @param count The number of bytes to convert
+ * @return The localized file size string
+ */
+ formattedCount(count) {
+ let value = this.implementation.formattedCount(count);
+ if (!value) {
+ value = this.nullString();
+ }
+ return value;
+ }
+ /**
+ * Converts a number into a formatted string representation using the preferred locale identifier.
+ *
+ * @param count The number to format.
+ * @param locale The locale identifier to prefer for lookup.
+ * @return The localized string.
+ */
+ formattedCountForPreferredLocale(objectGraph, count, locale) {
+ if (isNullOrEmpty(locale)) {
+ return this.formattedCount(count);
+ }
+ let value = this.implementation.formattedCountForPreferredLocale(count, locale);
+ if (!value) {
+ value = this.nullString();
+ }
+ return value;
+ }
+ /**
+ * Converts a date into a time ago label, showing how long ago
+ * the date occurred
+ *
+ * @param date The date object to convert
+ * @param context The context in which the date should be formatted
+ * @return The localized string representing the amount of time that has passed
+ */
+ timeAgoWithContext(date, context) {
+ let value = this.implementation.timeAgoWithContext(date, context);
+ if (!value) {
+ value = this.nullString();
+ }
+ return value;
+ }
+ /**
+ * Converts a date into a localized date string using the provided format
+ *
+ * @param format The format string describing how the date should be formatted
+ * @param date The date object to convert
+ * @return The localized string representing the date
+ */
+ formatDate(format, date) {
+ let value = this.implementation.formatDate(format, date);
+ if (!value) {
+ value = this.nullString();
+ }
+ return value;
+ }
+ formatDateWithContext(format, date, context) {
+ let value = this.implementation.formatDateWithContext(format, date, context);
+ if (!value) {
+ value = this.nullString();
+ }
+ return value;
+ }
+ formatDateInSentence(sentence, format, date) {
+ let value = this.implementation.formatDateInSentence(sentence, format, date);
+ if (!value) {
+ value = this.nullString();
+ }
+ return value;
+ }
+ /**
+ * Converts a date into a relative date, showing how long ago
+ * the date occurred
+ *
+ * @param date The date object to convert
+ * @return The localized string representing the amount of time that has passed
+ */
+ relativeDate(date) {
+ let value = this.implementation.relativeDate(date);
+ if (!value) {
+ value = this.nullString();
+ }
+ return value;
+ }
+ formatDuration(value, unit) {
+ let result = this.implementation.formatDuration(value, unit);
+ if (!result) {
+ result = this.nullString();
+ }
+ return result;
+ }
+ // endregion
+ // region Private Methods
+ /**
+ * Applies a new localization file and localization dictionary as the new set of localizations
+ *
+ * @param file The file name of the file that is loaded
+ * @param localizations The localizations dictionary to use
+ * @param locale Current normalized locale which includes language code
+ */
+ applyLocalizations(file, localizations, locale) {
+ if (this.isLocFileLoaded(file)) {
+ return;
+ }
+ this.locFile = file;
+ // The first 2 characters of normalized locale are the language code.
+ this.ampLoc.updateLocalizationData(localizations, locale.slice(0, 2));
+ }
+ /**
+ * Checks if the named loc file is already loaded
+ * @param file The file name to check
+ * @return {boolean} True or false depending on whether the file name is loaded
+ */
+ isLocFileLoaded(file) {
+ return this.locFile === file;
+ }
+ /**
+ * Normalizes the given bag language into a locale.
+ * @param language The language to normalize.
+ * @param storefrontIdentifier The storefront identifier to use.
+ * @return A locale to use.
+ */
+ normalizedLocale(objectGraph, language, storefrontIdentifier) {
+ language = language.toLowerCase();
+ switch (language) {
+ case "yue-hant": {
+ // Country code from the bag is not available in JS, so we use the storefront.
+ const macauStoreFrontIdentifier = objectGraph.props.asString("macauStorefrontIdentifier");
+ if (typeof storefrontIdentifier === "string" &&
+ typeof macauStoreFrontIdentifier === "string" &&
+ storefrontIdentifier.indexOf(macauStoreFrontIdentifier) !== -1) {
+ return "zh-ma";
+ }
+ else {
+ return "zh-hk";
+ }
+ }
+ default:
+ return language;
+ }
+ }
+ nullString() {
+ return "* null *";
+ }
+ // endregion
+ // region API
+ /**
+ * Loads the localizations from the JetPack.
+ */
+ load(objectGraph) {
+ // Sanity check
+ if (objectGraph.bag.language === undefined || objectGraph.bag.language === null) {
+ throw new Error("Bag language is not available. Unable to load localizations.");
+ }
+ const locale = this.normalizedLocale(objectGraph, objectGraph.bag.language, objectGraph.client.storefrontIdentifier);
+ const locName = `local/${locale}`;
+ // Load localizations if needed
+ if (!this.isLocFileLoaded(locName)) {
+ const localizations = objectGraph.props.asDictionary(`localizations.${locale}`);
+ if (localizations !== undefined && localizations !== null) {
+ this.applyLocalizations(locName, localizations, locale);
+ }
+ else {
+ // Fallback to english
+ const fallbackLocalizations = objectGraph.props.asDictionary(`localizations.en-us`);
+ if (fallbackLocalizations !== undefined && fallbackLocalizations !== null) {
+ this.applyLocalizations(locName, fallbackLocalizations, locale);
+ }
+ }
+ }
+ }
+ /**
+ * Returns the localized name for the provided device type.
+ * The company policy seems to be to always use the branded,
+ * non-localized text, but this is just-in-case.
+ * @returns {string} The display name for the device.
+ */
+ deviceDisplayName(objectGraph) {
+ if (objectGraph.client.isVision && isSome(objectGraph.host.deviceMarketingFamilyName)) {
+ return objectGraph.host.deviceMarketingFamilyName;
+ }
+ if (objectGraph.host.deviceLocalizedModel) {
+ return objectGraph.host.deviceLocalizedModel;
+ }
+ // TODO: <rdar://problem/33575833> Jet: Remove brand loc fallbacks
+ switch (objectGraph.client.deviceType) {
+ case "phone":
+ const localizedPhoneName = this.string("IPHONE_BRAND_NAME");
+ if (localizedPhoneName === "IPHONE_BRAND_NAME") {
+ return "iPhone";
+ }
+ return localizedPhoneName;
+ case "pad":
+ const localizedPadName = this.string("IPAD_BRAND_NAME");
+ if (localizedPadName === "IPAD_BRAND_NAME") {
+ return "iPad";
+ }
+ return localizedPadName;
+ case "tv":
+ const localizedTvName = this.string("APPLE_TV_BRAND_NAME");
+ if (localizedTvName === "APPLE_TV_BRAND_NAME") {
+ return "Apple\u00a0TV";
+ }
+ return localizedTvName;
+ case "watch":
+ const localizedWatchName = this.string("APPLE_WATCH_BRAND_NAME");
+ if (localizedWatchName === "APPLE_WATCH_BRAND_NAME") {
+ return "Apple\u00a0Watch";
+ }
+ return localizedWatchName;
+ case "mac":
+ const localizedMacName = this.string("MAC_BRAND_NAME");
+ if (localizedMacName === "MAC_BRAND_NAME") {
+ return "Mac";
+ }
+ return localizedMacName;
+ default:
+ return null;
+ }
+ }
+}
+// region Properties
+/**
+ * Localization wrapper metatype to use with object graph.
+ */
+LocalizationWrapper.type = makeMetatype("app-store:loc-wrapper");
+//# sourceMappingURL=localization.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/properties.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/properties.js
new file mode 100644
index 0000000..fa8b4e1
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/properties.js
@@ -0,0 +1,74 @@
+/**
+ * Created by keithpk on 4/2/17.
+ */
+import { makeMetatype } from "@jet/environment/util/metatype";
+import * as serverData from "../json-parsing/server-data";
+import { Wrapper } from "./wrapper";
+export class PropertiesWrapper extends Wrapper {
+ /**
+ * Returns the raw underlying value for the key path
+ * @param path The path of the property to lookup
+ * @return {JSONValue | null} The resulting value or null
+ */
+ value(path) {
+ return serverData.traverse(this.implementation, path);
+ }
+ /**
+ * Returns whether a property is enabled or not. This also
+ * looks in the clientFeatures key as well for client-enabled
+ * features
+ *
+ * @param key The key to look up
+ * @return {boolean} The boolean result or false if no value was found for the key
+ */
+ enabled(key) {
+ const propertyValue = this.value(key);
+ if (typeof propertyValue !== "undefined") {
+ return Boolean(propertyValue);
+ }
+ return Boolean(this.implementation.clientFeatures[key]);
+ }
+ /**
+ * Syntactic sugar for !enabled(key).
+ *
+ * @param key The key to look up
+ * @return {boolean} The boolean result or true if no value was found for the key
+ */
+ isNotEnabled(key) {
+ return !this.enabled(key);
+ }
+ /**
+ * Returns the value of the path coerced as a dictionary
+ * @param path The path to look up
+ * @return {JSONData|null} The dictionary or null if not found
+ */
+ asDictionary(path) {
+ return serverData.asDictionary(this.implementation, path);
+ }
+ /**
+ * Returns the value of the path coerced as a string
+ * @param path The path to look up
+ * @return {string|null} The string or null if not found
+ */
+ asString(path) {
+ return serverData.asString(this.implementation, path);
+ }
+ /**
+ * Returns the value of the path coerced as a number
+ * @param path The path to look up
+ * @return {string|null} The number or null if not found
+ */
+ asNumber(path) {
+ return serverData.asNumber(this.implementation, path);
+ }
+ /**
+ * Returns the value of the path coerced as an array
+ * @param path The path to look up
+ * @return {string|null} The array or empty if not found
+ */
+ asArray(path) {
+ return serverData.asArrayOrEmpty(this.implementation, path);
+ }
+}
+PropertiesWrapper.type = makeMetatype("app-store:props-wrapper");
+//# sourceMappingURL=properties.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/storage.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/storage.js
new file mode 100644
index 0000000..2643e0d
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/storage.js
@@ -0,0 +1,22 @@
+/**
+ * Creates and returns a mock Storage object.
+ * @return A new mock Storage object.
+ */
+import { makeMetatype } from "@jet/environment/util/metatype";
+import { Wrapper } from "./wrapper";
+export class StorageWrapper extends Wrapper {
+ retrieveString(key) {
+ const value = this.implementation.retrieveString(key);
+ if ((value === null || value === void 0 ? void 0 : value.length) > 0 && value !== "<null>") {
+ return value;
+ }
+ else {
+ return null;
+ }
+ }
+ storeString(key, value) {
+ this.implementation.storeString(value, key); // flip is deliberate.
+ }
+}
+StorageWrapper.type = makeMetatype("app-store:storage-wrapper");
+//# sourceMappingURL=storage.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/wrapper.js b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/wrapper.js
new file mode 100644
index 0000000..f03e835
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/foundation/wrappers/wrapper.js
@@ -0,0 +1,6 @@
+export class Wrapper {
+ constructor(implementation) {
+ this.implementation = implementation;
+ }
+}
+//# sourceMappingURL=wrapper.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/gameservicesui/src/foundation/media-api/requests/recommendation-request-types.js b/node_modules/@jet-app/app-store/tmp/src/gameservicesui/src/foundation/media-api/requests/recommendation-request-types.js
new file mode 100644
index 0000000..7005cf7
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/gameservicesui/src/foundation/media-api/requests/recommendation-request-types.js
@@ -0,0 +1,49 @@
+export const AllPlayNowFeedContent = [
+ "players",
+ "apps",
+ "achievements",
+ "leaderboards",
+ "contents",
+ "challenges",
+ "challenge-instances",
+ "activities",
+];
+export const AllPlayNowFeedContentTypes = [
+ "friend-request-modules",
+ "friend-playing-modules",
+ "achievement-modules",
+ "leaderboard-modules",
+ "chart-modules",
+ "content-modules",
+ "challenge-invite-modules",
+ "challenge-modules",
+ "challenge-suggestion-modules",
+ "on-device-modules",
+ "activities",
+ "activity-invite-modules",
+ "activity-modules",
+ "activity-suggestion-modules",
+];
+export const PlayNowFeedAppsAttributes = [
+ "compatibilityControllerRequirement",
+ "editorialArtwork",
+ "editorialClientParams",
+ "editorialVideo",
+ "expectedReleaseDateDisplayFormat",
+ "isAppleWatchSupported",
+ "minimumOSVersion",
+ "screenshotsByType",
+ "videoPreviewsByType",
+ "gameDisplayName",
+ "miniGamesDeepLink",
+];
+export const PlayNowFeedEditorialItemsAttributes = [
+ "editorialArtwork",
+ "editorialClientParams",
+ "editorialVideo",
+ "expectedReleaseDateDisplayFormat",
+ "showExpectedReleaseDate",
+];
+export const PlayNowFeedEditorialPagesAttributes = ["editorialArtwork", "editorialClientParams", "editorialVideo"];
+export const AppEventsAttributes = ["description", "productArtwork", "productVideo"];
+//# sourceMappingURL=recommendation-request-types.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/gameservicesui/src/foundation/object-graph-types.js b/node_modules/@jet-app/app-store/tmp/src/gameservicesui/src/foundation/object-graph-types.js
new file mode 100644
index 0000000..26d7621
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/gameservicesui/src/foundation/object-graph-types.js
@@ -0,0 +1,20 @@
+import { makeMetatype } from "@jet/environment/util/metatype";
+import * as jetTypes from "@jet/environment/types/globals/types";
+/**
+ * Known dependencies that injectable from ObjectGraph.
+ */
+export const ObjectGraphType = {
+ bag: jetTypes.bag,
+ dispatcher: makeMetatype("dispatcher"),
+ host: jetTypes.host,
+ localizer: jetTypes.localizer,
+ network: jetTypes.net,
+ plist: jetTypes.plist,
+ platform: jetTypes.platform,
+ router: makeMetatype("router"),
+ client: makeMetatype("client"),
+ debugSettings: makeMetatype("debugSettings"),
+ nativeIntentDispatcher: makeMetatype("nativeIntentDispatcher"),
+ personNameComponentsFormatter: makeMetatype("personNameComponentsFormatter"),
+};
+//# sourceMappingURL=object-graph-types.js.map \ No newline at end of file