this repo has no description
at main 328 lines 16 kB view raw
1import { isNothing, isSome } from "@jet/environment/types/optional"; 2import * as derivedData from "../../foundation/json-parsing/derived-data"; 3import * as serverData from "../../foundation/json-parsing/server-data"; 4import * as mediaAttributes from "../../foundation/media/attributes"; 5import * as mediaPlatformAttributes from "../../foundation/media/platform-attributes"; 6import { variantAttributeForKey } from "../product-page/product-page-variants"; 7import * as contentDeviceFamily from "./device-family"; 8/** 9 * Retrieve the specified attribute from the data, coercing it to a JSONData dictionary 10 * 11 * @param data The data from which to retrieve the attribute. 12 * @param attributePath The path of the attribute. 13 * @param attributePlatform The specific platform attribute to get content for. Omit to infer from the data's structure. 14 * @param defaultValue The object to return if the path search fails. 15 * @returns The dictionary of data 16 */ 17export function contentAttributeAsDictionary(objectGraph, data, attributePath, attributePlatform, defaultValue) { 18 if (!attributePlatform) { 19 attributePlatform = bestAttributePlatformFromData(objectGraph, data); 20 } 21 if (isNothing(attributePlatform)) { 22 return null; 23 } 24 let value = mediaPlatformAttributes.platformAttributeAsDictionary(data, attributePlatform, attributePath); 25 if (!value) { 26 value = mediaAttributes.attributeAsDictionary(data, attributePath, defaultValue); 27 } 28 return value; 29} 30/** 31 * Retrieve the specified attribute from the data as an array, coercing to an empty array if the object is not an array. 32 * 33 * @param data The data from which to retrieve the attribute. 34 * @param attributePath The path of the attribute. 35 * @param attributePlatformOverride An override platform, from which to fetch the attribute. 36 * @returns {any[]} The attribute value as an array. 37 */ 38export function contentAttributeAsArrayOrEmpty(objectGraph, data, attributePath, attributePlatformOverride = undefined) { 39 const attributePlatform = attributePlatformOverride !== null && attributePlatformOverride !== void 0 ? attributePlatformOverride : bestAttributePlatformFromData(objectGraph, data); 40 if (isNothing(attributePlatform)) { 41 return []; 42 } 43 let value = mediaPlatformAttributes.platformAttributeAsArrayOrEmpty(data, attributePlatform, attributePath); 44 if (serverData.isNullOrEmpty(value)) { 45 value = mediaAttributes.attributeAsArrayOrEmpty(data, attributePath); 46 } 47 return value; 48} 49/** 50 * Retrieve the specified attribute from the data as an array. 51 * 52 * @param data The data from which to retrieve the attribute. 53 * @param attributePath The path of the attribute. 54 * @param attributePlatformOverride An override platform, from which to fetch the attribute. 55 * @returns {any[]} The attribute value as an array. 56 */ 57export function contentAttributeAsArray(objectGraph, data, attributePath, attributePlatformOverride = undefined) { 58 const attributePlatform = attributePlatformOverride !== null && attributePlatformOverride !== void 0 ? attributePlatformOverride : bestAttributePlatformFromData(objectGraph, data); 59 if (isNothing(attributePlatform)) { 60 return null; 61 } 62 let value = mediaPlatformAttributes.platformAttributeAsArray(data, attributePlatform, attributePath); 63 if (isNothing(value)) { 64 value = mediaAttributes.attributeAsArray(data, attributePath); 65 } 66 return value; 67} 68/** 69 * Retrieve the specified attribute from the data as a string. 70 * 71 * @param data The data from which to retrieve the attribute. 72 * @param attributePath The object path for the attribute. 73 * @param attributePlatformOverride An override platform, from which to fetch the attribute. 74 * @param policy The validation policy to use when resolving this value. 75 * @returns {string} The attribute value as a string. 76 */ 77export function contentAttributeAsString(objectGraph, data, attributePath, attributePlatformOverride = undefined, policy = "coercible") { 78 let value; 79 const attributePlatform = attributePlatformOverride !== null && attributePlatformOverride !== void 0 ? attributePlatformOverride : bestAttributePlatformFromData(objectGraph, data); 80 if (isSome(attributePlatform)) { 81 value = mediaPlatformAttributes.platformAttributeAsString(data, attributePlatform, attributePath, policy); 82 } 83 if (!value) { 84 value = mediaAttributes.attributeAsString(data, attributePath, policy); 85 } 86 return value; 87} 88/** 89 * Retrieve the specified attribute from the data as a boolean. 90 * 91 * @param data The data from which to retrieve the attribute. 92 * @param attributePath The path of the attribute. 93 * @param policy The validation policy to use when resolving this value. 94 * @returns {boolean} The attribute value as a boolean. 95 */ 96export function contentAttributeAsBoolean(objectGraph, data, attributePath, attributePlatform, policy = "coercible") { 97 if (!attributePlatform) { 98 attributePlatform = bestAttributePlatformFromData(objectGraph, data); 99 } 100 if (isNothing(attributePlatform)) { 101 return null; 102 } 103 let value = mediaPlatformAttributes.platformAttributeAsBoolean(data, attributePlatform, attributePath, policy); 104 if (serverData.isNull(value)) { 105 value = mediaAttributes.attributeAsBoolean(data, attributePath, policy); 106 } 107 return value; 108} 109/** 110 * Retrieve the specified attribute from the data as a boolean, which will be `false` if the attribute does not exist. 111 * 112 * @param data The data from which to retrieve the attribute. 113 * @param attributePath The path of the attribute. 114 * @param attributePlatform The specific platform attribute to get content for. Omit to infer from the data's structure. 115 * @returns {boolean} The attribute value as a boolean, coercing to `false` if the value is not present.. 116 */ 117export function contentAttributeAsBooleanOrFalse(objectGraph, data, attributePath, attributePlatform) { 118 if (!attributePlatform) { 119 attributePlatform = bestAttributePlatformFromData(objectGraph, data); 120 } 121 if (isNothing(attributePlatform)) { 122 return false; 123 } 124 let value = mediaPlatformAttributes.platformAttributeAsBoolean(data, attributePlatform, attributePath); 125 if (serverData.isNull(value)) { 126 value = mediaAttributes.attributeAsBooleanOrFalse(data, attributePath); 127 } 128 return value; 129} 130/** 131 * Retrieve the specified attribute from the data as a number. 132 * 133 * @param data The data from which to retrieve the attribute. 134 * @param attributePath The path of the attribute. 135 * @param policy The validation policy to use when resolving this value. 136 * @returns {boolean} The attribute value as a number. 137 */ 138export function contentAttributeAsNumber(objectGraph, data, attributePath, policy = "coercible") { 139 const attributePlatform = bestAttributePlatformFromData(objectGraph, data); 140 if (isNothing(attributePlatform)) { 141 return null; 142 } 143 let value = mediaPlatformAttributes.platformAttributeAsNumber(data, attributePlatform, attributePath, policy); 144 if (serverData.isNull(value)) { 145 value = mediaAttributes.attributeAsNumber(data, attributePath); 146 } 147 return value; 148} 149/** 150 * Computes the best attribute platform for a given piece of content 151 * 152 * @param {Data} data The media API data representing the content 153 * @returns {AttributePlatform} 154 */ 155export function bestAttributePlatformFromData(objectGraph, data, clientIdentifierOverride) { 156 const baseCacheKey = "bestAttributePlatformFromData"; 157 const cacheKey = isSome(clientIdentifierOverride) ? `${baseCacheKey}.${clientIdentifierOverride}` : baseCacheKey; 158 return derivedData.value(data, cacheKey, () => { 159 const isIOSOnly = contentDeviceFamily.dataOnlyHasDeviceFamilies(objectGraph, data, ["iphone", "ipad", "ipod"], true); 160 const isTvOnly = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "tvos"); 161 const isMacOnly = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "mac"); 162 const isWatchOnly = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "watch"); 163 const isVisionOnly = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "realityDevice"); 164 // 1. The data is for a single platform only. 165 let dedicatedPlatform = null; 166 if (isTvOnly) { 167 dedicatedPlatform = "appletvos"; 168 } 169 else if (isMacOnly) { 170 dedicatedPlatform = "osx"; 171 } 172 else if (isIOSOnly) { 173 dedicatedPlatform = "ios"; 174 } 175 else if (isWatchOnly) { 176 dedicatedPlatform = "watch"; 177 } 178 else if (isVisionOnly) { 179 dedicatedPlatform = "xros"; 180 } 181 if (!serverData.isNull(dedicatedPlatform)) { 182 return dedicatedPlatform; 183 } 184 // 2. Loop through our preferred ordering of platforms and use the first one that has platformAttributes present. 185 const alternatePlatforms = defaultAttributePlatformOrdering(objectGraph, clientIdentifierOverride); 186 for (const candidatePlatform of alternatePlatforms) { 187 if (mediaPlatformAttributes.hasPlatformAttribute(data, candidatePlatform)) { 188 return candidatePlatform; 189 } 190 } 191 // 3. Catch-All 192 return defaultAttributePlatform(objectGraph); 193 }); 194} 195/** 196 * Computes the best attribute platform for a given Media API Marketplace 197 * response. Since Marketplace responses don't contain a top-level 198 * `deviceFamilies` property, this employs an alternative method from 199 * `bestAttributePlatformFromData()` to get the attribute platform. 200 * 201 * @param objectGraph The App Store object graph 202 * @param data The Media API Marketplace response to search for an attribute platform. 203 * @returns The most appropriate attribute platform available for the current client. 204 */ 205export function bestAttributePlatformFromMarketplaceData(objectGraph, data) { 206 // 1. Iterate through the client's preferred platform ordering until we 207 // find the first one present in the response. 208 const preferredAttributePlatforms = defaultAttributePlatformOrdering(objectGraph); 209 for (const attributePlatform of preferredAttributePlatforms) { 210 const versionsAttributes = contentAttributeAsDictionary(objectGraph, data, "versionAttributes", attributePlatform); 211 if (serverData.isDefinedNonNullNonEmpty(versionsAttributes)) { 212 return attributePlatform; 213 } 214 } 215 // 2. Catch-All 216 return defaultAttributePlatform(objectGraph); 217} 218/** 219 * The default attribute platform for the current client 220 */ 221export function defaultAttributePlatform(objectGraph) { 222 var _a; 223 if ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.attributePlatform) { 224 return objectGraph.activeIntent.attributePlatform; 225 } 226 switch (objectGraph.client.deviceType) { 227 case "phone": 228 case "pad": 229 return "ios"; 230 case "tv": 231 return "appletvos"; 232 case "mac": 233 return "osx"; 234 case "watch": 235 return "watch"; 236 case "vision": 237 return "xros"; 238 default: 239 return null; 240 } 241} 242/** 243 * The preferred ordering to use given our default platform. 244 */ 245function defaultAttributePlatformOrdering(objectGraph, clientIdentifierOverride) { 246 const defaultPlatform = defaultAttributePlatform(objectGraph); 247 if (defaultPlatform === null) { 248 // If the `"web"` client is active and there is not an "active intent" to 249 // inform a default platform, fall back to a hard-coded ordering 250 if (objectGraph.client.isWeb) { 251 return ["ios", "osx", "xros", "watch", "appletvos"]; 252 } 253 else { 254 return []; 255 } 256 } 257 switch (defaultPlatform) { 258 case "ios": 259 if (clientIdentifierOverride === "VisionAppStore" /* ClientIdentifier.VisionAppStore */ || 260 clientIdentifierOverride === "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */) { 261 return ["xros", "ios", "appletvos", "osx"]; 262 } 263 else { 264 return ["ios", "appletvos", "osx", "xros"]; 265 } 266 case "osx": 267 return ["osx", "ios", "appletvos", "xros"]; 268 case "appletvos": 269 return ["appletvos", "ios", "osx", "xros"]; 270 case "watch": 271 // Per Hiren Kotadia on 2019-2-26, watch platform attributes will always be under ios. 272 // We're going to promote ios to the head of the search list to speed up Media API 273 // response parsing. We'll keep watch as #2 in the list so that if this changes in 274 // the future, it should mostly just work. -km 275 return ["ios", "watch", "osx", "xros"]; 276 case "xros": 277 return ["xros", "ios", "appletvos", "osx"]; 278 default: 279 return [defaultPlatform]; 280 } 281} 282// region Variant Attributes 283/** 284 * Retrieve the attribute for a specific platform's variant attribute as a dictionary, from custom attributes or standard attributes. 285 * @param data Data to get attribute for. 286 * @param productVariantData Variant data to use when finding item. 287 * @param attributeKey The key to fetch in platform attributes. May be converted to custom attribute key. 288 * @param attributePlatform The platform to fetch attribute for. Defaults to current platform if unspecified. 289 */ 290export function customAttributeAsDictionary(objectGraph, data, productVariantData, attributeKey, attributePlatform) { 291 // Use `customAttributes.${customAttributeKey}` for platform if present 292 const customAttributeKey = mediaAttributes.attributeKeyAsCustomAttributeKey(attributeKey); 293 if (isNothing(customAttributeKey)) { 294 return null; 295 } 296 const customAttributes = contentAttributeAsDictionary(objectGraph, data, "customAttributes", attributePlatform); 297 const allowNondefaultTreatmentInNondefaultPage = mediaAttributes.attributeAllowsNonDefaultTreatmentInNonDefaultPage(customAttributeKey); 298 const customAttribute = variantAttributeForKey(objectGraph, customAttributes, productVariantData, customAttributeKey, allowNondefaultTreatmentInNondefaultPage); 299 if (serverData.isDefinedNonNullNonEmpty(customAttribute)) { 300 return serverData.asDictionary(customAttribute); 301 } 302 // Otherwise, use `${attributeKey}` for platform. 303 return contentAttributeAsDictionary(objectGraph, data, attributeKey, attributePlatform); 304} 305/** 306 * Retrieve the attribute for a specific platform's variant attribute as a dictionary, from custom attributes or standard attributes. 307 * @param data Data to get attribute for. 308 * @param productVariantData Variant data to use when finding item. 309 * @param attributeKey The key to fetch in platform attributes when custom attributes are not present. 310 * @param attributePlatform The platform to fetch attribute for. Defaults to current platform if unspecified. 311 */ 312export function customAttributeAsString(objectGraph, data, productVariantData, attributeKey, attributePlatform) { 313 // Use `customAttributes.${customAttributeKey}` for platform if present 314 const customAttributeKey = mediaAttributes.attributeKeyAsCustomAttributeKey(attributeKey); 315 if (isNothing(customAttributeKey)) { 316 return null; 317 } 318 const customAttributes = contentAttributeAsDictionary(objectGraph, data, "customAttributes", attributePlatform); 319 const allowNondefaultTreatmentInNondefaultPage = mediaAttributes.attributeAllowsNonDefaultTreatmentInNonDefaultPage(customAttributeKey); 320 const customAttribute = variantAttributeForKey(objectGraph, customAttributes, productVariantData, customAttributeKey, allowNondefaultTreatmentInNondefaultPage); 321 if (serverData.isDefinedNonNullNonEmpty(customAttribute)) { 322 return serverData.asString(customAttribute); 323 } 324 // Otherwise, use `${attributeKey}` for platform. 325 return contentAttributeAsString(objectGraph, data, attributeKey); 326} 327// endregion 328//# sourceMappingURL=attributes.js.map