@amo-tm/wsc
Version:
The amo WSC component of the amo JS SDK
658 lines (640 loc) • 26.2 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var tslib = require('tslib');
var Deferred = /** @class */ (function () {
function Deferred() {
var _this = this;
this.reject = function () { };
this.resolve = function () { };
this.promise = new Promise(function (resolve, reject) {
_this.resolve = resolve;
_this.reject = reject;
});
}
return Deferred;
}());
var DEFAULT_INSTANCE_NAME = '[DEFAULT_INSTANCE_NAME]';
/**
* Provider for instance for service name T, e.g. 'wsc', 'wsc-connector-internal'
* NameServiceMapping[T] is an alias for the type of the instance
*/
var Provider = /** @class */ (function () {
function Provider(name, container) {
this.name = name;
this.container = container;
this.component = null;
this.instances = new Map();
this.instancesDeferred = new Map();
}
Provider.prototype.get = function () {
var normalizedIdentifier = this.normalizeInstanceIdentifier();
if (this.instances.has(normalizedIdentifier)) {
return Promise.resolve(this.instances.get(normalizedIdentifier));
}
if (!this.instancesDeferred.has(normalizedIdentifier)) {
if (this.isInitialized(normalizedIdentifier) || this.shouldAutoInitialize()) {
// initialize the service if it can be auto-initialized
try {
void this.getOrInitializeService({
instanceIdentifier: normalizedIdentifier,
});
}
catch (e) {
// when the instance factory throws an exception during get(), it should not cause
// a fatal error. We just return the unresolved promise in this case.
}
}
else {
var deferred = new Deferred();
this.instancesDeferred.set(normalizedIdentifier, deferred);
}
}
return this.instancesDeferred.get(normalizedIdentifier).promise;
};
Provider.prototype.getImmediate = function (options) {
var _a;
var normalizedIdentifier = this.normalizeInstanceIdentifier();
var optional = (_a = options === null || options === void 0 ? void 0 : options.optional) !== null && _a !== void 0 ? _a : false;
if (this.isInitialized(normalizedIdentifier) || this.shouldAutoInitialize()) {
try {
var instanceOrInstancePromise = this.getOrInitializeService({
instanceIdentifier: normalizedIdentifier,
});
if (instanceOrInstancePromise instanceof Promise) {
if (optional) {
return null;
}
else {
throw Error("Service ".concat(this.name, " is not ready."));
}
}
return instanceOrInstancePromise;
}
catch (e) {
if (optional) {
return null;
}
else {
throw e;
}
}
}
else {
// In case a component is not initialized and should/can not be auto-initialized at the moment, return null if the optional flag is set, or throw
if (optional) {
return null;
}
else {
throw Error("Service ".concat(this.name, " is not available."));
}
}
};
Provider.prototype.setComponent = function (component) {
if (component.name !== this.name) {
throw Error("Mismatching Component ".concat(component.name, " for Provider ").concat(this.name, "."));
}
if (this.component) {
throw Error("Component for ".concat(this.name, " has already been provided"));
}
this.component = component;
// return early without attempting to initialize the component
if (!this.shouldAutoInitialize()) {
return;
}
// if the service is eager, initialize the default instance
if (isComponentEager(component)) {
try {
void this.getOrInitializeService({ instanceIdentifier: DEFAULT_INSTANCE_NAME });
}
catch (e) {
// when the instance factory for an eager Component throws an exception during the eager
// initialization, it should not cause a fatal error.
}
}
};
Provider.prototype.isComponentSet = function () {
return this.component !== null;
};
Provider.prototype.isInitialized = function (identifier) {
if (identifier === void 0) { identifier = DEFAULT_INSTANCE_NAME; }
return this.instances.has(identifier);
};
Provider.prototype.getOrInitializeService = function (_a) {
var _this = this;
var instanceIdentifier = _a.instanceIdentifier, _b = _a.options, options = _b === void 0 ? {} : _b;
var instance = this.instances.get(instanceIdentifier);
var instanceDeferred = this.instancesDeferred.get(instanceIdentifier);
if (this.component && !(instance || instanceDeferred)) {
var instanceOrInstancePromise = this.component.instanceFactory(this.container, {
options: options,
});
if (instanceOrInstancePromise instanceof Promise) {
instanceDeferred = instanceDeferred || new Deferred();
this.instancesDeferred.set(instanceIdentifier, instanceDeferred);
instanceOrInstancePromise.then(function (instance) {
var currentInstanceDeferred = _this.instancesDeferred.get(instanceIdentifier);
if (!currentInstanceDeferred || currentInstanceDeferred !== instanceDeferred) {
return;
}
_this.instances.set(instanceIdentifier, instance);
_this.instancesDeferred.delete(instanceIdentifier);
currentInstanceDeferred.resolve(instance);
}, instanceDeferred.reject);
}
else {
instance = instanceOrInstancePromise;
this.instances.set(instanceIdentifier, instance);
}
}
return instance || (instanceDeferred === null || instanceDeferred === void 0 ? void 0 : instanceDeferred.promise) || null;
};
Provider.prototype.normalizeInstanceIdentifier = function () {
return DEFAULT_INSTANCE_NAME;
};
Provider.prototype.shouldAutoInitialize = function () {
return Boolean(this.component);
};
return Provider;
}());
function isComponentEager(component) {
return component.instantiationMode === "EAGER" /* EAGER */;
}
/**
* ComponentContainer that provides Providers for service name T, e.g. `wsc`, `wsc-connector-internal`
*/
var ComponentContainer = /** @class */ (function () {
function ComponentContainer(name) {
this.name = name;
this.providers = new Map();
}
/**
* @param component Component being added
* @description When a component with the same name has already been registered throw an exception
*/
ComponentContainer.prototype.addComponent = function (component) {
var provider = this.getProvider(component.name);
if (provider.isComponentSet()) {
throw new Error("Component ".concat(component.name, " has already been registered with ").concat(this.name));
}
provider.setComponent(component);
};
/**
* getProvider provides a type safe interface where it can only be called with a field name
* present in NameServiceMapping interface.
*
* Amo SDKs providing services should extend NameServiceMapping interface to register
* themselves.
*/
ComponentContainer.prototype.getProvider = function (name) {
if (this.providers.has(name)) {
return this.providers.get(name);
}
// create a Provider for a service that hasn't registered with Amo
var provider = new Provider(name, this);
this.providers.set(name, provider);
return provider;
};
return ComponentContainer;
}());
var _a;
/**
* The JS SDK supports 5 log levels and also allows a user the ability to
* silence the logs altogether.
*
* The order is a follows:
* DEBUG < VERBOSE < INFO < WARN < ERROR
*
* All of the log types above the current log level will be captured (i.e. if
* you set the log level to `INFO`, errors will still be logged, but `DEBUG` and
* `VERBOSE` logs will not)
*/
var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
LogLevel[LogLevel["VERBOSE"] = 1] = "VERBOSE";
LogLevel[LogLevel["INFO"] = 2] = "INFO";
LogLevel[LogLevel["WARN"] = 3] = "WARN";
LogLevel[LogLevel["ERROR"] = 4] = "ERROR";
LogLevel[LogLevel["SILENT"] = 5] = "SILENT";
})(LogLevel || (LogLevel = {}));
var levelStringToEnum = {
debug: LogLevel.DEBUG,
verbose: LogLevel.VERBOSE,
info: LogLevel.INFO,
warn: LogLevel.WARN,
error: LogLevel.ERROR,
silent: LogLevel.SILENT,
};
/**
* The default log level
*/
var defaultLogLevel = LogLevel.INFO;
/**
* By default, `console.debug` is not displayed in the developer console (in
* chrome). To avoid forcing users to have to opt-in to these logs twice
* (i.e. once for amo, and once in the console), we are sending `DEBUG`
* logs to the `console.log` function.
*/
var ConsoleMethod = (_a = {},
_a[LogLevel.DEBUG] = 'log',
_a[LogLevel.VERBOSE] = 'log',
_a[LogLevel.INFO] = 'info',
_a[LogLevel.WARN] = 'warn',
_a[LogLevel.ERROR] = 'error',
_a);
/**
* The default log handler will forward DEBUG, VERBOSE, INFO, WARN, and ERROR
* messages on to their corresponding console counterparts (if the log method
* is supported by the current log level)
*/
var defaultLogHandler = function (instance, logType) {
var args = [];
for (var _i = 2; _i < arguments.length; _i++) {
args[_i - 2] = arguments[_i];
}
if (logType < instance.logLevel) {
return;
}
var now = new Date().toISOString();
var method = ConsoleMethod[logType];
if (method) {
console[method].apply(console, tslib.__spreadArray(["[".concat(now, "] ").concat(instance.name, ":")], tslib.__read(args), false));
}
else {
throw new Error("Attempted to log a message with an invalid logType (value: ".concat(logType, ")"));
}
};
var Logger = /** @class */ (function () {
/**
* Gives you an instance of a Logger to capture messages according to
* Amo's logging scheme.
*
* @param name The name that the logs will be associated with
*/
function Logger(name) {
this.name = name;
/**
* The log level of the given Logger instance.
*/
this._logLevel = defaultLogLevel;
/**
* The main (internal) log handler for the Logger instance.
* Can be set to a new function in internal package code but not by user.
*/
this._logHandler = defaultLogHandler;
}
Object.defineProperty(Logger.prototype, "logLevel", {
get: function () {
return this._logLevel;
},
set: function (val) {
if (!(val in LogLevel)) {
throw new TypeError("Invalid value \"".concat(val, "\" assigned to `logLevel`"));
}
this._logLevel = val;
},
enumerable: false,
configurable: true
});
// Workaround for setter/getter having to be the same type.
Logger.prototype.setLogLevel = function (val) {
this._logLevel = typeof val === 'string' ? levelStringToEnum[val] : val;
};
Object.defineProperty(Logger.prototype, "logHandler", {
get: function () {
return this._logHandler;
},
set: function (val) {
if (typeof val !== 'function') {
throw new TypeError('Value assigned to `logHandler` must be a function');
}
this._logHandler = val;
},
enumerable: false,
configurable: true
});
/**
* The functions below are all based on the `console` interface
*/
Logger.prototype.debug = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
this._logHandler.apply(this, tslib.__spreadArray([this, LogLevel.DEBUG], tslib.__read(args), false));
};
Logger.prototype.log = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
this._logHandler.apply(this, tslib.__spreadArray([this, LogLevel.VERBOSE], tslib.__read(args), false));
};
Logger.prototype.info = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
this._logHandler.apply(this, tslib.__spreadArray([this, LogLevel.INFO], tslib.__read(args), false));
};
Logger.prototype.warn = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
this._logHandler.apply(this, tslib.__spreadArray([this, LogLevel.WARN], tslib.__read(args), false));
};
Logger.prototype.error = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
this._logHandler.apply(this, tslib.__spreadArray([this, LogLevel.ERROR], tslib.__read(args), false));
};
return Logger;
}());
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var instanceFactoriesDeferred = new Map();
var _registerRemoteInstanceFactory = function (instanceFactory) {
var currentScript = document.currentScript;
if (!currentScript || !(currentScript instanceof HTMLScriptElement)) {
return;
}
var remoteInstanceUrl = currentScript.src;
var instanceFactoryDeferred = instanceFactoriesDeferred.get(remoteInstanceUrl);
if (!instanceFactoryDeferred) {
return;
}
instanceFactoryDeferred.resolve(instanceFactory);
};
var RemoteInstanceLoader = /** @class */ (function () {
function RemoteInstanceLoader(url) {
this.url = url;
}
RemoteInstanceLoader.prototype.getInstanceFactory = function () {
var _this = this;
return function (container, options) {
var instanceFactoryDeferred = instanceFactoriesDeferred.get(_this.url);
if (!instanceFactoryDeferred) {
instanceFactoryDeferred = new Deferred();
instanceFactoriesDeferred.set(_this.url, instanceFactoryDeferred);
_this.initializeRemoteScript(_this.url);
}
return instanceFactoryDeferred.promise.then(function (instanceFactory) {
return instanceFactory(container, options);
});
};
};
RemoteInstanceLoader.prototype.initializeRemoteScript = function (url) {
var scriptElement = document.createElement('script');
scriptElement.type = 'text/javascript';
scriptElement.async = true;
scriptElement.src = url;
var anchorElement = document.getElementsByTagName('script')[0];
anchorElement.parentNode.insertBefore(scriptElement, anchorElement);
};
return RemoteInstanceLoader;
}());
/**
* The default container name
*
* @internal
*/
var DEFAULT_CONTAINER_NAME = '[DEFAULT_CONTAINER_NAME]';
var container = new ComponentContainer(DEFAULT_CONTAINER_NAME);
var logger = new Logger('app');
window.AmoJsSdk = tslib.__assign(tslib.__assign({}, (window.AmoJsSdk || {})), { _registerInstanceFactory: _registerRemoteInstanceFactory });
/**
* Registered components.
*
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var _components = new Map();
/**
* @param component - the component being added to the default container
* @internal
*/
function _addComponent(component) {
try {
container.addComponent(component);
}
catch (e) {
logger.debug("Component ".concat(component.name, " failed to register with ComponentContainer ").concat(container.name), e);
}
}
/**
*
* @param component - the component to register
* @returns whether or not the component is registered successfully
*
* @internal
*/
function _registerComponent(component) {
var componentName = component.name;
if (_components.has(componentName)) {
logger.debug("There were multiple attempts to register component ".concat(componentName, "."));
return false;
}
_components.set(componentName, component);
// add the component to existing container instances
_addComponent(component);
return true;
}
/**
* @param name - service name
*
* @returns the provider for the service with the matching name
*
* @internal
*/
function _getProvider(name) {
return container.getProvider(name);
}
/**
* Component for service name T, e.g. `wsc`
*/
var Component = /** @class */ (function () {
/**
*
* @param name The public service name, e.g. wsc, wsc-connector-internal
* @param instanceFactory Service factory responsible for creating the public interface
*/
function Component(name, instanceFactory) {
this.name = name;
this.instanceFactory = instanceFactory;
this.instantiationMode = "LAZY" /* LAZY */;
}
Component.prototype.setInstantiationMode = function (mode) {
this.instantiationMode = mode;
return this;
};
return Component;
}());
var WscService = /** @class */ (function () {
function WscService(wscConnectorInnerProvider) {
this.wscConnectorInnerProvider = wscConnectorInnerProvider;
this.currentWscParams = null;
this.iframeElementDeffered = null;
this.connectingPromise = null;
}
WscService.prototype.updateParams = function (wscParams) {
// Preload wscConnectorInner
void this.wscConnectorInnerProvider.get();
this.currentWscParams = wscParams;
this.connectingPromise = null;
this.iframeElementDeffered = null;
};
WscService.prototype.getCurrentWscParams = function () {
return this.currentWscParams;
};
WscService.prototype.connectIframe = function (wscParams) {
return tslib.__awaiter(this, void 0, void 0, function () {
var connectingPromise_1;
var _this = this;
return tslib.__generator(this, function (_a) {
if (!this.connectingPromise) {
connectingPromise_1 = new Promise(function (resolve) { return tslib.__awaiter(_this, void 0, void 0, function () {
var _a, iframeElement, wscConnectorInner;
return tslib.__generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, Promise.all([
this.getIframeElement(),
this.wscConnectorInnerProvider.get(),
])];
case 1:
_a = tslib.__read.apply(void 0, [_b.sent(), 2]), iframeElement = _a[0], wscConnectorInner = _a[1];
if (connectingPromise_1 !== this.connectingPromise) {
return [2 /*return*/];
}
if (iframeElement instanceof Error) {
resolve(iframeElement);
return [2 /*return*/];
}
if (wscParams !== this.currentWscParams) {
resolve(new Error('The Wsc was reinitialized with other params.'));
return [2 /*return*/];
}
return [4 /*yield*/, wscConnectorInner.initializeIframe(iframeElement, wscParams)];
case 2:
_b.sent();
if (connectingPromise_1 !== this.connectingPromise) {
return [2 /*return*/];
}
resolve();
return [2 /*return*/];
}
});
}); });
this.connectingPromise = connectingPromise_1;
}
return [2 /*return*/, this.connectingPromise];
});
});
};
WscService.prototype.getIframeElement = function () {
return tslib.__awaiter(this, void 0, void 0, function () {
var deffered_1;
var _this = this;
return tslib.__generator(this, function (_a) {
if (!this.currentWscParams) {
return [2 /*return*/, new Error('The Wsc should be initialized before.')];
}
if (!this.iframeElementDeffered) {
deffered_1 = new Deferred();
this.iframeElementDeffered = deffered_1;
void this.wscConnectorInnerProvider
.get()
.then(function (wscConnectorInner) {
return wscConnectorInner.createIframeElement();
})
.then(function (iframeElement) {
if (deffered_1 === _this.iframeElementDeffered) {
deffered_1.resolve(iframeElement);
}
});
}
return [2 /*return*/, this.iframeElementDeffered.promise];
});
});
};
return WscService;
}());
var WscFactory = function (container) {
return new WscService(container.getProvider('wsc-connector-inner'));
};
function registerWsc() {
_registerComponent(new Component('wsc-connector-inner', new RemoteInstanceLoader('https://js.amo.tm/v1.3/wsc/connector.js').getInstanceFactory()));
_registerComponent(new Component('wsc', WscFactory));
}
var initializeWsc$1 = function (wscParams) {
var wsc = _getProvider('wsc').getImmediate();
wsc.updateParams(wscParams);
};
var mountWsc$1 = function (options) { return tslib.__awaiter(void 0, void 0, void 0, function () {
var wsc, wscParams, containerElement, iframeElement, wscParamsAfterIframeElementGet, connectIframeResult;
return tslib.__generator(this, function (_a) {
switch (_a.label) {
case 0:
wsc = _getProvider('wsc').getImmediate();
wscParams = wsc.getCurrentWscParams();
if (!wscParams) {
if (options.onError) {
options.onError(new Error('The Wsc should be initialized before mount.'));
}
return [2 /*return*/];
}
containerElement = null;
if (options.container instanceof HTMLElement) {
containerElement = options.container;
}
else {
containerElement = document.querySelector(options.container);
}
if (!containerElement) {
if (options.onError) {
options.onError(new Error('The container element is not found.'));
}
return [2 /*return*/];
}
return [4 /*yield*/, wsc.getIframeElement()];
case 1:
iframeElement = _a.sent();
wscParamsAfterIframeElementGet = wsc.getCurrentWscParams();
if (wscParamsAfterIframeElementGet !== wscParams) {
return [2 /*return*/];
}
if (iframeElement instanceof Error) {
if (options.onError) {
options.onError(iframeElement);
}
return [2 /*return*/];
}
containerElement.innerHTML = '';
containerElement.append(iframeElement);
return [4 /*yield*/, wsc.connectIframe(wscParams)];
case 2:
connectIframeResult = _a.sent();
if (connectIframeResult instanceof Error) {
if (options.onError) {
options.onError(connectIframeResult);
}
return [2 /*return*/];
}
if (options.onSuccess) {
options.onSuccess();
}
return [2 /*return*/];
}
});
}); };
var initializeWsc = function (wscParams) {
initializeWsc$1(wscParams);
};
var mountWsc = function (options) {
void mountWsc$1(options);
};
registerWsc();
exports.initializeWsc = initializeWsc;
exports.mountWsc = mountWsc;
//# sourceMappingURL=index.cjs.js.map