diff options
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/foundation/network')
4 files changed, 999 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/network/http.js b/node_modules/@jet-app/app-store/tmp/src/foundation/network/http.js new file mode 100644 index 0000000..34c6a9a --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/foundation/network/http.js @@ -0,0 +1,42 @@ +/** + * Created by keithpk on 12/3/16. + */ +/** + * The `FormBuilder` class can be used to construct HTTP + * the form parameters for use with an HTTP post operation. + */ +export class FormBuilder { + /** + * The content type to use for form parameters. + */ + static get contentType() { + return "application/x-www-form-urlencoded"; + } + /** + * Construct an empty form builder. + */ + constructor() { + this._params = ""; + } + /** + * Append a parameter to the builder's form parameters. + * @param key The key of the parameter. + * @param value The value of the parameter. + * @return The builder. + */ + param(key, value) { + if (key && value) { + const separator = this._params.length > 0 ? "&" : ""; + this._params += `${separator}${encodeURIComponent(key)}=${encodeURIComponent(value)}`; + } + return this; + } + /** + * Create and return a form parameters string based on the contents of the builder. + * @return The form parameters string. + */ + build() { + return this._params; + } +} +//# sourceMappingURL=http.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/network/network.js b/node_modules/@jet-app/app-store/tmp/src/foundation/network/network.js new file mode 100644 index 0000000..db302e7 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/foundation/network/network.js @@ -0,0 +1,123 @@ +/** + * Created by ls on 9/7/2018. + * + * This `network.ts` is the NON-MEDIA API arm of network fetch requests. + * It is built on `Network` object and provides standard functionality, such as: + * 1. Parsing the body into specific format. + * 2. Adding timing metrics onto blob. + * + * This should *only* be used for objects that should have timing metrics, i.e. requests to Non-MediaAPI endpoints + * that will ultimately render some whole page. Otherwise, use `objectGraph.network.fetch` directly. + * + * @see `src/media/network.ts` for fetching from Media API endpoints + */ +import { isSome } from "@jet/environment/types/optional"; +import * as serverData from "../json-parsing/server-data"; +/** @public */ +// eslint-disable-next-line @typescript-eslint/no-namespace +export var ResponseMetadata; +(function (ResponseMetadata) { + ResponseMetadata.requestedUrl = "_jet-internal:metricsHelpers_requestedUrl"; + /** + * Symbol used to place timing metrics values onto fetch responses + * without interfering with the data returned by the server. + */ + ResponseMetadata.timingValues = "_jet-internal:metricsHelpers_timingValues"; + /** + * Key used to access the page information gathered from a response's headers + */ + ResponseMetadata.pageInformation = "_jet-internal:metricsHelpers_pageInformation"; + /** + * Key used to access the content max-age gathered from a response's headers. + */ + ResponseMetadata.contentMaxAge = "_jet-internal:responseMetadata_contentMaxAge"; +})(ResponseMetadata || (ResponseMetadata = {})); +/** + * Module's private fetch implementation built off `net` global. + * + * @param {FetchRequest} request describes fetch request. + * @param {(value: string) => Type} parser Some function parsing response body `string` into specific type. + * @returns {Promise<Type>} Promise resolving to specific object. + * @throws {Error} Throws error if status code of request is not 200. + * + * @note Similar to `fetchWithToken` in `media` module, but excludes media token specific functionality. + * Top level data fetches to endpoints that don't do redirects, and can benefit from metrics should + * call methods that build off of this instead of calling `objectGraph.network.fetch(...)` directly. + */ +async function fetch(objectGraph, request, parser) { + const response = await objectGraph.network.fetch(request); + if (!response.ok) { + throw Error(`Bad Status code ${response.status} for ${request.url}`); + } + const parseStartTime = Date.now(); + const result = parser(response.body); + const parseEndTime = Date.now(); + if (result) { + // Build full network timing metrics. + const completeTimingMetrics = networkTimingMetricsWithParseTime(response.metrics, parseStartTime, parseEndTime); + if (serverData.isDefinedNonNull(completeTimingMetrics)) { + result[ResponseMetadata.timingValues] = completeTimingMetrics; + } + } + result[ResponseMetadata.requestedUrl] = request.url.toString(); + return result; +} +/** + * Fetch from an endpoint with JSON response body. + * + * @param {FetchRequest} request to fetch from endpoint with JSON response.. + * @returns {Promise<Type>} Promise resolving to body of response parsed as `Type`. + * @throws {Error} Throws error if status code of request is not 200. + */ +export async function fetchJSON(objectGraph, request) { + return await fetch(objectGraph, request, (body) => { + if (isSome(body)) { + return JSON.parse(body); + } + else { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return {}; + } + }); +} +/** + * Fetch from an endpoint with XML response body. + * + * @param {FetchRequest} request to fetch from endpoint with XML response. + * @returns {Promise<Type>} Promise resolving to body of response parsed as `Type`. + * @throws {Error} Throws error if status code of request is not 200. + */ +export async function fetchPlist(objectGraph, request) { + return await fetch(objectGraph, request, (body) => { + if (isSome(body)) { + return objectGraph.plist.parse(body); + } + else { + throw new Error(`Could not fetch Plist, response body was not defined for ${request.url}`); + } + }); +} +/** + * With network requests now being created and parsed in JS, different timing metrics are measured in both Native and JS. + * This function populates the missing values from `HTTPTimingMetrics`'s native counterpart, `JSNetworkPerformanceMetrics`. + * + * @param {HTTPTimingMetrics[] | null} responseMetrics Array of response metrics provided by native. + * @param {number} parseStartTime Time at which response body string parse began in JS. + * @param {number} parseEndTime Time at which response body string parse ended in JS. + * @returns {HTTPTimingMetrics | null} Fully populated timing metrics, or `null` if native response provided no metrics events to build off of. + */ +function networkTimingMetricsWithParseTime(responseMetrics, parseStartTime, parseEndTime) { + // No metrics events to build from. + if (serverData.isNull(responseMetrics) || responseMetrics.length === 0) { + return null; + } + // Append parse times to first partial timing metrics from native. + const firstPartialTimingMetrics = { + ...responseMetrics[0], + parseStartTime: parseStartTime, + parseEndTime: parseEndTime, + }; + // Timing metrics with all properties populated. + return firstPartialTimingMetrics; +} +//# sourceMappingURL=network.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/network/url-constants.js b/node_modules/@jet-app/app-store/tmp/src/foundation/network/url-constants.js new file mode 100644 index 0000000..f25d9cb --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/foundation/network/url-constants.js @@ -0,0 +1,452 @@ +/** + * Copied from SKUIUrl these action types come in urls like scheme:/?action=[UrlAction] + */ +/* tslint:disable:variable-name */ +export const Protocol = { + /// Protocol used for internal app store routing + internal: "x-as3-internal", + /// Protocol used for routing HTTP URLs + http: "http", + /// Protocol used for routing standard HTTPS URLs + https: "https", + /// The protocol used for iTunes app urls + itms: "itms", + /// The protocol used for secure iTunes app urls + itmss: "itmss", + /// Protocol used to target the app store + itmsAppss: "itms-appss", + /// Protocol used to target the app store + itmsApps: "itms-apps", + /// Protocol used to target the messages app store + itmsMessagess: "itms-messagess", + /// Protocol used to target the messages app store + itmsMessages: "itms-messages", + /// Protocol used to target the watch app store + itmsWatchs: "itms-watchs", + /// Protocol used to target the watch app store + itmsWatch: "itms-watch", + /// Protocol used for internal file routing + file: "file", + /// Protocol used for referencing resources in the host app's bundles. + resource: "resource", + /// Protocol used to target the mac app store + macappstore: "macappstore", + /// Protocol used to target the mac app store + macappstores: "macappstores", + /// Legacy protocol used to target the tv app store, + /// kept to maintain backwards compatibility. + tvappstoreLegacy: "com.apple.tvappstore", + /// Protocol used to target the tv app store + tvappstore: "com.apple.TVAppStore", + /// Protocol used to target Arcade + arcade: "com.apple.Arcade", + /// Protocol used to target StoreKitUI App Store service + storeKitUIServiceAppStore: "appstore-ui", +}; +export const Path = { + // Path for loading a shelf + shelf: "shelf", + // Path for MZStore requests + store: "WebObjects/MZStore.woa/wa", + // Path for MZStoreElements requests + storeElements: "WebObjects/MZStoreElements.woa/wa", + // Path for performing a lookup + lookup: "lookup", + // Path for performing a docTypeLookup + docTypeLookup: "docTypeLookup", + // Path for performing an install sheet + install: "install", + // Path to the grouping page + grouping: "viewGrouping", + // Path to the artist page + viewArtist: "viewArtist", + // Path to the product page + viewSoftware: "viewSoftware", + // Path to the today page + today: "today", + // Path to the arcade main page + arcade: "arcade", + arcadeUpsellPreview: "arcadeUpsellPreview", + // Path to Arcade See All + arcadeSeeAllGames: "arcadeSeeAllGames", + // Pagination path for Arcade See All + arcadeSeeAllGamesLoadMore: "arcadeSeeAllGamesLoadMore", + // Path to the genre page (which is a grouping page) + genre: "genre", + // Path to the genre page (which is a grouping page) + viewGenre: "viewGenre", + recommendationsSeeAll: "recommendationsSeeAll", + // Path for a screenshots lookup. + screenshots: "screenshots", + // Path to a single room + room: "viewRoom", + // Path to a programmed multi-room + multiRoom: "viewMultiRoom", + // Path to the main top charts page + charts: "charts", + // Path to the product page + product: "app", + // Path to the game page + game: "game", + // Path to siri product page deep links on tvOS App Store. + siri: "siri", + // Path to the product page for an app bundle. + productBundle: "app-bundle", + // Path component for the developer page + developer: "developer", + artist: "artist", + // Path component for the ratings shelf + ratings: "ratings", + // Path component for the reviews shelf + reviews: "reviews", + // Path to view reviews for MZStore + viewReviews: "viewContentsUserReviews", + // Path component for the personalized review shelf + personalizedReviews: "personalizedReviews", + // Path to editorial items (articles) + article: "article", + story: "story", + editorialItem: "editorialItem", + viewEditorialItem: "viewEditorialItem", + todayCardPreview: "todayCardPreview", + // Path component for MSO rooms + mso: "mso", + // Path for resetting the storefront + resetAndRedirect: "resetAndRedirect", + // Account + account: "account", + personalizationTransparency: "personalizationTransparency", + // Path component for an href-driven route. + href: "href", + // Path component for the EULA. + eula: "eula", + // Path component for the EULA on tvOS. + tvEula: "tv-eula", + // Path component for the Privacy Policy on tvOS. + privacyPolicy: "privacyPolicy", + // Path component for the Safety and Compliance Page on tvOS. + safetyCompliance: "safety-compliance", + // Path component for recommendations + appsForYou: "apps-for-you", + // Path for the storeFront + storeFront: "storeFront", + // Path for the tab + tab: "tab", + // Path component for the search landing page. + searchLandingPage: "searchLandingPage", + // Path component for search trending content page. + searchTrendingApps: "searchTrendingApps", + // Path for showing product privacy detail page + privacyDetail: "privacyDetail", + // Path for showing product privacy definitions page + privacyDefinitions: "privacyDefinitions", + // Path for the report a problem link + reportAProblem: "reportaproblem.apple.com/store", + // Path for showing a game center player profile + gameCenterProfile: "gameCenterProfile", + // Path for showing the ODP page + onDeviceRecommendations: "onDeviceRecommendations", + // Path for editorial pages + editorialPage: "editorialPage", + // Path to acessibility details + accessibilityDetails: "accessibilityDetails", + // ---------------------- + // Debug + // ---------------------- + test: "test", + shelfTypes: "shelfTypes", + groupingTest: "grouping", + builtIn: "builtIn", + shelfTextTest: "shelfText", + shelfLockupsTest: "shelfLockups", + shelfMiscTest: "shelfMisc", + lockupTest: "lockupTest", + articleTestArtwork: "articleArtworkTest", + articleTestSingleAppIcon: "articleSingleAppIconTest", + articleTestSingleAppIconHeroArt: "articleSingleAppIconHeroArtTest", + articleTestBrandedApp: "articleBrandedAppTest", + articleTestMultiAppTwo: "articleMultiAppTwoTest", + articleTestMultiAppThree: "articleMultiAppThreeTest", + articleTestGrid: "articleGridTest", + articleTestInAppPurchase: "articleInAppPurchaseTest", +}; +export const Host = { + // Legacy Mac Software Update URL + showUpdatesPage: "showUpdatesPage", + // Legacy Deep Link Actions + showAccountPage: "showAccountPage", + showPurchasesPage: "showPurchasesPage", + showSubscriptionsPage: "showSubscriptionsPage", + // Legacy MAS Safari Extension Search + searchExtensions: "searchExtensions", + // Legacy search.itunes.apple.com redirected urls + searchItunes: "search.itunes.apple.com", + // Legacy itunes.apple.com redirected urls + iTunes: "itunes.apple.com", + // Urls allowing programming to preview programmed groupings + storePreview: "storepreview.apple.com", + // Urls allowing programming to preview programmed stories + appsPreview: "preview.apps.apple.com", + // Legacy product url for tvOS App Store + product: "product", + // Host for arcade subscribe page + arcadeSubscribePage: "arcadeSubscribePage", + // Host for an upsell that must necessarily go through our own Arcade subscribe page, + // rather than through an AMS marketing item page. + arcadeSubscribePageCustomContext: "arcadeSubscribePageCustomContext", + // Host for arcade welcome page + arcadeWelcomePage: "arcadeWelcomePage", + // Host for Arcade See All page for standalone case. + arcadeSeeAllPage: "arcadeSeeAllGames", + // Host for the deep link from an Arcade app clip. + appClipSubscribe: "appClipSubscribe", + familyCircle: "familyCircle", + // Host for showing spam blocking extensions + spamBlockingExtensions: "spamBlockingExtensions", + // Host for showing Safari extensions + safariExtensions: "safariExtensions", + // Host for app launch trampoline + launchApp: "launchApp", + // Host for SharePlay more deeplink + sharePlayApps: "sharePlayApps", + // Host for opening a deeplink at the end of Buddy + buddyOnboarding: "buddyOnboarding", + // Host for Arcade download (starter) pack page + arcadeDownloadPackPage: "arcadeDownloadPack", + // Host for showing settings page + showSettingsPage: "showSettingsPage", + // Host for showing settings page + showHiddenPurchasesPage: "showHiddenPurchases", +}; +/// The different preview hosts that require the media api preview endpoint +export const previewHosts = new Set([Host.storePreview, Host.appsPreview]); +export const Parameters = { + // Generic ID + id: "id", + // Generic IDs + ids: "ids", + // Product variant ID for given `id` + productVariantID: "ppid", + // Country Code + countryCode: "cc", + // Language override + language: "l", + // Featured Content ID + featuredContentId: "fcId", + fetchData: "fetchData", + isTodaySection: "isTodaySection", + isTodayFeedPreview: "isTodayFeedPreview", + // Genre + genre: "genre", + // Bundle Identifier + bundleIdentifier: "bundleIdentifier", + // BundleID + bundleId: "bundleId", + // Spotlight In-App Purchase Identifier + offerName: "offerName", + // Top Charts + charts: "charts", + ages: "ages", + chart: "chart", + types: "types", + // v0 urls (used e.g. when redirected from MZContentLink URLs). + v0: "v0", + // Action urls + action: "action", + // Type parameter + type: "type", + context: "context", + isArcade: "isArcade", + isSubscribed: "isSubscribed", + isTrialAvailable: "isTrialAvailable", + isTrialEnrolled: "isTrialEnrolled", + groupingFeaturedContentId: "groupingFeaturedContentId", + editorialPageShelfType: "editorialPageShelfType", + nativeGroupingShelfId: "nativeGroupingShelfId", + isArcadeSeeAllGamesShelf: "isArcadeSeeAllGamesShelf", + isGameCenterActivityFeedShelf: "isGameCenterActivityFeedShelf", + isGameCenterPlayerShelf: "isGameCenterPlayerShelf", + isGameCenterPlayerRibbonItem: "isGameCenterPlayerRibbonItem", + isGameCenterAchievementsShelf: "isGameCenterAchievementsShelf", + isGameCenterContinuePlayingShelf: "isGameCenterContinuePlayingShelf", + isGameCenterPopularWithYourFriendsShelf: "isGameCenterPopularWithYourFriendsShelf", + isGameCenterSuggestedFriendsShelf: "isGameCenterSuggestedFriendsShelf", + isGameCenterReengagementShelf: "isGameCenterReengagementShelf", + isOnDeviceRecommendationsShelf: "isOnDeviceRecommendationsShelf", + isOnDeviceSearchHistoryShelf: "isOnDeviceSearchHistoryShelf", + isSearchFocusHeaderShelf: "isSearchFocusHeaderShelf", + isArcadeDownloadPackShelfPlaceholder: "isArcadeDownloadPackShelfPlaceholder", + onDeviceRecommendationsUseCase: "onDeviceRecommendationsUseCase", + onDevicePersonalizationUseCase: "onDevicePersonalizationUseCase", + // Denotes that this url is coming from the purchases page. + isPurchasesApp: "isPurchasesApp", + // Used for single sign on, the parameter is passed from store kit + isViewOnly: "isViewOnly", + // Determines whether unlisted apps should be returned in the response. + // Note that for direct lookups, MAPI allows the App Store to view unlisted + // apps without this parameter, although they would like to eventually gate + // the behavior behind this parameter. + includeUnlistedApps: "includeUnlistedApps", + enabled: "enabled", + href: "href", + recoMetrics: "recoMetrics", + // Used on article urls when the TodayCard is showing fallback media + showingFallbackMedia: "showingFallbackMedia", + path: "path", + useReleaseId: "useReleaseId", + // The client identifier that should be used to display the item, regardless of host device. + clientIdentifierOverride: "clientIdentifierOverride", + // The message attached to the subscribe page, for ATB. + subscribePageMessage: "message", + // The ID for a specific editorial item, in the case of a subscribe page. + editorialItem: "editorialItem", + // The identifer for the ATB request. + askToBuyId: "askToBuyId", + // Indicates an ID, when the 'app' disambiguation is necessary. + appId: "appId", + // Whether the URL has been launched from a PPT (Purple Performance Test). + isPPT: "isPPT", + // How the shelf is sorted, represented by a value of `ArcadeSeeAllGamesPageSort` + sort: "sort", + // Featured Content ID + grouping: "grouping", + // Code Parameter, e.g. for Redeem code + code: "code", + // Parameter to include attribution on offer action for post subcribge + includePostSubscribeAttribution: "includePostSubscribeAttribution", + // Campaign Token + campaignToken: "ct", + // Provider Token + providerToken: "pt", + // Q Token + qToken: "its_qt", + // Advertisement ID + advertisementId: "adId", + // The token used to pass arbitrary data in a url + token: "token", + // The current parse context for the a page + parseContext: "parseContext", + // An ID for a privacy type + privacyTypeId: "privacyTypeId", + // The token used to pass arbitrary data in a url + requestDescriptor: "requestDescriptor", + // Page Facet + ageRating: "ageRating", + // Page Facet + controllerSupport: "controllerSupport", + // Page Facet + multiplayerSupport: "multiplayerSupport", + // Page Facet + comingSoon: "comingSoon", + // Page Facet + binCompatGames: "binCompatGames", + // Page Facet + gamePreviews: "gamePreviews", + // Upsell (marketing item) marketing hint + offerHints: "offerHints", + // Indicates that a 204 network error should invalidate the App Store's widgets. + invalidateWidgetsOnFailure: "invalidateWidgetsOnFailure", + // Metrics + metrics: "metrics", + // App event ID + appEventId: "eventid", + // Offer Item ID + offerItemId: "offerItemId", + // App event deep link + appEventDeepLink: "deepLink", + // The use case param for collection deeplinking + useCaseShort: "uc", + // The collection id param for collection deeplinking + collectionId: "collection-id", + // The seed id param for collection deeplinking + seedId: "seed-id", + // Whether the shelf is the product page similar items shelf. + isShelfWithAd: "isShelfWithAd", + // The ad placement type for the shelf. Only used if `isShelfWithAd` is true. + shelfWithAdPlacementType: "shelfWithAdPlacementType", + // Provides a refresh type to route a refresh request for a shelf. + // See `refreshUrl` on `Shelf`. + shelfRefreshType: "refreshType", + // Param to denote that this url originated for preloading purposes. + isPreloading: "isPreloading", + // The name parameter for editorial pages with known names + name: "name", + // The editorial page shelf type + shelfType: "shelfType", + // The id of the shelf in a url + shelfId: "shelfId", + // Parameter for the onboarding cards for the today tab + onbaordingCardIds: "onboardingCardIds", + // Parameter for the today card previews to provide a future date and time + preview: "preview", + // The card config for used for the card on a today page + todayCardConfig: "todayCardConfig", + // The parameter that is expanded into the header used for today previews. + experimentId: "experimentId", + shortEditorialNotes: "shortEditorialNotes", + // User subscription status defined by Mercury and passed to Arcade download pack onboarding. + // It is used to select the right copy variant. + arcadeSubscriptionStatus: "subscriptionStatus", + // Used to indicate a web browser is being displayed in the web browser context via App Store Components. + webBrowser: "webBrowser", + // The id of the editorial-page that was used to create a url + editorialPageId: "editorialPageId", + // A filter ID to add to a editorial item request in order to fetch a certain version of the story. + editorialCardId: "filter[canvas:cardId]", + // A filter to add to the hydration call for the Today tab recommended candidates to fetch only recommendable apps. + filterRecommendable: "filter[recommendable]", + // A device family, used for accessibility labels + deviceFamily: "deviceFamily", +}; +// Install Page-specific parameters +export const InAppPurchaseInstallPageParameters = { + // The adamId for the IAP. + inAppPurchaseId: "inAppPurchaseId", +}; +// Product Page-specific parameters +export const ProductPageParameters = { + // Product URL + url: "productUrl", + // Whether an iAP is a subscription. + isSubscription: "isSubscription", + // The minimum version of the app to display + // Typically used to prevent older cached versions being displayed + // when deep-linking from an app clip. + minExternalVersionId: "minExternalVersionId", +}; +// Review Page-specific parameters +export const ReviewsPageParameters = { + // Product adamId + adamId: "adamId", + // Sort index for the reviews request + sort: "sort", +}; +export const MSOPageParameters = { + // Ids of available apps. + availableAdamIds: "availableAdamIds", +}; +export const ShelfParameters = { + // Shelf Title + title: "shelfTitle", + // Whether the shelf should auto-configure a see-all on fetch + shouldInferSeeAllFromFetchedItems: "shelfShouldInferSeeAllFromFetchedItems", + // Shelf content type + contentType: "shelfContentType", + // Shelf offer them + offerTheme: "shelfOfferTheme", + // metrics page information + metricsPageInformation: "metricsPage", +}; +export const ShareURLParameters = { + // Which client should be the link be opened with + clientSpecifier: "app", +}; +export const Hashes = { + // Reviews action anchor (#reviews) + reviews: "reviews", +}; +export const ShelfRefreshType = { + productPageSimilarItems: "productPageSimilarItems", +}; +/* tslint:enable:variable-name */ +//# sourceMappingURL=url-constants.js.map
\ No newline at end of file diff --git a/node_modules/@jet-app/app-store/tmp/src/foundation/network/urls.js b/node_modules/@jet-app/app-store/tmp/src/foundation/network/urls.js new file mode 100644 index 0000000..6abbd37 --- /dev/null +++ b/node_modules/@jet-app/app-store/tmp/src/foundation/network/urls.js @@ -0,0 +1,382 @@ +/** + * Created by keithpk on 12/2/16. + */ +import { isNothing } from "@jet/environment/types/optional"; +const protocolRegex = /^([a-z][a-z0-9.+-]*:)(\/\/)?([\S\s]*)/i; +const queryParamRegex = /([^=?&]+)=?([^&]*)/g; +const componentOrder = ["hash", "query", "pathname", "host"]; +function splitUrlComponent(input, marker, style) { + const index = input.indexOf(marker); + let result; + let remainder = input; + if (index !== -1) { + const prefix = input.slice(0, index); + const suffix = input.slice(index + marker.length, input.length); + if (style === "prefix") { + result = prefix; + remainder = suffix; + } + else { + result = suffix; + remainder = prefix; + } + } + // log("Token: " + marker + " String: " + input, " Result: " + result + " Remainder: " + remainder) + return { + result: result, + remainder: remainder, + }; +} +export class URL { + constructor(url) { + this.query = {}; + if (!url) { + return; + } + // Split the protocol from the rest of the urls + let remainder = url; + const match = protocolRegex.exec(url); + if (match) { + // Pull out the protocol + let protocol = match[1]; + if (protocol) { + protocol = protocol.split(":")[0]; + } + this.protocol = protocol; + // Save the remainder + remainder = match[3]; + } + // Then match each component in a specific order + let parse = { remainder: remainder, result: undefined }; + for (const component of componentOrder) { + if (!parse.remainder) { + break; + } + switch (component) { + case "hash": { + parse = splitUrlComponent(parse.remainder, "#", "suffix"); + this.hash = parse.result; + break; + } + case "query": { + parse = splitUrlComponent(parse.remainder, "?", "suffix"); + if (parse.result) { + this.query = URL.queryFromString(parse.result); + } + break; + } + case "pathname": { + parse = splitUrlComponent(parse.remainder, "/", "suffix"); + if (parse.result) { + // Replace the initial /, since paths require it + this.pathname = "/" + parse.result; + } + break; + } + case "host": { + if (parse.remainder) { + const authorityParse = splitUrlComponent(parse.remainder, "@", "prefix"); + const userInfo = authorityParse.result; + const hostPort = authorityParse.remainder; + if (userInfo) { + const userInfoSplit = userInfo.split(":"); + this.username = decodeURIComponent(userInfoSplit[0]); + this.password = decodeURIComponent(userInfoSplit[1]); + } + if (hostPort) { + const hostPortSplit = hostPort.split(":"); + this.host = hostPortSplit[0]; + this.port = hostPortSplit[1]; + } + } + break; + } + default: { + throw new Error("Unhandled case!"); + } + } + } + } + set(component, value) { + if (!value) { + return this; + } + if (component === "query") { + if (typeof value === "string") { + value = URL.queryFromString(value); + } + } + switch (component) { + // Exhaustive match to make sure TS property minifiers and other + // transformer plugins do not break this code. + case "protocol": + this.protocol = value; + break; + case "username": + this.username = value; + break; + case "password": + this.password = value; + break; + case "port": + this.port = value; + break; + case "pathname": + this.pathname = value; + break; + case "query": + this.query = value; + break; + case "hash": + this.hash = value; + break; + default: + // The fallback for component which is not a property of URL object. + this[component] = value; + break; + } + return this; + } + get(component) { + switch (component) { + // Exhaustive match to make sure TS property minifiers and other + // transformer plugins do not break this code. + case "protocol": + return this.protocol; + case "username": + return this.username; + case "password": + return this.password; + case "port": + return this.port; + case "pathname": + return this.pathname; + case "query": + return this.query; + case "hash": + return this.hash; + default: + // The fallback for component which is not a property of URL object. + return this[component]; + } + } + append(component, value) { + const existingValue = this.get(component); + let newValue; + if (component === "query") { + if (typeof value === "string") { + value = URL.queryFromString(value); + } + if (typeof existingValue === "string") { + newValue = { existingValue, ...value }; + } + else { + newValue = { ...existingValue, ...value }; + } + } + else { + let existingValueString = existingValue; + if (!existingValueString) { + existingValueString = ""; + } + let newValueString = existingValueString; + if (component === "pathname") { + const pathLength = existingValueString.length; + if (!pathLength || existingValueString[pathLength - 1] !== "/") { + newValueString += "/"; + } + } + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-base-to-string + newValueString += value; + newValue = newValueString; + } + return this.set(component, newValue); + } + param(key, value) { + if (!key) { + return this; + } + if (!this.query) { + this.query = {}; + } + this.query[key] = value; + return this; + } + removeParam(key) { + if (!key || !this.query) { + return this; + } + if (this.query[key] !== undefined) { + delete this.query[key]; + } + return this; + } + /** + * Push a new string value onto the path for this url + * @returns URL this object with the updated path. + */ + path(value) { + return this.append("pathname", value); + } + pathExtension() { + var _a; + // Extract path extension if one exists + if (isNothing(this.pathname)) { + return null; + } + const lastFilenameComponents = (_a = this.pathname + .split("/") + .filter((item) => item.length > 0) // Remove any double or trailing slashes + .pop()) === null || _a === void 0 ? void 0 : _a.split("."); + if (lastFilenameComponents === undefined) { + return null; + } + if (lastFilenameComponents.filter((part) => { + return part !== ""; + }).length < 2 // Remove any empty parts (e.g. .ssh_config -> ["ssh_config"]) + ) { + return null; + } + return lastFilenameComponents.pop(); + } + /** + * Returns the path components of the URL + * @returns An array of non-empty path components from `urls`. + */ + pathComponents() { + if (!this.pathname) { + return []; + } + return this.pathname.split("/").filter((component) => component.length > 0); + } + /** + * Returns the last path component from this url, updating the url to not include this path component + * @returns String the last path component from this url. + */ + popPathComponent() { + if (!this.pathname) { + return null; + } + const lastPathComponent = this.pathname.slice(this.pathname.lastIndexOf("/") + 1); + if (lastPathComponent.length === 0) { + return null; + } + this.pathname = this.pathname.slice(0, this.pathname.lastIndexOf("/")); + return lastPathComponent; + } + /** + * Same as toString + * + * @returns {string} A string representation of the URL + */ + build() { + return this.toString(); + } + /** + * Converts the URL to a string + * + * @returns {string} A string representation of the URL + */ + toString() { + let url = ""; + if (this.protocol) { + url += this.protocol + "://"; + } + if (this.username) { + url += encodeURIComponent(this.username); + if (this.password) { + url += ":" + encodeURIComponent(this.password); + } + url += "@"; + } + if (this.host) { + url += this.host; + if (this.port) { + url += ":" + this.port; + } + } + if (this.pathname) { + url += this.pathname; + /// Trim off trailing path separators when we have a valid path + /// We don't do this unless pathname has elements otherwise we will trim the `://` + if (url.endsWith("/") && this.pathname.length > 0) { + url = url.slice(0, -1); + } + } + if (this.query && Object.keys(this.query).length) { + url += "?" + URL.toQueryString(this.query); + } + if (this.hash) { + url += "#" + this.hash; + } + return url; + } + // ---------------- + // Static API + // ---------------- + /** + * Converts a string into a query dictionary + * @param query The string to parse + * @returns The query dictionary containing the key-value pairs in the query string + */ + static queryFromString(query) { + const result = {}; + let parseResult = queryParamRegex.exec(query); + while (parseResult) { + const key = decodeURIComponent(parseResult[1]); + const value = decodeURIComponent(parseResult[2]); + result[key] = value; + parseResult = queryParamRegex.exec(query); + } + return result; + } + /** + * Converts a query dictionary into a query string + * + * @param query The query dictionary + * @returns {string} The string representation of the query dictionary + */ + static toQueryString(query) { + let queryString = ""; + let first = true; + for (const key of Object.keys(query)) { + if (!first) { + queryString += "&"; + } + first = false; + queryString += encodeURIComponent(key); + const value = query[key]; + if (value && value.length) { + queryString += "=" + encodeURIComponent(value); + } + } + return queryString; + } + /** + * Convenience method to instantiate a URL from a string + * @param url The URL string to parse + * @returns {URL} The new URL object representing the URL + */ + static from(url) { + return new URL(url); + } + /** + * Convenience method to instantiate a URL from numerous (optional) components + * @param protocol The protocol type + * @param host The host name + * @param path The path + * @param query The query + * @param hash The hash + * @returns {URL} The new URL object representing the URL + */ + static fromComponents(protocol, host, path, query, hash) { + const url = new URL(); + url.protocol = protocol; + url.host = host; + url.pathname = path; + url.query = query; + url.hash = hash; + return url; + } +} +//# sourceMappingURL=urls.js.map
\ No newline at end of file |
