1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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
|