summaryrefslogtreecommitdiff
path: root/node_modules/@apple-media-services
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/@apple-media-services
init commit
Diffstat (limited to 'node_modules/@apple-media-services')
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/index.js23
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/json/validation.js250
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/alert-action.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/compound-action.js22
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/empty-action.js21
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/external-url-action.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/flow-action.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/flow-back-action.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/http-action.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/http-template-action.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/index.js26
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/toast-action.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/artwork.js39
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/button.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/color.js196
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/index.js25
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/menu.js8
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/paragraph.js4
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/programmed-text.js5
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/video.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/bag.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/bundle.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/cookie-provider.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/cryptography.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/host.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/index.js55
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/jscookie.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/net.js19
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/platform.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/plist.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/preprocessor.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/random.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/service.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/types.js16
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/javascriptcore/console.js14
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/javascriptcore/index.js18
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/metrics.js57
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/models.js3
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/optional.js71
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/util/metatype.js10
-rw-r--r--node_modules/@apple-media-services/media-api/node_modules/@jet/environment/util/urls.js373
-rw-r--r--node_modules/@apple-media-services/media-api/src/models/attributes.ts289
-rw-r--r--node_modules/@apple-media-services/media-api/src/models/server-data.ts476
-rw-r--r--node_modules/@apple-media-services/media-api/src/models/urls.ts469
-rw-r--r--node_modules/@apple-media-services/media-api/src/network.ts403
45 files changed, 2952 insertions, 0 deletions
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/index.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/index.js
new file mode 100644
index 0000000..335aa3a
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/index.js
@@ -0,0 +1,23 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./models"), exports);
+__exportStar(require("./types/globals"), exports);
+__exportStar(require("./types/javascriptcore"), exports);
+__exportStar(require("./types/metrics"), exports);
+__exportStar(require("./types/models"), exports);
+__exportStar(require("./types/optional"), exports);
+//# sourceMappingURL=index.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/json/validation.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/json/validation.js
new file mode 100644
index 0000000..b338c4e
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/json/validation.js
@@ -0,0 +1,250 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.unexpectedNull = exports.catchingContext = exports.context = exports.recordValidationIncidents = exports.endContext = exports.getContextNames = exports.beginContext = exports.messageForRecoveryAction = exports.isValidatable = exports.unexpectedType = exports.extendedTypeof = void 0;
+const optional_1 = require("../types/optional");
+/**
+ * Returns a string containing the type of a given value.
+ * This function augments the built in `typeof` operator
+ * to return sensible values for arrays and null values.
+ *
+ * @privateRemarks
+ * This function is exported for testing.
+ *
+ * @param value - The value to find the type of.
+ * @returns A string containing the type of `value`.
+ */
+function extendedTypeof(value) {
+ if (Array.isArray(value)) {
+ return "array";
+ }
+ else if (value === null) {
+ return "null";
+ }
+ else {
+ return typeof value;
+ }
+}
+exports.extendedTypeof = extendedTypeof;
+/**
+ * Reports a non-fatal validation failure, logging a message to the console.
+ * @param recovery - The recovery action taken when the bad type was found.
+ * @param expected - The expected type of the value.
+ * @param actual - The actual value.
+ * @param pathString - A string containing the path to the value on the object which failed type validation.
+ */
+function unexpectedType(recovery, expected, actual, pathString) {
+ const actualType = extendedTypeof(actual);
+ const prettyPath = (0, optional_1.isSome)(pathString) && pathString.length > 0 ? pathString : "<this>";
+ trackIncident({
+ type: "badType",
+ expected: expected,
+ // Our test assertions are matching the string interpolation of ${actual} value.
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ actual: `${actualType} (${actual})`,
+ objectPath: prettyPath,
+ contextNames: getContextNames(),
+ recoveryAction: recovery,
+ stack: new Error().stack,
+ });
+}
+exports.unexpectedType = unexpectedType;
+// endregion
+/**
+ * Determines if a given object conforms to the Validatable interface
+ * @param possibleValidatable - An object that might be considered validatable
+ *
+ * @returns `true` if it is an instance of Validatable, `false` if not
+ */
+function isValidatable(possibleValidatable) {
+ if ((0, optional_1.isNothing)(possibleValidatable)) {
+ return false;
+ }
+ // MAINTAINER'S NOTE: We must check for either the existence of a pre-existing incidents
+ // property *or* the ability to add one. Failure to do so will cause
+ // problems for clients that either a) use interfaces to define their
+ // view models; or b) return collections from their service routes.
+ return (Object.prototype.hasOwnProperty.call(possibleValidatable, "$incidents") ||
+ Object.isExtensible(possibleValidatable));
+}
+exports.isValidatable = isValidatable;
+/**
+ * Returns a developer-readable diagnostic message for a given recovery action.
+ * @param action - The recovery action to get the message for.
+ * @returns The message for `action`.
+ */
+function messageForRecoveryAction(action) {
+ switch (action) {
+ case "coercedValue":
+ return "Coerced format";
+ case "defaultValue":
+ return "Default value used";
+ case "ignoredValue":
+ return "Ignored value";
+ default:
+ return "Unknown";
+ }
+}
+exports.messageForRecoveryAction = messageForRecoveryAction;
+// region Contexts
+/**
+ * Shared validation context "stack".
+ *
+ * Because validation incidents propagate up the context stack,
+ * the representation used here is optimized for memory usage.
+ * A more literal representation of this would be a singly linked
+ * list describing a basic stack, but that will produce a large
+ * amount of unnecessary garbage and require copying `incidents`
+ * arrays backwards.
+ */
+const contextState = {
+ /// The names of each validation context on the stack.
+ nameStack: Array(),
+ /// All incidents reported so far. Cleared when the
+ /// context stack is emptied.
+ incidents: Array(),
+ // TODO: Removal of this is being tracked here:
+ // <rdar://problem/35015460> Intro Pricing: Un-suppress missing parent 'offers' error when server address missing key
+ /// The paths for incidents we wish to forgo tracking.
+ suppressedIncidentPaths: Array(),
+};
+/**
+ * Begin a new validation context with a given name,
+ * pushing it onto the validation context stack.
+ * @param name - The name for the validation context.
+ */
+function beginContext(name) {
+ contextState.nameStack.push(name);
+}
+exports.beginContext = beginContext;
+/**
+ * Traverses the validation context stack and collects all of the context names.
+ * @returns The names of all validation contexts on the stack, from oldest to newest.
+ */
+function getContextNames() {
+ if (contextState.nameStack.length === 0) {
+ return ["<empty stack>"];
+ }
+ return contextState.nameStack.slice(0);
+}
+exports.getContextNames = getContextNames;
+/**
+ * Ends the current validation context
+ */
+function endContext() {
+ if (contextState.nameStack.length === 0) {
+ console.warn("endContext() called without active validation context, ignoring");
+ }
+ contextState.nameStack.pop();
+}
+exports.endContext = endContext;
+/**
+ * Records validation incidents back into an object that implements Validatable.
+ *
+ * Note: This method has a side-effect that the incident queue and name stack are cleared
+ * to prepare for the next thread's invocation.
+ *
+ * @param possibleValidatable - An object that may conform to Validatable, onto which we
+ * want to stash our validation incidents
+ */
+function recordValidationIncidents(possibleValidatable) {
+ if (isValidatable(possibleValidatable)) {
+ possibleValidatable.$incidents = contextState.incidents;
+ }
+ contextState.incidents = [];
+ contextState.nameStack = [];
+ contextState.suppressedIncidentPaths = [];
+}
+exports.recordValidationIncidents = recordValidationIncidents;
+/**
+ * Create a transient validation context, and call a function that will return a value.
+ *
+ * Prefer this function over manually calling begin/endContext,
+ * it is exception safe.
+ *
+ * @param name - The name of the context
+ * @param producer - A function that produces a result
+ * @returns <Result> The resulting type
+ */
+function context(name, producer, suppressingPath) {
+ let suppressingName = null;
+ if ((0, optional_1.isSome)(suppressingPath) && suppressingPath.length > 0) {
+ suppressingName = name;
+ contextState.suppressedIncidentPaths.push(suppressingPath);
+ }
+ let result;
+ try {
+ beginContext(name);
+ result = producer();
+ }
+ catch (e) {
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ if (!e.hasThrown) {
+ unexpectedType("defaultValue", "no exception", e.message);
+ e.hasThrown = true;
+ }
+ throw e;
+ }
+ finally {
+ if (name === suppressingName) {
+ contextState.suppressedIncidentPaths.pop();
+ }
+ endContext();
+ }
+ return result;
+}
+exports.context = context;
+/**
+ * Create a transient validation context, that catches errors and returns null
+ *
+ * @param name - The name of the context
+ * @param producer - A function that produces a result
+ * @param caught - An optional handler to provide a value when an error is caught
+ * @returns <Result> The resulting type
+ */
+function catchingContext(name, producer, caught) {
+ let result = null;
+ try {
+ result = context(name, producer);
+ }
+ catch (e) {
+ result = null;
+ if ((0, optional_1.isSome)(caught)) {
+ result = caught(e);
+ }
+ }
+ return result;
+}
+exports.catchingContext = catchingContext;
+/**
+ * Track an incident within the current validation context.
+ * @param incident - An incident object describing the problem.
+ */
+function trackIncident(incident) {
+ if (contextState.suppressedIncidentPaths.includes(incident.objectPath)) {
+ return;
+ }
+ contextState.incidents.push(incident);
+}
+// endregion
+// region Nullability
+/**
+ * Reports a non-fatal error indicating a value was unexpectedly null.
+ * @param recovery - The recovery action taken when the null value was found.
+ * @param expected - The expected type of the value.
+ * @param pathString - A string containing the path to the value on the object which was null.
+ */
+function unexpectedNull(recovery, expected, pathString) {
+ const prettyPath = (0, optional_1.isSome)(pathString) && pathString.length > 0 ? pathString : "<this>";
+ trackIncident({
+ type: "nullValue",
+ expected: expected,
+ actual: "null",
+ objectPath: prettyPath,
+ contextNames: getContextNames(),
+ recoveryAction: recovery,
+ stack: new Error().stack,
+ });
+}
+exports.unexpectedNull = unexpectedNull;
+// endregion
+//# sourceMappingURL=validation.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/alert-action.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/alert-action.js
new file mode 100644
index 0000000..fcc6ea5
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/alert-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=alert-action.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/compound-action.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/compound-action.js
new file mode 100644
index 0000000..6c3e159
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/compound-action.js
@@ -0,0 +1,22 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.makeCompoundAction = exports.isCompoundAction = void 0;
+/**
+ * Custom type guard to determine if an action is a CompoundAction.
+ */
+function isCompoundAction(action) {
+ return (action === null || action === void 0 ? void 0 : action.$kind) === "compoundAction";
+}
+exports.isCompoundAction = isCompoundAction;
+/**
+ * Helper that returns a CompoundAction, given an ActionMetrics and ActionModel[] of subactions.
+ */
+function makeCompoundAction(actionMetrics, subactions) {
+ return {
+ $kind: "compoundAction",
+ subactions,
+ actionMetrics,
+ };
+}
+exports.makeCompoundAction = makeCompoundAction;
+//# sourceMappingURL=compound-action.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/empty-action.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/empty-action.js
new file mode 100644
index 0000000..da7f93a
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/empty-action.js
@@ -0,0 +1,21 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.makeEmptyAction = exports.isEmptyAction = void 0;
+/**
+ * Custom type guard to determine if an action is an EmptyAction.
+ */
+function isEmptyAction(action) {
+ return (action === null || action === void 0 ? void 0 : action.$kind) === "emptyAction";
+}
+exports.isEmptyAction = isEmptyAction;
+/**
+ * Helper that returns an EmptyAction, given an ActionMetrics.
+ */
+function makeEmptyAction(actionMetrics) {
+ return {
+ $kind: "emptyAction",
+ actionMetrics,
+ };
+}
+exports.makeEmptyAction = makeEmptyAction;
+//# sourceMappingURL=empty-action.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/external-url-action.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/external-url-action.js
new file mode 100644
index 0000000..479a640
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/external-url-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=external-url-action.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/flow-action.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/flow-action.js
new file mode 100644
index 0000000..6f70d98
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/flow-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=flow-action.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/flow-back-action.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/flow-back-action.js
new file mode 100644
index 0000000..75c66a2
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/flow-back-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=flow-back-action.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/http-action.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/http-action.js
new file mode 100644
index 0000000..e1fb6c3
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/http-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=http-action.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/http-template-action.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/http-template-action.js
new file mode 100644
index 0000000..6cb84d4
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/http-template-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=http-template-action.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/index.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/index.js
new file mode 100644
index 0000000..ff961dd
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/index.js
@@ -0,0 +1,26 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./alert-action"), exports);
+__exportStar(require("./compound-action"), exports);
+__exportStar(require("./empty-action"), exports);
+__exportStar(require("./external-url-action"), exports);
+__exportStar(require("./flow-action"), exports);
+__exportStar(require("./flow-back-action"), exports);
+__exportStar(require("./http-action"), exports);
+__exportStar(require("./http-template-action"), exports);
+__exportStar(require("./toast-action"), exports);
+//# sourceMappingURL=index.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/toast-action.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/toast-action.js
new file mode 100644
index 0000000..5d6a299
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/actions/toast-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=toast-action.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/artwork.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/artwork.js
new file mode 100644
index 0000000..324138d
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/artwork.js
@@ -0,0 +1,39 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.makeArtworkURLTemplate = void 0;
+const validation = require("../json/validation");
+const optional_1 = require("../types/optional");
+const urls_1 = require("../util/urls");
+/**
+ * Regex to parse artwork URL template string.
+ */
+const URL_TEMPLATE_PARSER = new RegExp("^({w}|[0-9]+(?:.[0-9]*)?)x({h}|[0-9]+(?:.[0-9]*)?)({c}|[a-z]{2}).({f}|[a-z]+)$");
+/**
+ * Create an instance of artwork URL template from string.
+ * @param fromString - String to create artwork URL template from.
+ * @returns A new artwork URL template or `null` if string
+ * does not represent a valid artwork URL template.
+ */
+function makeArtworkURLTemplate(fromString) {
+ // A valid URL that ends with '{w}x{h}{c}.{f}'
+ // with any of placeholders possibly resolved to an actual value.
+ const url = new urls_1.URL(fromString);
+ if (url.pathname === undefined) {
+ validation.context("makeArtworkURLTemplate", () => {
+ validation.unexpectedType("ignoredValue", "A valid URL string", fromString);
+ });
+ return null;
+ }
+ // Expecting 5 matches: whole string + width, height, crop code and format.
+ const lastPathComponent = fromString.substring(fromString.lastIndexOf("/") + 1);
+ const matches = URL_TEMPLATE_PARSER.exec(lastPathComponent);
+ if ((0, optional_1.isNothing)(matches) || matches.length !== 5) {
+ validation.context("makeArtworkURLTemplate", () => {
+ validation.unexpectedType("ignoredValue", "A valid artwork URL template ending with {w}x{h}{c}.{f} format", lastPathComponent);
+ });
+ return null;
+ }
+ return fromString;
+}
+exports.makeArtworkURLTemplate = makeArtworkURLTemplate;
+//# sourceMappingURL=artwork.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/button.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/button.js
new file mode 100644
index 0000000..036c19a
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/button.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=button.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/color.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/color.js
new file mode 100644
index 0000000..82ffd64
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/color.js
@@ -0,0 +1,196 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.areEqual = exports.luminanceFrom = exports.dynamicWith = exports.named = exports.rgbWith = exports.htmlWith = exports.Color = void 0;
+const optional_1 = require("../types/optional");
+// endregion
+// region Constructors
+// eslint-disable-next-line no-redeclare, @typescript-eslint/no-redeclare
+exports.Color = {
+ /**
+ * Create new `HTMLColor` from hexadecimal string representation.
+ *
+ * @param hexString - Hexadecimal string representation.
+ */
+ fromHex(string) {
+ if ((0, optional_1.isNothing)(string)) {
+ return null;
+ }
+ return {
+ $kind: "html",
+ value: string,
+ };
+ },
+ /**
+ * Create new `RBGColor` with RGB components and opacity value.
+ *
+ * @param red - Red color value.
+ * @param green - Green color value.
+ * @param blue - Blue color value.
+ * @param alpha - Opacity value.
+ */
+ fromRGB(red, green, blue, alpha = 1.0) {
+ const newColor = {
+ $kind: "rgb",
+ red: red,
+ green: green,
+ blue: blue,
+ alpha: alpha,
+ };
+ return newColor;
+ },
+ /**
+ * Create new named color using the color name.
+ *
+ * @param name - The name of the color.
+ */
+ named(name) {
+ const newColor = {
+ $kind: "named",
+ name: name,
+ };
+ return newColor;
+ },
+ /**
+ * Create new dynamic color with light and dark color variants.
+ *
+ * @param lightColor - The light color variant.
+ * @param lightHighContrastColor - The light hight-contrast color variant.
+ * @param darkColor - The dark color variant.
+ * @param darkHighContrastColor - The dark hight-contrast color variant.
+ */
+ dynamicWith(lightColor, lightHighContrastColor, darkColor, darkHighContrastColor) {
+ const newColor = {
+ $kind: "dynamic",
+ lightColor: lightColor,
+ lightHighContrastColor: lightHighContrastColor,
+ darkColor: darkColor,
+ darkHighContrastColor: darkHighContrastColor,
+ };
+ return newColor;
+ },
+ // endregion
+ // region Properties
+ /**
+ * Get the luminance of the color.
+ *
+ * @param rgbColor - The RGB color to get luminance for.
+ */
+ luminanceFrom(rgbColor) {
+ // Note: This is lifted from UIColor_Private
+ // Using RGB color components, calculates and returns (0.2126 * r) + (0.7152 * g) + (0.0722 * b).
+ return rgbColor.red * 0.2126 + rgbColor.green * 0.7152 + rgbColor.blue * 0.0722;
+ },
+ // endregion
+ // region Identity
+ /**
+ * Compare two colors for equality.
+ *
+ * @param color1 - Left hand side color to compare.
+ * @param color2 - Right hand side color to compare.
+ * @returns A Boolean indicating whether the colors are equal.
+ */
+ areEqual(color1, color2) {
+ if ((0, optional_1.isNothing)(color1)) {
+ return (0, optional_1.isNothing)(color2);
+ }
+ else if ((0, optional_1.isNothing)(color2)) {
+ return (0, optional_1.isNothing)(color1);
+ }
+ const kind1 = color1.$kind;
+ const kind2 = color2.$kind;
+ if (kind1 === "named" && kind2 === "named") {
+ const namedColor1 = color1;
+ const namedColor2 = color2;
+ return namedColor1.name === namedColor2.name;
+ }
+ else if (kind1 === "rgb" && kind2 === "rgb") {
+ const rgbColor1 = color1;
+ const rgbColor2 = color2;
+ return (rgbColor1.red === rgbColor2.red &&
+ rgbColor1.green === rgbColor2.green &&
+ rgbColor1.blue === rgbColor2.blue &&
+ rgbColor1.alpha === rgbColor2.alpha);
+ }
+ else if (kind1 === "dynamic" && kind2 === "dynamic") {
+ const dynamicColor1 = color1;
+ const dynamicColor2 = color2;
+ return (exports.Color.areEqual(dynamicColor1.lightColor, dynamicColor2.lightColor) &&
+ exports.Color.areEqual(dynamicColor1.lightHighContrastColor, dynamicColor2.lightHighContrastColor) &&
+ exports.Color.areEqual(dynamicColor1.darkColor, dynamicColor2.darkColor) &&
+ exports.Color.areEqual(dynamicColor1.darkHighContrastColor, dynamicColor2.darkHighContrastColor));
+ }
+ else {
+ return false;
+ }
+ },
+};
+/**
+ * Create new `HTMLColor` from hexadecimal string representation.
+ *
+ * @param hexString - Hexadecimal string representation.
+ *
+ * @deprecated This symbol has been moved to `Color.fromHex` and will be removed
+ * in the future.
+ */
+const htmlWith = exports.Color.fromHex;
+exports.htmlWith = htmlWith;
+/**
+ * Create new `RBGColor` with RGB components and opacity value.
+ *
+ * @param red - Red color value.
+ * @param green - Green color value.
+ * @param blue - Blue color value.
+ * @param alpha - Opacity value.
+ *
+ * @deprecated This symbol has been moved to `Color.fromRGB` and will be removed
+ * in the future.
+ */
+const rgbWith = exports.Color.fromRGB;
+exports.rgbWith = rgbWith;
+/**
+ * Create new named color using the color name.
+ *
+ * @param name - The name of the color.
+ *
+ * @deprecated This symbol has been moved to `Color.named` and will be removed
+ * in the future.
+ */
+const named = exports.Color.named;
+exports.named = named;
+/**
+ * Create new dynamic color with light and dark color variants.
+ *
+ * @param lightColor - The light color variant.
+ * @param lightHighContrastColor - The light hight-contrast color variant.
+ * @param darkColor - The dark color variant.
+ * @param darkHighContrastColor - The dark hight-contrast color variant.
+ *
+ * @deprecated This symbol has been moved to `Color.dynamicWith` and will be removed
+ * in the future.
+ */
+const dynamicWith = exports.Color.dynamicWith;
+exports.dynamicWith = dynamicWith;
+/**
+ * Get the luminance of the color.
+ *
+ * @param rgbColor - The RGB color to get luminance for.
+ *
+ * @deprecated This symbol has been moved to `Color.luminanceFrom` and will be removed
+ * in the future.
+ */
+const luminanceFrom = exports.Color.luminanceFrom;
+exports.luminanceFrom = luminanceFrom;
+/**
+ * Compare two colors for equality.
+ *
+ * @param color1 - Left hand side color to compare.
+ * @param color2 - Right hand side color to compare.
+ * @returns A Boolean indicating whether the colors are equal.
+ *
+ * @deprecated This symbol has been moved to `Color.areEqual` and will be removed
+ * in the future.
+ */
+const areEqual = exports.Color.areEqual;
+exports.areEqual = areEqual;
+// endregion
+//# sourceMappingURL=color.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/index.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/index.js
new file mode 100644
index 0000000..a041c4c
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/index.js
@@ -0,0 +1,25 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./actions"), exports);
+__exportStar(require("./artwork"), exports);
+__exportStar(require("./button"), exports);
+__exportStar(require("./color"), exports);
+__exportStar(require("./menu"), exports);
+__exportStar(require("./paragraph"), exports);
+__exportStar(require("./programmed-text"), exports);
+__exportStar(require("./video"), exports);
+//# sourceMappingURL=index.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/menu.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/menu.js
new file mode 100644
index 0000000..200dc6b
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/menu.js
@@ -0,0 +1,8 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.menuSeparatorID = void 0;
+/**
+ * A standard identifier for including a separator in a menu.
+ */
+exports.menuSeparatorID = "com.apple.JetEngine.separator";
+//# sourceMappingURL=menu.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/paragraph.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/paragraph.js
new file mode 100644
index 0000000..3518ea7
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/paragraph.js
@@ -0,0 +1,4 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+// endregion
+//# sourceMappingURL=paragraph.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/programmed-text.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/programmed-text.js
new file mode 100644
index 0000000..18a8337
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/programmed-text.js
@@ -0,0 +1,5 @@
+"use strict";
+// region ProgrammedText
+Object.defineProperty(exports, "__esModule", { value: true });
+// endregion
+//# sourceMappingURL=programmed-text.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/video.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/video.js
new file mode 100644
index 0000000..0f0031f
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/models/video.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=video.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/bag.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/bag.js
new file mode 100644
index 0000000..ffe6106
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/bag.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=bag.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/bundle.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/bundle.js
new file mode 100644
index 0000000..9a818e7
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/bundle.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=bundle.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/cookie-provider.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/cookie-provider.js
new file mode 100644
index 0000000..e681941
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/cookie-provider.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=cookie-provider.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/cryptography.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/cryptography.js
new file mode 100644
index 0000000..de648d8
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/cryptography.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=cryptography.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/host.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/host.js
new file mode 100644
index 0000000..9dbd12d
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/host.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=host.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/index.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/index.js
new file mode 100644
index 0000000..bc0f016
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/index.js
@@ -0,0 +1,55 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+/* `preprocessor` and `testContent` are normally replaced by inline literals while bundling an app's JS.
+ *
+ * If these values have not been set we want to provide defaults however
+ * attempting to access them can trigger a ReferenceError as the
+ * variables are undefined (distinct from a defined variable being set to
+ * `undefined`).
+ *
+ * `typeof` checks can safely test undefined variables, note that these checks will become:
+ * `typeof { DEBUG_BUILD: true, ... }` when webpack's DefinePlugin is used in @jet/build's webpack task.
+ * When these variables have not been replaced we need to use `globalThis` to set them on the global scope
+ * in order to avoid ReferenceErrors attempting to access them.
+ */
+if (typeof preprocessor === "undefined") {
+ globalThis.preprocessor = {
+ PRODUCTION_BUILD: false,
+ CARRY_BUILD: false,
+ DEBUG_BUILD: false,
+ INTERNAL_BUILD: false,
+ };
+}
+if (typeof testContent === "undefined") {
+ globalThis.testContent = {
+ INCLUDE_TEST_CONTENT: false,
+ };
+}
+__exportStar(require("./bag"), exports);
+__exportStar(require("./bundle"), exports);
+__exportStar(require("./cookie-provider"), exports);
+__exportStar(require("./cryptography"), exports);
+__exportStar(require("./host"), exports);
+__exportStar(require("./jscookie"), exports);
+__exportStar(require("./net"), exports);
+__exportStar(require("./platform"), exports);
+__exportStar(require("./plist"), exports);
+__exportStar(require("./preprocessor"), exports);
+__exportStar(require("./random"), exports);
+__exportStar(require("./service"), exports);
+__exportStar(require("./types"), exports);
+//# sourceMappingURL=index.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/jscookie.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/jscookie.js
new file mode 100644
index 0000000..bfd5e29
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/jscookie.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=jscookie.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/net.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/net.js
new file mode 100644
index 0000000..eed04ec
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/net.js
@@ -0,0 +1,19 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.ProxiedNetwork = void 0;
+/**
+ * `Network` object designed to wrap the bridged native network object,
+ * which automatically includes page intent instrumentation data in the fetch request.
+ */
+class ProxiedNetwork {
+ constructor(original, pageIntentInstrumentation) {
+ this.original = original;
+ this.pageIntentInstrumentation = pageIntentInstrumentation;
+ }
+ async fetch(request) {
+ request["$pageIntentInstrumentation"] = this.pageIntentInstrumentation;
+ return await this.original.fetch(request);
+ }
+}
+exports.ProxiedNetwork = ProxiedNetwork;
+//# sourceMappingURL=net.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/platform.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/platform.js
new file mode 100644
index 0000000..eafaa33
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/platform.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=platform.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/plist.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/plist.js
new file mode 100644
index 0000000..29503e6
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/plist.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=plist.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/preprocessor.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/preprocessor.js
new file mode 100644
index 0000000..a04398d
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/preprocessor.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=preprocessor.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/random.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/random.js
new file mode 100644
index 0000000..3484776
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/random.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=random.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/service.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/service.js
new file mode 100644
index 0000000..a4b3c49
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/service.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=service.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/types.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/types.js
new file mode 100644
index 0000000..354ee66
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/globals/types.js
@@ -0,0 +1,16 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.services = exports.random = exports.plist = exports.platform = exports.net = exports.localizer = exports.host = exports.cryptography = exports.cookieProvider = exports.bundle = exports.bag = void 0;
+const metatype_1 = require("../../util/metatype");
+exports.bag = (0, metatype_1.makeMetatype)("jet-engine:bag");
+exports.bundle = (0, metatype_1.makeMetatype)("jet-engine:bundle");
+exports.cookieProvider = (0, metatype_1.makeMetatype)("jet-engine:cookieProvider");
+exports.cryptography = (0, metatype_1.makeMetatype)("jet-engine:cryptography");
+exports.host = (0, metatype_1.makeMetatype)("jet-engine:host");
+exports.localizer = (0, metatype_1.makeMetatype)("jet-engine:localizer");
+exports.net = (0, metatype_1.makeMetatype)("jet-engine:net");
+exports.platform = (0, metatype_1.makeMetatype)("jet-engine:platform");
+exports.plist = (0, metatype_1.makeMetatype)("jet-engine:plist");
+exports.random = (0, metatype_1.makeMetatype)("jet-engine:random");
+exports.services = (0, metatype_1.makeMetatype)("jet-engine:services");
+//# sourceMappingURL=types.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/javascriptcore/console.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/javascriptcore/console.js
new file mode 100644
index 0000000..c4fb39a
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/javascriptcore/console.js
@@ -0,0 +1,14 @@
+"use strict";
+/*
+ * Describes standard functionality available in JSContexts
+ *
+ * Types are defined here to allow us to match the behavior available in JSContext in the target OS
+ * which may not exactly match the definitions in standard TypeScript lib files, particularly on a
+ * pre-release OS.
+ *
+ * The living standard for the Console API is available at https://console.spec.whatwg.org
+ * The WebKit team has documented their interfaces at https://webkit.org/web-inspector/console-object-api/
+ * The equivalent interface in Node is https://nodejs.org/api/console.html
+ */
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=console.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/javascriptcore/index.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/javascriptcore/index.js
new file mode 100644
index 0000000..3d7b648
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/javascriptcore/index.js
@@ -0,0 +1,18 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./console"), exports);
+//# sourceMappingURL=index.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/metrics.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/metrics.js
new file mode 100644
index 0000000..adc8456
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/metrics.js
@@ -0,0 +1,57 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.notInstrumented = exports.PageInvocationPoint = exports.EMPTY_LINTED_METRICS_EVENT = void 0;
+/**
+ * An empty linted metrics event.
+ *
+ * The empty events should be skipped from recording
+ * by metrics event recorders.
+ */
+exports.EMPTY_LINTED_METRICS_EVENT = {
+ fields: {},
+ issues: [],
+};
+var PageInvocationPoint;
+(function (PageInvocationPoint) {
+ PageInvocationPoint["pageEnter"] = "pageEnter";
+ PageInvocationPoint["pageExit"] = "pageExit";
+ PageInvocationPoint["appExit"] = "appExit";
+ PageInvocationPoint["appEnter"] = "appEnter";
+ PageInvocationPoint["backButton"] = "backButton";
+})(PageInvocationPoint = exports.PageInvocationPoint || (exports.PageInvocationPoint = {}));
+/**
+ * Returns an empty metrics instance of the specified metrics type.
+ * @param metricsType - Type of the metrics data to return.
+ *
+ * @deprecated Do not use, all metrics events should be instrumented.
+ */
+function notInstrumented(metricsType) {
+ switch (metricsType) {
+ case 0 /* NotInstrumentedMetricsType.ActionMetrics */:
+ return {
+ data: [],
+ custom: {},
+ };
+ case 1 /* NotInstrumentedMetricsType.FetchTimingMetrics */:
+ return {};
+ case 2 /* NotInstrumentedMetricsType.PageMetrics */:
+ return {
+ instructions: [],
+ custom: {},
+ };
+ case 3 /* NotInstrumentedMetricsType.ImpressionMetrics */:
+ return {
+ id: {
+ id: "",
+ impressionIndex: NaN,
+ },
+ fields: {},
+ custom: {},
+ };
+ default:
+ return {};
+ }
+}
+exports.notInstrumented = notInstrumented;
+// endregion
+//# sourceMappingURL=metrics.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/models.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/models.js
new file mode 100644
index 0000000..b2dccd6
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/models.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=models.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/optional.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/optional.js
new file mode 100644
index 0000000..ea3aaeb
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/types/optional.js
@@ -0,0 +1,71 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.flatMapOptional = exports.mapOptional = exports.unsafeUnwrapOptional = exports.unwrapOptional = exports.isSome = exports.isNothing = exports.unsafeUninitialized = void 0;
+/**
+ * Bypass the protection provided by the `Optional` type
+ * and pretend to produce a value of `Some<T>` while
+ * actually returning `Nothing`.
+ */
+function unsafeUninitialized() {
+ return undefined;
+}
+exports.unsafeUninitialized = unsafeUninitialized;
+/**
+ * Test whether an optional does not contain a value.
+ *
+ * @param value - An optional value to test.
+ */
+function isNothing(value) {
+ return value === undefined || value === null;
+}
+exports.isNothing = isNothing;
+/**
+ * Test whether an optional contains a value.
+ * @param value - An optional value to test.
+ */
+function isSome(value) {
+ return value !== undefined && value !== null;
+}
+exports.isSome = isSome;
+/**
+ * Unwrap the value contained in a given optional,
+ * throwing an error if there is no value.
+ *
+ * @param value - A value to unwrap.
+ */
+function unwrapOptional(value) {
+ if (isNothing(value)) {
+ throw new ReferenceError();
+ }
+ return value;
+}
+exports.unwrapOptional = unwrapOptional;
+/**
+ * Unwrap the value contained in a given optional
+ * without checking if the value exists.
+ *
+ * @param value - A value to unwrap.
+ */
+function unsafeUnwrapOptional(value) {
+ return value;
+}
+exports.unsafeUnwrapOptional = unsafeUnwrapOptional;
+function mapOptional(value, body) {
+ if (isSome(value)) {
+ return body(value);
+ }
+ else {
+ return value;
+ }
+}
+exports.mapOptional = mapOptional;
+function flatMapOptional(value, body) {
+ if (isSome(value)) {
+ return body(value);
+ }
+ else {
+ return value;
+ }
+}
+exports.flatMapOptional = flatMapOptional;
+//# sourceMappingURL=optional.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/util/metatype.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/util/metatype.js
new file mode 100644
index 0000000..372f58f
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/util/metatype.js
@@ -0,0 +1,10 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.makeMetatype = void 0;
+function makeMetatype(name) {
+ return {
+ name: name,
+ };
+}
+exports.makeMetatype = makeMetatype;
+//# sourceMappingURL=metatype.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/util/urls.js b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/util/urls.js
new file mode 100644
index 0000000..9b52b6d
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/node_modules/@jet/environment/util/urls.js
@@ -0,0 +1,373 @@
+"use strict";
+// MARK: - Parsing Regular Expressions
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.URL = void 0;
+const optional_1 = require("../types/optional");
+const protocolRegex = /^([a-z][a-z0-9.+-]*:)(\/\/)?([\S\s]*)/i;
+const queryParamRegex = /([^=?&]+)=?([^&]*)/g;
+const componentOrder = ["hash", "query", "pathname", "host"];
+class URL {
+ constructor(url) {
+ var _a;
+ this.query = {};
+ if ((0, optional_1.isNothing)(url)) {
+ return;
+ }
+ // Split the protocol from the rest of the urls
+ let remainder = url;
+ const match = protocolRegex.exec(url);
+ if ((0, optional_1.isSome)(match)) {
+ // Pull out the protocol
+ let protocol = match[1];
+ if (protocol !== null && protocol !== undefined) {
+ protocol = protocol.split(":")[0];
+ }
+ this.protocol = protocol !== null && protocol !== void 0 ? protocol : undefined;
+ // Save the remainder
+ remainder = (_a = match[3]) !== null && _a !== void 0 ? _a : undefined;
+ }
+ // Then match each component in a specific order
+ let parse = { remainder: remainder, result: undefined };
+ for (const component of componentOrder) {
+ if (parse === undefined || parse.remainder === undefined) {
+ break;
+ }
+ switch (component) {
+ case "hash": {
+ parse = splitUrlComponent(parse.remainder, "#", "suffix");
+ this.hash = parse === null || parse === void 0 ? void 0 : parse.result;
+ break;
+ }
+ case "query": {
+ parse = splitUrlComponent(parse.remainder, "?", "suffix");
+ if ((parse === null || parse === void 0 ? void 0 : parse.result) !== undefined) {
+ this.query = URL.queryFromString(parse.result);
+ }
+ break;
+ }
+ case "pathname": {
+ parse = splitUrlComponent(parse.remainder, "/", "suffix");
+ if ((parse === null || parse === void 0 ? void 0 : parse.result) !== undefined) {
+ // Replace the initial /, since paths require it
+ this.pathname = "/" + parse.result;
+ }
+ break;
+ }
+ case "host": {
+ const authorityParse = splitUrlComponent(parse.remainder, "@", "prefix");
+ const userInfo = authorityParse === null || authorityParse === void 0 ? void 0 : authorityParse.result;
+ const hostPort = authorityParse === null || authorityParse === void 0 ? void 0 : authorityParse.remainder;
+ if (userInfo !== undefined) {
+ const userInfoSplit = userInfo.split(":");
+ this.username = decodeURIComponent(userInfoSplit[0]);
+ this.password = decodeURIComponent(userInfoSplit[1]);
+ }
+ if (hostPort !== undefined) {
+ const hostPortSplit = hostPort.split(":");
+ this.host = hostPortSplit[0];
+ this.port = hostPortSplit[1];
+ }
+ break;
+ }
+ default: {
+ throw new Error("Unhandled case!");
+ }
+ }
+ }
+ }
+ 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];
+ }
+ }
+ set(component, value) {
+ if (value === undefined) {
+ 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;
+ }
+ append(component, value) {
+ let existingValue = this.get(component);
+ let newValue;
+ if (component === "query") {
+ if (existingValue === undefined) {
+ existingValue = {};
+ }
+ if (typeof value === "string") {
+ value = URL.queryFromString(value);
+ }
+ if (typeof existingValue === "string") {
+ newValue = { existingValue, ...value };
+ }
+ else {
+ newValue = { ...existingValue, ...value };
+ }
+ }
+ else {
+ if (existingValue === undefined) {
+ existingValue = "";
+ }
+ let existingValueString = existingValue;
+ if (existingValueString === undefined) {
+ existingValueString = "";
+ }
+ let newValueString = existingValueString;
+ if (component === "pathname") {
+ const pathLength = existingValueString.length;
+ if (pathLength === 0 || existingValue[pathLength - 1] !== "/") {
+ newValueString += "/";
+ }
+ }
+ // The component is not "query" so we treat value as string.
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-plus-operands
+ newValueString += value;
+ newValue = newValueString;
+ }
+ return this.set(component, newValue);
+ }
+ param(key, value) {
+ if (key === null) {
+ return this;
+ }
+ if (this.query === undefined) {
+ this.query = {};
+ }
+ if (value === undefined) {
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+ delete this.query[key];
+ }
+ else {
+ this.query[key] = value;
+ }
+ return this;
+ }
+ removeParam(key) {
+ if (key === undefined || this.query === undefined) {
+ return this;
+ }
+ if (key in this.query) {
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+ delete this.query[key];
+ }
+ return this;
+ }
+ path(value) {
+ return this.append("pathname", value);
+ }
+ pathExtension() {
+ var _a, _b;
+ // Extract path extension if one exists
+ if (this.pathname === undefined) {
+ return undefined;
+ }
+ const lastFilenameComponents = (_b = (_a = this.pathname
+ .split("/")
+ .filter((item) => item.length > 0) // Remove any double or trailing slashes
+ .pop()) === null || _a === void 0 ? void 0 : _a.split(".")) !== null && _b !== void 0 ? _b : [];
+ if (lastFilenameComponents.filter(function (part) {
+ return part !== "";
+ }).length < 2 // Remove any empty parts (e.g. .ssh_config -> ["ssh_config"])
+ ) {
+ return undefined;
+ }
+ return lastFilenameComponents.pop();
+ }
+ /**
+ * Returns the path components of the URL
+ * @returns An array of non-empty path components from `urls`.
+ */
+ pathComponents() {
+ if (this.pathname === undefined) {
+ return [];
+ }
+ return this.pathname.split("/").filter((component) => component.length > 0);
+ }
+ /**
+ * Same as toString
+ *
+ * @returns A string representation of the URL
+ */
+ build() {
+ return this.toString();
+ }
+ /**
+ * Converts the URL to a string
+ *
+ * @returns A string representation of the URL
+ */
+ toString() {
+ let url = "";
+ if (this.protocol !== undefined) {
+ url += this.protocol + "://";
+ }
+ if (this.username !== undefined) {
+ url += encodeURIComponent(this.username);
+ if (this.password !== undefined) {
+ url += ":" + encodeURIComponent(this.password);
+ }
+ url += "@";
+ }
+ if (this.host !== undefined) {
+ url += this.host;
+ if (this.port !== undefined) {
+ url += ":" + this.port;
+ }
+ }
+ if (this.pathname !== undefined) {
+ url += this.pathname;
+ }
+ if (this.query !== undefined && Object.keys(this.query).length !== 0) {
+ url += "?" + URL.toQueryString(this.query);
+ }
+ if (this.hash !== undefined) {
+ 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 !== null && parseResult.length >= 3) {
+ // We support the legacy query format for "application/x-www-form-urlencoded" which can represent spaces as "+" symbols.
+ // https://url.spec.whatwg.org/#concept-urlencoded-parser
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#decoding_query_parameters_from_a_url
+ const key = decodeURIComponent(parseResult[1].replace(/\+/g, " "));
+ const value = decodeURIComponent(parseResult[2].replace(/\+/g, " "));
+ result[key] = value;
+ parseResult = queryParamRegex.exec(query);
+ }
+ return result;
+ }
+ /**
+ * Converts a query dictionary into a query string
+ *
+ * @param query - The query dictionary
+ * @returns 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 !== null && value.length > 0) {
+ queryString += "=" + encodeURIComponent(value);
+ }
+ }
+ return queryString;
+ }
+ /**
+ * Convenience method to instantiate a URL from a string
+ * @param url - The URL string to parse
+ * @returns 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 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 !== null && query !== void 0 ? query : {};
+ url.hash = hash;
+ return url;
+ }
+}
+exports.URL = URL;
+// MARK: - Helpers
+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;
+ }
+ }
+ return {
+ result: result,
+ remainder: remainder,
+ };
+}
+//# sourceMappingURL=urls.js.map \ No newline at end of file
diff --git a/node_modules/@apple-media-services/media-api/src/models/attributes.ts b/node_modules/@apple-media-services/media-api/src/models/attributes.ts
new file mode 100644
index 0000000..edb5fca
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/src/models/attributes.ts
@@ -0,0 +1,289 @@
+import { Opt, isNothing } from "@jet/environment/types/optional";
+import * as serverData from "./server-data";
+import * as media from "./data-structure";
+import { JSONValue, MapLike, JSONData } from "./json-types";
+import * as errors from "./errors";
+
+// region Generic Attribute retrieval
+
+// region Attribute retrieval
+
+/**
+ * Retrieve the specified attribute from the data, coercing it to a JSONData dictionary
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param defaultValue The object to return if the path search fails.
+ * @returns The dictionary of data
+ */
+export function attributeAsDictionary<Type extends JSONValue>(
+ data: media.Data,
+ attributePath?: serverData.ObjectPath,
+ defaultValue?: MapLike<Type>,
+): MapLike<Type> | null {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ return serverData.asDictionary(data.attributes, attributePath, defaultValue);
+}
+
+/**
+ * Retrieve the specified attribute from the data, coercing it to an Interface
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param defaultValue The object to return if the path search fails.
+ * @returns The dictionary of data as an interface
+ */
+export function attributeAsInterface<Interface>(
+ data: media.Data,
+ attributePath?: serverData.ObjectPath,
+ defaultValue?: JSONData,
+): Interface | null {
+ return attributeAsDictionary(data, attributePath, defaultValue) as unknown as Interface;
+}
+
+/**
+ * Retrieve the specified attribute from the data as an array, coercing to an empty array if the object is not an array.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @returns {any[]} The attribute value as an array.
+ */
+export function attributeAsArrayOrEmpty<T extends JSONValue>(
+ data: media.Data,
+ attributePath?: serverData.ObjectPath,
+): T[] {
+ if (serverData.isNull(data)) {
+ return [];
+ }
+ return serverData.asArrayOrEmpty(data.attributes, attributePath);
+}
+
+/**
+ * Retrieve the specified attribute from the data as a string.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The object path for the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {string} The attribute value as a string.
+ */
+export function attributeAsString(
+ data: media.Data,
+ attributePath?: serverData.ObjectPath,
+ policy: serverData.ValidationPolicy = "coercible",
+): Opt<string> {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ return serverData.asString(data.attributes, attributePath, policy);
+}
+
+/**
+ * Retrieve the specified meta from the data as a string.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param metaPath The object path for the meta.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {string} The meta value as a string.
+ */
+export function metaAsString(
+ data: media.Data,
+ metaPath?: serverData.ObjectPath,
+ policy: serverData.ValidationPolicy = "coercible",
+): Opt<string> {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ return serverData.asString(data.meta, metaPath, policy);
+}
+
+/**
+ * Retrieve the specified attribute from the data as a date.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The object path for the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {Date} The attribute value as a date.
+ */
+export function attributeAsDate(
+ data: media.Data,
+ attributePath?: serverData.ObjectPath,
+ policy: serverData.ValidationPolicy = "coercible",
+): Opt<Date> {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ const dateString = serverData.asString(data.attributes, attributePath, policy);
+ if (isNothing(dateString)) {
+ return null;
+ }
+ return new Date(dateString);
+}
+
+/**
+ * Retrieve the specified attribute from the data as a boolean.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a boolean.
+ */
+export function attributeAsBoolean(
+ data: media.Data,
+ attributePath?: serverData.ObjectPath,
+ policy: serverData.ValidationPolicy = "coercible",
+): boolean | null {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ return serverData.asBoolean(data.attributes, attributePath, policy);
+}
+
+/**
+ * Retrieve the specified attribute from the data as a boolean, which will be `false` if the attribute does not exist.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @returns {boolean} The attribute value as a boolean, coercing to `false` if the value is not present..
+ */
+export function attributeAsBooleanOrFalse(data: media.Data, attributePath?: serverData.ObjectPath): boolean {
+ if (serverData.isNull(data)) {
+ return false;
+ }
+ return serverData.asBooleanOrFalse(data.attributes, attributePath);
+}
+
+/**
+ * Retrieve the specified attribute from the data as a number.
+ *
+ * @param data The data from which to retrieve the attribute.
+ * @param attributePath The path of the attribute.
+ * @param policy The validation policy to use when resolving this value.
+ * @returns {boolean} The attribute value as a number.
+ */
+export function attributeAsNumber(
+ data: media.Data,
+ attributePath?: serverData.ObjectPath,
+ policy: serverData.ValidationPolicy = "coercible",
+): Opt<number> {
+ if (serverData.isNull(data)) {
+ return null;
+ }
+ return serverData.asNumber(data.attributes, attributePath, policy);
+}
+
+export function hasAttributes(data: media.Data): boolean {
+ return !serverData.isNull(serverData.asDictionary(data, "attributes"));
+}
+
+/**
+ * The canonical way to detect if an item from Media API is hydrated or not.
+ *
+ * @param data The data from which to retrieve the attributes.
+ */
+export function isNotHydrated(data: media.Data): boolean {
+ return !hasAttributes(data);
+}
+
+// region Custom Attributes
+
+/**
+ * Performs conversion for a custom variant of given attribute, if any are available.
+ * @param attribute Attribute to get custom attribute key for, if any.
+ */
+export function attributeKeyAsCustomAttributeKey(attribute: string): string | undefined {
+ return customAttributeMapping[attribute];
+}
+
+/**
+ * Whether or not given custom attributes key allows fallback to default page with AB testing treatment within a nondefault page.
+ * This is to allow AB testing to affect only icons within custom product pages.
+ */
+export function attributeAllowsNonDefaultTreatmentInNonDefaultPage(customAttribute: string): boolean {
+ return customAttribute === "customArtwork" || customAttribute === "customIconArtwork"; // Only the icon artwork.
+}
+
+/**
+ * Defines mapping of attribute to custom attribute.
+ */
+const customAttributeMapping: { [key: string]: string } = {
+ artwork: "customArtwork",
+ iconArtwork: "customIconArtwork",
+ screenshotsByType: "customScreenshotsByType",
+ promotionalText: "customPromotionalText",
+ videoPreviewsByType: "customVideoPreviewsByType",
+ customScreenshotsByTypeForAd: "customScreenshotsByTypeForAd",
+ customVideoPreviewsByTypeForAd: "customVideoPreviewsByTypeForAd",
+};
+
+export function requiredAttributeAsString(data: media.Data, attributePath: serverData.ObjectPath): string {
+ const value = attributeAsString(data, attributePath);
+ if (isNothing(value)) {
+ throw new errors.MissingFieldError(data, concatObjectPaths("attributes", attributePath));
+ }
+ return value;
+}
+
+export function requiredAttributeAsDate(data: media.Data, attributePath: serverData.ObjectPath): Date {
+ const value = attributeAsDate(data, attributePath);
+ if (isNothing(value)) {
+ throw new errors.MissingFieldError(data, concatObjectPaths("attributes", attributePath));
+ }
+ return value;
+}
+
+export function requiredAttributeAsDictionary<Type extends JSONValue>(
+ data: media.Data,
+ attributePath: serverData.ObjectPath,
+): MapLike<Type> {
+ const value: MapLike<Type> | null = attributeAsDictionary(data, attributePath);
+ if (isNothing(value)) {
+ throw new errors.MissingFieldError(data, concatObjectPaths("attributes", attributePath));
+ }
+ return value;
+}
+
+export function requiredMeta(data: media.Data): MapLike<JSONValue> {
+ const value = serverData.asDictionary(data, "meta");
+ if (isNothing(value)) {
+ throw new errors.MissingFieldError(data, "meta");
+ }
+ return value;
+}
+
+export function requiredMetaAttributeAsString(data: media.Data, attributePath: serverData.ObjectPath): string {
+ const meta = requiredMeta(data);
+ const value = serverData.asString(meta, attributePath);
+ if (isNothing(value)) {
+ throw new errors.MissingFieldError(data, concatObjectPaths("meta", attributePath));
+ }
+ return value;
+}
+
+export function requiredMetaAttributeAsNumber(data: media.Data, attributePath: serverData.ObjectPath): number {
+ const meta = requiredMeta(data);
+ const value = serverData.asNumber(meta, attributePath);
+ if (isNothing(value)) {
+ throw new errors.MissingFieldError(data, concatObjectPaths("meta", attributePath));
+ }
+ return value;
+}
+
+export function concatObjectPaths(prefix: serverData.ObjectPath, suffix: serverData.ObjectPath): serverData.ObjectPath {
+ let finalPath: string[];
+ if (Array.isArray(prefix)) {
+ finalPath = prefix;
+ } else {
+ finalPath = [prefix];
+ }
+
+ if (Array.isArray(suffix)) {
+ finalPath.push(...suffix);
+ } else {
+ finalPath.push(suffix);
+ }
+ return finalPath;
+}
+
+// endregion
diff --git a/node_modules/@apple-media-services/media-api/src/models/server-data.ts b/node_modules/@apple-media-services/media-api/src/models/server-data.ts
new file mode 100644
index 0000000..a1aca3f
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/src/models/server-data.ts
@@ -0,0 +1,476 @@
+//
+// server-data.ts
+// AppStoreKit
+//
+// Created by Kevin MacWhinnie on 8/17/16.
+// Copyright (c) 2016 Apple Inc. All rights reserved.
+//
+
+// TODO: Replace this utility for JSON Parsing
+import * as validation from "@jet/environment/json/validation";
+import { Nothing, Opt, isNothing } from "@jet/environment/types/optional";
+import { JSONArray, JSONData, JSONValue, MapLike } from "./json-types";
+
+// region Traversal
+
+/**
+ * Union type that describes the possible representations for an object traversal path.
+ */
+export type ObjectPath = string | string[];
+
+/**
+ * Returns the string representation of a given object path.
+ * @param path The object path to coerce to a string.
+ * @returns A string representation of `path`.
+ */
+export function objectPathToString(path: Opt<ObjectPath>): Opt<string> {
+ if (isNull(path)) {
+ return null;
+ } else if (Array.isArray(path)) {
+ return path.join(".");
+ } else {
+ return path;
+ }
+}
+
+const PARSED_PATH_CACHE: { [key: string]: string[] } = {};
+
+/**
+ * Traverse a nested JSON object structure, short-circuiting
+ * when finding `undefined` or `null` values. Usage:
+ *
+ * const object = {x: {y: {z: 42}}};
+ * const meaningOfLife = serverData.traverse(object, 'x.y.z');
+ *
+ * @param object The JSON object to traverse.
+ * @param path The path to search. If falsy, `object` will be returned without being traversed.
+ * @param defaultValue The object to return if the path search fails.
+ * @return The value at `path` if found; default value otherwise.
+ */
+export function traverse(object: JSONValue, path?: ObjectPath, defaultValue?: JSONValue): JSONValue {
+ if (object === undefined || object === null) {
+ return defaultValue;
+ }
+
+ if (isNullOrEmpty(path)) {
+ return object;
+ }
+
+ let components: string[];
+ if (typeof path === "string") {
+ components = PARSED_PATH_CACHE[path];
+ if (isNullOrEmpty(components)) {
+ // Fast Path: If the path contains only a single component, we can skip
+ // all of the work below here and speed up storefronts that
+ // don't have JIT compilation enabled.
+ if (!path.includes(".")) {
+ const value = object[path];
+ if (value !== undefined && value !== null) {
+ return value;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ components = path.split(".");
+ PARSED_PATH_CACHE[path] = components;
+ }
+ } else {
+ components = path;
+ }
+
+ let current: JSONValue = object;
+ for (const component of components) {
+ current = current[component];
+ if (current === undefined || current === null) {
+ return defaultValue;
+ }
+ }
+ return current;
+}
+
+// endregion
+
+// region Nullability
+
+/**
+ * Returns a bool indicating whether or not a given object null or undefined.
+ * @param object The object to test.
+ * @return true if the object is null or undefined; false otherwise.
+ */
+export function isNull<Type>(object: Type | Nothing): object is Nothing {
+ return object === null || object === undefined;
+}
+
+/**
+ * Returns a bool indicating whether or not a given object is null or empty.
+ * @param object The object to test
+ * @return true if object is null or empty; false otherwise.
+ */
+export function isNullOrEmpty<Type>(object: Type | Nothing): object is Nothing {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return isNull(object) || Object.keys(object as any).length === 0;
+}
+
+/**
+ * Returns a bool indicating whether or not a given object is non-null.
+ * @param object The object to test.
+ * @return true if the object is not null or undefined; false otherwise.
+ */
+export function isDefinedNonNull<Type>(object: Type | null | undefined): object is Type {
+ return typeof object !== "undefined" && object !== null;
+}
+
+/**
+ * Returns a bool indicating whether or not a given object is non-null or empty.
+ * @param object The object to test.
+ * @return true if the object is not null or undefined and not empty; false otherwise.
+ */
+export function isDefinedNonNullNonEmpty<Type>(object: Type | Nothing): object is Type {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return isDefinedNonNull(object) && Object.keys(object as any).length !== 0;
+}
+
+/**
+ * Checks if the passed string or number is a number
+ *
+ * @param value The value to check
+ * @return True if the value is an number, false if not
+ */
+export function isNumber(value: number | string | null | undefined): value is number {
+ if (isNull(value)) {
+ return false;
+ }
+
+ let valueToCheck;
+ if (typeof value === "string") {
+ valueToCheck = parseInt(value);
+ } else {
+ valueToCheck = value;
+ }
+
+ return !Number.isNaN(valueToCheck);
+}
+
+/**
+ * Returns a bool indicating whether or not a given object is defined but empty.
+ * @param object The object to test.
+ * @return true if the object is not null and empty; false otherwise.
+ */
+export function isArrayDefinedNonNullAndEmpty<Type extends JSONArray>(object: Type | null | undefined): object is Type {
+ return isDefinedNonNull(object) && object.length === 0;
+}
+
+// endregion
+
+// region Defaulting Casts
+
+/**
+ * Check that a given object is an array, substituting an empty array if not.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find an array.
+ * Omit this parameter if `object` is itself an array.
+ * @returns An untyped array.
+ */
+export function asArrayOrEmpty<T extends JSONValue>(object: JSONValue, path?: ObjectPath): T[] {
+ const target = traverse(object, path, null);
+ if (Array.isArray(target)) {
+ // Note: This is kind of a nasty cast, but I don't think we want to validate that everything is of type T
+ return target as T[];
+ } else {
+ if (!isNull(target)) {
+ validation.context("asArrayOrEmpty", () => {
+ validation.unexpectedType("defaultValue", "array", target, objectPathToString(path));
+ });
+ }
+ return [];
+ }
+}
+
+/**
+ * Check that a given object is a boolean, substituting the value `false` if not.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a boolean.
+ * Omit this parameter if `object` is itself a boolean.
+ * @returns A boolean from `object`, or defaults to `false`.
+ */
+export function asBooleanOrFalse(object: JSONValue, path?: ObjectPath): boolean {
+ const target = traverse(object, path, null);
+ if (typeof target === "boolean") {
+ return target;
+ } else {
+ if (!isNull(target)) {
+ validation.context("asBooleanOrFalse", () => {
+ validation.unexpectedType("defaultValue", "boolean", target, objectPathToString(path));
+ });
+ }
+ return false;
+ }
+}
+
+// endregion
+
+// region Coercing Casts
+
+export type ValidationPolicy = "strict" | "coercible" | "none";
+
+/**
+ * Safely coerce an object into a string.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a string.
+ * Omit this parameter if `object` is itself a string.
+ * @param policy The validation policy to use when resolving this value
+ * @returns A string from `object`, or `null` if `object` is null.
+ */
+export function asString(object: JSONValue, path?: ObjectPath, policy: ValidationPolicy = "coercible"): Opt<string> {
+ const target = traverse(object, path, null);
+ if (isNull(target)) {
+ return target;
+ } else if (typeof target === "string") {
+ return target;
+ } else {
+ // We don't consider arbitrary objects as convertable to strings even through they will result in some value
+ const coercedValue = typeof target === "object" ? null : String(target);
+ switch (policy) {
+ case "strict": {
+ validation.context("asString", () => {
+ validation.unexpectedType("coercedValue", "string", target, objectPathToString(path));
+ });
+ break;
+ }
+ case "coercible": {
+ if (isNull(coercedValue)) {
+ validation.context("asString", () => {
+ validation.unexpectedType("coercedValue", "string", target, objectPathToString(path));
+ });
+ }
+ break;
+ }
+ case "none":
+ default: {
+ break;
+ }
+ }
+
+ return coercedValue;
+ }
+}
+
+/**
+ * Safely coerce an object into a date.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a date.
+ * @param policy The validation policy to use when resolving this value
+ * @returns A date from `object`, or `null` if `object` is null.
+ */
+export function asDate(object: JSONValue, path?: ObjectPath, policy: ValidationPolicy = "coercible"): Opt<Date> {
+ const dateString = asString(object, path, policy);
+ if (isNothing(dateString)) {
+ return null;
+ }
+ return new Date(dateString);
+}
+
+/**
+ * Safely coerce an object into a number.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a number.
+ * Omit this parameter if `object` is itself a number.
+ * @param policy The validation policy to use when resolving this value
+ * @returns A number from `object`, or `null` if `object` is null.
+ */
+export function asNumber(object: JSONValue, path?: ObjectPath, policy: ValidationPolicy = "coercible"): Opt<number> {
+ const target = traverse(object, path, null);
+ if (isNull(target) || typeof target === "number") {
+ return target;
+ } else {
+ const coercedValue = Number(target);
+ switch (policy) {
+ case "strict": {
+ validation.context("asNumber", () => {
+ validation.unexpectedType("coercedValue", "number", target, objectPathToString(path));
+ });
+ break;
+ }
+ case "coercible": {
+ if (isNaN(coercedValue)) {
+ validation.context("asNumber", () => {
+ validation.unexpectedType("coercedValue", "number", target, objectPathToString(path));
+ });
+ return null;
+ }
+ break;
+ }
+ case "none":
+ default: {
+ break;
+ }
+ }
+
+ return coercedValue;
+ }
+}
+
+/**
+ * Safely coerce an object into a dictionary.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find the dictionary.
+ * Omit this parameter if `object` is itself a dictionary.
+ * @param defaultValue The object to return if the path search fails.
+ * @returns A sub-dictionary from `object`, or `null` if `object` is null.
+ */
+export function asDictionary<Type extends JSONValue>(
+ object: JSONValue,
+ path?: ObjectPath,
+ defaultValue?: MapLike<Type>,
+): MapLike<Type> | null {
+ const target = traverse(object, path, null);
+ if (target instanceof Object && !Array.isArray(target)) {
+ // Note: It's too expensive to actually validate this is a dictionary of { string : Type } at run time
+ return target as MapLike<Type>;
+ } else {
+ if (!isNull(target)) {
+ validation.context("asDictionary", () => {
+ validation.unexpectedType("defaultValue", "object", target, objectPathToString(path));
+ });
+ }
+
+ if (isDefinedNonNull(defaultValue)) {
+ return defaultValue;
+ }
+ return null;
+ }
+}
+
+/**
+ * Safely coerce an object into a given interface.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a string.
+ * Omit this parameter if `object` is itself a string.
+ * @param defaultValue The object to return if the path search fails.
+ * @returns A sub-dictionary from `object`, or `null` if `object` is null.
+ */
+export function asInterface<Interface>(
+ object: JSONValue,
+ path?: ObjectPath,
+ defaultValue?: JSONData,
+): Interface | null {
+ return asDictionary(object, path, defaultValue) as unknown as Interface;
+}
+
+/**
+ * Coerce an object into a boolean.
+ * @param object The object to coerce.
+ * @param path The path to traverse on `object` to find a boolean.
+ * Omit this parameter if `object` is itself a boolean.
+ * @param policy The validation policy to use when resolving this value
+ * @returns A boolean from `object`, or `null` if `object` is null.
+ * @note This is distinct from `asBooleanOrFalse` in that it doesn't default to false,
+ * and it tries to convert string boolean values into actual boolean types
+ */
+export function asBoolean(
+ object: JSONValue,
+ path?: ObjectPath,
+ policy: ValidationPolicy = "coercible",
+): boolean | null {
+ const target = traverse(object, path, null);
+
+ // Value was null
+ if (isNull(target)) {
+ return null;
+ }
+
+ // Value was boolean.
+ if (typeof target === "boolean") {
+ return target;
+ }
+
+ // Value was string.
+ if (typeof target === "string") {
+ if (target === "true") {
+ return true;
+ } else if (target === "false") {
+ return false;
+ }
+ }
+
+ // Else coerce.
+ const coercedValue = Boolean(target);
+ switch (policy) {
+ case "strict": {
+ validation.context("asBoolean", () => {
+ validation.unexpectedType("coercedValue", "number", target, objectPathToString(path));
+ });
+ break;
+ }
+ case "coercible": {
+ if (isNull(coercedValue)) {
+ validation.context("asBoolean", () => {
+ validation.unexpectedType("coercedValue", "number", target, objectPathToString(path));
+ });
+ return null;
+ }
+ break;
+ }
+ case "none":
+ default: {
+ break;
+ }
+ }
+
+ return coercedValue;
+}
+
+/**
+ * Attempts to coerce the passed value to a JSONValue
+ *
+ * Note: due to performance concerns this does not perform a deep inspection of Objects or Arrays.
+ *
+ * @param value The value to coerce
+ * @return A JSONValue or null if value is not a valid JSONValue type
+ */
+export function asJSONValue(value: unknown): JSONValue | null {
+ if (value === null || value === undefined) {
+ return null;
+ }
+ switch (typeof value) {
+ case "string":
+ case "number":
+ case "boolean":
+ return value as JSONValue;
+ case "object":
+ // Note: It's too expensive to actually validate this is an array of JSONValues at run time
+ if (Array.isArray(value)) {
+ return value as JSONValue;
+ }
+ // Note: It's too expensive to actually validate this is a dictionary of { string : JSONValue } at run time
+ return value as JSONValue;
+ default:
+ validation.context("asJSONValue", () => {
+ validation.unexpectedType("defaultValue", "JSONValue", typeof value);
+ });
+ return null;
+ }
+}
+
+/**
+ * Attempts to coerce the passed value to JSONData
+ *
+ * @param value The value to coerce
+ * @return A JSONData or null if the value is not a valid JSONData object
+ */
+export function asJSONData(value: unknown): JSONData | null {
+ if (value === null || value === undefined) {
+ return null;
+ }
+ if (value instanceof Object && !Array.isArray(value)) {
+ // Note: It's too expensive to actually validate this is a dictionary of { string : Type } at run time
+ return value as JSONData;
+ }
+ validation.context("asJSONValue", () => {
+ validation.unexpectedType("defaultValue", "object", typeof value);
+ });
+ return null;
+}
+
+// endregion
diff --git a/node_modules/@apple-media-services/media-api/src/models/urls.ts b/node_modules/@apple-media-services/media-api/src/models/urls.ts
new file mode 100644
index 0000000..c1c09f1
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/src/models/urls.ts
@@ -0,0 +1,469 @@
+/**
+ * Created by keithpk on 12/2/16.
+ */
+
+import { isNothing, Nothing, Opt } from "@jet/environment/types/optional";
+import { isDefinedNonNullNonEmpty, isNullOrEmpty } from "./server-data";
+
+export type Query = {
+ [key: string]: string | undefined;
+};
+
+export type URLComponent = "protocol" | "username" | "password" | "host" | "port" | "pathname" | "query" | "hash";
+
+const protocolRegex = /^([a-z][a-z0-9.+-]*:)(\/\/)?([\S\s]*)/i;
+const queryParamRegex = /([^=?&]+)=?([^&]*)/g;
+const componentOrder: URLComponent[] = ["hash", "query", "pathname", "host"];
+
+type URLSplitStyle = "prefix" | "suffix";
+
+type URLSplitResult = {
+ result?: string;
+ remainder: string;
+};
+
+function splitUrlComponent(input: string, marker: string, style: URLSplitStyle): URLSplitResult {
+ 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 {
+ protocol?: Opt<string>;
+ username: string;
+ password: string;
+ host?: Opt<string>;
+ port: string;
+ pathname?: Opt<string>;
+ query?: Query = {};
+ hash?: string;
+
+ constructor(url?: string) {
+ if (isNullOrEmpty(url)) {
+ return;
+ }
+
+ // Split the protocol from the rest of the urls
+ let remainder = url;
+ const match = protocolRegex.exec(url);
+ if (match != null) {
+ // 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: URLSplitResult = { 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 (isDefinedNonNullNonEmpty(parse.result)) {
+ this.query = URL.queryFromString(parse.result);
+ }
+ break;
+ }
+ case "pathname": {
+ parse = splitUrlComponent(parse.remainder, "/", "suffix");
+
+ if (isDefinedNonNullNonEmpty(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 (isDefinedNonNullNonEmpty(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: URLComponent, value: string | Query): URL {
+ if (isNullOrEmpty(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 as string;
+ break;
+ case "username":
+ this.username = value as string;
+ break;
+ case "password":
+ this.password = value as string;
+ break;
+ case "port":
+ this.port = value as string;
+ break;
+ case "pathname":
+ this.pathname = value as string;
+ break;
+ case "query":
+ this.query = value as Query;
+ break;
+ case "hash":
+ this.hash = value as string;
+ break;
+ default:
+ // The fallback for component which is not a property of URL object.
+ this[component] = value as string;
+ break;
+ }
+ return this;
+ }
+
+ private get(component: URLComponent): string | Query | Nothing {
+ 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: URLComponent, value: string | Query): URL {
+ 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 as string;
+
+ 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: string, value?: string): URL {
+ if (!key) {
+ return this;
+ }
+ if (this.query == null) {
+ this.query = {};
+ }
+ this.query[key] = value;
+ return this;
+ }
+
+ removeParam(key: string): URL {
+ if (!key || this.query == null) {
+ 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: string): URL {
+ return this.append("pathname", value);
+ }
+
+ pathExtension(): Opt<string> {
+ // Extract path extension if one exists
+ if (isNothing(this.pathname)) {
+ return null;
+ }
+
+ const lastFilenameComponents = this.pathname
+ .split("/")
+ .filter((item) => item.length > 0) // Remove any double or trailing slashes
+ .pop()
+ ?.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(): string[] {
+ if (isNullOrEmpty(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(): string | null {
+ if (isNullOrEmpty(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(): string {
+ return this.toString();
+ }
+
+ /**
+ * Converts the URL to a string
+ *
+ * @returns {string} A string representation of the URL
+ */
+ toString(): string {
+ let url = "";
+
+ if (isDefinedNonNullNonEmpty(this.protocol)) {
+ url += this.protocol + "://";
+ }
+
+ if (this.username) {
+ url += encodeURIComponent(this.username);
+
+ if (this.password) {
+ url += ":" + encodeURIComponent(this.password);
+ }
+
+ url += "@";
+ }
+
+ if (isDefinedNonNullNonEmpty(this.host)) {
+ url += this.host;
+
+ if (this.port) {
+ url += ":" + this.port;
+ }
+ }
+
+ if (isDefinedNonNullNonEmpty(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 != null && Object.keys(this.query).length > 0) {
+ url += "?" + URL.toQueryString(this.query);
+ }
+
+ if (isDefinedNonNullNonEmpty(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: string): Query {
+ const result = {};
+
+ let parseResult = queryParamRegex.exec(query);
+ while (parseResult != null) {
+ 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: 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 (isDefinedNonNullNonEmpty(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: string): 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?: Opt<string>,
+ host?: Opt<string>,
+ path?: Opt<string>,
+ query?: Query,
+ hash?: string,
+ ): URL {
+ const url = new URL();
+ url.protocol = protocol;
+ url.host = host;
+ url.pathname = path;
+ url.query = query;
+ url.hash = hash;
+ return url;
+ }
+}
diff --git a/node_modules/@apple-media-services/media-api/src/network.ts b/node_modules/@apple-media-services/media-api/src/network.ts
new file mode 100644
index 0000000..48f1ccf
--- /dev/null
+++ b/node_modules/@apple-media-services/media-api/src/network.ts
@@ -0,0 +1,403 @@
+/**
+ * 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 { FetchRequest, FetchResponse, HTTPTimingMetrics } from "@jet/environment/types/globals/net";
+import { FetchTimingMetrics, MetricsFields } from "@jet/environment/types/metrics";
+import { FetchTimingMetricsBuilder } from "@jet/environment/metrics";
+import { isSome, Opt } from "@jet/environment/types/optional";
+import * as serverData from "./models/server-data";
+import { ParsedNetworkResponse } from "./models/data-structure";
+import { Request } from "./models/request";
+import * as urls from "./models/urls";
+import * as urlBuilder from "./url-builder";
+import { MediaConfigurationType, MediaTokenService } from "./models/mediapi-configuration";
+import { HTTPMethod, HTTPCachePolicy, HTTPSigningStyle, HTTPHeaders } from "@jet/environment";
+
+/** @public */
+// eslint-disable-next-line @typescript-eslint/no-namespace
+export namespace ResponseMetadata {
+ export const requestedUrl = "_jet-internal:metricsHelpers_requestedUrl";
+
+ /**
+ * Symbol used to place timing metrics values onto fetch responses
+ * without interfering with the data returned by the server.
+ */
+ export const timingValues = "_jet-internal:metricsHelpers_timingValues";
+
+ /**
+ * Key used to access the page information gathered from a response's headers
+ */
+ export const pageInformation = "_jet-internal:metricsHelpers_pageInformation";
+
+ /**
+ * Key used to access the content max-age gathered from a response's headers.
+ */
+ export const contentMaxAge = "_jet-internal:responseMetadata_contentMaxAge";
+}
+
+/**
+ * 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.
+ */
+export async function fetch<Type>(
+ configuration: MediaConfigurationType,
+ request: FetchRequest,
+ parser: (value: Opt<string>) => Type,
+): Promise<Type & ParsedNetworkResponse> {
+ const response = await configuration.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) as Type & ParsedNetworkResponse;
+ const parseEndTime = Date.now();
+
+ // 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<Type>(configuration: MediaConfigurationType, request: FetchRequest): Promise<Type> {
+ return await fetch(configuration, request, (body) => {
+ if (isSome(body)) {
+ return JSON.parse(body) as Type;
+ } else {
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+ return {} as Type;
+ }
+ });
+}
+
+/**
+ * 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<Type>(configuration: MediaConfigurationType, request: FetchRequest): Promise<Type> {
+ return await fetch(configuration, request, (body) => {
+ if (isSome(body)) {
+ return configuration.plist.parse(body) as Type;
+ } 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: HTTPTimingMetrics[] | null,
+ parseStartTime: number,
+ parseEndTime: number,
+): FetchTimingMetrics | null {
+ // 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: FetchTimingMetrics = {
+ ...responseMetrics[0],
+ parseStartTime: parseStartTime,
+ parseEndTime: parseEndTime,
+ };
+ // Timing metrics with all properties populated.
+ return firstPartialTimingMetrics;
+}
+
+export type FetchOptions = {
+ headers?: { [key: string]: string };
+ method?: "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE" | "PATCH";
+ requestBodyString?: string;
+ timeout?: number; // in seconds. Check for feature 'supportsRequestTimeoutOption'.
+ /// When true the fetch wont throw if we dont get any data back for given request.
+ allowEmptyDataResponse?: boolean;
+ excludeIdentifierHeadersForAccount?: boolean; // Defaults to false
+ alwaysIncludeAuthKitHeaders?: boolean; // Defaults to true
+ alwaysIncludeMMeClientInfoAndDeviceHeaders?: boolean; // Defaults to true
+};
+
+/**
+ * Implements the MAPI fetch, building URL from MAPI Request and opaquely managing initial token request and refreshes.
+ *
+ * @param {MediaConfigurationType} configuration Base media API configuration.
+ * @param {Request} request MAPI Request to fetch with.
+ * @param {FetchOptions} [options] FetchOptions for the MAPI request.
+ * @returns {Promise<Type>} Promise resolving to some type for given MAPI request.
+ */
+export async function fetchData<Type>(
+ configuration: MediaConfigurationType,
+ mediaToken: MediaTokenService,
+ request: Request,
+ options?: FetchOptions,
+): Promise<Type & ParsedNetworkResponse> {
+ const url = urlBuilder.buildURLFromRequest(configuration, request).toString();
+ const startTime = Date.now();
+ const token = await mediaToken.refreshToken();
+ const response = await fetchWithToken<Type>(
+ configuration,
+ mediaToken,
+ url,
+ token,
+ options,
+ false,
+ configuration.fetchTimingMetricsBuilder,
+ );
+ const endTime = Date.now();
+ if (request.canonicalUrl) {
+ response[ResponseMetadata.requestedUrl] = request.canonicalUrl;
+ }
+ const roundTripTimeIncludingWaiting = endTime - startTime;
+ if (roundTripTimeIncludingWaiting > 500) {
+ console.warn("Fetch took too long (" + roundTripTimeIncludingWaiting.toString() + "ms) " + url);
+ }
+ return response;
+}
+
+export function redirectParametersInUrl(configuration: MediaConfigurationType, url: urls.URL): string[] {
+ const redirectURLParams = configuration.redirectUrlWhitelistedQueryParams;
+ return redirectURLParams.filter((param) => serverData.isDefinedNonNull(url.query?.[param]));
+}
+
+export type MediaAPIFetchRequest = {
+ url: string;
+ excludeIdentifierHeadersForAccount?: boolean;
+ alwaysIncludeAuthKitHeaders?: boolean;
+ alwaysIncludeMMeClientInfoAndDeviceHeaders?: boolean;
+ method?: Opt<HTTPMethod>;
+ cache?: Opt<HTTPCachePolicy>;
+ signingStyle?: Opt<HTTPSigningStyle>;
+ headers?: Opt<HTTPHeaders>;
+ timeout?: Opt<number>;
+ body?: Opt<string>;
+};
+
+/**
+ * Given a built URL, token, and options, calls into native networking APIs to fetch content.
+ *
+ * @param {string} url URL to fetch data from.
+ * @param {string} token MAPI token key.
+ * @param {FetchOptions} options Fetch options for MAPI requests.
+ * @param {boolean} isRetry flag indicating whether this is a fetch retry following a 401 request, and media token was refreshed.
+ * @returns {Promise<Type>} Promise resolving to some type for given MAPI request.
+ */
+async function fetchWithToken<Type>(
+ configuration: MediaConfigurationType,
+ mediaToken: MediaTokenService,
+ url: string,
+ token: string,
+ options: FetchOptions = {},
+ isRetry = false,
+ fetchTimingMetricsBuilder: Opt<FetchTimingMetricsBuilder>,
+): Promise<Type & ParsedNetworkResponse> {
+ // Removes all affiliate/redirect params for caching (https://connectme.apple.com/docs/DOC-577671)
+ const filteredURL = new urls.URL(url);
+ const redirectParameters = redirectParametersInUrl(configuration, filteredURL);
+ for (const param of redirectParameters) {
+ filteredURL.removeParam(param);
+ }
+ const filteredUrlString = filteredURL.toString();
+
+ let headers = options.headers;
+ if (headers == null) {
+ headers = {};
+ }
+ headers["Authorization"] = "Bearer " + token;
+
+ const fetchRequest: MediaAPIFetchRequest = {
+ url: filteredUrlString,
+ excludeIdentifierHeadersForAccount: options.excludeIdentifierHeadersForAccount ?? false,
+ alwaysIncludeAuthKitHeaders: options.alwaysIncludeAuthKitHeaders ?? true,
+ alwaysIncludeMMeClientInfoAndDeviceHeaders: options.alwaysIncludeMMeClientInfoAndDeviceHeaders ?? true,
+ headers: headers,
+ method: options.method,
+ body: options.requestBodyString,
+ timeout: options.timeout,
+ };
+
+ const response = await configuration.network.fetch(fetchRequest);
+
+ try {
+ if (response.status === 401 || response.status === 403) {
+ if (isRetry) {
+ throw Error("We refreshed the token but we still get 401 from the API");
+ }
+ mediaToken.resetToken();
+ return await mediaToken.refreshToken().then(async (newToken) => {
+ // Explicitly re-fetch with the original request so logging and metrics are correct
+ return await fetchWithToken<Type>(
+ configuration,
+ mediaToken,
+ url,
+ newToken,
+ options,
+ true,
+ fetchTimingMetricsBuilder,
+ );
+ });
+ } else if (response.status === 404) {
+ // item is not available in this storefront or perhaps not at all
+ throw noContentError();
+ } else if (!response.ok) {
+ const correlationKey = response.headers["x-apple-jingle-correlation-key"] ?? "N/A";
+ const error = new NetworkError(
+ `Bad Status code ${response.status} (correlationKey: ${correlationKey}) for ${filteredUrlString}, original ${url}`,
+ );
+ error.statusCode = response.status;
+ throw error;
+ }
+
+ const parser = (resp: FetchResponse) => {
+ const parseStartTime = Date.now();
+ let result: Type & ParsedNetworkResponse;
+ if (serverData.isNull(resp.body) || resp.body === "") {
+ if (resp.status === 204) {
+ // 204 indicates a success, but the response will typically be empty
+ // Create a fake result so that we don't throw an error when JSON parsing
+ const emptyData: ParsedNetworkResponse = {};
+ result = emptyData as Type & ParsedNetworkResponse;
+ } else {
+ throw noContentError();
+ }
+ } else {
+ result = JSON.parse(resp.body) as Type & ParsedNetworkResponse;
+ }
+ const parseEndTime = Date.now();
+
+ result[ResponseMetadata.pageInformation] = serverData.asJSONData(
+ getPageInformationFromResponse(configuration, resp),
+ );
+ if (resp.metrics.length > 0) {
+ const metrics: FetchTimingMetrics = {
+ ...resp.metrics[0],
+ parseStartTime: parseStartTime,
+ parseEndTime: parseEndTime,
+ };
+ result[ResponseMetadata.timingValues] = metrics;
+ } else {
+ const fallbackMetrics: FetchTimingMetrics = {
+ pageURL: resp.url,
+ parseStartTime,
+ parseEndTime,
+ };
+ result[ResponseMetadata.timingValues] = fallbackMetrics;
+ }
+ result[ResponseMetadata.contentMaxAge] = getContentTimeToLiveFromResponse(resp);
+
+ // If we have an empty data object, throw a 204 (No Content).
+ if (
+ Array.isArray(result.data) &&
+ serverData.isArrayDefinedNonNullAndEmpty(result.data) &&
+ !serverData.asBooleanOrFalse(options.allowEmptyDataResponse)
+ ) {
+ throw noContentError();
+ }
+
+ result[ResponseMetadata.requestedUrl] = url;
+ return result;
+ };
+ if (isSome(fetchTimingMetricsBuilder)) {
+ return fetchTimingMetricsBuilder.measureParsing(response, parser);
+ } else {
+ return parser(response);
+ }
+ } catch (e) {
+ if (e instanceof NetworkError) {
+ throw e;
+ }
+ throw new Error(`Error Fetching - filtered: ${filteredUrlString}, original: ${url}, ${e.name}, ${e.message}`);
+ }
+}
+
+export class NetworkError extends Error {
+ statusCode?: number;
+}
+
+function noContentError(): NetworkError {
+ const error = new NetworkError(`No content`);
+ error.statusCode = 204;
+ return error;
+}
+
+const serverInstanceHeader = "x-apple-application-instance";
+
+const environmentDataCenterHeader = "x-apple-application-site";
+
+function getPageInformationFromResponse(
+ configuration: MediaConfigurationType,
+ response: FetchResponse,
+): MetricsFields | null {
+ const storeFrontHeader: string = configuration.storefrontIdentifier;
+
+ let storeFront: Opt<string> = null;
+ if (serverData.isDefinedNonNullNonEmpty(storeFrontHeader)) {
+ const storeFrontHeaderComponents: string[] = storeFrontHeader.split("-");
+ if (serverData.isDefinedNonNullNonEmpty(storeFrontHeaderComponents)) {
+ storeFront = storeFrontHeaderComponents[0];
+ }
+ }
+
+ return {
+ serverInstance: response.headers[serverInstanceHeader],
+ storeFrontHeader: storeFrontHeader,
+ language: configuration.bagLanguage,
+ storeFront: storeFront,
+ environmentDataCenter: response.headers[environmentDataCenterHeader],
+ };
+}
+
+function getContentTimeToLiveFromResponse(response: FetchResponse): Opt<number> {
+ const cacheControlHeaderKey = Object.keys(response.headers).find((key) => key.toLowerCase() === "cache-control");
+ if (serverData.isNull(cacheControlHeaderKey) || cacheControlHeaderKey === "") {
+ return null;
+ }
+
+ const headerValue = response.headers[cacheControlHeaderKey];
+ if (serverData.isNullOrEmpty(headerValue)) {
+ return null;
+ }
+ const matches = headerValue.match(/max-age=(\d+)/);
+ if (serverData.isNull(matches) || matches.length < 2) {
+ return null;
+ }
+ return serverData.asNumber(matches[1]);
+}