init commit

This commit is contained in:
rxliuli
2025-11-04 05:03:50 +08:00
commit bce557cc2d
1396 changed files with 172991 additions and 0 deletions

View File

@@ -0,0 +1,728 @@
import { AbstractEventRecorder, Delegates } from '@amp-metrics/mt-metricskit-delegates-core';
import { EventRecorder as EventRecorder$1, ImmediateEventRecorder, environment, network, logger } from '@amp-metrics/mt-event-queue';
import { reflect, backoff } from '@amp-metrics/mt-metricskit-utils-private';
/*
* src/environment.js
* mt-metricskit-delegates-web
*
* Copyright © 2022 Apple Inc. All rights reserved.
*
*/
/**
* Provides a pre-built HTML delegate to use against the metrics.system.environment delegate via metrics.system.environment.setDelegate()
* If you want to use *most* of these methods, but not *all* of them, you can set this delegate and then create your own with whichever few methods you need to
* customize additionally, and then setDelegate() *that* delegate, in order to override those methods.
* @constructor
* @param {Config} config (optional) - An instance of Config, which contains the topic and the relevant configurations for the topic.
*/
function Environment(config) {
this._config = config;
}
/**
************************************ PSEUDO-PRIVATE METHODS/IVARS ************************************
* These functions need to be accessible for ease of testing, but should not be used by clients
*/
Environment.prototype._document = function _document() {
if (typeof document != 'undefined') {
return document;
} else {
throw "metricskit-delegates-html.environment HTML delegate 'document' object not found";
}
};
Environment.prototype._window = function _window() {
if (typeof window != 'undefined') {
return window;
} else {
throw "metricskit-delegates-html.environment HTML delegate 'window' object not found";
}
};
/**
************************************ PUBLIC METHODS/IVARS ************************************
*/
/**
* The name of the browser that generated the event, it only return browser field if "browserMaps" config property exists in the topic config
* Common userAgent format: "Mozilla/5.0 (<system-information>) <platform> (<platform-details>) <extensions>"
* This implementation will read the browser identifer from the first item in the extensions part of the userAgent.
* For those browsers which do not store their browser identifier in the first item (can be configured in "specifiedBrowsers" config, see the below example),
* the function will search the defined string in the userAgent to verify if it is the case.
* It will load the browser related configurations from "browserMaps" in the topic. The config looks like
* {
* specifiedBrowsers: string[], // listing the browsers identifiers which does not located in the first item in "extensions" in userAgent.
* browserMap: Record<string, string> // map the browser identifiers to readable string. For example, Edg -> Edge, OPR -> Opera
* }
* @example Safari
* @returns {Promise<string | null> | null}
* @overridable
*/
Environment.prototype.browser = function browser() {
var userAgent = this._window().navigator.userAgent;
if (!reflect.isDefinedNonNull(this._config) || !reflect.isDefinedNonNullNonEmpty(userAgent)) {
return null;
}
return this._config.value('browserMaps').then(function (browserConfig) {
if (!reflect.isDefinedNonNull(browserConfig)) {
return null;
}
var specifiedBrowsers = browserConfig.specifiedBrowsers || [];
var browserMap = browserConfig.browserMap || {};
var browserIdentifier = null;
for (var i = 0; i < specifiedBrowsers.length; i++) {
var specifiedBrowser = specifiedBrowsers[i];
if (userAgent.indexOf(specifiedBrowser) > -1) {
browserIdentifier = specifiedBrowser;
break;
}
}
if (!reflect.isDefinedNonNull(browserIdentifier)) {
// The Named capturing group: (?<name>...) requires "ECMAScript 2018"
var matches = userAgent.match(/Mozilla\/5.0 \((.*?)\)(\s|$)((.*?)\/(.*?)(\s)\(.*\))?(.*?)\/(.*?)(\s|$)/);
if (reflect.isDefinedNonNullNonEmpty(matches) && matches.length >= 8) {
browserIdentifier = matches[7];
}
}
if (reflect.isDefinedNonNull(browserIdentifier)) {
browserIdentifier = browserIdentifier.trim();
} else {
browserIdentifier = 'unknown';
}
var browser = browserMap[browserIdentifier] || browserIdentifier;
return browser;
});
};
/**
* The cookie string, e.g. "iTunes.cookie" (iTunes desktop), "iTunes.cookieForDefaultURL" (HTML iOS), "itms.cookie" (itml app), "document.cookie" (browser)
* NOTE: Callers should override this method if they want to supply a different cookie.
* @overridable
*/
Environment.prototype.cookie = function cookie() {
return this._window().document.cookie;
};
/**
* The URL that represents this page.
* Typically this is a "deep link" type URL.
* If no URL is available, this field may be omitted.
* @example "https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewGrouping?cc=us&mt=8"
* @returns {String}
* @overridable
*/
Environment.prototype.pageUrl = function pageUrl() {
return this._window().location.href;
};
/**
* The URL of the parent page, if the app is embedded in a parent context.
* Typically this is a "deep link" type URL.
* If no URL is available, or if the app is not embedded, this field may be omitted.
* @example "https://www.apple.com/blog/top-tracks.html"
* @returns {String}
* @overridable
* Note: due to iframe sandbox rules, the parent window's location may not be accessible.
* In that case, we fall back to document.referrer, which should be reliable if the app
* within the iframe is a single page app (document.referrer changes on every page turn).
* If the app in the iframe is not a single page app, we will have to persist the
* original referrer from the first page across page turns via e.g. localStorage.
* However, this use case is not currently needed by any client.
*/
Environment.prototype.parentPageUrl = function parentPageUrl() {
var windowObject = this._window();
var parentWindow = windowObject.parent;
var parentPageUrl;
if (parentWindow !== windowObject) {
try {
parentPageUrl = parentWindow.location.href;
} catch (e) {
parentPageUrl = this._document().referrer;
}
}
return parentPageUrl;
};
/**
* Pixel multiplier factor
* @example 2
* @returns {number}
* @overridable
*/
Environment.prototype.pixelRatio = function pixelRatio() {
return this._window().devicePixelRatio;
};
/**
* Client screen height in pixels
* @example 568
* @returns {number}
* @overridable
*/
Environment.prototype.screenHeight = function screenHeight() {
return this._window().screen.height;
};
/**
* Client screen width in pixels
* @example 320
* @returns {number}
* @overridable
*/
Environment.prototype.screenWidth = function screenWidth() {
return this._window().screen.width;
};
/**
* Clients user agent string. If the "app field is not provided, "userAgent may be used to derive the value of the "app field
* @example AppStore/2.0 iOS/8.3 model/iPhone7,2 build/12F70 (6; dt:106)
* @returns {String}
* @overridable
*/
Environment.prototype.userAgent = function userAgent() {
return this._window().navigator.userAgent;
};
/**
* App viewport height in pixels. Does not include window “chrome”, status bars, etc.
* Typically only available on desktop windowing systems.
* @example 1920
* @returns {number/undefined}
* @overridable
*/
Environment.prototype.windowInnerHeight = function windowInnerHeight() {
return this._window().innerHeight;
};
/**
* App viewport width in pixels. Does not include window “chrome”, status bars, etc.
* Typically only available on desktop windowing systems.
* @example 1080
* @returns {number/undefined}
* @overridable
*/
Environment.prototype.windowInnerWidth = function windowInnerWidth() {
return this._window().innerWidth;
};
/**
* Height in pixels of containing window, encompassing app viewport as well as window chrome, status bars, etc.
* Typically only available on desktop windowing systems.
* @example 1080
* @returns {number/undefined}
* @overridable
*/
Environment.prototype.windowOuterHeight = function windowOuterHeight() {
return this._window().outerHeight;
};
/**
* Width in pixels of containing window, encompassing app viewport as well as window chrome, status bars, etc.
* Typically only available on desktop windowing systems.
* @example 1920
* @returns {number/undefined}
* @overridable
*/
Environment.prototype.windowOuterWidth = function windowOuterWidth() {
return this._window().outerWidth;
};
/**
* The offset between W3C timing entry timestamps (which are relative to the page lifecycle) and the epoch time
* and the epoch time, in milliseconds
* @return {Number}
* @overridable
* Note: this is only currently used by PerfKit
* TODO: <rdar://problem/44976037> Refactor: Delegates: revisit HTML delegate packaging
*/
Environment.prototype.timeOriginOffset = function timeOriginOffset() {
var returnValue = null;
var performance = this._window().performance;
if (performance && performance.timing) {
returnValue = performance.timing.navigationStart;
}
return returnValue;
};
/**
* THE FOLLOWING DATA ARE UNAVAILABLE IN A PURE WEB BROWSER CONTEXT,
* BUT MAY BE IMPLEMENTED (VIA POTENTIALLY DIFFERENT APIS) IN VARIOUS HTML WEB VIEW CONTEXTS (iOS vs Desktop vs tvOS)
* THEY ARE LEFT UNIMPLEMENTED FOR CONTEXT-SPECIFIC DELEGATES TO OVERWRITE IF APPLICABLE
*/
/**
* The app identifier of the binary app
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @example "com.apple.appstore" or "com.apple.gamecenter"
* @returns {String}
* @overridable
*/
Environment.prototype.app = function app() {};
/**
* The version number of this application
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @example "1.0", "5.43", etc.
* @returns {String}
* @overridable
* @defaultimpl navigator.appVersion
*/
Environment.prototype.appVersion = function appVersion() {};
/**
* The total data capacity of the system, without regard for what's already been used or not.
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @returns {number}
* @overridable
*/
Environment.prototype.capacityData = function capacityData() {};
/**
* The total available data capacity of the system.
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @returns {number}
* @overridable
*/
Environment.prototype.capacityDataAvailable = function capacityDataAvailable() {};
/**
* The total disk capacity of the system, without regard for what's already been used or not.
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @returns {number}
* @overridable
*/
Environment.prototype.capacityDisk = function capacityDisk() {};
/**
* The total system capacity, without regard for what's already been used or not.
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @returns {number}
* @overridable
*/
Environment.prototype.capacitySystem = function capacitySystem() {};
/**
* The total available system capacity of the system.
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @api public
* @overridable
*/
Environment.prototype.capacitySystemAvailable = function capacitySystemAvailable() {};
/**
* Type of internet connection.
* Only applicable to devices
* Beware that users on WiFi may actually be receiving 3G speeds (i.e. if device is tethered to a portable hotspot.)
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @example "WiFi, "3G, etc.
* @returns {String}
* @overridable
*/
Environment.prototype.connectionType = function connectionType() {};
/**
* The id of this user ("directory service id").
* This id will get anonymized on the server prior to being saved.
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @example 659261189
* @returns {String}
* @overridable
*/
Environment.prototype.dsId = function dsId() {};
/**
* The hardware brand of the device. Not required for Apple devices.
* NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
* @example "Samsung", "LG", "Google"
* @returns {String}
*/
Environment.prototype.hardwareBrand = function hardwareBrand() {};
/**
* The hardware family of the device
* NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
* @example "iPhone", "Macbook Pro"
* @returns {String}
*/
Environment.prototype.hardwareFamily = function hardwareFamily() {};
/**
* The model of the device
* NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
* @example "iPhone10,2", "MacbookPro11,5"
* @returns {String}
*/
Environment.prototype.hardwareModel = function hardwareModel() {};
/**
* App that is hosting the storesheet or app
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @example com.rovio.AngryBirds
* @returns {String}
* @overridable
*/
Environment.prototype.hostApp = function hostApp() {};
/**
* Version of the app that is hosting the storesheet or app
* NO DEFAULT IMPLEMENTATION... HOWEVER: this field is optional
* @example "1.0.1"
* @returns {String}
* @overridable
*/
Environment.prototype.hostAppVersion = function hostAppVersion() {
// Optional field value
};
/**
* The name of the OS
* NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
* @example "ios", "macos", "windows"
* @returns {String}
*/
Environment.prototype.os = function os() {};
/**
* The build number of the OS
* NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
* @example "15D60", "17E192"
* @returns {String}
*/
Environment.prototype.osBuildNumber = function osBuildNumber() {};
/**
* A string array of language IDs, ordered in descending preference
* NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
* @example ["en-US", "fr-CA"]
* @returns {Array}
*/
Environment.prototype.osLanguages = function osLanguages() {};
/**
* The full OS version number
* In ITML, the value can be retrieved via Device.systemVersion
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @example "8.2.1" (iOS) "10.10.3" (Desktop)
* @returns {String}
* @overridable
*/
Environment.prototype.osVersion = function osVersion() {};
/**
* HTML resources revision number
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @example 2C97 or 8.4.0.0.103
* @returns {String}
* @overridable
*/
Environment.prototype.resourceRevNum = function resourceRevNum() {};
/**
* ISO 3166 Country Code. Apps that cannot provide a storeFrontHeader should provide a storeFrontCountryCode instead
* NO DEFAULT IMPLEMENTATION... Either this method or storeFrontHeader must be replaced.
* @example US
* @returns {String}
* @overridable
*/
Environment.prototype.storeFrontCountryCode = function storeFrontCountryCode() {};
/**
* The value contained in the X-Apple-Store-Front header value at the time the event is being created.
* NO DEFAULT IMPLEMENTATION... Either this method or storeFrontCountryCode must be replaced.
* @example K143441-1,29 ab:rSwnYxS0
* @returns {String}
* @overridable
*/
Environment.prototype.storeFrontHeader = function storeFrontHeader() {};
/**
* The best supported language for a storefront
* NO DEFAULT IMPLEMENTATION... HOWEVER: this field is optional
* @example en-US
* @returns {String}
* @overridable
*/
Environment.prototype.storeFrontLanguage = function storeFrontLanguage() {};
/**
* The type of subscriber this user is.
* NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
* @example subscribed, notSubscribed, unknown, needsAuthentication
* @returns {String}
* @overridable
*/
Environment.prototype.userType = function userType() {};
/*
* src/event_recorder
* mt-metricskit-delegates-web
*
* Copyright © 2022 Apple Inc. All rights reserved.
*
*/
/**
* A proxy EventRecorder to bridge the delegates layer to the mt-event-queue lib
* @param eventRecorder - A eventRecorder implementation from the mt-event-queue lib
* @constructor
*/
function EventRecorder(eventRecorder) {
AbstractEventRecorder.call(this);
this._proxyEventRecorder = eventRecorder;
this.SEND_METHOD = eventRecorder.SEND_METHOD;
}
EventRecorder.prototype = Object.create(AbstractEventRecorder.prototype);
EventRecorder.prototype.constructor = EventRecorder;
/**
* recordEvent implementation
* This is an implementation for "AbstractEventRecorder._recordEvent"
* @override
* @param topic
* @param eventFields
* @private
*/
EventRecorder.prototype._recordEvent = function _recordEvent(topic, eventFields) {
return this._proxyEventRecorder.recordEvent.apply(this._proxyEventRecorder, arguments);
};
/**
* This overrides the same method in AbstractEventRecorder to ignore the pending recorded event when the appExitSendMethod === 'SEND_METHOD.BEACON_SYNCHRONOUS'.
* @param {Boolean} appIsExiting - Pass true if events are being flushed due to your app exiting or page going away
* (the send method will be different in order to attempt to post events prior to actual termination)
* @param {String} appExitSendMethod (optional) the send method for how events will be flushed when the app is exiting.
* Possible options are enumerated in the `eventRecorder.SEND_METHOD` object.
* Note: This argument will be ignored if appIsExiting is false.
* @returns {Promise}
*/
EventRecorder.prototype.flushUnreportedEvents = function flushUnreportedEvents(appIsExiting, appExitSendMethod) {
var self = this;
var args = Array.prototype.slice.call(arguments);
// if this._proxyEventRecorder is an instance of QueuedEventRecorder and the callers wanted to flush events synchronously, ignore the pending events
if (
reflect.isDefinedNonNull(this._proxyEventRecorder.SEND_METHOD) &&
appExitSendMethod === this._proxyEventRecorder.SEND_METHOD.BEACON_SYNCHRONOUS
) {
return this._proxyEventRecorder.flushUnreportedEvents.apply(this._proxyEventRecorder, arguments);
} else {
return this._operationPromiseChain.then(function () {
// Reset the promise chain
self._operationPromiseChain = Promise.resolve();
return self._proxyEventRecorder.flushUnreportedEvents.apply(self._proxyEventRecorder, args);
});
}
};
/**
* Sends any remaining events in the queue
* This is an implementation for "AbstractEventRecorder._flushUnreportedEvents"
* @override
*/
EventRecorder.prototype._flushUnreportedEvents = function _flushUnreportedEvents() {
return this._proxyEventRecorder.flushUnreportedEvents.apply(this._proxyEventRecorder, arguments);
};
/**
* The methodology being used to send batches of events to the server
* This field should be hardcoded in the client based on what method it is using to encode and send its events to Figaro.
* The three typical values are:
* "itms" - use this value when/if JavaScript code enqueues events for sending via the "itms.recordEvent()" method in ITML.
* "itunes" - use this value when/if JavaScript code enqueues events by calling the "iTunes.recordEvent()" method in Desktop Store apps.
* "javascript" - use this value when/if JavaScript code enqueues events for sending via the JavaScript eventQueue management. This is typically only used by older clients which don't have the built-in functionality of itms or iTunes available to them.
* DEFAULT implementation: console.debug()
* @example "itms", "itunes", "javascript"
* @returns {String}
* @overridable
*/
EventRecorder.prototype.sendMethod = function sendMethod() {
return this._proxyEventRecorder.sendMethod.apply(this._proxyEventRecorder, arguments);
};
/**
* Set event queue related properties for the giving topic
* @param {String} topic defines the Figaro "topic" that this event should be stored under
* @param {Object} properties the event queue properties for the topic
* @param {Boolean} properties.anonymous true if sending all events for the topic with credentials omitted(no cookies, no PII fields)
*/
EventRecorder.prototype.setProperties = function setProperties(topic, properties) {
return this._proxyEventRecorder.setProperties.apply(this._proxyEventRecorder, arguments);
};
/**
* clean resources of event recorder
* Subclasses implement this method to handle how to clean resources
* @returns {Promise} returns a Promise if the cleanup will asynchronously execute or undefined for synchronously executing
*/
EventRecorder.prototype.cleanup = function cleanup() {
return this._proxyEventRecorder.cleanup.apply(this._proxyEventRecorder, arguments);
};
/*
* src/web_delegate.js
* mt-metricskit-delegates-web
*
* Copyright © 2022 Apple Inc. All rights reserved.
*
*/
/**
* Delegate for providing access to the "canned" web MetricsKit delegates.
* If further modification of these delegates is required, clients may pass in delegate options to override any of the fields within any delegate.
* @constructor
* @param {String} topic - Defines the AMP Analytics "topic" for events to be stored under
* @param {Object} delegateOptions (optional) - Options that can be passed to either add additional delegates or to extend/override existing ones.
*/
var WebDelegates = function WebDelegates(topic, delegateOptions) {
var config = this.getOrCreateConfig(topic);
Delegates.call(this, topic, {
environment: new Environment(config),
eventRecorder: new EventRecorder(new EventRecorder$1(config))
});
this.immediateEventRecorder = new EventRecorder(new ImmediateEventRecorder(config));
this.network = null;
this.logger = null;
if (delegateOptions) {
this.mergeDelegates(delegateOptions);
if (delegateOptions.environment) {
this.setEnvironment(delegateOptions.environment);
}
if (delegateOptions.eventRecorder) {
this.setEventRecorder(delegateOptions.eventRecorder);
}
if (delegateOptions.network) {
this.setNetwork(delegateOptions.network);
}
if (delegateOptions.logger) {
this.setLogger(delegateOptions.logger);
}
/**
* TODO: We are temporarily setting this configUrl() delegate on the config to avoid breaking changes.
* In the next major release, we will remove this and just fetch the config from
* the delegateOptions.config.url directly instead of reading it from
* this config method (defaulting to https://xp.apple.com/config/1/report/<topic>).
*/
if (delegateOptions.config) {
if (delegateOptions.config.url) {
this.config.setDelegate({
configUrl: delegateOptions.config.url
});
}
this.setConfig(delegateOptions.config);
}
}
};
/**
* Inherit from the base Delegate class
*/
WebDelegates.prototype = Object.create(Delegates.prototype);
WebDelegates.prototype.constructor = WebDelegates;
/**
* Sets the environment for the web-specific event queue as well as setting it on the delegate itself.
* @param {Object} environment
* @returns {WebDelegates}
*/
WebDelegates.prototype.setEnvironment = function (environment$1) {
environment.setDelegate(environment$1);
return Delegates.prototype.setEnvironment.call(this, environment$1);
};
/**
* Sets the network for the web-specific event queue as well as setting it on the delegate itself.
* @param {Object} network
* @returns {WebDelegates}
*/
WebDelegates.prototype.setNetwork = function (network$1) {
if (network$1) {
this.network = network$1;
network.setDelegate(network$1);
}
return this;
};
/**
* Sets the logger for the web-specific event queue as well as setting it on the delegate itself.
* @param {Object} logger
* @returns {WebDelegates}
*/
WebDelegates.prototype.setLogger = function (logger$1) {
if (logger$1) {
this.logger = logger$1;
logger.setDelegate(logger$1);
}
return this;
};
/**
* Sets the immediate event recorder for the delegate.
* Note: Immediate event recorders are specific to web delegates.
* @param {Object} immediateEventRecorder
* @returns {WebDelegates}
*/
WebDelegates.prototype.setImmediateEventRecorder = function (immediateEventRecorder) {
if (immediateEventRecorder) {
var newImmediateEventRecorder = Object.create(this.immediateEventRecorder);
Object.assign(newImmediateEventRecorder, immediateEventRecorder);
this.immediateEventRecorder = newImmediateEventRecorder;
}
return this;
};
/**
* @returns {Object} The config sources.
*/
WebDelegates.prototype.configSources = function configSources() {
var self = this;
return new Promise(function (resolve, reject) {
var onFetchSuccess = function onFetchSuccess(response) {
try {
var configObject = JSON.parse(response);
self.config.setCachedSource(configObject);
self.config.setServiceSource(configObject); // TODO: Deprecated
resolve(configObject);
} catch (error) {
onFetchFailure(error);
}
};
var onFetchFailure = function onFetchFailure(error) {
self.config.setCachedSource(self.config.cachedSource());
reject(error);
};
var configUrlPromise = Promise.resolve(self.config.configUrl());
configUrlPromise
.then(function (configUrl) {
backoff.exponentialBackoff(
self.config.network.makeAjaxRequest.bind(self.config.network, configUrl, 'GET', null),
onFetchSuccess,
onFetchFailure
);
})
.catch(onFetchFailure);
});
};
export { Environment, WebDelegates };