diff options
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/privacy')
4 files changed, 870 insertions, 0 deletions
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 |
