diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /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')
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 |
