src/amo/utils/addons.js (129 lines of code) (raw):

/* @flow */ import { oneLine } from 'common-tags'; import invariant from 'invariant'; import { BADGE_CATEGORIES, DOWNLOAD_FAILED, ERROR_CORRUPT_FILE, FATAL_ERROR, FATAL_INSTALL_ERROR, FATAL_UNINSTALL_ERROR, INSTALL_FAILED, ALL_PROMOTED_CATEGORIES, } from 'amo/constants'; import log from 'amo/logger'; import { getPreviewImage } from 'amo/imageUtils'; import { removeUndefinedProps } from 'amo/utils/url'; import type { PromotedCategoryType } from 'amo/constants'; import type { SuggestionType } from 'amo/reducers/autocomplete'; import type { AddonVersionType } from 'amo/reducers/versions'; import type { AddonType, CollectionAddonType } from 'amo/types/addons'; import type { I18nType } from 'amo/types/i18n'; export const getErrorMessage = ({ i18n, error, }: {| i18n: I18nType, error: string | void, |}): string => { invariant(i18n, 'i18n is required'); switch (error) { case ERROR_CORRUPT_FILE: return i18n.gettext( 'Installation aborted because the add-on appears to be corrupt.', ); case INSTALL_FAILED: return i18n.gettext('Installation failed. Please try again.'); case DOWNLOAD_FAILED: return i18n.gettext('Download failed. Please check your connection.'); case FATAL_INSTALL_ERROR: return i18n.gettext('An unexpected error occurred during installation.'); case FATAL_UNINSTALL_ERROR: return i18n.gettext( 'An unexpected error occurred during uninstallation.', ); case FATAL_ERROR: default: return i18n.gettext('An unexpected error occurred.'); } }; export const getFileHash = ({ addon, installURL, version, }: {| addon: AddonType, installURL: string, version: AddonVersionType, |}): string | void => { const urlKey = installURL.split('?')[0]; const { file } = version; // The API sometimes appends ?src= to URLs so we just check the basename. if (file && file.url.startsWith(urlKey)) { return file.hash; } log.warn(oneLine`No file hash found for addon "${addon.slug}", installURL "${installURL}" (as "${urlKey}")`); return undefined; }; export const getAddonJsonLinkedData = ({ addon, currentVersion, ratingThreshold = 3.3, }: {| addon: AddonType, currentVersion: AddonVersionType | null, ratingThreshold?: number, |}): Object => { const { ratings } = addon; let aggregateRating; if (ratings && ratings.count > 0 && ratings.average >= ratingThreshold) { aggregateRating = { '@type': 'AggregateRating', ratingCount: ratings.count, ratingValue: ratings.average, }; } return removeUndefinedProps({ '@context': 'http://schema.org', '@type': 'WebApplication', name: addon.name, url: addon.url, image: getPreviewImage(addon), applicationCategory: 'http://schema.org/OtherApplication', operatingSystem: 'Firefox', description: addon.summary, offers: { '@type': 'Offer', availability: 'http://schema.org/InStock', price: 0, priceCurrency: 'USD', }, version: currentVersion ? currentVersion.version : undefined, aggregateRating, }); }; export const getPromotedCategory = ({ addon, clientApp, forBadging = false, }: {| addon: AddonType | CollectionAddonType | SuggestionType | null | void, clientApp: string, forBadging?: boolean, |}): PromotedCategoryType | null => { if (!addon?.promoted) { return null; } const categories: Array<PromotedCategoryType> = addon.promoted .filter((promoted) => { if (!promoted.apps.includes(clientApp)) { return false; } // Special logic if we're using the category for badging. // We shouldn't add badges that are in BADGE_CATEGORIES. return forBadging ? BADGE_CATEGORIES.includes(promoted.category) : true; }) .map((promoted) => promoted.category) .sort( (a, b) => ALL_PROMOTED_CATEGORIES.indexOf(a) - ALL_PROMOTED_CATEGORIES.indexOf(b), ); // Return only the 'most important' badge. return categories.shift() || null; };