summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-common.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-common.js')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/app-promotions/app-promotions-common.js385
1 files changed, 385 insertions, 0 deletions
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