summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/util
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 /node_modules/@jet-app/app-store/tmp/src/common/util
init commit
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/util')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/util/app-tags-util.js25
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js183
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/util/lottery.js62
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/util/page-common.js25
4 files changed, 295 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/util/app-tags-util.js b/node_modules/@jet-app/app-store/tmp/src/common/util/app-tags-util.js
new file mode 100644
index 0000000..0a7808a
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/util/app-tags-util.js
@@ -0,0 +1,25 @@
+import { isNothing } from "@jet/environment";
+/**
+ * @param objectGraph The object graph for our dependencies
+ * @returns Whether we should fetch app tags
+ */
+export function areAppTagsEnabled(objectGraph, featureSurface) {
+ const isClientFeatureFlagEnabled = objectGraph.client.isPhone && objectGraph.featureFlags.isEnabled("app_genome_2025A");
+ if (!isClientFeatureFlagEnabled) {
+ return false;
+ }
+ if (isNothing(featureSurface)) {
+ return true;
+ }
+ switch (featureSurface) {
+ case "grouping":
+ return objectGraph.bag.isAppsGroupingTagsEnabled;
+ case "product":
+ return objectGraph.bag.isAppsProductPageTagsEnabled;
+ case "slp":
+ return objectGraph.bag.isAppsSlpTagsEnabled;
+ default:
+ return false;
+ }
+}
+//# sourceMappingURL=app-tags-util.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js b/node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js
new file mode 100644
index 0000000..f370dbe
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/util/generate-routes.js
@@ -0,0 +1,183 @@
+import { isSome } from "@jet/environment";
+import { URL } from "@jet/environment/util/urls";
+import { Path, Parameters } from "../../foundation/network/url-constants";
+import { normalizeLocale, deriveLocaleForUrl } from "../locale";
+/**
+ * Generate the routes property for a `RouteProvider` and a canonical URL factory
+ * function for a {@link RoutableIntent} (non-detail page).
+ *
+ * `makeIntent` should be a function that accepts a single parameter,
+ * `options: Omit<YourIntent, '$kind'>`, and returns the intent.
+ *
+ * `path` is the static part of the path. For example, in `'/us/browse?l=es'`,
+ * `'/browse'` is the static part of the path. Path can contain multiple
+ * segments like `'/foo/bar'`, but it does not support parameters like
+ * `'/foo/{id}'`.
+ *
+ * `expectedQueryParameters` are the names of the query parameters for the routes
+ * that also double as properties on the intent. This instructs the intent
+ * builder on which properties to provide to `makeIntent`. For example,
+ * `SearchResultsPageIntent`'s `RouteProvider` recognizes a `'term'` query param, which
+ * is then stored in the intent as `{ term: 'valueFromParam' }`. For that case,
+ * this array should be `['term']`. Put another way, this should be a list of the
+ * non-optional (non-nullable, etc.) string properties on the intent:
+ *
+ * ```typescript
+ * interface SearchResultsPageIntent extends RouteableIntent {
+ * $kind: 'SearchResultsPageIntent'; // not this one
+ * term: string; // this one should be included!
+ * facet: Opt<String>; // but it doesn't include this; it's optional
+ * }
+ * ```
+ *
+ * NOTE: If you receive a type error like `Type 'string' is not assignable
+ * to type 'never'` for this argument, it means that your array of parameters
+ * doesn't match the intent definition. In the above example, if we added a
+ * `originalTerm: string` to the intent, then passing ['term'] would fail,
+ * because 'originalTerm' is missing. Adding it does mean, however, that you
+ * expect users to be able to pass it as a query parameter
+ * (ex. /search?term=foo&originalTerm=bar). If that's unintended, you may want
+ * to consider making this property Opt<> (and also maybe ?).
+ *
+ * Usage Example: For a contrived intent `FooIntent`,
+ *
+ * ```typescript
+ * interface FooIntent extends RouteableIntent {
+ * $kind: 'FooIntent';
+ * bar: string;
+ * }
+ * ```
+ *
+ * If we expect users to be able to route to this intent via `/us/foo?bar=baz`
+ * or `/foo?bar=baz&l=es`, then in the IntentController's file:
+ *
+ * ```typescript
+ * const {
+ * routes,
+ * makeCanonicalUrl,
+ * } = generateRoutes(makeFooIntent, '/foo', ['bar']);
+ * ```
+ *
+ * And then, `routes` can be used in the `RouteProvider`:
+ *
+ * ```typescript
+ * export const FooIntentController: RouteProvider<FooIntent> & ... = {
+ * routes,
+ *
+ * // ...
+ * };
+ * ```
+ *
+ * And then in perform, when returning a `Page`, append the `canonicalURL`:
+ *
+ * ```typescript
+ * async perform(intent: FooIntent, objectGraph: ObjectGraph): Promise<Page> {
+ * // ...
+ * return {
+ * ...page,
+ * canonicalURL: makeCanonicalUrl(intent),
+ * };
+ * },
+ * ```
+ */
+export function generateRoutes(makeIntent, path, requiredQueryParameters = [], extras) {
+ const ruleExtras = { exclusions: extras === null || extras === void 0 ? void 0 : extras.exclusions };
+ const queryParameters = requiredQueryParameters.slice();
+ if (extras === null || extras === void 0 ? void 0 : extras.optionalQuery) {
+ queryParameters.push(...extras.optionalQuery.map((p) => `${p}?`));
+ }
+ return {
+ routes(objectGraph) {
+ var _a;
+ return [
+ {
+ rules: [
+ {
+ ...ruleExtras,
+ path,
+ query: [`${Parameters.language}?`, ...queryParameters],
+ },
+ {
+ ...ruleExtras,
+ path: `/{${Path.storeFront}}${path}`,
+ query: [`${Parameters.language}?`, ...queryParameters],
+ },
+ ...((_a = extras === null || extras === void 0 ? void 0 : extras.extraRules) !== null && _a !== void 0 ? _a : []),
+ ],
+ handler(_, parameters) {
+ const { [Path.storeFront]: storefront, [Parameters.language]: language, ...rest } = parameters;
+ const typedRestParams = rest;
+ return makeIntent({
+ ...typedRestParams,
+ ...normalizeLocale(objectGraph, {
+ storefront,
+ language,
+ }),
+ });
+ },
+ },
+ ];
+ },
+ makeCanonicalUrl(objectGraph, intent) {
+ const url = buildBaseUrl(objectGraph, path, intent);
+ // Replace dynamic segments with `Intent` properties
+ const pathComponents = url.pathComponents();
+ for (const [i, pathComponent] of pathComponents.entries()) {
+ if (pathComponent.startsWith("{") && pathComponent.endsWith("}")) {
+ const variableInPathComponent = pathComponent.substring(1, pathComponent.length - 1);
+ pathComponents[i] = intent[variableInPathComponent];
+ }
+ }
+ url.set("pathname", "/" + pathComponents.join("/"));
+ // Set query params
+ const sortedQueryParameters = requiredQueryParameters.slice();
+ // Sorted for consistent order
+ sortedQueryParameters.sort((a, b) => a.localeCompare(b));
+ for (const queryParameter of sortedQueryParameters) {
+ if (!(queryParameter in intent)) {
+ throw new Error(`expected queryParmeters to contain: ${queryParameter}`);
+ }
+ const value = intent[queryParameter];
+ // NOTE: TypeScript is unfortunately not able to see that
+ // parameters[queryParameter] will always be string. We prove
+ // this in the type. queryParameters is required to contain the
+ // string names of all keys on I with a string value (see
+ // RequiredIntentOptions<I>). This escape hatch is preferable
+ // within this function as it means that the function is
+ // externally typesafe. If a user forgets to pass in params,
+ // there will be a type error.
+ url.param(queryParameter, value);
+ }
+ if (extras === null || extras === void 0 ? void 0 : extras.optionalQuery) {
+ const optionalParams = extras.optionalQuery.slice();
+ optionalParams.sort((a, b) => a.localeCompare(b));
+ optionalParams.forEach((query) => {
+ const value = intent[query];
+ if (value) {
+ url.param(query, value);
+ }
+ });
+ }
+ return url.toString();
+ },
+ };
+}
+/**
+ * Builds the basis for a "canonical URL" given a {@linkcode path} and {@linkcode locale}
+ *
+ * The locale data will be encoded into the resulting {@linkcode URL}; other "dynamic segments"
+ * within {@linkcode path} are expected to be replaced separately
+ */
+function buildBaseUrl(objectGraph, path, locale) {
+ const url = new URL("https://apps.apple.com");
+ const { storefront, language } = deriveLocaleForUrl(objectGraph, locale);
+ url.path(storefront);
+ // Actual path (ex. browse in https://apps.apple.com/us/browse)
+ // substr(1), because URL.path doesn't expect a leading slash
+ url.path(path.substr(1));
+ if (isSome(language)) {
+ url.param("l", language);
+ }
+ return url;
+}
+//# sourceMappingURL=generate-routes.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/util/lottery.js b/node_modules/@jet-app/app-store/tmp/src/common/util/lottery.js
new file mode 100644
index 0000000..5916128
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/util/lottery.js
@@ -0,0 +1,62 @@
+import { isNothing } from "@jet/environment";
+import { MetricsIdentifierType } from "../../foundation/metrics/metrics-identifiers-cache";
+export function isFeatureEnabledForCurrentUser(objectGraph, rolloutRate) {
+ var _a;
+ if (rolloutRate <= 0.0) {
+ return false;
+ }
+ if (rolloutRate >= 1.0) {
+ // It is important that we not require a metrics ID when our rollout rate is 100%
+ return true;
+ }
+ const metricsIdString = (_a = objectGraph.metricsIdentifiersCache) === null || _a === void 0 ? void 0 : _a.getMetricsIdForType(MetricsIdentifierType.user);
+ if (isNothing(metricsIdString) || metricsIdString.length === 0) {
+ // if the user does not have a DSID, err on the side of caution.
+ return false;
+ }
+ if (metricsIdString.length < 2) {
+ // If we don't have enough characters to normalize, which should never happen, safely return false.
+ return false;
+ }
+ return normalizedMetricsIdLotteryValue(metricsIdString) < rolloutRate;
+}
+/**
+ * Gets a normalized value based on the metrics ID to match against the rollout rate to bucket a user.
+ * @param metricsId The user's metrics ID string
+ * @returns A value from 0.0 to 1.0 that we use to bucket the user
+ */
+function normalizedMetricsIdLotteryValue(metricsId) {
+ // Character mapping function that converts a character to its corresponding number.
+ // This value is guaranteed to be between 0 and 61, inclusive.
+ function charToNumber(char) {
+ const charCode = char.charCodeAt(0);
+ if (charCode >= 48 && charCode <= 57) {
+ // '0'-'9'
+ return 52 + charCode - 48;
+ }
+ else if (charCode >= 65 && charCode <= 90) {
+ // 'A'-'Z'
+ return 26 + charCode - 65;
+ }
+ else if (charCode >= 97 && charCode <= 122) {
+ // 'a'-'z'
+ return charCode - 97;
+ }
+ // This should never happen, if it does, the metrics ID is corrupt, but we rely on that not happening.
+ // This would indeed break the value guarantee.
+ return charCode;
+ }
+ // Extract the last two characters from the string.
+ const lastTwoChars = metricsId.slice(-2);
+ // Convert each of the last two characters to their respective number representation.
+ const num1 = charToNumber(lastTwoChars[0]);
+ const num2 = charToNumber(lastTwoChars[1]);
+ // Combine the two numbers into a single base-62 number.
+ const combinedValue = num1 * 62 + num2;
+ // Calculate the maximum possible value for two characters (each char has a value of 0 to 61, inclusive).
+ const maxValue = 61 * 62 + 61;
+ // Normalize the combined value to be between 0.0 and 1.0.
+ const normalizedValue = combinedValue / maxValue;
+ return normalizedValue;
+}
+//# sourceMappingURL=lottery.js.map \ No newline at end of file
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/util/page-common.js b/node_modules/@jet-app/app-store/tmp/src/common/util/page-common.js
new file mode 100644
index 0000000..706fa9e
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/util/page-common.js
@@ -0,0 +1,25 @@
+/**
+ * Created by ls on 6/19/2019.
+ */
+/**
+ * On different platforms, different shelves are expected to be in certain multiples to not appear like there is a "gap"
+ * This function is used to truncate an array of models to fit given expected multiple.
+ * Note that ideally we want to request the exact number of items needed, but this accounts for cases where some data fails to build into full UI elements.
+ * @param items Array of items to truncate
+ * @param mod Modulo to apply.
+ */
+export function truncateItems(items, mod) {
+ if (!items) {
+ return null;
+ }
+ const length = items.length;
+ const remainder = length % mod;
+ // If we have less than mod items, we should return them all instead of 0 (because i - (i % mod)=0 when i < mod)
+ if (length >= mod) {
+ return items.slice(0, length - remainder);
+ }
+ else {
+ return items;
+ }
+}
+//# sourceMappingURL=page-common.js.map \ No newline at end of file