UNPKG

@yqg/multiple-click

Version:

Monitor user's multiple click behavior and report

207 lines (171 loc) 6.48 kB
import { CONFIG_CENTER_HOST_MAP, DEFAULT_TRIGGER_CONTINUOUS_COUNT, DEFAULT_TRIGGER_INTERVAL, DEFAULT_TRIGGER_RANGE, EVENT_TRACKING_HOST_PROD_INDO, EVENT_TRACKING_HOST_PROD, EVENT_TRACKING_HOST_TEST, OUTER_HTML_MAX_LENGTH } from './constant'; import { ENV_TYPE, ExtraTrackingInfo, IExposeMultipleClickTrackingConfig, MultipleClickTrackingConfig, RemoteMultipleClickTrackingConfig } from './type'; import {castArray, getCountry, getOsType, htmlTreeAsString, pick} from './utils'; /** 获取上报的url */ const getTrackingUrl = ({env}: IExposeMultipleClickTrackingConfig) => { let trackingHost; if (env === ENV_TYPE.PROD_INDO) { trackingHost = EVENT_TRACKING_HOST_PROD_INDO; } else if (env.startsWith('prod')) { trackingHost = EVENT_TRACKING_HOST_PROD; } else if (env !== ENV_TYPE.DEV) { trackingHost = EVENT_TRACKING_HOST_TEST; } const trackingUrl = trackingHost ? `${trackingHost}/logMetrics` : null; return trackingUrl; }; /** 拉取远程配置 */ const fetchRemoteConfig = ({ appId, env }: IExposeMultipleClickTrackingConfig): Promise<RemoteMultipleClickTrackingConfig> => { const configCenterHost = CONFIG_CENTER_HOST_MAP[env]; if (!configCenterHost) { return Promise.resolve({}); } const configUrl = `${configCenterHost}/api-web/hostCond?app=web-multiple-click&prefix=${appId}`; return fetch(configUrl) .then(async res => { const data = (await res.json())?.body?.[appId]; const config: RemoteMultipleClickTrackingConfig = data ? JSON.parse(data) : {}; return pick(config, ['disabled', 'interval', 'continuousCount', 'range', 'excludeRules']); }) .catch(() => ({})); }; /** 日志上报方法的工厂函数 */ const uploadTrackingInfoFactory = (config: IExposeMultipleClickTrackingConfig) => { const {appId, appVersion, customTrackingInfo} = config; // 信息上报url const trackingUrl = getTrackingUrl(config); // 操作系统类型 const osType = getOsType(); // 请求头 const headers = { // eslint-disable-next-line quote-props Country: getCountry(), 'YQG-PLATFORM-SDK-TYPE': appId, 'CONTENT-TYPE': 'application/json;charset=UTF-8' }; // 事件上报 const extraInfoGetter = ({target: _target}: MouseEvent): ExtraTrackingInfo => { const target = _target as HTMLElement; const {outerHTML} = target; const {x: clientLeft, y: clientTop} = target.getBoundingClientRect(); return { url: location.href, outerHTML: outerHTML.slice(0, OUTER_HTML_MAX_LENGTH), selector: htmlTreeAsString(target), offsetTop: clientTop + window.scrollY, offsetLeft: clientLeft + window.scrollX, clientTop, clientLeft }; }; const uploadTrackingInfo = (event: MouseEvent) => { const trackingInfo = customTrackingInfo(event, extraInfoGetter(event)); if (!trackingUrl) { // eslint-disable-next-line no-console console.info('无需上报多次点击事件,事件信息:', trackingInfo); return; } const info = { level: 'INFO', logs: [ { appId, appVersion: appVersion || null, osType, measurement: 'app_metrics_for_multiple_click', metricsType: 'metricsType1', time: Date.now().toString(), message: 'multiple click', parameter: trackingInfo } ] }; fetch(trackingUrl, {method: 'post', headers, body: JSON.stringify(info)}).catch(err => // eslint-disable-next-line no-console console.error('多次点击上报异常', err) ); }; return uploadTrackingInfo; }; /** 配置的检查、转换 */ export const processTrackingConfig = async ( config: IExposeMultipleClickTrackingConfig ): Promise<MultipleClickTrackingConfig> => { const {disabled, ...remoteConfig} = await fetchRemoteConfig(config); if (disabled) { return null; } const defaultConfig = { interval: DEFAULT_TRIGGER_INTERVAL, continuousCount: DEFAULT_TRIGGER_CONTINUOUS_COUNT, range: DEFAULT_TRIGGER_RANGE, customTrackingInfo: (_: Event, extra: ExtraTrackingInfo) => extra, filter: () => false }; const mergedConfig = { ...defaultConfig, ...config, ...remoteConfig, excludeRules: [...castArray(config.excludeRules || []), ...castArray(remoteConfig.excludeRules || [])] } as Required<IExposeMultipleClickTrackingConfig>; const {appId, appVersion, env, interval, continuousCount, range, customTrackingInfo, filter, excludeRules} = mergedConfig; // 配置检查 if (typeof appId !== 'string' || !appId) { throw new Error('appId需为非空字符串'); } if (appVersion && typeof appVersion !== 'string') { throw new Error('appVersion需为字符串'); } const envTypeList = Object.values(ENV_TYPE); if (!envTypeList.includes(env)) { throw new Error(`env需为${envTypeList.join('、')}中的一个`); } if (typeof interval !== 'number' || interval <= 0) { throw new Error('interval需为大于0的数字'); } if (typeof continuousCount !== 'number' || continuousCount <= 1) { throw new Error('continuousCount需为大于1的数字'); } if (typeof range !== 'number' || range <= 0) { throw new Error('range需为大于0的数字'); } if (typeof customTrackingInfo !== 'function') { throw new Error('customTrackingInfo需为函数'); } if (typeof filter !== 'function') { throw new Error('filter需为函数'); } const validatedConfig = { ...config, interval, continuousCount, range, customTrackingInfo, filter }; const uploadTrackingInfo = uploadTrackingInfoFactory(validatedConfig); const parsedExcludeRules = excludeRules.map(({page, ...rule}) => ({ ...rule, page: page ? castArray(page) : [] })); return {interval, continuousCount, range, uploadTrackingInfo, filter, excludeRules: parsedExcludeRules}; };