summaryrefslogtreecommitdiff
path: root/src/jet/intents
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 /src/jet/intents
init commit
Diffstat (limited to 'src/jet/intents')
-rw-r--r--src/jet/intents/charts-page-redirect-intent-controller.ts68
-rw-r--r--src/jet/intents/error-page-intent-controller.ts52
-rw-r--r--src/jet/intents/lint-metrics-event/lint-metrics-event-controller.ts18
-rw-r--r--src/jet/intents/lint-metrics-event/lint-metrics-event-intent.ts23
-rw-r--r--src/jet/intents/route-url/route-url-controller.ts28
-rw-r--r--src/jet/intents/route-url/route-url-intent.ts48
-rw-r--r--src/jet/intents/static-message-pages/carrier-page-intent-controller.ts41
-rw-r--r--src/jet/intents/static-message-pages/contingent-price-page-intent-controller.ts49
-rw-r--r--src/jet/intents/static-message-pages/invoice-page-intent-controller.ts41
-rw-r--r--src/jet/intents/static-message-pages/win-back-page-intent-controller.ts49
10 files changed, 417 insertions, 0 deletions
diff --git a/src/jet/intents/charts-page-redirect-intent-controller.ts b/src/jet/intents/charts-page-redirect-intent-controller.ts
new file mode 100644
index 0000000..06d41ce
--- /dev/null
+++ b/src/jet/intents/charts-page-redirect-intent-controller.ts
@@ -0,0 +1,68 @@
+import type { IntentController } from '@jet/environment/dispatching';
+import type { RouteProvider } from '@jet/environment/routing';
+import type { AppStoreObjectGraph } from '@jet-app/app-store/foundation/runtime/app-store-object-graph';
+import { withActiveIntent } from '@jet-app/app-store/foundation/dependencies/active-intent';
+import { generateRoutes } from '@jet-app/app-store/common/util/generate-routes';
+import { injectWebNavigation } from '@jet-app/app-store/common/web-navigation/inject-web-navigation';
+import { makeChartsPageURL } from '@jet-app/app-store/common/charts/charts-page-url';
+import { makeChartsPageIntent } from '@jet-app/app-store/api/intents/charts-page-intent';
+import { GenericPage } from '@jet-app/app-store/api/models';
+import { isPreviewPlatform } from '@jet-app/app-store/api/models/preview-platform';
+import { notFoundError } from '@jet-app/app-store/foundation/media/network';
+
+const makeIntent = (opts) => ({
+ ...opts,
+ $kind: 'ChartsPageRedirect',
+});
+
+// This will catch URLs like `/charts/iphone`
+const { routes: routesWithoutGenreId } = generateRoutes(
+ makeIntent,
+ '/charts/{platform}',
+);
+
+// This will catch URLs like `/charts/iphone/utilities-apps/6002`
+const { routes: routesWithGenreId } = generateRoutes(
+ makeIntent,
+ '/charts/{platform}/{slug}/{genreId}',
+);
+
+function chartsPageRedirectRoutes(objectGraph: AppStoreObjectGraph) {
+ return [
+ ...routesWithoutGenreId(objectGraph),
+ ...routesWithGenreId(objectGraph),
+ ];
+}
+
+export const ChartsPageRedirectIntentController: IntentController<any> &
+ RouteProvider = {
+ $intentKind: 'ChartsPageRedirect',
+
+ routes: chartsPageRedirectRoutes,
+
+ async perform(intent, objectGraphWithoutActiveIntent: AppStoreObjectGraph) {
+ return await withActiveIntent(
+ objectGraphWithoutActiveIntent,
+ intent,
+ async (objectGraph) => {
+ const page = new GenericPage([]);
+ const chartPageIntent = makeChartsPageIntent(intent);
+
+ if (!isPreviewPlatform(intent.platform)) {
+ throw notFoundError();
+ }
+
+ // Setting the `canonicalUrl` on the page to normal Charts Page URL (e.g. /{platform}/charts)
+ // will trigger a 301 redirect to the that page.
+ page.canonicalURL = makeChartsPageURL(
+ objectGraph,
+ chartPageIntent,
+ );
+
+ injectWebNavigation(objectGraph, page, intent.platform);
+
+ return page;
+ },
+ );
+ },
+};
diff --git a/src/jet/intents/error-page-intent-controller.ts b/src/jet/intents/error-page-intent-controller.ts
new file mode 100644
index 0000000..59ac1fd
--- /dev/null
+++ b/src/jet/intents/error-page-intent-controller.ts
@@ -0,0 +1,52 @@
+import type { Intent, IntentController } from '@jet/environment/dispatching';
+import type { Opt } from '@jet/environment/types/optional';
+import type { AppStoreObjectGraph } from '@jet-app/app-store/foundation/runtime/app-store-object-graph';
+import { withActiveIntent } from '@jet-app/app-store/foundation/dependencies/active-intent';
+import { injectWebNavigation } from '@jet-app/app-store/common/web-navigation/inject-web-navigation';
+
+import { ErrorPage } from '~/jet/models/error-page';
+import type { Page } from '~/jet/models/page';
+import { getRejectedIntent } from '~/jet/utils/error-metadata';
+import { isWithPlatform } from '~/jet/utils/with-platform';
+
+interface ErrorPageIntent extends Intent<Page> {
+ $kind: 'ErrorPageIntent';
+ error: Opt<Error>;
+}
+
+export function makeErrorPageIntent(
+ options: Omit<ErrorPageIntent, '$kind'>,
+): ErrorPageIntent {
+ return {
+ ...options,
+ $kind: 'ErrorPageIntent',
+ };
+}
+
+export const ErrorPageIntentController: IntentController<ErrorPageIntent> = {
+ $intentKind: 'ErrorPageIntent',
+
+ async perform(
+ intent,
+ objectGraphWithoutActiveIntent: AppStoreObjectGraph,
+ ): Promise<Page> {
+ const { error } = intent;
+ const rejectedIntent = error ? getRejectedIntent(error) : null;
+ const platform =
+ (rejectedIntent && isWithPlatform(rejectedIntent)
+ ? rejectedIntent.platform
+ : null) ?? 'iphone';
+
+ return await withActiveIntent(
+ objectGraphWithoutActiveIntent,
+ { ...intent, platform },
+ async (objectGraph) => {
+ const page = new ErrorPage({ error: intent.error });
+
+ injectWebNavigation(objectGraph, page, platform);
+
+ return page;
+ },
+ );
+ },
+};
diff --git a/src/jet/intents/lint-metrics-event/lint-metrics-event-controller.ts b/src/jet/intents/lint-metrics-event/lint-metrics-event-controller.ts
new file mode 100644
index 0000000..046914b
--- /dev/null
+++ b/src/jet/intents/lint-metrics-event/lint-metrics-event-controller.ts
@@ -0,0 +1,18 @@
+import type { IntentController } from '@jet/environment/dispatching/base/intent-controller';
+import type { LintedMetricsEvent } from '@jet/environment/types/metrics';
+
+import {
+ type LintMetricsEventIntent,
+ LintMetricsEventIntentKind,
+} from './lint-metrics-event-intent';
+
+export const LintMetricsEventIntentController: IntentController<LintMetricsEventIntent> =
+ {
+ $intentKind: LintMetricsEventIntentKind.Name,
+
+ async perform(
+ intent: LintMetricsEventIntent,
+ ): Promise<LintedMetricsEvent> {
+ return { fields: intent.fields };
+ },
+ };
diff --git a/src/jet/intents/lint-metrics-event/lint-metrics-event-intent.ts b/src/jet/intents/lint-metrics-event/lint-metrics-event-intent.ts
new file mode 100644
index 0000000..a2a085e
--- /dev/null
+++ b/src/jet/intents/lint-metrics-event/lint-metrics-event-intent.ts
@@ -0,0 +1,23 @@
+import type { Intent } from '@jet/environment/dispatching';
+import type {
+ LintedMetricsEvent,
+ MetricsFields,
+} from '@jet/environment/types/metrics';
+
+export const enum LintMetricsEventIntentKind {
+ Name = 'LintMetricsEventIntent',
+}
+
+export interface LintMetricsEventIntent extends Intent<LintedMetricsEvent> {
+ $kind: LintMetricsEventIntentKind.Name;
+ fields: MetricsFields;
+}
+
+export function makeLintMetricsEventIntent(
+ options: Omit<LintMetricsEventIntent, '$kind'>,
+): LintMetricsEventIntent {
+ return {
+ ...options,
+ $kind: LintMetricsEventIntentKind.Name,
+ };
+}
diff --git a/src/jet/intents/route-url/route-url-controller.ts b/src/jet/intents/route-url/route-url-controller.ts
new file mode 100644
index 0000000..8c8fdb6
--- /dev/null
+++ b/src/jet/intents/route-url/route-url-controller.ts
@@ -0,0 +1,28 @@
+import { isSome } from '@jet/environment/types/optional';
+import type { IntentController } from '@jet/environment/dispatching';
+import type { AppStoreObjectGraph } from '@jet-app/app-store/foundation/runtime/app-store-object-graph';
+import { isRoutableIntent } from '@jet-app/app-store/api/intents/routable-intent';
+
+import type { RouteUrlIntent } from '~/jet/intents';
+import { makeFlowAction } from '~/jet/models';
+
+export const RouteUrlIntentController: IntentController<RouteUrlIntent> = {
+ $intentKind: 'RouteUrlIntent',
+
+ async perform(intent: RouteUrlIntent, objectGraph: AppStoreObjectGraph) {
+ const targetIntent = objectGraph.router.intentFor(intent.url);
+
+ if (isSome(targetIntent) && isRoutableIntent(targetIntent)) {
+ return {
+ // intent needed for SSR
+ intent: targetIntent,
+ // only ever used by client; only clients have actions
+ action: makeFlowAction(targetIntent),
+ storefront: targetIntent.storefront,
+ language: targetIntent.language,
+ };
+ }
+
+ return null;
+ },
+};
diff --git a/src/jet/intents/route-url/route-url-intent.ts b/src/jet/intents/route-url/route-url-intent.ts
new file mode 100644
index 0000000..841bd25
--- /dev/null
+++ b/src/jet/intents/route-url/route-url-intent.ts
@@ -0,0 +1,48 @@
+import type { Optional } from '@jet/environment/types/optional';
+import type { Intent } from '@jet/environment/dispatching';
+import type { FlowAction } from '@jet-app/app-store/api/models';
+
+import type {
+ NormalizedStorefront,
+ NormalizedLanguage,
+} from '@jet-app/app-store/api/locale';
+
+/**
+ * A response from the router given an incoming (deeplink) URL.
+ */
+export interface RouterResponse {
+ /**
+ * The intent to dispatch to get the view model for this URL.
+ */
+ intent: Intent<unknown>;
+
+ /**
+ * action to navigate to a new page of the app.
+ */
+ action: FlowAction;
+
+ storefront: NormalizedStorefront;
+
+ language: NormalizedLanguage;
+}
+
+export interface RouteUrlIntent extends Intent<Optional<RouterResponse>> {
+ $kind: 'RouteUrlIntent';
+
+ /**
+ * The URL to route (ex. "https://podcasts.apple.com/us/show/serial/id123").
+ */
+ url: string;
+}
+
+export function isRouteUrlIntent(
+ intent: Intent<unknown>,
+): intent is RouteUrlIntent {
+ return intent.$kind === 'RouteUrlIntent';
+}
+
+export function makeRouteUrlIntent(
+ options: Omit<RouteUrlIntent, '$kind'>,
+): RouteUrlIntent {
+ return { $kind: 'RouteUrlIntent', ...options };
+}
diff --git a/src/jet/intents/static-message-pages/carrier-page-intent-controller.ts b/src/jet/intents/static-message-pages/carrier-page-intent-controller.ts
new file mode 100644
index 0000000..a1b049c
--- /dev/null
+++ b/src/jet/intents/static-message-pages/carrier-page-intent-controller.ts
@@ -0,0 +1,41 @@
+import type { IntentController } from '@jet/environment/dispatching';
+import type { RouteProvider } from '@jet/environment/routing';
+import type { AppStoreObjectGraph } from '@jet-app/app-store/foundation/runtime/app-store-object-graph';
+import { withActiveIntent } from '@jet-app/app-store/foundation/dependencies/active-intent';
+import { generateRoutes } from '@jet-app/app-store/common/util/generate-routes';
+import { injectWebNavigation } from '@jet-app/app-store/common/web-navigation/inject-web-navigation';
+
+import { StaticMessagePage } from '~/jet/models/static-message-page';
+
+const { routes, makeCanonicalUrl } = generateRoutes(
+ (opts) => ({
+ ...opts,
+ $kind: 'CarrierPageIntent',
+ }),
+ '/carrier',
+);
+
+export const CarrierPageIntentController: IntentController<any> &
+ RouteProvider = {
+ $intentKind: 'CarrierPageIntent',
+
+ routes,
+
+ async perform(intent, objectGraphWithoutActiveIntent: AppStoreObjectGraph) {
+ return await withActiveIntent(
+ objectGraphWithoutActiveIntent,
+ intent,
+ async (objectGraph) => {
+ const page = new StaticMessagePage({
+ titleLocKey: 'ASE.Web.AppStore.Carrier.Title',
+ contentType: 'carrier',
+ });
+
+ page.canonicalURL = makeCanonicalUrl(objectGraph, intent);
+
+ injectWebNavigation(objectGraph, page, intent.platform);
+ return page;
+ },
+ );
+ },
+};
diff --git a/src/jet/intents/static-message-pages/contingent-price-page-intent-controller.ts b/src/jet/intents/static-message-pages/contingent-price-page-intent-controller.ts
new file mode 100644
index 0000000..ba2babd
--- /dev/null
+++ b/src/jet/intents/static-message-pages/contingent-price-page-intent-controller.ts
@@ -0,0 +1,49 @@
+import type { IntentController } from '@jet/environment/dispatching';
+import type { RouteProvider } from '@jet/environment/routing';
+import type { AppStoreObjectGraph } from '@jet-app/app-store/foundation/runtime/app-store-object-graph';
+import { withActiveIntent } from '@jet-app/app-store/foundation/dependencies/active-intent';
+import { generateRoutes } from '@jet-app/app-store/common/util/generate-routes';
+import { injectWebNavigation } from '@jet-app/app-store/common/web-navigation/inject-web-navigation';
+
+import { StaticMessagePage } from '~/jet/models/static-message-page';
+
+const { routes, makeCanonicalUrl } = generateRoutes(
+ (opts) => ({
+ ...opts,
+ $kind: 'ContingentPriceIntent',
+ }),
+ '/contingent-price/{offerId}',
+ [],
+ {
+ extraRules: [
+ {
+ regex: [/(?:\/[a-z]{2})?\/contingent-price/],
+ },
+ ],
+ },
+);
+
+export const ContingentPricingIntentController: IntentController<any> &
+ RouteProvider = {
+ $intentKind: 'ContingentPriceIntent',
+
+ routes,
+
+ async perform(intent, objectGraphWithoutActiveIntent: AppStoreObjectGraph) {
+ return await withActiveIntent(
+ objectGraphWithoutActiveIntent,
+ intent,
+ async (objectGraph) => {
+ const page = new StaticMessagePage({
+ titleLocKey: 'ASE.Web.AppStore.WinBack.Title',
+ contentType: 'contingent-price',
+ });
+
+ page.canonicalURL = makeCanonicalUrl(objectGraph, intent);
+
+ injectWebNavigation(objectGraph, page, intent.platform);
+ return page;
+ },
+ );
+ },
+};
diff --git a/src/jet/intents/static-message-pages/invoice-page-intent-controller.ts b/src/jet/intents/static-message-pages/invoice-page-intent-controller.ts
new file mode 100644
index 0000000..caa02f4
--- /dev/null
+++ b/src/jet/intents/static-message-pages/invoice-page-intent-controller.ts
@@ -0,0 +1,41 @@
+import type { IntentController } from '@jet/environment/dispatching';
+import type { RouteProvider } from '@jet/environment/routing';
+import type { AppStoreObjectGraph } from '@jet-app/app-store/foundation/runtime/app-store-object-graph';
+import { withActiveIntent } from '@jet-app/app-store/foundation/dependencies/active-intent';
+import { generateRoutes } from '@jet-app/app-store/common/util/generate-routes';
+import { injectWebNavigation } from '@jet-app/app-store/common/web-navigation/inject-web-navigation';
+
+import { StaticMessagePage } from '~/jet/models/static-message-page';
+
+const { routes, makeCanonicalUrl } = generateRoutes(
+ (opts) => ({
+ ...opts,
+ $kind: 'InvoicePageIntent',
+ }),
+ '/invoice',
+);
+
+export const InvoicePageIntentController: IntentController<any> &
+ RouteProvider = {
+ $intentKind: 'InvoicePageIntent',
+
+ routes,
+
+ async perform(intent, objectGraphWithoutActiveIntent: AppStoreObjectGraph) {
+ return await withActiveIntent(
+ objectGraphWithoutActiveIntent,
+ intent,
+ async (objectGraph) => {
+ const page = new StaticMessagePage({
+ titleLocKey: 'ASE.Web.AppStore.Invoice.Title',
+ contentType: 'invoice',
+ });
+
+ page.canonicalURL = makeCanonicalUrl(objectGraph, intent);
+
+ injectWebNavigation(objectGraph, page, intent.platform);
+ return page;
+ },
+ );
+ },
+};
diff --git a/src/jet/intents/static-message-pages/win-back-page-intent-controller.ts b/src/jet/intents/static-message-pages/win-back-page-intent-controller.ts
new file mode 100644
index 0000000..2b78ba0
--- /dev/null
+++ b/src/jet/intents/static-message-pages/win-back-page-intent-controller.ts
@@ -0,0 +1,49 @@
+import type { IntentController } from '@jet/environment/dispatching';
+import type { RouteProvider } from '@jet/environment/routing';
+import type { AppStoreObjectGraph } from '@jet-app/app-store/foundation/runtime/app-store-object-graph';
+import { withActiveIntent } from '@jet-app/app-store/foundation/dependencies/active-intent';
+import { generateRoutes } from '@jet-app/app-store/common/util/generate-routes';
+import { injectWebNavigation } from '@jet-app/app-store/common/web-navigation/inject-web-navigation';
+
+import { StaticMessagePage } from '~/jet/models/static-message-page';
+
+const { routes, makeCanonicalUrl } = generateRoutes(
+ (opts) => ({
+ ...opts,
+ $kind: 'WinBackPageIntent',
+ }),
+ '/win-back/{offerId}',
+ [],
+ {
+ extraRules: [
+ {
+ regex: [/(?:\/[a-z]{2})?\/win-back/],
+ },
+ ],
+ },
+);
+
+export const WinBackPageIntentController: IntentController<any> &
+ RouteProvider = {
+ $intentKind: 'WinBackPageIntent',
+
+ routes,
+
+ async perform(intent, objectGraphWithoutActiveIntent: AppStoreObjectGraph) {
+ return await withActiveIntent(
+ objectGraphWithoutActiveIntent,
+ intent,
+ async (objectGraph) => {
+ const page = new StaticMessagePage({
+ titleLocKey: 'ASE.Web.AppStore.WinBack.Title',
+ contentType: 'win-back',
+ });
+
+ page.canonicalURL = makeCanonicalUrl(objectGraph, intent);
+
+ injectWebNavigation(objectGraph, page, intent.platform);
+ return page;
+ },
+ );
+ },
+};