├── .nvmrc ├── .prettierignore ├── src ├── inject │ ├── index.ts │ ├── inject.ts │ ├── types.ts │ └── inject.test.ts ├── providers │ ├── hit │ │ └── index.ts │ ├── destruct │ │ ├── index.ts │ │ └── const.ts │ ├── sameSite │ │ ├── index.ts │ │ ├── sameSite.ts │ │ └── __tests__ │ │ │ └── sameSite.spec.ts │ ├── searchTLD │ │ ├── index.ts │ │ ├── searchTLD.ts │ │ └── __test__ │ │ │ └── searchTLD.spec.ts │ ├── callbackInit │ │ ├── index.ts │ │ └── callbackInit.ts │ ├── ecommerce │ │ ├── const.ts │ │ └── types.ts │ ├── phoneHide │ │ ├── const.ts │ │ └── index.ts │ ├── userParams │ │ ├── types.ts │ │ ├── const.ts │ │ └── index.ts │ ├── firstPartyMethod │ │ ├── process │ │ │ ├── index.ts │ │ │ └── const.ts │ │ ├── const.ts │ │ ├── types.ts │ │ └── index.ts │ ├── stackProxy │ │ ├── const.ts │ │ ├── index.ts │ │ └── types.ts │ ├── retransmit │ │ ├── const.ts │ │ └── retransmit.ts │ ├── artificialHit │ │ ├── const.ts │ │ └── type.ts │ ├── counterOptions │ │ ├── index.ts │ │ ├── const.ts │ │ └── types.ts │ ├── firstPaint │ │ ├── const.ts │ │ └── index.ts │ ├── debugConsole │ │ ├── const.ts │ │ ├── index.ts │ │ └── debugEnabled.ts │ ├── yan │ │ └── index.ts │ ├── trackHash │ │ ├── const.ts │ │ └── index.ts │ ├── getClientID │ │ ├── index.ts │ │ ├── const.ts │ │ └── getClientID.ts │ ├── statusCheck │ │ ├── index.ts │ │ ├── urlSearchParams.ts │ │ ├── statusCheckFn.ts │ │ └── statusCheck.ts │ ├── triggerEvent │ │ ├── index.ts │ │ └── triggerEvent.ts │ ├── submitTracking │ │ └── index.ts │ ├── consoleRenderer │ │ └── index.ts │ ├── enableAll │ │ ├── const.ts │ │ └── index.ts │ ├── index.ts │ ├── reportNonNativeFunctions │ │ └── report.ts │ ├── clickmapMethod │ │ ├── const.ts │ │ ├── index.ts │ │ ├── clickmapMethod.ts │ │ └── __tests__ │ │ │ └── clickmapMethod.spec.ts │ ├── setUserID │ │ ├── const.ts │ │ └── index.ts │ ├── getCounters │ │ ├── const.ts │ │ └── types.ts │ ├── clickmap │ │ └── type.ts │ ├── remoteControl │ │ └── index.ts │ ├── clickTracking │ │ └── index.ts │ ├── clicks │ │ ├── getTextFromLink.ts │ │ └── index.ts │ ├── params │ │ └── const.ts │ ├── siteStatistics │ │ ├── index.ts │ │ └── __tests__ │ │ │ └── layout.spec.ts │ ├── goal │ │ ├── const.ts │ │ └── index.ts │ ├── turboParams │ │ └── index.ts │ └── notBounce │ │ └── const.ts ├── transport │ ├── xhr │ │ └── index.ts │ ├── fetch │ │ └── index.ts │ ├── image │ │ └── index.ts │ ├── jsonp │ │ └── index.ts │ ├── beacon │ │ └── index.ts │ ├── __tests__ │ │ └── transport.spec.ts │ └── watchModes.ts ├── utils │ ├── json │ │ ├── index.ts │ │ ├── __tests__ │ │ │ └── json.spec.ts │ │ └── json.ts │ ├── uid │ │ └── index.ts │ ├── url │ │ └── index.ts │ ├── console │ │ ├── index.ts │ │ └── console.ts │ ├── direct │ │ ├── index.ts │ │ └── direct.ts │ ├── encoder │ │ ├── index.ts │ │ └── encoder.ts │ ├── history │ │ ├── index.ts │ │ └── history.ts │ ├── string │ │ ├── index.ts │ │ ├── types.ts │ │ ├── startsWith.ts │ │ ├── __test__ │ │ │ ├── remove.spec.ts │ │ │ └── string.spec.ts │ │ ├── remove.ts │ │ └── repeat.ts │ ├── asyncMap │ │ ├── index.ts │ │ └── __tests__ │ │ │ └── AsyncMap.spec.ts │ ├── condition │ │ ├── index.ts │ │ ├── condition.ts │ │ └── __tests__ │ │ │ └── condition.spec.ts │ ├── treeWalker │ │ └── index.ts │ ├── deobfuscate │ │ ├── index.ts │ │ ├── deobfuscate.ts │ │ └── __tests__ │ │ │ └── deobfuscate.spec.ts │ ├── querystring │ │ ├── index.ts │ │ ├── safeEncodeURI.ts │ │ └── __tests__ │ │ │ └── querystring.spec.ts │ ├── turboParams │ │ ├── index.ts │ │ └── turboParams.ts │ ├── isCounterSilent │ │ ├── index.ts │ │ └── isCounterSilent.ts │ ├── userTimeDefer │ │ └── index.ts │ ├── methodDecorators │ │ ├── telCallCount │ │ │ ├── index.ts │ │ │ └── telCallCount.ts │ │ ├── errors.ts │ │ ├── types.ts │ │ ├── destructing.ts │ │ ├── selfReturn.ts │ │ └── decoratorPipe.ts │ ├── function │ │ ├── bind │ │ │ ├── index.ts │ │ │ ├── ctxBind.ts │ │ │ └── __test__ │ │ │ │ └── bind.spec.ts │ │ ├── noop.ts │ │ ├── construct.ts │ │ ├── cont.ts │ │ ├── isNativeFunction │ │ │ ├── index.ts │ │ │ ├── toNativeOrFalse.ts │ │ │ ├── isNativeFunction.ts │ │ │ ├── getNativeFunction.ts │ │ │ └── __tests__ │ │ │ │ ├── isNative.spec.ts │ │ │ │ └── isNativeFunction.spec.ts │ │ ├── identity.ts │ │ ├── args.ts │ │ ├── utils.ts │ │ ├── finallyCallUserCallback.ts │ │ ├── pipe.ts │ │ ├── curry.ts │ │ ├── callUserCallback.ts │ │ ├── __tests__ │ │ │ └── isNativeFunction.spec.ts │ │ └── memo.ts │ ├── counterSettings │ │ ├── const.ts │ │ └── types.ts │ ├── browser │ │ ├── const.ts │ │ ├── utils.ts │ │ └── firefox.ts │ ├── errorLogger │ │ ├── throwFunction.ts │ │ ├── consts.ts │ │ ├── onError.ts │ │ └── knownError.ts │ ├── ecommerce │ │ ├── index.ts │ │ └── waitForDataLayer.ts │ ├── iframeConnector │ │ ├── index.ts │ │ └── const.ts │ ├── counterOptions │ │ ├── index.ts │ │ ├── getCounterKey.ts │ │ └── counterOptionsStore.ts │ ├── phones │ │ ├── index.ts │ │ ├── isBrokenPhones.ts │ │ ├── const.ts │ │ └── phonesSubscribe.ts │ ├── object │ │ ├── index.ts │ │ ├── has.ts │ │ ├── types.ts │ │ ├── isPrimitive.ts │ │ ├── isPlainObject.ts │ │ ├── assertions.ts │ │ └── mix.ts │ ├── dom │ │ ├── nonce.ts │ │ ├── isRemovedFromDoc.ts │ │ ├── queySelect.ts │ │ ├── form.ts │ │ ├── selection.ts │ │ ├── block.ts │ │ ├── __tests__ │ │ │ ├── nodeName.spec.ts │ │ │ └── matches.spec.ts │ │ ├── nodeText.ts │ │ ├── targetLink.ts │ │ └── insertScript.ts │ ├── array │ │ ├── sort.ts │ │ ├── __tests__ │ │ │ └── merge.spec.ts │ │ ├── arrayFrom.ts │ │ ├── merge.ts │ │ ├── reverse.ts │ │ ├── find.ts │ │ ├── isArray.ts │ │ ├── join.ts │ │ ├── every.ts │ │ ├── some.ts │ │ ├── includes.ts │ │ └── filter.ts │ ├── boolean │ │ ├── index.ts │ │ └── __tests__ │ │ │ └── index.spec.ts │ ├── counter │ │ ├── type.ts │ │ └── getInstance.ts │ ├── location │ │ └── __tests__ │ │ │ └── location.spec.ts │ ├── fnv32a │ │ └── fnv32a.ts │ ├── defer │ │ ├── base.ts │ │ └── defer.ts │ ├── async │ │ └── helpers.ts │ ├── number │ │ ├── random.ts │ │ ├── __tests__ │ │ │ └── number.spec.ts │ │ └── number.ts │ ├── events │ │ ├── bufferObserver.ts │ │ ├── ready.ts │ │ ├── throttleObserver.ts │ │ └── asyncHandlerObserver.ts │ ├── debugEvents │ │ └── wrapLoggerFunction.ts │ ├── telemetry │ │ ├── __test__ │ │ │ └── telemetry.spec.ts │ │ └── telemetry.ts │ ├── mouseEvents │ │ └── mouseEvents.ts │ ├── flagsStorage │ │ ├── __test__ │ │ │ └── flagsStorage.spec.ts │ │ └── flagsStorage.ts │ ├── fletcher │ │ └── index.ts │ ├── time │ │ └── performance.ts │ ├── types.ts │ └── promise │ │ └── index.ts ├── sender │ ├── default │ │ ├── index.ts │ │ ├── const.ts │ │ └── query │ │ │ ├── index.ts │ │ │ └── watchAPI.ts │ ├── watch │ │ ├── index.ts │ │ └── watch.ts │ ├── const.ts │ └── middleware │ │ ├── index.ts │ │ ├── returnFullHost.ts │ │ └── __test__ │ │ └── returnFullHost.spec.ts ├── middleware │ ├── params │ │ └── index.ts │ ├── csrf │ │ └── index.ts │ ├── index.ts │ ├── pageTitle │ │ └── index.ts │ ├── prepareUrl │ │ └── index.ts │ ├── prerender │ │ ├── index.ts │ │ └── prerender.ts │ ├── userParams │ │ ├── index.ts │ │ └── userParams.ts │ ├── counterFirstHit │ │ └── index.ts │ ├── senderWatchInfo │ │ ├── const.ts │ │ └── index.ts │ ├── senderCollectInfo │ │ ├── const.ts │ │ └── index.ts │ ├── bfcacheFreezeMiddleware │ │ └── index.ts │ ├── watchSyncFlags │ │ ├── utils.ts │ │ ├── brinfoFlags │ │ │ ├── hid.ts │ │ │ ├── getCounterNumber.ts │ │ │ ├── timeFlags.ts │ │ │ ├── parentHid.ts │ │ │ ├── numRequests.ts │ │ │ ├── uid.ts │ │ │ └── falseUrl.ts │ │ ├── const.ts │ │ └── telemetryFlags │ │ │ └── numRequests.ts │ ├── types.ts │ ├── retransmit │ │ └── const.ts │ └── utils.ts ├── storage │ ├── closureStorage │ │ ├── index.ts │ │ ├── types.ts │ │ └── closureStorage.ts │ ├── cookie │ │ ├── const.ts │ │ ├── types.ts │ │ └── isAllowed.ts │ └── global │ │ ├── consts.ts │ │ └── getGlobal.ts ├── api │ ├── clmap │ │ ├── index.ts │ │ ├── telemetry.ts │ │ ├── browserInfo.ts │ │ └── urlParams.ts │ ├── watch │ │ ├── index.ts │ │ ├── urlParams.ts │ │ └── telemetry.ts │ ├── common │ │ ├── index.ts │ │ ├── telemetry.ts │ │ └── browserInfo.ts │ └── README.md ├── const.ts ├── __tests__ │ ├── config.spec.ts │ └── utils │ │ └── syncPromise.ts └── config.ts ├── hooks └── pre-commit ├── .gitattributes ├── .piglet-meta.json ├── .npmrc ├── types └── @ampproject │ └── rollup-plugin-closure-compiler.d.ts ├── tsconfig.eslint.json ├── tsconfig.rollup.json ├── scripts ├── utils │ └── fs.ts └── build │ └── utils.ts ├── closure-compiler.js ├── .prettierrc.js ├── .gitignore ├── tsconfig.unit.json ├── AUTHORS.md ├── LICENSE.md ├── tsconfig.json └── .github └── workflows └── check-code.yml /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | _build 2 | coverage 3 | -------------------------------------------------------------------------------- /src/inject/index.ts: -------------------------------------------------------------------------------- 1 | export * from './inject'; 2 | -------------------------------------------------------------------------------- /src/providers/hit/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hit'; 2 | -------------------------------------------------------------------------------- /src/transport/xhr/index.ts: -------------------------------------------------------------------------------- 1 | export * from './xhr'; 2 | -------------------------------------------------------------------------------- /src/utils/json/index.ts: -------------------------------------------------------------------------------- 1 | export * from './json'; 2 | -------------------------------------------------------------------------------- /src/utils/uid/index.ts: -------------------------------------------------------------------------------- 1 | export * from './uid'; 2 | -------------------------------------------------------------------------------- /src/utils/url/index.ts: -------------------------------------------------------------------------------- 1 | export * from './url'; 2 | -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npm run precommit 3 | -------------------------------------------------------------------------------- /src/sender/default/index.ts: -------------------------------------------------------------------------------- 1 | export * from './default'; 2 | -------------------------------------------------------------------------------- /src/sender/watch/index.ts: -------------------------------------------------------------------------------- 1 | export * from './watch'; 2 | -------------------------------------------------------------------------------- /src/transport/fetch/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fetch'; 2 | -------------------------------------------------------------------------------- /src/transport/image/index.ts: -------------------------------------------------------------------------------- 1 | export * from './image'; 2 | -------------------------------------------------------------------------------- /src/transport/jsonp/index.ts: -------------------------------------------------------------------------------- 1 | export * from './jsonp'; 2 | -------------------------------------------------------------------------------- /src/utils/console/index.ts: -------------------------------------------------------------------------------- 1 | export * from './console'; 2 | -------------------------------------------------------------------------------- /src/utils/direct/index.ts: -------------------------------------------------------------------------------- 1 | export * from './direct'; 2 | -------------------------------------------------------------------------------- /src/utils/encoder/index.ts: -------------------------------------------------------------------------------- 1 | export * from './encoder'; 2 | -------------------------------------------------------------------------------- /src/utils/history/index.ts: -------------------------------------------------------------------------------- 1 | export * from './history'; 2 | -------------------------------------------------------------------------------- /src/utils/string/index.ts: -------------------------------------------------------------------------------- 1 | export * from './string'; 2 | -------------------------------------------------------------------------------- /src/middleware/params/index.ts: -------------------------------------------------------------------------------- 1 | export * from './params'; 2 | -------------------------------------------------------------------------------- /src/providers/destruct/index.ts: -------------------------------------------------------------------------------- 1 | export * from './destruct'; 2 | -------------------------------------------------------------------------------- /src/providers/sameSite/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sameSite'; 2 | -------------------------------------------------------------------------------- /src/transport/beacon/index.ts: -------------------------------------------------------------------------------- 1 | export * from './beacon'; 2 | -------------------------------------------------------------------------------- /src/utils/asyncMap/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AsyncMap'; 2 | -------------------------------------------------------------------------------- /src/utils/condition/index.ts: -------------------------------------------------------------------------------- 1 | export * from './condition'; 2 | -------------------------------------------------------------------------------- /src/utils/treeWalker/index.ts: -------------------------------------------------------------------------------- 1 | export * from './treeWalker'; 2 | -------------------------------------------------------------------------------- /src/middleware/csrf/index.ts: -------------------------------------------------------------------------------- 1 | export * from './csrfMiddleware'; 2 | -------------------------------------------------------------------------------- /src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export * from './providerMiddlewares'; 2 | -------------------------------------------------------------------------------- /src/middleware/pageTitle/index.ts: -------------------------------------------------------------------------------- 1 | export * from './pageTitle'; 2 | -------------------------------------------------------------------------------- /src/middleware/prepareUrl/index.ts: -------------------------------------------------------------------------------- 1 | export * from './prepareUrl'; 2 | -------------------------------------------------------------------------------- /src/middleware/prerender/index.ts: -------------------------------------------------------------------------------- 1 | export * from './prerender'; 2 | -------------------------------------------------------------------------------- /src/middleware/userParams/index.ts: -------------------------------------------------------------------------------- 1 | export * from './userParams'; 2 | -------------------------------------------------------------------------------- /src/providers/searchTLD/index.ts: -------------------------------------------------------------------------------- 1 | export * from './searchTLD'; 2 | -------------------------------------------------------------------------------- /src/utils/deobfuscate/index.ts: -------------------------------------------------------------------------------- 1 | export * from './deobfuscate'; 2 | -------------------------------------------------------------------------------- /src/utils/querystring/index.ts: -------------------------------------------------------------------------------- 1 | export * from './querystring'; 2 | -------------------------------------------------------------------------------- /src/utils/turboParams/index.ts: -------------------------------------------------------------------------------- 1 | export * from './turboParams'; 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | package-lock.json binary 2 | package-lock.json -diff 3 | -------------------------------------------------------------------------------- /src/providers/callbackInit/index.ts: -------------------------------------------------------------------------------- 1 | export * from './callbackInit'; 2 | -------------------------------------------------------------------------------- /src/storage/closureStorage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './closureStorage'; 2 | -------------------------------------------------------------------------------- /src/utils/isCounterSilent/index.ts: -------------------------------------------------------------------------------- 1 | export * from './isCounterSilent'; 2 | -------------------------------------------------------------------------------- /src/utils/userTimeDefer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './userTimeDefer'; 2 | -------------------------------------------------------------------------------- /src/middleware/counterFirstHit/index.ts: -------------------------------------------------------------------------------- 1 | export * from './counterFirstHit'; 2 | -------------------------------------------------------------------------------- /src/middleware/senderWatchInfo/const.ts: -------------------------------------------------------------------------------- 1 | export const WATCH_RESOURCE = 'watch'; 2 | -------------------------------------------------------------------------------- /src/sender/default/const.ts: -------------------------------------------------------------------------------- 1 | export const CONTENT_TYPE_HEADER = 'Content-Type'; 2 | -------------------------------------------------------------------------------- /.piglet-meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "project":"metrica-tag", 3 | "repository":"arcadia" 4 | } -------------------------------------------------------------------------------- /src/providers/ecommerce/const.ts: -------------------------------------------------------------------------------- 1 | export const ECOMMERCE_PARAMS_KEY = 'ecommerce'; 2 | -------------------------------------------------------------------------------- /src/storage/cookie/const.ts: -------------------------------------------------------------------------------- 1 | export const ENABLED_COOKIE_KEY = 'metrika_enabled'; 2 | -------------------------------------------------------------------------------- /src/utils/methodDecorators/telCallCount/index.ts: -------------------------------------------------------------------------------- 1 | export * from './telCallCount'; 2 | -------------------------------------------------------------------------------- /src/middleware/senderCollectInfo/const.ts: -------------------------------------------------------------------------------- 1 | export const COLLECT_RESOURCE = 'pcollect'; 2 | -------------------------------------------------------------------------------- /src/utils/function/bind/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bind'; 2 | export * from './ctxBind'; 3 | -------------------------------------------------------------------------------- /src/middleware/bfcacheFreezeMiddleware/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bfcacheFreezeMiddleware'; 2 | -------------------------------------------------------------------------------- /src/providers/phoneHide/const.ts: -------------------------------------------------------------------------------- 1 | export const COUNTER_SETTINGS_HIDE_PHONES_KEY = 'phhide'; 2 | -------------------------------------------------------------------------------- /src/providers/userParams/types.ts: -------------------------------------------------------------------------------- 1 | export type UserParamsHandler = (...a: any[]) => T; 2 | -------------------------------------------------------------------------------- /src/utils/counterSettings/const.ts: -------------------------------------------------------------------------------- 1 | export const COUNTER_SETTINGS_SETTINGS_KEY = 'settings'; 2 | -------------------------------------------------------------------------------- /src/sender/const.ts: -------------------------------------------------------------------------------- 1 | export const SENDER_MIDDLEWARE = 'mw'; 2 | export const SENDER_WATCH = 'w'; 3 | -------------------------------------------------------------------------------- /src/sender/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export * from './middleware'; 2 | export * from './returnFullHost'; 3 | -------------------------------------------------------------------------------- /src/utils/json/__tests__/json.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | describe('json', () => {}); 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | progress = false 2 | loglevel = warn 3 | save-exact = true 4 | scripts-prepend-node-path = true 5 | -------------------------------------------------------------------------------- /src/middleware/senderWatchInfo/index.ts: -------------------------------------------------------------------------------- 1 | export * from './const'; 2 | export * from './senderWatchInfo'; 3 | -------------------------------------------------------------------------------- /src/providers/firstPartyMethod/process/index.ts: -------------------------------------------------------------------------------- 1 | export * from './email'; 2 | export * from './phone'; 3 | -------------------------------------------------------------------------------- /src/storage/cookie/types.ts: -------------------------------------------------------------------------------- 1 | export type CookieGetter = (ctx: Window, name: string) => string | null; 2 | -------------------------------------------------------------------------------- /src/utils/function/noop.ts: -------------------------------------------------------------------------------- 1 | export const noop = (): void | undefined => { 2 | // DO NOTHING 3 | }; 4 | -------------------------------------------------------------------------------- /src/middleware/senderCollectInfo/index.ts: -------------------------------------------------------------------------------- 1 | export * from './const'; 2 | export * from './senderCollectInfo'; 3 | -------------------------------------------------------------------------------- /src/utils/browser/const.ts: -------------------------------------------------------------------------------- 1 | export const MIN_FIREFOX_VERSION = 68; 2 | export const MIN_EDGE_VERSION = 79; 3 | -------------------------------------------------------------------------------- /src/utils/condition/condition.ts: -------------------------------------------------------------------------------- 1 | export const ternary = (a: A, b: B, cond: unknown) => (cond ? a : b); 2 | -------------------------------------------------------------------------------- /src/providers/stackProxy/const.ts: -------------------------------------------------------------------------------- 1 | export const STACK_FN_NAME = 'ym'; 2 | export const STACK_DATA_LAYER_NAME = 'a'; 3 | -------------------------------------------------------------------------------- /src/api/clmap/index.ts: -------------------------------------------------------------------------------- 1 | export * from './browserInfo'; 2 | export * from './telemetry'; 3 | export * from './urlParams'; 4 | -------------------------------------------------------------------------------- /src/api/watch/index.ts: -------------------------------------------------------------------------------- 1 | export * from './browserInfo'; 2 | export * from './telemetry'; 3 | export * from './urlParams'; 4 | -------------------------------------------------------------------------------- /src/providers/retransmit/const.ts: -------------------------------------------------------------------------------- 1 | export const RETRANSMIT_PROVIDER = 'r'; 2 | export const SENDER_RETRANSMIT = 'r'; 3 | -------------------------------------------------------------------------------- /src/utils/errorLogger/throwFunction.ts: -------------------------------------------------------------------------------- 1 | export const throwFunction = (reason: unknown) => { 2 | throw reason; 3 | }; 4 | -------------------------------------------------------------------------------- /src/utils/function/construct.ts: -------------------------------------------------------------------------------- 1 | export const constructObject = () => ({}); 2 | export const constructArray = () => []; 3 | -------------------------------------------------------------------------------- /src/api/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './browserInfo'; 2 | export * from './telemetry'; 3 | export * from './urlParams'; 4 | -------------------------------------------------------------------------------- /src/providers/artificialHit/const.ts: -------------------------------------------------------------------------------- 1 | export const METHOD_NAME_HIT = 'hit'; 2 | export const ARTIFICIAL_HIT_PROVIDER = 'a'; 3 | -------------------------------------------------------------------------------- /src/storage/global/consts.ts: -------------------------------------------------------------------------------- 1 | export const LAST_REFERRER_KEY = 'lastReferrer'; 2 | export const HIT_PARAMS_KEY = 'hitParam'; 3 | -------------------------------------------------------------------------------- /src/utils/function/cont.ts: -------------------------------------------------------------------------------- 1 | export const cCont = (arg: T, fn: (_arg: T) => R): R => { 2 | return fn(arg); 3 | }; 4 | -------------------------------------------------------------------------------- /src/api/clmap/telemetry.ts: -------------------------------------------------------------------------------- 1 | export { 2 | TRANSPORT_ID_BR_KEY, 3 | RETRANSMIT_BRINFO_KEY, 4 | } from '../common/telemetry'; 5 | -------------------------------------------------------------------------------- /src/providers/counterOptions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './counterOptions'; 2 | export * from './const'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /src/utils/ecommerce/index.ts: -------------------------------------------------------------------------------- 1 | export * from './const'; 2 | export * from './transform'; 3 | export * from './waitForDataLayer'; 4 | -------------------------------------------------------------------------------- /src/utils/iframeConnector/index.ts: -------------------------------------------------------------------------------- 1 | export * from './iframeConnector'; 2 | export * from './types'; 3 | export * from './const'; 4 | -------------------------------------------------------------------------------- /src/providers/userParams/const.ts: -------------------------------------------------------------------------------- 1 | export const METHOD_NAME_USER_PARAMS = 'userParams'; 2 | export const USER_PARAMS_KEY = '__ymu'; 3 | -------------------------------------------------------------------------------- /src/utils/counterOptions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './counterOptions'; 2 | export * from './getCounterKey'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /src/providers/counterOptions/const.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_COUNTER_TYPE = '0'; 2 | export const RSYA_COUNTER_TYPE = '1'; 3 | export const DEFAULT_ID = '0'; 4 | -------------------------------------------------------------------------------- /src/utils/phones/index.ts: -------------------------------------------------------------------------------- 1 | export * from './const'; 2 | export * from './phonesDom'; 3 | export * from './phonesHide'; 4 | export * from './phonesSubscribe'; 5 | -------------------------------------------------------------------------------- /src/utils/function/isNativeFunction/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getNativeFunction'; 2 | export * from './isNativeFunction'; 3 | export * from './toNativeOrFalse'; 4 | -------------------------------------------------------------------------------- /types/@ampproject/rollup-plugin-closure-compiler.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@ampproject/rollup-plugin-closure-compiler' { 2 | export default (...args: any[]): any => {}; 3 | } 4 | -------------------------------------------------------------------------------- /src/api/clmap/browserInfo.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SENDER_TIME_BR_KEY, 3 | BUILD_FLAGS_BR_KEY, 4 | BUILD_VERSION_BR_KEY, 5 | UID_BR_KEY, 6 | } from '../common/browserInfo'; 7 | -------------------------------------------------------------------------------- /src/storage/global/getGlobal.ts: -------------------------------------------------------------------------------- 1 | import { memo } from 'src/utils/function/memo'; 2 | import { globalStorage } from './global'; 3 | 4 | export const getGlobalStorage = memo(globalStorage); 5 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "./**/.*.js", 5 | "./**/*.js", 6 | "./**/*.ts", 7 | "./**/*.tsx" 8 | ], 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.rollup.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "es2015", 5 | "moduleResolution": "bundler", 6 | "target": "ES6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/providers/firstPaint/const.ts: -------------------------------------------------------------------------------- 1 | export const FIRST_PAINT_ENABLED_GS_KEY = 'fpe'; 2 | export const FIRST_HIDE_TIME_GS_KEY = 'fht'; 3 | 4 | export const CONTENTFUL_PAINT = 'first-contentful-paint'; 5 | -------------------------------------------------------------------------------- /scripts/utils/fs.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | export function readAsJSON(path: string): T { 4 | const content = fs.readFileSync(path, 'utf8'); 5 | return JSON.parse(content); 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/object/index.ts: -------------------------------------------------------------------------------- 1 | export * from './assertions'; 2 | export * from './isPlainObject'; 3 | export * from './path'; 4 | export * from './utils'; 5 | export * from './mix'; 6 | export * from './has'; 7 | -------------------------------------------------------------------------------- /src/utils/string/types.ts: -------------------------------------------------------------------------------- 1 | export type StringIndexOf = ( 2 | inputString: string, 3 | searchString: string, 4 | ) => number; 5 | 6 | export type Repeat = (inputString: string, count: number) => string; 7 | -------------------------------------------------------------------------------- /closure-compiler.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const ym = {}; 3 | Object.prototype.onClick; 4 | Object.prototype.onInput; 5 | Object.prototype.ref; 6 | Object.prototype.onMouseEnter; 7 | Object.prototype.onMouseLeave; 8 | -------------------------------------------------------------------------------- /src/const.ts: -------------------------------------------------------------------------------- 1 | export const yaNamespace = 'Ya'; 2 | 3 | // Надёжнее всего просто по одной таски запускать 4 | // Потому что там есть переходы из потоков выполнения которые 5 | // Не измеряются нормально 6 | export const ASYNC_PROVIDERS_MAX_EXEC_TIME = 1; 7 | -------------------------------------------------------------------------------- /src/utils/function/bind/ctxBind.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '../curry'; 2 | import { bindArgs, bindThisForMethod } from './bind'; 3 | 4 | export const ctxBindArgs = curry2(bindArgs); 5 | 6 | export const ctxBindThisForMethod = curry2(bindThisForMethod); 7 | -------------------------------------------------------------------------------- /src/utils/function/identity.ts: -------------------------------------------------------------------------------- 1 | export type FirstArgOfType = (a: T) => T; 2 | 3 | export const firstArg = (a: T): T => a; 4 | 5 | export const secondArg = (a?: E, b?: T): T | undefined => b; 6 | 7 | export const notFn = (res: any) => !res; 8 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 4, 4 | useTabs: false, 5 | semi: true, 6 | singleQuote: true, 7 | trailingComma: 'all', 8 | bracketSpacing: true, 9 | arrowParens: 'always', 10 | }; 11 | -------------------------------------------------------------------------------- /src/providers/debugConsole/const.ts: -------------------------------------------------------------------------------- 1 | export const DEBUG_STORAGE_FLAG = 'debug' as const; 2 | export const DEBUG_CTX_FLAG = `_ym_${DEBUG_STORAGE_FLAG}` as const; 3 | export const DEBUG_URL_PARAM = DEBUG_CTX_FLAG; 4 | export const DEBUG_COOKIE = DEBUG_CTX_FLAG; 5 | -------------------------------------------------------------------------------- /src/utils/counterOptions/getCounterKey.ts: -------------------------------------------------------------------------------- 1 | import { memo } from '../function/memo'; 2 | import type { CounterOptions } from './types'; 3 | 4 | export const getCounterKey = memo((opt: CounterOptions) => { 5 | return `${opt.id}:${opt.counterType}`; 6 | }); 7 | -------------------------------------------------------------------------------- /src/storage/closureStorage/types.ts: -------------------------------------------------------------------------------- 1 | export type ClosureState = Record>; 2 | 3 | export type StateManager = (fn: (state: ClosureState) => R) => R; 4 | 5 | export type DeleteVal = (key: string) => (state: ClosureState) => void; 6 | -------------------------------------------------------------------------------- /src/providers/firstPartyMethod/const.ts: -------------------------------------------------------------------------------- 1 | export const METHOD_NAME_FIRST_PARTY = 'firstPartyParams'; 2 | export const METHOD_NAME_FIRST_PARTY_HASHED = 'firstPartyParamsHashed'; 3 | export const FIRST_PARTY_PARAMS_KEY = 'fpp'; 4 | export const FIRST_PARTY_HASHED_PARAMS_KEY = 'fpmh'; 5 | -------------------------------------------------------------------------------- /src/sender/middleware/returnFullHost.ts: -------------------------------------------------------------------------------- 1 | import { config, host } from 'src/config'; 2 | 3 | /** 4 | * Convert host and resource to full URL 5 | */ 6 | export const returnFullHost = (resource: string, argHost?: string) => 7 | `${config.cProtocol}//${argHost || host}/${resource}`; 8 | -------------------------------------------------------------------------------- /src/sender/watch/watch.ts: -------------------------------------------------------------------------------- 1 | import { MiddlewareBasedSender, useMiddlewareBasedSender } from '../middleware'; 2 | import { SENDER_WATCH } from '../const'; 3 | 4 | export type SenderWatch = MiddlewareBasedSender; 5 | 6 | export const useSenderWatch = useMiddlewareBasedSender(SENDER_WATCH); 7 | -------------------------------------------------------------------------------- /src/api/common/telemetry.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Request index in a series of retries 3 | * 4 | * Value Type: number 5 | */ 6 | export const RETRANSMIT_BRINFO_KEY = 'rqnl'; 7 | 8 | /** 9 | * Transport index. 10 | * Value Type: number 11 | */ 12 | export const TRANSPORT_ID_BR_KEY = 'ti'; 13 | -------------------------------------------------------------------------------- /src/sender/default/query/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { createMPQuery } from './measurementProtocol'; 3 | import { createWatchQuery } from './watchAPI'; 4 | 5 | export const createQuery = flags.SENDER_COLLECT_FEATURE 6 | ? createMPQuery 7 | : createWatchQuery; 8 | -------------------------------------------------------------------------------- /src/providers/yan/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { useYan } from './yan'; 4 | 5 | export const initProvider = () => { 6 | if (flags.YAN_FEATURE) { 7 | providersSync.push(useYan); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/string/startsWith.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from 'src/utils/function/pipe'; 2 | import { curry2SwapArgs, equal } from 'src/utils/function/curry'; 3 | import { stringIndexOf } from './string'; 4 | 5 | export const startsWith = pipe(stringIndexOf, equal(0)); 6 | export const startsWithString = curry2SwapArgs(startsWith); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build/** 2 | .env 3 | .idea 4 | .vscode 5 | *.orig 6 | .gits 7 | __pycache__ 8 | .DS_Store 9 | .eslintcache 10 | generated 11 | code/ 12 | node_modules 13 | npm-debug.log 14 | test/**/coverage 15 | images 16 | stats.html 17 | tsconfig.tsbuildinfo 18 | tsconfig.unit.tsbuildinfo 19 | bundleSize.json 20 | cert 21 | -------------------------------------------------------------------------------- /src/providers/stackProxy/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { checkStack } from './stackProxy'; 4 | 5 | export const initProvider = () => { 6 | if (flags.STACK_PROXY_FEATURE) { 7 | providersSync.push(checkStack); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/providers/trackHash/const.ts: -------------------------------------------------------------------------------- 1 | export const METHOD_TRACK_HASH = 'trackHash'; 2 | export const TRACK_HASH_PROVIDER = 't'; 3 | 4 | declare module 'src/utils/counter/type' { 5 | interface CounterObject { 6 | /** Tracks URL hash change */ 7 | [METHOD_TRACK_HASH]?: (run?: boolean) => void; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/providers/getClientID/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { getClientID } from './getClientID'; 4 | 5 | export const initProvider = () => { 6 | if (flags.GET_CLIENT_ID_FEATURE) { 7 | providersSync.push(getClientID); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/providers/statusCheck/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersAsync } from 'src/providersEntrypoint'; 3 | import { checkStatus } from './statusCheck'; 4 | 5 | export const initProvider = () => { 6 | if (flags.CHECK_STATUS_FEATURE) { 7 | providersAsync.push(checkStatus); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/methodDecorators/errors.ts: -------------------------------------------------------------------------------- 1 | import { errorLogger } from 'src/utils/errorLogger/errorLogger'; 2 | import type { Decorator } from './types'; 3 | 4 | export const errorsDecorator: Decorator = ( 5 | ctx, 6 | counterOptions, 7 | methodName, 8 | fn, 9 | ) => errorLogger(ctx, `cm.${methodName}`, fn); 10 | -------------------------------------------------------------------------------- /src/utils/function/isNativeFunction/toNativeOrFalse.ts: -------------------------------------------------------------------------------- 1 | import { isNativeFunction } from 'src/utils/function/isNativeFunction/isNativeFunction'; 2 | import { AnyFunc } from '../types'; 3 | 4 | export const toNativeOrFalse = ( 5 | fn: F, 6 | functionName: string, 7 | ) => isNativeFunction(functionName, fn) && fn; 8 | -------------------------------------------------------------------------------- /src/providers/triggerEvent/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { useTriggerEvent } from './triggerEvent'; 4 | 5 | export const initProvider = () => { 6 | if (flags.TRIGGER_EVENT_FEATURE) { 7 | providersSync.push(useTriggerEvent); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/object/has.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from './assertions'; 2 | 3 | const nativeHasOwnProperty = Object.prototype.hasOwnProperty; 4 | 5 | export const has = ( 6 | object: any, 7 | property: string, 8 | ): ReturnType => 9 | isNil(object) ? false : nativeHasOwnProperty.call(object, property); 10 | -------------------------------------------------------------------------------- /src/api/clmap/urlParams.ts: -------------------------------------------------------------------------------- 1 | export { URL_PARAM as CLICKMAP_URL_PARAM } from '../common/urlParams'; 2 | 3 | /** 4 | * Click data 5 | * 6 | * Value Type: string 7 | */ 8 | export const CLICKMAP_POINTER_PARAM = 'pointer-click'; 9 | 10 | /** Hit type for clickmap events */ 11 | export const HIT_TYPE_POINTER_CLICK = CLICKMAP_POINTER_PARAM; 12 | -------------------------------------------------------------------------------- /src/middleware/watchSyncFlags/utils.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from 'src/utils/function/curry'; 2 | import { getGlobalStorage } from 'src/storage/global/getGlobal'; 3 | 4 | export const getGSFlag = curry2((flagName: string, ctx: Window) => { 5 | const gs = getGlobalStorage(ctx); 6 | return gs.getVal(flagName, null); 7 | }); 8 | -------------------------------------------------------------------------------- /src/utils/phones/isBrokenPhones.ts: -------------------------------------------------------------------------------- 1 | import { isBrokenFromCharCode } from '../browser/browser'; 2 | import { isQuerySelectorSupported } from '../dom/queySelect'; 3 | import { memo } from '../function/memo'; 4 | 5 | export const isBrokenPhones = memo((ctx: Window) => { 6 | return isBrokenFromCharCode(ctx) || !isQuerySelectorSupported(ctx); 7 | }); 8 | -------------------------------------------------------------------------------- /src/providers/submitTracking/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { useSubmitTracking } from './submitTracking'; 4 | 5 | export const initProvider = () => { 6 | if (flags.SUBMIT_TRACKING_FEATURE) { 7 | providersSync.push(useSubmitTracking); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/providers/consoleRenderer/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersAsync } from 'src/providersEntrypoint'; 3 | import { useConsoleRenderer } from './consoleRenderer'; 4 | 5 | export const initProvider = () => { 6 | if (flags.DEBUG_CONSOLE_RENDER_FEATURE) { 7 | providersAsync.push(useConsoleRenderer); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/providers/destruct/const.ts: -------------------------------------------------------------------------------- 1 | export const METHOD_DESTRUCT = 'destruct'; 2 | 3 | export type DestructHandler = () => T; 4 | 5 | declare module 'src/utils/counter/type' { 6 | interface CounterObject { 7 | /** Method for deinitializing the counter */ 8 | [METHOD_DESTRUCT]?: DestructHandler; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/dom/nonce.ts: -------------------------------------------------------------------------------- 1 | import { cFind } from '../array/find'; 2 | import { select } from './select'; 3 | 4 | export const getNonce = (ctx: Window) => { 5 | const nonceEl = cFind( 6 | ({ nonce }: Element) => !!nonce, 7 | select('style, link, script', ctx.document), 8 | ); 9 | return nonceEl ? nonceEl.nonce : undefined; 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/array/sort.ts: -------------------------------------------------------------------------------- 1 | import { curry2 } from '../function/curry'; 2 | 3 | export const cSort = (fn: (a: T, b: T) => number, array: T[]) => { 4 | return Array.prototype.sort.call(array, fn); 5 | }; 6 | 7 | /** 8 | * @type function(...?): ? 9 | */ 10 | export const currSort: (fn: (a: T, b: T) => number) => (arr: T[]) => T[] = 11 | curry2(cSort); 12 | -------------------------------------------------------------------------------- /src/providers/getClientID/const.ts: -------------------------------------------------------------------------------- 1 | export const METHOD_NAME_GET_CLIENT_ID = 'getClientID'; 2 | export type GetClientIDHandler = (...args: any[]) => string; 3 | 4 | declare module 'src/utils/counter/type' { 5 | interface CounterObject { 6 | /** Gets the user ID assigned by Metrica */ 7 | [METHOD_NAME_GET_CLIENT_ID]?: GetClientIDHandler; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/boolean/index.ts: -------------------------------------------------------------------------------- 1 | import { bindArgs } from 'src/utils/function/bind'; 2 | import { ternary } from 'src/utils/condition/condition'; 3 | 4 | export const toOneOrNull = bindArgs([1, null], ternary) as ( 5 | smt: any, 6 | ) => 1 | null; 7 | 8 | export const toZeroOrOne = bindArgs([1, 0], ternary) as (smt: unknown) => 1 | 0; 9 | export const toBoolean = Boolean; 10 | -------------------------------------------------------------------------------- /src/utils/function/args.ts: -------------------------------------------------------------------------------- 1 | import { arrayFrom, arrayFromPoly } from 'src/utils/array/arrayFrom'; 2 | 3 | export const argsToArray = (args: IArguments) => { 4 | if (arrayFrom) { 5 | try { 6 | return arrayFrom(args); 7 | } catch (e) { 8 | // do nothing 9 | } 10 | } 11 | 12 | return arrayFromPoly(args); 13 | }; 14 | -------------------------------------------------------------------------------- /src/providers/enableAll/const.ts: -------------------------------------------------------------------------------- 1 | export const METHOD_NAME_ENABLE_ALL = 'enableAll'; 2 | 3 | export type EnableAllHandler = () => T; 4 | 5 | declare module 'src/utils/counter/type' { 6 | interface CounterObject { 7 | /** Enables trackLinks, clickmap and notBounce */ 8 | [METHOD_NAME_ENABLE_ALL]?: EnableAllHandler; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/dom/isRemovedFromDoc.ts: -------------------------------------------------------------------------------- 1 | import { has } from '../object'; 2 | import { closest } from './closest'; 3 | 4 | export const isRemovedFromDoc = (ctx: Window, element: HTMLElement) => { 5 | if (has(element, 'isConnected')) { 6 | return !element.isConnected; 7 | } 8 | 9 | return closest('html', ctx, element) !== ctx.document.documentElement; 10 | }; 11 | -------------------------------------------------------------------------------- /src/providers/index.ts: -------------------------------------------------------------------------------- 1 | export const HIT_PROVIDER = 'h'; 2 | 3 | export const UNSUBSCRIBE_PROPERTY = 'u'; 4 | 5 | /** 6 | * Interface for extending providers in modules 7 | */ 8 | export interface PROVIDERS { 9 | HIT_PROVIDER: typeof HIT_PROVIDER; 10 | } 11 | 12 | export type Provider = PROVIDERS[keyof PROVIDERS]; 13 | 14 | export type ProvidersMap = Partial>; 15 | -------------------------------------------------------------------------------- /src/__tests__/config.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import * as chai from 'chai'; 3 | import { config } from 'src/config'; 4 | import { isIE } from 'src/utils/browser/browser'; 5 | 6 | describe('config', () => { 7 | it('should not ie config', () => { 8 | if (!isIE(window)) { 9 | chai.expect(config.MAX_LEN_URL).to.be.equal(2048); 10 | } 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/providers/firstPartyMethod/process/const.ts: -------------------------------------------------------------------------------- 1 | export const GMAIL_DOMAIN = 'gmail.com'; 2 | export const GOOGLEMAIL_DOMAIN = 'googlemail.com'; 3 | export const EMAIL_LOCAL_PART_REGEX = /^[a-zA-Z0-9'!#$%&*+-/=?^_`{|}~]+$/; 4 | 5 | export const PHONE_MIN_VALID_DIGIT_CNT = 10; 6 | export const PHONE_MAX_VALID_DIGIT_CNT = 13; 7 | export const MIN_EMAIL_LENGTH = 5; 8 | export const MAX_EMAIL_LENGTH = 100; 9 | -------------------------------------------------------------------------------- /src/utils/deobfuscate/deobfuscate.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from 'src/utils/function/pipe'; 2 | import { cKeys } from 'src/utils/object'; 3 | import { head } from '../array/utils'; 4 | 5 | export type ObfuscatedKey = { [key: string]: 1 }; 6 | 7 | type Deobfuscate = { 8 | (obj: Record): K; 9 | }; 10 | 11 | export const deobfuscate: Deobfuscate = pipe(cKeys, head) as Deobfuscate; 12 | -------------------------------------------------------------------------------- /src/providers/reportNonNativeFunctions/report.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { noop } from 'src/utils/function/noop'; 3 | 4 | export const nonNativeFunctionsList: [string, any][] = []; 5 | export const reportNonNativeFunction = flags.DEBUG_CONSOLE_FEATURE 6 | ? (fn: any, functionName: string) => { 7 | nonNativeFunctionsList.push([functionName, fn]); 8 | } 9 | : noop; 10 | -------------------------------------------------------------------------------- /src/providers/ecommerce/types.ts: -------------------------------------------------------------------------------- 1 | export type GTagEcommerceEvent = [string, string, Record]; 2 | export type GTag4EcommerceEvent = { 3 | event: string; 4 | ecommerce: Record; 5 | }; 6 | export type EcommerceEvent = { ecommerce: Record }; 7 | 8 | export type EcommerceEventType = 9 | | GTagEcommerceEvent 10 | | GTag4EcommerceEvent 11 | | EcommerceEvent; 12 | -------------------------------------------------------------------------------- /src/utils/dom/queySelect.ts: -------------------------------------------------------------------------------- 1 | import { isNativeFunction } from '../function/isNativeFunction/isNativeFunction'; 2 | import { getPath } from '../object/path'; 3 | 4 | export const isQuerySelectorSupported = (ctx: Window) => 5 | !!( 6 | isNativeFunction( 7 | 'querySelectorAll', 8 | getPath(ctx, 'Element.prototype.querySelectorAll')!, 9 | ) && ctx.document.querySelectorAll 10 | ); 11 | -------------------------------------------------------------------------------- /src/__tests__/utils/syncPromise.ts: -------------------------------------------------------------------------------- 1 | import type { TransportResponse } from 'src/transport/types'; 2 | import type { AnyFunc } from 'src/utils/function/types'; 3 | 4 | export const syncPromise = { 5 | then(callback?: AnyFunc) { 6 | if (callback) { 7 | callback(); 8 | } 9 | return this; 10 | }, 11 | catch() { 12 | return this; 13 | }, 14 | } as Promise; 15 | -------------------------------------------------------------------------------- /tsconfig.unit.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "importHelpers": true, 5 | "paths": { 6 | "@inject": ["./src/inject/inject.test.ts"], 7 | }, 8 | }, 9 | "include": [ 10 | "src/**/*", 11 | "**/*.d.ts", 12 | ], 13 | "ts-node": { 14 | "transpileOnly": true, 15 | "swc": false, 16 | "compilerOptions": { 17 | "module": "node16", 18 | }, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/array/__tests__/merge.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { arrayMerge } from '../merge'; 3 | 4 | describe('arrayMerge', () => { 5 | it('mutate first array to second', () => { 6 | const first: any[] = []; 7 | const second = [1, 2, 3]; 8 | const result = arrayMerge(first, second); 9 | expect(result).to.be.eq(first); 10 | expect(first).to.be.deep.eq(second); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/utils/condition/__tests__/condition.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import * as chai from 'chai'; 3 | import { ternary } from '../condition'; 4 | 5 | describe('Condition utils', () => { 6 | it('ternary', () => { 7 | const first = 1; 8 | const second = 2; 9 | 10 | chai.expect(ternary(first, second, true)).to.eq(first); 11 | chai.expect(ternary(first, second, false)).to.eq(second); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/providers/clickmapMethod/const.ts: -------------------------------------------------------------------------------- 1 | import { TClickMapParams } from 'src/providers/clickmap/const'; 2 | 3 | export const METHOD_NAME_CLICK_MAP = 'clickmap'; 4 | 5 | export type ClickmapHandler = (value?: TClickMapParams) => T; 6 | 7 | declare module 'src/utils/counter/type' { 8 | interface CounterObject { 9 | /** Heat map of clicks */ 10 | [METHOD_NAME_CLICK_MAP]?: ClickmapHandler; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/providers/setUserID/const.ts: -------------------------------------------------------------------------------- 1 | export const METHOD_NAME_SET_USER_ID = 'setUserID'; 2 | export const USER_ID_PARAM = 'user_id'; 3 | 4 | export type SetUserIDHandler = (...args: any[]) => T; 5 | 6 | declare module 'src/utils/counter/type' { 7 | interface CounterObject { 8 | /** Method for transmitting the user ID set by the site owner */ 9 | [METHOD_NAME_SET_USER_ID]?: SetUserIDHandler; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/providers/getCounters/const.ts: -------------------------------------------------------------------------------- 1 | export const GLOBAL_COUNTERS_METHOD_NAME = 'getCounters'; 2 | export const COUNTER_STATE_ID = 'id'; 3 | export const COUNTER_STATE_TYPE = 'type'; 4 | export const COUNTER_STATE_CLICKMAP = 'clickmap'; 5 | export const COUNTER_STATE_TRACK_HASH = 'trackHash'; 6 | export const COUNTER_STATE_NOT_BOUNCE = 'accurateTrackBounce'; 7 | export const COUNTER_STATE_TRACK_LINKS = 'trackLinks'; 8 | export const METHOD_NAME_COUNTERS = 'counters'; 9 | -------------------------------------------------------------------------------- /src/utils/array/arrayFrom.ts: -------------------------------------------------------------------------------- 1 | import { toNativeOrFalse } from 'src/utils/function/isNativeFunction/toNativeOrFalse'; 2 | 3 | export const arrayFrom = toNativeOrFalse( 4 | Array.from, 5 | 'from', 6 | ) as typeof Array.from; 7 | export const arrayFromPoly = (smth: any) => { 8 | const len = smth.length; 9 | const result = []; 10 | for (let i = 0; i < len; i += 1) { 11 | result.push(smth[i]); 12 | } 13 | 14 | return result; 15 | }; 16 | -------------------------------------------------------------------------------- /src/utils/history/history.ts: -------------------------------------------------------------------------------- 1 | import { isNativeFunction } from 'src/utils/function/isNativeFunction/isNativeFunction'; 2 | 3 | export const replaceState = ( 4 | ctx: Window, 5 | url: string, 6 | stateObj?: Record, 7 | ) => { 8 | if ( 9 | ctx?.history?.replaceState && 10 | isNativeFunction('replaceState', ctx.history.replaceState) 11 | ) { 12 | ctx.history.replaceState(stateObj, '', url); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/providers/clickmapMethod/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { useClickmapMethodProvider } from './clickmapMethod'; 4 | 5 | export const initProvider = () => { 6 | if ( 7 | flags.CLICK_MAP_FEATURE && 8 | flags.CLICK_MAP_METHOD_FEATURE && 9 | !flags.SENDER_COLLECT_FEATURE 10 | ) { 11 | providersSync.push(useClickmapMethodProvider); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils/counter/type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This interface is used by the instance of the counter 3 | * It is empty because every provider is meant to declare methods they add to CounterObject themselves. 4 | * To see an example of this see src/providers/params/const.ts 5 | * This approach is meant to make providers more self-contained and code overall more modular. 6 | */ 7 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 8 | export interface CounterObject {} 9 | -------------------------------------------------------------------------------- /src/utils/location/__tests__/location.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { isYandexDomain } from '../location'; 3 | 4 | describe('Location', () => { 5 | it('isYandexDomain', () => { 6 | ['yandex.ru', '123.yandex.ru'].forEach((hostname) => { 7 | const ctx = { 8 | location: { hostname }, 9 | } as Window; 10 | chai.expect(isYandexDomain(ctx)).to.be.true; 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/utils/methodDecorators/types.ts: -------------------------------------------------------------------------------- 1 | import type { CounterOptions } from 'src/utils/counterOptions/types'; 2 | import { AnyFunc } from '../function/types'; 3 | 4 | export type Decorator = < 5 | FN extends AnyFunc, 6 | R extends ReturnType, 7 | >( 8 | ctx: Window, 9 | counterOptions: CounterOptions, 10 | methodName: string, 11 | fn: FN, 12 | ) => (...args: Parameters) => E extends true ? R | A : A; 13 | -------------------------------------------------------------------------------- /src/providers/clickmap/type.ts: -------------------------------------------------------------------------------- 1 | import { TMouseButton } from 'src/utils/mouseEvents/mouseEvents'; 2 | 3 | /** 4 | * Structure to store click event 5 | */ 6 | export type ClickInfo = { 7 | /** DOM element */ 8 | element: HTMLElement | null; 9 | /** Position on page */ 10 | position: { x: number; y: number }; 11 | /** Pressed button number (left/right/middle/etc) */ 12 | button: TMouseButton; 13 | /** Timestamp */ 14 | time: number; 15 | } | null; 16 | -------------------------------------------------------------------------------- /src/utils/object/types.ts: -------------------------------------------------------------------------------- 1 | export interface Entries { 2 | ( 3 | o?: { [s in K]: T } | ArrayLike, 4 | ): [K, T][]; 5 | (o?: { [s: string]: T } | ArrayLike): [string, T][]; 6 | // eslint-disable-next-line @typescript-eslint/ban-types -- object allows application of entries to object without index signature. 7 | (o?: object): [string, unknown][]; 8 | } 9 | 10 | export type Keys = (o: Record) => string[]; 11 | -------------------------------------------------------------------------------- /src/providers/setUserID/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { YM_LOG_WHITELIST_KEYS } from 'src/providers/params/const'; 4 | import { USER_ID_PARAM } from './const'; 5 | import { setUserID } from './setUserID'; 6 | 7 | export const initProvider = () => { 8 | if (flags.SET_USER_ID_FEATURE) { 9 | providersSync.push(setUserID); 10 | YM_LOG_WHITELIST_KEYS.push(USER_ID_PARAM); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/boolean/__tests__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { toOneOrNull, toZeroOrOne } from '../index'; 3 | 4 | describe('Boolean utils', () => { 5 | it('toZeroOrOne', () => { 6 | chai.expect(toZeroOrOne(true)).to.eq(1); 7 | chai.expect(toZeroOrOne(false)).to.eq(0); 8 | }); 9 | it('toNullOrOne', () => { 10 | chai.expect(toOneOrNull(true)).to.eq(1); 11 | chai.expect(toOneOrNull(false)).to.eq(null); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/middleware/watchSyncFlags/brinfoFlags/hid.ts: -------------------------------------------------------------------------------- 1 | import { getGlobalStorage } from 'src/storage/global/getGlobal'; 2 | import { getRandom } from 'src/utils/number/random'; 3 | 4 | export const HID_NAME = 'hitId'; 5 | export const getHid = (ctx: Window) => { 6 | const storage = getGlobalStorage(ctx); 7 | let val = storage.getVal(HID_NAME); 8 | if (!val) { 9 | val = getRandom(ctx); 10 | storage.setVal(HID_NAME, val); 11 | } 12 | 13 | return val; 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/errorLogger/consts.ts: -------------------------------------------------------------------------------- 1 | export const TOO_LONG_FUNCTION_EXECUTION = 300; 2 | export const TOO_LONG_ERROR_NAME = `t.l.${TOO_LONG_FUNCTION_EXECUTION}`; 3 | export const DELIMITER = '.'; 4 | export const KNOWN_ERROR = 'err.kn'; 5 | export const UNCATCHABLE_ERROR_PROPERTY = 'unk'; 6 | export const IGNORED_ERRORS = [ 7 | // игнорим CSP ошибки 8 | 'http.0.st..rt.', 9 | 'network error occurred', 10 | 'send beacon', 11 | 'Content Security Policy', 12 | 'DOM Exception 18', 13 | ]; 14 | -------------------------------------------------------------------------------- /src/providers/remoteControl/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { windowProviderInitializers } from 'src/providersEntrypoint'; 3 | import { remoteControl } from './remoteControl'; 4 | 5 | declare global { 6 | interface HTMLElement { 7 | // Cached xpath for remote control 8 | _ymp?: string; 9 | } 10 | } 11 | 12 | export const initProvider = () => { 13 | if (flags.REMOTE_CONTROL_FEATURE) { 14 | windowProviderInitializers.push(remoteControl); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | The following is an inevitably incomplete list of authors who have created 2 | the source code of "metrica-tag" published and distributed by YANDEX LLC 3 | as the owner: 4 | 5 | Eroshenko Dmitrii 6 | Golikov Stanislav 7 | Vasilyev Roman 8 | Novikov Evgenii 9 | Kanatnikov Dmitrii 10 | Semenova Ekaterina 11 | Stepan Galagan 12 | -------------------------------------------------------------------------------- /src/utils/counterOptions/counterOptionsStore.ts: -------------------------------------------------------------------------------- 1 | import { constructObject } from 'src/utils/function/construct'; 2 | import { memo } from 'src/utils/function/memo'; 3 | import { getCounterKey } from './getCounterKey'; 4 | import type { CounterOptions, RawCounterOptions } from './types'; 5 | 6 | type RawCounterOptionsState = { 7 | rawOptions?: RawCounterOptions; 8 | }; 9 | 10 | export const getCounterOptionsState = memo( 11 | constructObject as (options: CounterOptions) => RawCounterOptionsState, 12 | getCounterKey, 13 | ); 14 | -------------------------------------------------------------------------------- /src/utils/function/utils.ts: -------------------------------------------------------------------------------- 1 | import { firstArg } from './identity'; 2 | import { pipe } from './pipe'; 3 | import { AnyFunc } from './types'; 4 | 5 | export type CallWithoutArguments = (fn: () => T) => T; 6 | 7 | export const call = ( 8 | fn: FnType, 9 | arg?: ArgType, 10 | ): FnType extends AnyFunc ? ReturnType : never => 11 | arg ? fn(arg) : fn(); 12 | 13 | export const callFirstArgument: ( 14 | fn: FnType, 15 | ) => ReturnType = pipe(firstArg, call); 16 | -------------------------------------------------------------------------------- /src/utils/querystring/safeEncodeURI.ts: -------------------------------------------------------------------------------- 1 | import { cFilter } from '../array/filter'; 2 | import { arrayJoin } from '../array/join'; 3 | 4 | export const safeEncodeURIComponent = (str: string) => { 5 | try { 6 | return encodeURIComponent(str); 7 | } catch (e) {} 8 | const simpleUTF = arrayJoin( 9 | '', 10 | cFilter((char) => { 11 | const charCode = char.charCodeAt(0); 12 | return charCode <= 0xd800; 13 | }, str.split('')), 14 | ); 15 | return encodeURIComponent(simpleUTF); 16 | }; 17 | -------------------------------------------------------------------------------- /src/providers/firstPaint/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { FIRST_PAINT_BR_KEY } from 'src/api/watch'; 3 | import { BRINFO_FLAG_GETTERS } from 'src/middleware/watchSyncFlags/brinfoFlags'; 4 | import { windowProviderInitializers } from 'src/providersEntrypoint'; 5 | import { firstPaint, useFirstPaint } from './firstPaint'; 6 | 7 | export const initProvider = () => { 8 | if (flags.FIRST_PAINT_FEATURE) { 9 | windowProviderInitializers.unshift(useFirstPaint); 10 | BRINFO_FLAG_GETTERS[FIRST_PAINT_BR_KEY] = firstPaint; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/fnv32a/fnv32a.ts: -------------------------------------------------------------------------------- 1 | export function fnv32a(val: any) { 2 | const str = `${val}`; 3 | let hval = 0x811c9dc5; 4 | const len = str.length; 5 | for (let i = 0; i < len; i += 1) { 6 | /* eslint-disable */ 7 | hval ^= str.charCodeAt(i); 8 | hval += 9 | (hval << 1) + 10 | (hval << 4) + 11 | (hval << 7) + 12 | (hval << 8) + 13 | (hval << 24); 14 | /* eslint-enable */ 15 | } 16 | 17 | // eslint-disable-next-line no-bitwise 18 | return hval >>> 0; 19 | } 20 | -------------------------------------------------------------------------------- /src/providers/clickTracking/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { useClickTracking } from './clickTracking'; 4 | 5 | declare module 'src/utils/counterSettings/types' { 6 | interface CounterSettingsParams { 7 | /** button goals enabled */ 8 | // eslint-disable-next-line camelcase 9 | button_goals?: number; 10 | } 11 | } 12 | 13 | export const initProvider = () => { 14 | if (flags.CLICK_TRACKING_FEATURE) { 15 | providersSync.push(useClickTracking); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/providers/clicks/getTextFromLink.ts: -------------------------------------------------------------------------------- 1 | import { trimText } from 'src/utils/string/remove'; 2 | 3 | export const textFromLink = (elem: HTMLElement) => { 4 | const text = trimText( 5 | elem.innerHTML && elem.innerHTML.replace(/<\/?[^>]+>/gi, ''), 6 | ); 7 | 8 | if (text) { 9 | return text; 10 | } 11 | 12 | const innerImage = elem.querySelector('img'); 13 | if (innerImage) { 14 | return trimText( 15 | innerImage.getAttribute('title') || innerImage.getAttribute('alt'), 16 | ); 17 | } 18 | 19 | return ''; 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/direct/direct.ts: -------------------------------------------------------------------------------- 1 | import { yaNamespace } from 'src/const'; 2 | import { CounterTypeInterface, isRsyaCounter } from '../counterOptions'; 3 | 4 | const DIRECT = 'Direct'; 5 | 6 | declare global { 7 | interface yaNamespaceStorage { 8 | [DIRECT]?: boolean; 9 | } 10 | } 11 | 12 | /** 13 | * Detects Direct ads on the page. 14 | */ 15 | export function yaDirectExists(ctx: Window, counterType: CounterTypeInterface) { 16 | return ( 17 | isRsyaCounter(counterType) && 18 | ctx[yaNamespace] && 19 | ctx[yaNamespace]![DIRECT] 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/object/isPrimitive.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '../string'; 2 | import { isNumber } from '../number/number'; 3 | import { isNull, isUndefined } from './assertions'; 4 | 5 | export type Primitive = null | undefined | number | string | boolean; 6 | 7 | export const isPrimitive = ( 8 | ctx: Window, 9 | object: unknown, 10 | ): object is Primitive => { 11 | return ( 12 | isNull(object) || 13 | isUndefined(object) || 14 | isNumber(ctx, object) || 15 | isString(object) || 16 | !!object === object // is boolean 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/dom/form.ts: -------------------------------------------------------------------------------- 1 | import { getData, ID, NAME, PATH } from 'src/utils/dom/identifiers'; 2 | import { select } from 'src/utils/dom/select'; 3 | import { closest } from 'src/utils/dom/closest'; 4 | import { bindArg } from '../function/bind'; 5 | 6 | const FORM_SELECTOR = 'form'; 7 | 8 | export const closestForm = bindArg(FORM_SELECTOR, closest); 9 | export const selectForms = bindArg(FORM_SELECTOR, select); 10 | 11 | export const getFormData = ( 12 | ctx: Window, 13 | form: HTMLElement, 14 | ignored?: HTMLElement, 15 | ) => getData(ctx, form, [ID, NAME, PATH], undefined, ignored); 16 | -------------------------------------------------------------------------------- /src/utils/dom/selection.ts: -------------------------------------------------------------------------------- 1 | import { bind } from '../function/bind/bind'; 2 | import { toNativeOrFalse } from '../function/isNativeFunction/toNativeOrFalse'; 3 | import { memo } from '../function/memo'; 4 | import { noop } from '../function/noop'; 5 | import { pipe } from '../function/pipe'; 6 | import { call } from '../function/utils'; 7 | 8 | const getSelectFn = memo((ctx: Window) => { 9 | const nativeSelect = toNativeOrFalse(ctx.getSelection, 'getSelection'); 10 | return nativeSelect ? bind(nativeSelect, ctx) : noop; 11 | }); 12 | 13 | export const getSelect = pipe(getSelectFn, call); 14 | -------------------------------------------------------------------------------- /src/providers/sameSite/sameSite.ts: -------------------------------------------------------------------------------- 1 | import { memo } from 'src/utils/function/memo'; 2 | import { isSameSiteBrowser } from 'src/utils/browser/browser'; 3 | import { isHttps } from 'src/utils/location/location'; 4 | 5 | /** 6 | * Decides if we need to set same site option 7 | * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite 8 | * @param ctx - Current window 9 | */ 10 | export const getSameSiteCookieInfo = memo((ctx: Window): string => { 11 | if (isSameSiteBrowser(ctx) && isHttps(ctx)) { 12 | return 'SameSite=None;Secure;'; 13 | } 14 | return ''; 15 | }); 16 | -------------------------------------------------------------------------------- /src/utils/defer/base.ts: -------------------------------------------------------------------------------- 1 | import { getNativeFunction } from 'src/utils/function/isNativeFunction/getNativeFunction'; 2 | import type { AnyFunc } from '../function/types'; 3 | 4 | // Без errorLogger - для избежания циклических зависимостей в транспортах и callForeignCallback 5 | export const setDeferBase = ( 6 | ctx: Window, 7 | fn: AnyFunc, 8 | timeOut: number, 9 | ): number => { 10 | const setTimeout: Window['setTimeout'] = getNativeFunction( 11 | 'setTimeout', 12 | ctx, 13 | ); 14 | // eslint-disable-next-line ban/ban 15 | return setTimeout(fn, timeOut); 16 | }; 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2023 YANDEX LLC 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /src/utils/array/merge.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from '../function/pipe'; 2 | import { bindThisForMethod } from '../function/bind/bind'; 3 | import { firstArg } from '../function/identity'; 4 | import { cForEach } from './map'; 5 | 6 | /** 7 | @function arrayMerge 8 | @summary Why use this instaead of a.push(...b)? Because of the bug here https://bugs.webkit.org/show_bug.cgi?id=80797 9 | */ 10 | export const arrayMerge = ( 11 | source: A, 12 | part: B, 13 | ) => { 14 | cForEach(pipe(firstArg, bindThisForMethod('push', source)), part); 15 | return source; 16 | }; 17 | -------------------------------------------------------------------------------- /src/providers/enableAll/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { useEnableAllProvider } from './enableAll'; 4 | 5 | export const initProvider = () => { 6 | if ( 7 | (flags.CLICK_MAP_FEATURE || 8 | flags.CLICK_MAP_METHOD_FEATURE || 9 | flags.EXTERNAL_LINK_FEATURE || 10 | flags.NOT_BOUNCE_HIT_FEATURE || 11 | flags.ACCURATE_TRACK_BOUNCE_METHOD_FEATURE) && 12 | flags.ENABLE_ALL_METHOD_FEATURE 13 | ) { 14 | providersSync.push(useEnableAllProvider); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/providers/phoneHide/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { COUNTER_SETTINGS_HIDE_PHONES_KEY } from './const'; 4 | import { usePhoneHideProvider } from './phoneHide'; 5 | 6 | declare module 'src/utils/counterSettings/types' { 7 | interface CounterSettingsParams { 8 | /** An array of phones to hide */ 9 | [COUNTER_SETTINGS_HIDE_PHONES_KEY]?: string[]; 10 | } 11 | } 12 | 13 | export const initProvider = () => { 14 | if (flags.HIDE_PHONES_FEATURE) { 15 | providersSync.push(usePhoneHideProvider); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/middleware/watchSyncFlags/const.ts: -------------------------------------------------------------------------------- 1 | import type { SenderInfo } from 'src/sender/SenderInfo'; 2 | import type { CounterOptions } from 'src/utils/counterOptions'; 3 | 4 | export const BRINFO_LOGGER_PREFIX = 'bi'; 5 | 6 | export const LS_ID_KEY = 'lsid'; 7 | export const REQUEST_NUMBER_KEY = 'reqNum'; 8 | 9 | export type FlagGetter = ( 10 | /** Current window */ 11 | ctx: Window, 12 | /** Options passed for initialization */ 13 | options: CounterOptions, 14 | /** Request context */ 15 | senderParams: SenderInfo, 16 | ) => number | string | null; 17 | export type FlagGettersHash = Record; 18 | -------------------------------------------------------------------------------- /src/providers/params/const.ts: -------------------------------------------------------------------------------- 1 | import { IS_TRUSTED_EVENT_BR_KEY } from 'src/api/watch'; 2 | 3 | export const PARAMS_PROVIDER = '1'; 4 | export const METHOD_NAME_PARAMS = 'params'; 5 | export const INTERNAL_PARAMS_KEY = '__ym'; 6 | export const IS_TRUSTED_EVENT_KEY = IS_TRUSTED_EVENT_BR_KEY; 7 | export const YM_LOG_WHITELIST_KEYS: string[] = []; 8 | 9 | export type ParamsHandler = (...a: any[]) => T; 10 | 11 | declare module 'src/utils/counter/type' { 12 | interface CounterObject { 13 | /** Transmits custom session parameters */ 14 | [METHOD_NAME_PARAMS]?: ParamsHandler; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/async/helpers.ts: -------------------------------------------------------------------------------- 1 | import { iterForOf } from './iterator'; 2 | import { executeIterator } from './executor'; 3 | import { taskFork } from './task'; 4 | import { errorLogger } from '../errorLogger/errorLogger'; 5 | import { noop } from '../function/noop'; 6 | 7 | export const iterateTaskWithConstraints = ( 8 | ctx: Window, 9 | collection: T[], 10 | callback: (item: T) => void, 11 | maxTime = 1, 12 | errorNamespace = 'itc', 13 | ) => { 14 | const iterator = iterForOf(collection, callback); 15 | const task = executeIterator(ctx, iterator, maxTime); 16 | task(taskFork(errorLogger(ctx, errorNamespace), noop)); 17 | }; 18 | -------------------------------------------------------------------------------- /src/utils/dom/block.ts: -------------------------------------------------------------------------------- 1 | import { bindArg } from '../function/bind'; 2 | import { closest } from './closest'; 3 | import { CSS_KEY, PATH, getData } from './identifiers'; 4 | import { select } from './select'; 5 | 6 | const BLOCK_SELECTOR = 7 | 'div,span,main,section,p,b,h1,h2,h3,h4,h5,h6,td,small,a,i,td,li,q'; 8 | 9 | export const getClosestTextContainer = bindArg(BLOCK_SELECTOR, closest); 10 | export const selectTextContainer = bindArg(BLOCK_SELECTOR, select); 11 | export const getTextContainerData = ( 12 | ctx: Window, 13 | form: HTMLElement, 14 | ignored?: HTMLElement, 15 | ) => getData(ctx, form, [PATH, CSS_KEY], undefined, ignored); 16 | -------------------------------------------------------------------------------- /src/utils/iframeConnector/const.ts: -------------------------------------------------------------------------------- 1 | export const IFRAME_MESSAGE_TYPE = 'type'; 2 | export const IFRAME_MESSAGE_HID = 'hid'; 3 | export const IFRAME_MESSAGE_COUNTER_ID = 'counterId'; 4 | export const IFRAME_MESSAGE_DUID = 'duid'; 5 | export const IFRAME_MESSAGE_DATA = 'data'; 6 | export const IFRAME_MESSAGE_TO_COUNTER = 'toCounter'; 7 | 8 | export const NAME_SPACE = '__yminfo'; 9 | 10 | export const SEND_TIMEOUT = 5000; 11 | export const INIT_MESSAGE_CHILD = 'initToChild'; 12 | export const INIT_MESSAGE_PARENT = 'initToParent'; 13 | export const INIT_MESSAGE = 'parentConnect'; 14 | export const OUT_DIRECTION = 0 as const; 15 | export const SPLITTER = ':'; 16 | -------------------------------------------------------------------------------- /src/utils/function/isNativeFunction/isNativeFunction.ts: -------------------------------------------------------------------------------- 1 | import { reportNonNativeFunction } from 'src/providers/reportNonNativeFunctions/report'; 2 | import { flags } from '@inject'; 3 | import type { AnyConstructor, AnyFunc } from '../types'; 4 | import { isNativeFn } from './isNativeFn'; 5 | 6 | export const isNativeFunction = ( 7 | functionName: string, 8 | fn: AnyFunc | AnyConstructor, 9 | ) => { 10 | const isNative = isNativeFn(functionName, fn); 11 | if (flags.DEBUG_CONSOLE_FEATURE) { 12 | if (fn && !isNative) { 13 | reportNonNativeFunction(fn, functionName); 14 | } 15 | } 16 | 17 | return isNative; 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/object/isPlainObject.ts: -------------------------------------------------------------------------------- 1 | import { cEvery } from '../array/every'; 2 | import { bindArg } from '../function/bind'; 3 | import { isObject } from './assertions'; 4 | import { isPrimitive, Primitive } from './isPrimitive'; 5 | import { cValues } from './utils'; 6 | 7 | /** 8 | * Check if an input is an object and if max depth condition is met. 9 | * Used to verify an object does not contain circular loops. 10 | */ 11 | export const isPlainObject = ( 12 | ctx: Window, 13 | object: unknown, 14 | ): object is Record => { 15 | return ( 16 | isObject(object) && cEvery(bindArg(ctx, isPrimitive), cValues(object)) 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/querystring/__tests__/querystring.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import * as chai from 'chai'; 3 | import { stringify, parse } from '..'; 4 | 5 | describe('QueryString', () => { 6 | it('should parse objects', () => { 7 | const parsed = parse('val&info=1'); 8 | chai.expect(parsed.val).to.be.equal(undefined); 9 | chai.expect(parsed.info).to.be.equal('1'); 10 | }); 11 | it('should stringify objects', () => { 12 | const obj = { 13 | test: 1, 14 | badGuy: undefined, 15 | info: 2, 16 | }; 17 | chai.expect(stringify(obj)).to.be.equal('test=1&info=2'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/utils/function/finallyCallUserCallback.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { AnyFunc } from './types'; 3 | import { bindArgs } from './bind'; 4 | import { handleError } from '../errorLogger/handleError'; 5 | import { callUserCallback } from './callUserCallback'; 6 | 7 | export const finallyCallUserCallback = ( 8 | ctx: Window, 9 | errorNamespace: string, 10 | promise: Promise, 11 | callback: AnyFunc, 12 | userCtx?: any, 13 | ) => { 14 | const userCallback = bindArgs([ctx, callback, userCtx], callUserCallback); 15 | 16 | return promise.then(userCallback, (e) => { 17 | userCallback(); 18 | handleError(ctx, errorNamespace, e); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /src/providers/debugConsole/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { windowProviderInitializers } from 'src/providersEntrypoint'; 3 | import { COOKIES_WHITELIST } from 'src/storage/cookie/isAllowed'; 4 | import { useReportNonNativeFunctionProvider } from '../reportNonNativeFunctions'; 5 | import { DEBUG_CTX_FLAG, DEBUG_COOKIE } from './const'; 6 | 7 | declare global { 8 | interface Window { 9 | [DEBUG_CTX_FLAG]: boolean; 10 | } 11 | } 12 | 13 | export const initProvider = () => { 14 | if (flags.DEBUG_CONSOLE_FEATURE) { 15 | COOKIES_WHITELIST.push(DEBUG_COOKIE); 16 | windowProviderInitializers.unshift(useReportNonNativeFunctionProvider); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/providers/firstPartyMethod/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | METHOD_NAME_FIRST_PARTY, 3 | METHOD_NAME_FIRST_PARTY_HASHED, 4 | } from './const'; 5 | 6 | export interface FirstPartyInputData { 7 | [x: string]: string | number | FirstPartyInputData; 8 | } 9 | 10 | export type FirstPartyOutputData = [string, string | FirstPartyOutputData[]]; 11 | 12 | export type FirstPartyMethodHandler = ( 13 | data: FirstPartyInputData, 14 | ) => Promise | void; 15 | 16 | declare module 'src/utils/counter/type' { 17 | interface CounterObject { 18 | [METHOD_NAME_FIRST_PARTY]?: FirstPartyMethodHandler; 19 | [METHOD_NAME_FIRST_PARTY_HASHED]?: FirstPartyMethodHandler; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/transport/__tests__/transport.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import * as chai from 'chai'; 3 | import { JSDOMWrapper } from 'src/__tests__/utils/jsdom'; 4 | import { DEFAULT_COUNTER_TYPE } from 'src/providers/counterOptions'; 5 | import type { CounterOptions } from 'src/utils/counterOptions'; 6 | import { getTransportList } from '..'; 7 | 8 | describe('transportList', () => { 9 | const { window } = new JSDOMWrapper(); 10 | it('returns at least one transport', () => { 11 | const opt: CounterOptions = { 12 | id: 123, 13 | counterType: DEFAULT_COUNTER_TYPE, 14 | }; 15 | chai.expect(getTransportList(window, opt).length).to.be.ok; 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/utils/methodDecorators/destructing.ts: -------------------------------------------------------------------------------- 1 | import { getCounterInstance } from 'src/utils/counter/getInstance'; 2 | import { argsToArray } from 'src/utils/function/args'; 3 | import type { Decorator } from './types'; 4 | 5 | export const destructingDecorator: Decorator = ( 6 | ctx, 7 | counterOptions, 8 | methodName, 9 | fn, 10 | ) => { 11 | return function destructing() { 12 | const counter = getCounterInstance(ctx, counterOptions); 13 | if (!counter) { 14 | return undefined; 15 | } 16 | 17 | // eslint-disable-next-line prefer-rest-params 18 | const fnArgs = argsToArray(arguments); 19 | return fn(...fnArgs); 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/function/pipe.ts: -------------------------------------------------------------------------------- 1 | import { F as Func } from 'ts-toolbelt'; 2 | import { cReduce } from 'src/utils/array/reduce'; 3 | import { argsToArray } from './args'; 4 | import { cCont } from './cont'; 5 | import { AnyFunc } from './types'; 6 | 7 | export const pipe: Func.Pipe = function b() { 8 | const fnList: Func.Piper = argsToArray(arguments) as any; 9 | const firstFn = fnList.shift(); 10 | return function pipeStartFunction() { 11 | // @ts-ignore 12 | const firstResult = firstFn!(...arguments); 13 | return cReduce(cCont, firstResult, fnList); 14 | }; 15 | }; 16 | 17 | export const dirtyPipe = pipe; // тоже самое что pipe, но он может иметь сайд эффекты 18 | -------------------------------------------------------------------------------- /src/providers/siteStatistics/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersAsync } from 'src/providersEntrypoint'; 3 | import { ctxErrorLogger } from 'src/utils/errorLogger/errorLogger'; 4 | import { 5 | STATISTICS_MODE_KEY, 6 | useSiteStatisticsProvider, 7 | } from './siteStatistics'; 8 | 9 | declare module 'src/utils/counterSettings/types' { 10 | interface CounterSettingsParams { 11 | /** is site statistics iframe enabled */ 12 | [STATISTICS_MODE_KEY]?: number; 13 | } 14 | } 15 | 16 | export const initProvider = () => { 17 | if (flags.INSERT_SITE_STATISTICS_SCRIPT) { 18 | providersAsync.push(ctxErrorLogger('p.st', useSiteStatisticsProvider)); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/string/__test__/remove.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { removeNonDigits, removeSpaces, removeDigits } from '../remove'; 3 | 4 | describe('string remove utils behave as expected', () => { 5 | it('removeSpaces', () => { 6 | chai.expect(removeSpaces('+8 (777) 666-55-44')).to.equal( 7 | '+8(777)666-55-44', 8 | ); 9 | }); 10 | 11 | it('removeNonDigits', () => { 12 | chai.expect(removeNonDigits('+8 (777) 666-55-44')).to.equal( 13 | '87776665544', 14 | ); 15 | }); 16 | it('removeDigit', () => { 17 | chai.expect(removeDigits(';A1QdA1AAAA1A}A2AA3')).to.equal( 18 | ';AQdAAAAAA}AAA', 19 | ); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/utils/number/random.ts: -------------------------------------------------------------------------------- 1 | import { isUndefined } from '../object'; 2 | 3 | export const RND_MAX = 1073741824; 4 | export const RND_MIN = 1; 5 | 6 | /** 7 | * Generate a random number in a given frame 8 | */ 9 | export const getRandom = (ctx: Window, rawMin?: number, rawMax?: number) => { 10 | let min: number; 11 | let max: number; 12 | const isMaxUndef = isUndefined(rawMax); 13 | 14 | if (isUndefined(rawMin) && isMaxUndef) { 15 | min = 1; 16 | max = RND_MAX; 17 | } else if (isMaxUndef) { 18 | min = 1; 19 | max = rawMin as number; 20 | } else { 21 | min = rawMin as number; 22 | max = rawMax!; 23 | } 24 | return ctx.Math.floor(ctx.Math.random() * (max - min)) + min; 25 | }; 26 | -------------------------------------------------------------------------------- /src/utils/array/reverse.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { toNativeOrFalse } from '../function/isNativeFunction/toNativeOrFalse'; 3 | 4 | const nativeReverse = toNativeOrFalse(Array.prototype.reverse, 'reverse'); 5 | export const reversePoly = (arr: T[]) => { 6 | const result: T[] = []; 7 | for (let i = arr.length - 1; i >= 0; i -= 1) { 8 | result[arr.length - 1 - i] = arr[i]; 9 | } 10 | 11 | return result; 12 | }; 13 | 14 | const callNativeOrPoly = nativeReverse 15 | ? (array: ArrayLike) => nativeReverse.call(array) 16 | : reversePoly; 17 | 18 | export const cReverse: (arr: T[]) => T[] = flags.POLYFILLS_FEATURE 19 | ? callNativeOrPoly 20 | : (array: ArrayLike) => Array.prototype.reverse.call(array); 21 | -------------------------------------------------------------------------------- /src/middleware/watchSyncFlags/brinfoFlags/getCounterNumber.ts: -------------------------------------------------------------------------------- 1 | import { getGlobalStorage } from 'src/storage/global/getGlobal'; 2 | import { getCounterKey } from 'src/utils/counterOptions'; 3 | import { memo } from 'src/utils/function/memo'; 4 | import { secondArg } from 'src/utils/function/identity'; 5 | import { pipe } from 'src/utils/function/pipe'; 6 | 7 | export const COUNTER_NO = 'counterNum'; 8 | export const getCounterNumber = memo( 9 | (ctx: Window) => { 10 | const name = COUNTER_NO; 11 | const storage = getGlobalStorage(ctx); 12 | const privCn = storage.getVal(name, 0); 13 | const newCn = privCn + 1; 14 | storage.setVal(name, newCn); 15 | return newCn; 16 | }, 17 | pipe(secondArg, getCounterKey), 18 | ); 19 | -------------------------------------------------------------------------------- /src/utils/function/isNativeFunction/getNativeFunction.ts: -------------------------------------------------------------------------------- 1 | import { getPath as getFunctionUtils } from 'src/utils/object/path'; 2 | 3 | // FIXME: get rid of any 4 | export const getNativeFunction = (functionName: string, owner: any) => { 5 | const ownerFn = getFunctionUtils(owner, functionName); 6 | const fn = 7 | getFunctionUtils(owner, `constructor.prototype.${functionName}`) || 8 | ownerFn; 9 | try { 10 | if (fn && fn.apply) { 11 | return function nativeFunction() { 12 | // eslint-disable-next-line prefer-rest-params 13 | return fn.apply(owner, arguments); 14 | }; 15 | } 16 | } catch (e) { 17 | // ie 8 18 | return ownerFn; 19 | } 20 | return fn; 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/events/bufferObserver.ts: -------------------------------------------------------------------------------- 1 | import { ctxMapSwap } from '../array/map'; 2 | import { observer, Observer } from './observer'; 3 | 4 | // сохраняет N событий из потока 5 | // и тригерит их у вновь подписавшихся 6 | 7 | export const bufferObserver = ( 8 | ctx: Window, 9 | sourceObserver: Observer, 10 | maxBuffer: number, 11 | ): Observer => { 12 | const resultObserver = observer(ctx); 13 | const buffer: T[] = []; 14 | sourceObserver.on((data) => { 15 | buffer.push(data); 16 | if (buffer.length > maxBuffer) { 17 | buffer.shift(); 18 | } 19 | return resultObserver.trigger(data) as any; 20 | }); 21 | resultObserver.on = ctxMapSwap(buffer) as any; 22 | return resultObserver; 23 | }; 24 | -------------------------------------------------------------------------------- /src/inject/inject.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Build time variables defined via string replacement by rollup-plugin-replace 3 | */ 4 | 5 | import { BuildFlags, Args } from './types'; 6 | 7 | /** 8 | * Build flags, indicate which features are included and which are not. 9 | */ 10 | export const flags: BuildFlags = process.env.BUILD_FLAGS as any; 11 | 12 | /** 13 | * Build arguments - the definition of the script identity, 14 | * how the counter is initialized and where the data is sent. 15 | */ 16 | export const argOptions: Args = process.env.ARG_OPTIONS as any; 17 | 18 | /** 19 | * File name 20 | */ 21 | export const resourceId = process.env.JS_NAME as string; 22 | 23 | /** 24 | * Release version 25 | */ 26 | export const getVersion: () => string = () => process.env.VERSION! || '25'; 27 | -------------------------------------------------------------------------------- /src/providers/counterOptions/types.ts: -------------------------------------------------------------------------------- 1 | import type { CounterOption } from 'src/utils/counterOptions'; 2 | 3 | /** 4 | * Function for parsing parameters into a single format 5 | */ 6 | export type NormalizeFunction = (value: unknown) => unknown; 7 | 8 | /** 9 | * Option definition object 10 | */ 11 | export type OptionInitializer = { 12 | /** Option name */ 13 | optKey: string; 14 | /** A function for parsing the option into a unified format */ 15 | normalizeFunction?: NormalizeFunction; 16 | }; 17 | 18 | export type OptionInitializerMap = Record< 19 | T, 20 | OptionInitializer 21 | >; 22 | 23 | /** 24 | * Normalization functions of the corresponding parameters 25 | */ 26 | export type OptionsKeysMaps = Record; 27 | -------------------------------------------------------------------------------- /src/utils/json/json.ts: -------------------------------------------------------------------------------- 1 | export type JSONValue = 2 | | null 3 | | boolean 4 | | number 5 | | string 6 | | unknown[] 7 | | Record; 8 | 9 | function parse(ctx: Window, text: string | null): JSONValue { 10 | if (!text) { 11 | return null; 12 | } 13 | try { 14 | return ctx.JSON.parse(text); 15 | } catch (e) { 16 | return null; 17 | } 18 | } 19 | /** 20 | * @type function (Object, ...?): (string|null) 21 | */ 22 | const stringify = function a( 23 | ctx: Window, 24 | value: any, 25 | space?: number, 26 | ): string | null { 27 | try { 28 | return ctx.JSON.stringify(value, null, space); 29 | } catch (e) { 30 | return null; 31 | } 32 | }; 33 | 34 | export { parse, stringify }; 35 | -------------------------------------------------------------------------------- /src/providers/retransmit/retransmit.ts: -------------------------------------------------------------------------------- 1 | import { getRetransmitState } from 'src/middleware/retransmit/state'; 2 | import type { CounterOptions } from 'src/utils/counterOptions'; 3 | import { sendRetransmitRequests } from './sendRetransmitRequests'; 4 | 5 | /** 6 | * Looks for saved requests in local storage and tries to retransmit them 7 | * @param ctx - Current window 8 | * @param counterOpt - Counter options during initialization 9 | */ 10 | export const useRetransmitProvider = ( 11 | ctx: Window, 12 | counterOpt: CounterOptions, 13 | ) => { 14 | const retransmitState = getRetransmitState(ctx); 15 | retransmitState.clearExpired(); 16 | return sendRetransmitRequests( 17 | ctx, 18 | counterOpt, 19 | retransmitState.getNotExpired(), 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/function/bind/__test__/bind.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { callPoly } from '../bind'; 3 | 4 | describe('callPoly', () => { 5 | it('Works', () => { 6 | let counter = 0; 7 | const fn = () => { 8 | counter += 1; 9 | return counter; 10 | }; 11 | let result = callPoly(fn); 12 | chai.expect(result).to.be.equal(1); 13 | result = callPoly(fn, [1]); 14 | chai.expect(result).to.be.equal(2); 15 | result = callPoly(fn, [1, 1]); 16 | chai.expect(result).to.be.equal(3); 17 | result = callPoly(fn, [1, 1, 1]); 18 | chai.expect(result).to.be.equal(4); 19 | result = callPoly(fn, [1, 1, 1, 1]); 20 | chai.expect(result).to.be.equal(5); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/providers/searchTLD/searchTLD.ts: -------------------------------------------------------------------------------- 1 | import { host } from 'src/config'; 2 | import { cReduce } from 'src/utils/array/reduce'; 3 | 4 | type TldOverride = (ctx: Window, resource?: string) => string | undefined; 5 | export const TLD_OVERRIDES: TldOverride[] = []; 6 | 7 | /** 8 | * Changes recipient domain based on active features 9 | * @param ctx - Current window 10 | * @param resource - Domain where to send data 11 | */ 12 | export const getDomainAndTLD = (ctx: Window, resource?: string) => { 13 | return ( 14 | cReduce( 15 | (result, overrideFunction) => { 16 | return result || overrideFunction(ctx, resource); 17 | }, 18 | undefined, 19 | TLD_OVERRIDES, 20 | ) || host 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/debugEvents/wrapLoggerFunction.ts: -------------------------------------------------------------------------------- 1 | import { argsToArray } from 'src/utils/function/args'; 2 | import { dispatchDebuggerEvent } from './index'; 3 | 4 | type LogFn = (...data: any[]) => void; 5 | export const wrapLogFunction = ( 6 | ctx: Window, 7 | type: 'log' | 'warn' | 'error', 8 | counterKey: string, 9 | func: LogFn, 10 | ) => { 11 | return function l() { 12 | // eslint-disable-next-line prefer-rest-params 13 | const args = argsToArray(arguments); 14 | dispatchDebuggerEvent(ctx, { 15 | ['counterKey']: counterKey, 16 | ['name']: 'log', 17 | ['data']: { 18 | ['args']: args, 19 | ['type']: type, 20 | }, 21 | }); 22 | return func(...args); 23 | } as LogFn; 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/errorLogger/onError.ts: -------------------------------------------------------------------------------- 1 | import { cForEach } from '../array/map'; 2 | import { ctxBindArgs } from '../function/bind/ctxBind'; 3 | import { firstArg } from '../function/identity'; 4 | import { pipe } from '../function/pipe'; 5 | import { call } from '../function/utils'; 6 | 7 | export type OnErrorCallback = ( 8 | namespace: string, 9 | errorMessage: string, 10 | scope: string, 11 | stack?: string, 12 | ) => void; 13 | export const ON_ERROR_CALLBACKS: OnErrorCallback[] = []; 14 | 15 | export const runOnErrorCallbacks = ( 16 | namespace: string, 17 | errorMessage: string, 18 | scope: string, 19 | stack?: string, 20 | ) => { 21 | const args = [namespace, errorMessage, scope, stack]; 22 | cForEach(pipe(firstArg, ctxBindArgs(args), call), ON_ERROR_CALLBACKS); 23 | }; 24 | -------------------------------------------------------------------------------- /src/utils/deobfuscate/__tests__/deobfuscate.spec.ts: -------------------------------------------------------------------------------- 1 | import { cReverse } from 'src/utils/array/reverse'; 2 | import * as chai from 'chai'; 3 | import { deobfuscate } from '../deobfuscate'; 4 | 5 | const obfuscate = (obj: Record) => { 6 | const cp = { ...obj }; 7 | Object.keys(cp).forEach((key) => { 8 | const value = cp[key]; 9 | delete cp[key]; 10 | cp[cReverse(key.split('')).join('')] = value; 11 | }); 12 | return cp; 13 | }; 14 | 15 | describe('deobfuscate', () => { 16 | it('should deobfuscate key', () => { 17 | const value = 'value'; 18 | const testObj = obfuscate({ value }); 19 | const deobfuscatedKey = deobfuscate(obfuscate({ value: 1 })); 20 | 21 | chai.expect(testObj[deobfuscatedKey]).to.eq(value); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/utils/telemetry/__test__/telemetry.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import * as chai from 'chai'; 3 | import { telemetry } from '../telemetry'; 4 | 5 | describe('telemetry', () => { 6 | it('inits', () => { 7 | const flags = { 8 | test: 1, 9 | }; 10 | const info = telemetry(flags); 11 | const emInfo = telemetry(null); 12 | chai.expect(emInfo.ctx()).to.be.deep.equal({}); 13 | chai.expect(info.ctx()).to.be.deep.equal(flags); 14 | chai.expect(info.setVal('q', 1).ctx()).to.be.deep.equal({ 15 | test: 1, 16 | q: 1, 17 | }); 18 | chai.expect(flags).to.be.deep.equal({ 19 | test: 1, 20 | q: 1, 21 | }); 22 | chai.expect(info.serialize()).to.equal('test(1)q(1)'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/utils/browser/utils.ts: -------------------------------------------------------------------------------- 1 | import { ctxPath, getPath } from 'src/utils/object/path'; 2 | import { memo } from 'src/utils/function/memo'; 3 | import { stringIndexOf } from '../string'; 4 | 5 | export const getAppleUAProps = memo((ctx: Window) => { 6 | const navigator = getPath(ctx, 'navigator') || {}; 7 | const userAgentInfo = getPath(navigator, 'userAgent') || ''; 8 | const vendor = getPath(navigator, 'vendor') || ''; 9 | const isApple = stringIndexOf(vendor, 'Apple') > -1; 10 | return { isApple, userAgentInfo }; 11 | }); 12 | 13 | export const checkUserAgent = (str: RegExp, ctx: Window) => { 14 | return ( 15 | (getPath(ctx, 'navigator.userAgent') || '') 16 | .toLowerCase() 17 | .search(str) !== -1 18 | ); 19 | }; 20 | 21 | export const getAgent = memo(ctxPath('navigator.userAgent')); 22 | -------------------------------------------------------------------------------- /src/utils/function/curry.ts: -------------------------------------------------------------------------------- 1 | import { cCont } from './cont'; 2 | import { AnyFunc } from './types'; 3 | 4 | export const curry2 = 5 | (func: (a: A, b: B) => R) => 6 | (a: A) => 7 | (b: B) => 8 | func(a, b); 9 | 10 | export const curry2SwapArgs = 11 | (func: (a: A, b: B) => R) => 12 | (b: B) => 13 | (a: A) => 14 | func(a, b); 15 | 16 | export const equal: (a: T) => (b: T) => boolean = curry2((a, b) => { 17 | return a === b; 18 | }); 19 | 20 | /** 21 | * Run a function with argument and return the argument 22 | */ 23 | export const asSideEffect = curry2( 24 | [0]>(fn: F, arg: A) => { 25 | fn(arg); 26 | return arg; 27 | }, 28 | ); 29 | 30 | /** 31 | * continuation monad 32 | */ 33 | export const cont = curry2(cCont); 34 | -------------------------------------------------------------------------------- /src/utils/browser/firefox.ts: -------------------------------------------------------------------------------- 1 | import { getPath } from 'src/utils/object/path'; 2 | import { memo } from 'src/utils/function/memo'; 3 | import { bindArg } from 'src/utils/function/bind/bind'; 4 | import { checkUserAgent } from './utils'; 5 | import { isNil } from '../object/assertions'; 6 | 7 | export const isGecko = memo(bindArg(/gecko/, checkUserAgent)); 8 | export const isFFVersionRegExp = /Firefox\/([0-9]+)/i; 9 | 10 | export const isFF = memo((ctx: Window) => { 11 | const style = getPath(ctx, 'document.documentElement.style'); 12 | const InstallTrigger = getPath(ctx, 'InstallTrigger'); 13 | const hasFFVersion = checkUserAgent(isFFVersionRegExp, ctx); 14 | isFFVersionRegExp.lastIndex = 0; 15 | 16 | return ( 17 | !!(style && 'MozAppearance' in style && !isNil(InstallTrigger)) || 18 | hasFFVersion 19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /src/inject/types.ts: -------------------------------------------------------------------------------- 1 | import { Feature } from 'generated/features'; 2 | 3 | /** 4 | * Build-time parameters 5 | */ 6 | export type ConfigOptions = { 7 | /** 8 | * The name of constrictor functions in Ya namespace. 9 | * The counter constructor is written to window.Ya.(constructor). 10 | */ 11 | construct: string; 12 | 13 | /** 14 | * The name of callback to be called after all metrika functionality being initialized. 15 | */ 16 | callbackPostfix: string; 17 | 18 | /** 19 | * The script version uniquely identifying the combination of flags being used. 20 | */ 21 | version: string; 22 | 23 | /** 24 | * The host to send data to. 25 | */ 26 | host: string; 27 | }; 28 | 29 | export type Args = Readonly; 30 | 31 | export type BuildFlags = { 32 | [key in Feature]: boolean; 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/array/find.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { toNativeOrFalse } from 'src/utils/function/isNativeFunction'; 3 | import { FindCallback, Find } from './types'; 4 | 5 | const nativeFind = toNativeOrFalse(Array.prototype.find, 'find'); 6 | 7 | export const findPoly: Find = (fn, array) => { 8 | for (let i = 0; i < array.length; i += 1) { 9 | if (fn.call(array, array[i], i)) { 10 | return array[i]; 11 | } 12 | } 13 | return undefined; 14 | }; 15 | 16 | const callNativeOrPoly: Find = nativeFind 17 | ? (fn: FindCallback, array: ArrayLike) => 18 | nativeFind.call(array, fn) 19 | : findPoly; 20 | 21 | export const cFind: Find = flags.POLYFILLS_ES6_FEATURE 22 | ? callNativeOrPoly 23 | : (fn: FindCallback, array: ArrayLike) => 24 | Array.prototype.find.call(array, fn); 25 | -------------------------------------------------------------------------------- /src/middleware/watchSyncFlags/brinfoFlags/timeFlags.ts: -------------------------------------------------------------------------------- 1 | import { cont } from 'src/utils/function/curry'; 2 | import { memo } from 'src/utils/function/memo'; 3 | import { pipe } from 'src/utils/function/pipe'; 4 | import { 5 | TimeOne, 6 | getTimezone, 7 | getTimestamp, 8 | getSec, 9 | getNs, 10 | TimeState, 11 | } from 'src/utils/time/time'; 12 | 13 | export const timeZone = memo( 14 | pipe(TimeOne, cont<(timeState: TimeState) => number, number>(getTimezone)), 15 | ); 16 | export const timeStamp = pipe( 17 | TimeOne, 18 | cont<(timeState: TimeState) => string, string>(getTimestamp), 19 | ); 20 | export const timeSeconds = pipe( 21 | TimeOne, 22 | cont<(timeState: TimeState) => number, number>(getSec), 23 | ); 24 | export const timeNavigationStart = memo( 25 | pipe(TimeOne, cont<(timeState: TimeState) => number, number>(getNs)), 26 | ); 27 | -------------------------------------------------------------------------------- /src/utils/methodDecorators/selfReturn.ts: -------------------------------------------------------------------------------- 1 | import { getCounterInstance } from 'src/utils/counter/getInstance'; 2 | import { CounterObject } from 'src/utils/counter/type'; 3 | import { isUndefined } from 'src/utils/object'; 4 | import { argsToArray } from '../function/args'; 5 | import type { Decorator } from './types'; 6 | 7 | export const selfReturnDecorator: Decorator = ( 8 | ctx, 9 | counterOptions, 10 | methodName, 11 | fn, 12 | ) => { 13 | return function selfReturn() { 14 | // eslint-disable-next-line prefer-rest-params 15 | const fnArgs = argsToArray(arguments); 16 | const result = fn(...fnArgs); 17 | if (isUndefined(result)) { 18 | const counter = getCounterInstance(ctx, counterOptions); 19 | return counter; 20 | } 21 | 22 | return result; 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/providers/goal/const.ts: -------------------------------------------------------------------------------- 1 | export const GOAL_PROVIDER = 'g'; 2 | 3 | export const METHOD_NAME_GOAL = 'reachGoal'; 4 | 5 | export const DEFAULT_SCHEME_PREFIX = 'goal'; 6 | 7 | /** 8 | * Function to send information about goal reach (conversion) 9 | */ 10 | export type GoalHandler = ( 11 | /** Goal id in counter settings */ 12 | goalName: string, 13 | /** Object or tree to be sent as plain arrays */ 14 | rawParams?: Record | (() => any), 15 | /** The function that will be called after sending the goal */ 16 | rawCallback?: (() => any) | any, 17 | /** Callback context */ 18 | rawFnCtx?: any, 19 | ) => T; 20 | 21 | declare module 'src/utils/counter/type' { 22 | interface CounterObject { 23 | /** Transmits information about a completed goal (conversion) */ 24 | [METHOD_NAME_GOAL]?: GoalHandler; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/middleware/types.ts: -------------------------------------------------------------------------------- 1 | import { CounterOptions } from 'src/utils/counterOptions/types'; 2 | import { SenderInfo } from 'src/sender/SenderInfo'; 3 | 4 | export type MiddlewareHandler = ( 5 | /** Request context */ 6 | senderParams: SenderInfo, 7 | /** Callback */ 8 | next: () => void, 9 | ) => void; 10 | 11 | export type Middleware = { 12 | /** Handler to run before request 13 | * - NOTE: This function mutates the `senderParams` parameter 14 | */ 15 | beforeRequest?: MiddlewareHandler; 16 | /** Handler to run after request 17 | * - NOTE: This function mutates the `senderParams` parameter 18 | */ 19 | afterRequest?: MiddlewareHandler; 20 | }; 21 | 22 | export type MiddlewareGetter = (ctx: Window, opt: CounterOptions) => Middleware; 23 | 24 | export type MiddlewareWeightTuple = [ 25 | middleware: MiddlewareGetter, 26 | weight: number, 27 | ]; 28 | -------------------------------------------------------------------------------- /src/utils/array/isArray.ts: -------------------------------------------------------------------------------- 1 | import { protoToString } from 'src/utils/string'; 2 | import { flags } from '@inject'; 3 | import { toNativeOrFalse } from '../function/isNativeFunction/toNativeOrFalse'; 4 | 5 | type isArrayType = (arg: unknown) => arg is T[]; 6 | 7 | export function isArrayPolyfill(obj: unknown): obj is T[] { 8 | return protoToString(obj) === '[object Array]'; 9 | } 10 | 11 | const nativeIsArray = toNativeOrFalse(Array.isArray, 'isArray'); 12 | 13 | const callNativeOrPoly: isArrayType = nativeIsArray 14 | ? (obj: unknown): obj is T[] => nativeIsArray(obj) 15 | : isArrayPolyfill; 16 | 17 | export const isArray: isArrayType = flags.POLYFILLS_FEATURE 18 | ? callNativeOrPoly 19 | : (obj: unknown): obj is T[] => Array.isArray(obj); 20 | 21 | export const isIterable = (arg: any): arg is Iterable => { 22 | return typeof arg[Symbol.iterator] === 'function'; 23 | }; 24 | -------------------------------------------------------------------------------- /src/providers/stackProxy/types.ts: -------------------------------------------------------------------------------- 1 | import type { MetrikaCounter } from 'src/types'; 2 | import type { CounterObject } from 'src/utils/counter/type'; 3 | import { type CounterOptions } from 'src/utils/counterOptions'; 4 | 5 | export type CounterMethods = keyof CounterObject | 'init'; 6 | export type StaticMethods = keyof MetrikaCounter; 7 | export type StackCallOnInstance = [number | string, CounterMethods, ...any[]]; 8 | export type StackCallStatic = [StaticMethods, ...any[]]; 9 | export type StackCall = (StackCallOnInstance | StackCallStatic) & { 10 | /** Is call executed */ 11 | executed?: boolean; 12 | }; 13 | export type StackProxyListener = ( 14 | /** Current window */ 15 | ctx: Window, 16 | /** Counter options on initialization */ 17 | counterOptions: CounterOptions, 18 | /** Arguments */ 19 | args: any[], 20 | /** Counter instance */ 21 | counter?: CounterObject, 22 | ) => boolean | undefined; 23 | -------------------------------------------------------------------------------- /src/transport/watchModes.ts: -------------------------------------------------------------------------------- 1 | import { stringify } from 'src/utils/querystring'; 2 | import { addQuery } from 'src/utils/url'; 3 | import { TransportOptions } from './types'; 4 | 5 | export const WATCH_WMODE_JSON = '7'; 6 | export const WATCH_WMODE_JSONP = '5'; 7 | export const WATCH_WMODE_IMAGE = '0'; 8 | 9 | /** 10 | * Moves the body of the POST request to the arguments of the GET request 11 | * for transports that cannot transmit the request body, e.g. beacon 12 | */ 13 | export const getSrcUrl = ( 14 | senderUrl: string, 15 | opt: TransportOptions, 16 | query: Record, 17 | ) => { 18 | let resultUrl = senderUrl; 19 | const stringifiedQuery = stringify(query); 20 | 21 | if (stringifiedQuery) { 22 | resultUrl = addQuery(resultUrl, stringifiedQuery); 23 | } 24 | 25 | if (opt.rBody) { 26 | resultUrl = addQuery(resultUrl, opt.rBody); 27 | } 28 | return resultUrl; 29 | }; 30 | -------------------------------------------------------------------------------- /src/utils/number/__tests__/number.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { getRandom, RND_MAX } from '../random'; 3 | 4 | describe('number util', () => { 5 | const rnd = 0.42; 6 | const win = { 7 | Math: { 8 | random: () => { 9 | return rnd; 10 | }, 11 | floor: Math.floor.bind(Math), 12 | round: Math.round.bind(Math), 13 | }, 14 | } as any as Window; 15 | 16 | it('random', () => { 17 | const n = getRandom(win); 18 | chai.expect(n).to.be.a('number'); 19 | chai.expect(n).to.be.equal(Math.floor(RND_MAX * rnd)); 20 | const max = 100; 21 | const min = 90; 22 | const n2 = getRandom(win, max); 23 | chai.expect(n2).to.be.equal(Math.floor(max * rnd)); 24 | const n3 = getRandom(win, min, max); 25 | chai.expect(n3).to.be.equal(Math.floor(rnd * (max - min)) + min); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/utils/array/join.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { curry2 } from '../function/curry'; 3 | import { Join } from './types'; 4 | import { toNativeOrFalse } from '../function/isNativeFunction/toNativeOrFalse'; 5 | 6 | export const joinPoly: Join = (separator, array) => { 7 | let result = ''; 8 | for (let i = 0; i < array.length; i += 1) { 9 | result += `${i ? separator : ''}${array[i]}`; 10 | } 11 | return result; 12 | }; 13 | 14 | const nativeJoin = toNativeOrFalse(Array.prototype.join, 'join'); 15 | 16 | const callNativeOrPoly = nativeJoin 17 | ? (separator: string, array: ArrayLike) => 18 | nativeJoin.call(array, separator) 19 | : joinPoly; 20 | 21 | export const arrayJoin: Join = flags.POLYFILLS_FEATURE 22 | ? callNativeOrPoly 23 | : (separator: string, array: ArrayLike) => 24 | Array.prototype.join.call(array, separator); 25 | 26 | export const ctxJoin = curry2(arrayJoin); 27 | -------------------------------------------------------------------------------- /src/utils/counter/getInstance.ts: -------------------------------------------------------------------------------- 1 | import { CounterOptions, getCounterKey } from 'src/utils/counterOptions'; 2 | import { getGlobalStorage } from 'src/storage/global/getGlobal'; 3 | import { CounterObject } from './type'; 4 | 5 | export const COUNTERS_GLOBAL_KEY = 'counters'; 6 | 7 | export const getCounterInstance = ( 8 | ctx: Window, 9 | counterOptions: CounterOptions, 10 | ): CounterObject | undefined => { 11 | const storage = getGlobalStorage(ctx); 12 | const dict = storage.getVal>( 13 | COUNTERS_GLOBAL_KEY, 14 | {}, 15 | ); 16 | const counterKey = getCounterKey(counterOptions); 17 | return dict[counterKey]; 18 | }; 19 | 20 | export const removeCounter = (ctx: Window, counterKey: string) => { 21 | const counters = getGlobalStorage(ctx).getVal>( 22 | COUNTERS_GLOBAL_KEY, 23 | {}, 24 | ); 25 | delete counters[counterKey]; 26 | }; 27 | -------------------------------------------------------------------------------- /src/utils/array/every.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { Every, EveryCallback } from './types'; 3 | import { cReduce } from './reduce'; 4 | import { toNativeOrFalse } from '../function/isNativeFunction/toNativeOrFalse'; 5 | 6 | const nativeEvery = toNativeOrFalse(Array.prototype.every, 'every'); 7 | 8 | const everyPoly = (fn: EveryCallback, array: ArrayLike) => { 9 | return cReduce( 10 | (flag, value, index) => { 11 | return flag ? !!fn(value, index) : false; 12 | }, 13 | true, 14 | array, 15 | ); 16 | }; 17 | 18 | const callNativeOrPoly: Every = nativeEvery 19 | ? (fn: EveryCallback, array: ArrayLike) => 20 | nativeEvery.call(array, fn) 21 | : everyPoly; 22 | 23 | export const cEvery: Every = flags.POLYFILLS_FEATURE 24 | ? callNativeOrPoly 25 | : (fn: EveryCallback, array: ArrayLike) => 26 | Array.prototype.every.call(array, fn); 27 | -------------------------------------------------------------------------------- /src/utils/array/some.ts: -------------------------------------------------------------------------------- 1 | import { toNativeOrFalse } from 'src/utils/function/isNativeFunction'; 2 | import { flags } from '@inject'; 3 | import { Some, SomeCallback } from './types'; 4 | 5 | const nativeSome = toNativeOrFalse(Array.prototype.some, 'some'); 6 | 7 | export const somePoly: Some = (fn, array) => { 8 | for (let i = 0; i < array.length; i += 1) { 9 | // fn не выполняется для отсутствующих или удаленных значений массива (по спецификации) 10 | if (i in array && fn.call(array, array[i], i)) { 11 | return true; 12 | } 13 | } 14 | return false; 15 | }; 16 | 17 | const callNativeOrPoly: Some = nativeSome 18 | ? (fn: SomeCallback, array: ArrayLike) => 19 | nativeSome.call(array, fn) 20 | : somePoly; 21 | 22 | export const cSome: Some = flags.POLYFILLS_FEATURE 23 | ? callNativeOrPoly 24 | : (fn: SomeCallback, array: ArrayLike) => 25 | Array.prototype.some.call(array, fn); 26 | -------------------------------------------------------------------------------- /src/utils/events/ready.ts: -------------------------------------------------------------------------------- 1 | import { includes } from 'src/utils/array/includes'; 2 | import { cEvent } from 'src/utils/events/events'; 3 | 4 | import { runAsync } from 'src/utils/async/async'; 5 | import { AnyFunc } from '../function/types'; 6 | 7 | const INTERACTIVE_READY_STATE = 'interactive'; 8 | const COMPLETE_READY_STATE = 'complete'; 9 | 10 | export const runCallbackOnReady = (ctx: Window, callback: AnyFunc): void => { 11 | const { document: doc } = ctx; 12 | const state: string = doc.readyState; 13 | if (includes(state, [INTERACTIVE_READY_STATE, COMPLETE_READY_STATE])) { 14 | runAsync(ctx, callback); 15 | } else { 16 | const { on, un } = cEvent(ctx); 17 | const onload = () => { 18 | un(doc, ['DOMContentLoaded'], onload); 19 | un(ctx, ['load'], onload); 20 | callback(); 21 | }; 22 | on(doc, ['DOMContentLoaded'], onload); 23 | on(ctx, ['load'], onload); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/providers/triggerEvent/triggerEvent.ts: -------------------------------------------------------------------------------- 1 | import { ctxErrorLogger } from 'src/utils/errorLogger/errorLogger'; 2 | import { CounterOptions } from 'src/utils/counterOptions'; 3 | import { createAndDispatchEvent } from 'src/utils/dom/dom'; 4 | import { runAsync } from 'src/utils/async/async'; 5 | import { bindArgs } from 'src/utils/function/bind'; 6 | 7 | /** 8 | * Triggers event yacounterXXXXXinited on counter initialization 9 | * @param ctx - Current window 10 | * @param counterOptions - Counter options on initialization 11 | */ 12 | export const useTriggerEvent = ctxErrorLogger( 13 | 'trigger.in', 14 | (ctx: Window, { id, triggerEvent }: CounterOptions) => { 15 | if (!triggerEvent) { 16 | return; 17 | } 18 | 19 | const eventName = `yacounter${id}inited`; 20 | 21 | runAsync( 22 | ctx, 23 | bindArgs([ctx, eventName], createAndDispatchEvent), 24 | 't.i', 25 | ); 26 | }, 27 | ); 28 | -------------------------------------------------------------------------------- /src/utils/events/throttleObserver.ts: -------------------------------------------------------------------------------- 1 | import { setDefer } from 'src/utils/defer/defer'; 2 | import { noop } from '../function/noop'; 3 | import { observer, Observer } from './observer'; 4 | 5 | export const throttleObserver = ( 6 | ctx: Window, 7 | rawObserver: Observer, 8 | timeOut: number, 9 | ) => { 10 | const throttledObserver: Observer = observer(ctx); 11 | let timer: number; 12 | let callNextTime = false; 13 | let latestData: T; 14 | const cb = () => { 15 | timer = 0; 16 | if (callNextTime) { 17 | callNextTime = false; 18 | timer = setDefer(ctx, cb, timeOut); 19 | throttledObserver.trigger(latestData); 20 | } 21 | }; 22 | rawObserver.on((data) => { 23 | callNextTime = true; 24 | latestData = data; 25 | if (!timer) { 26 | cb(); 27 | } 28 | return noop as any; 29 | }); 30 | return throttledObserver; 31 | }; 32 | -------------------------------------------------------------------------------- /src/utils/isCounterSilent/isCounterSilent.ts: -------------------------------------------------------------------------------- 1 | import { ctxIncludes } from 'src/utils/array/includes'; 2 | import { getCounterKey } from 'src/utils/counterOptions/getCounterKey'; 3 | import { CounterOptions } from 'src/utils/counterOptions/types'; 4 | import { memo } from 'src/utils/function/memo'; 5 | import { ctxPath } from 'src/utils/object'; 6 | import { pipe } from 'src/utils/function/pipe'; 7 | import { parseIntSafe } from '../number/number'; 8 | 9 | export const ignoredCounters: number[] = [26812653]; 10 | 11 | export const isCounterIdSilent = ctxIncludes(ignoredCounters); 12 | 13 | export const isCounterKeySilent = (counterKey: string) => { 14 | if (!counterKey) { 15 | return false; 16 | } 17 | const counterId = counterKey.split(':')[0]; 18 | 19 | return isCounterIdSilent(parseIntSafe(counterId!)!); 20 | }; 21 | 22 | export const isCounterSilent = memo( 23 | pipe(ctxPath('id'), isCounterIdSilent) as (a: CounterOptions) => boolean, 24 | getCounterKey, 25 | ); 26 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { isIE as isIEFn } from 'src/utils/browser/browser'; 2 | import { flags, argOptions, getVersion } from '@inject'; 3 | import { Constructor } from 'src/types'; 4 | 5 | const constructorName: Constructor = 6 | argOptions.construct || `Metr${argOptions.version}`; 7 | 8 | const isTestBuild = flags.LOCAL_FEATURE && typeof argOptions === 'object'; 9 | 10 | export const host = argOptions.host || 'localhost:3030'; 11 | 12 | const isIE = isIEFn(window); 13 | 14 | export const config = { 15 | METRIKA_COUNTER: 24226447, 16 | ERROR_LOGGER_COUNTER: 26302566, 17 | RESOURCES_TIMINGS_COUNTER: 51533966, 18 | GOALS_EXP_COUNTER: 65446441, 19 | cProtocol: isTestBuild ? 'http:' : 'https:', 20 | buildVersion: getVersion(), 21 | constructorName, 22 | MAX_LEN_URL: isIE ? 512 : 2048, 23 | MAX_LEN_SITE_INFO: isIE ? 512 : 2048, 24 | MAX_LEN_TITLE: isIE ? 100 : 400, 25 | MAX_LEN_IL: 100, // макс. длина текста внутренних ссылок 26 | NOINDEX: 'noindex', 27 | }; 28 | -------------------------------------------------------------------------------- /src/middleware/watchSyncFlags/brinfoFlags/parentHid.ts: -------------------------------------------------------------------------------- 1 | import { isIframe } from 'src/utils/browser/browser'; 2 | import { 3 | counterIframeConnector, 4 | IFRAME_MESSAGE_HID, 5 | } from 'src/utils/iframeConnector'; 6 | import { cKeys } from 'src/utils/object'; 7 | import { CounterOptions } from 'src/utils/counterOptions'; 8 | import { FlagGetter } from 'src/middleware/watchSyncFlags/const'; 9 | 10 | // Uses iframeConnector parent hid, that is written in counterFirstHit/waitParentDuid middleware 11 | export const getParentHid: FlagGetter = (ctx: Window, opt: CounterOptions) => { 12 | if (!isIframe(ctx)) { 13 | return null; 14 | } 15 | 16 | const iframeConnector = counterIframeConnector(ctx, opt); 17 | 18 | if (!iframeConnector) { 19 | return null; 20 | } 21 | 22 | const keys = cKeys(iframeConnector.parents); 23 | 24 | if (!keys.length) { 25 | return null; 26 | } 27 | 28 | return iframeConnector.parents[keys[0]].info[IFRAME_MESSAGE_HID]!; 29 | }; 30 | -------------------------------------------------------------------------------- /src/providers/goal/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { SenderWatch, useSenderWatch } from 'src/sender/watch'; 4 | import { providerMap } from 'src/sender'; 5 | import { fullList, nameMap } from 'src/transport'; 6 | import { commonMiddlewares, providerMiddlewareList } from 'src/middleware'; 7 | import { GOAL_PROVIDER } from './const'; 8 | import { useGoal } from './goal'; 9 | 10 | declare module 'src/providers/index' { 11 | interface PROVIDERS { 12 | GOAL_PROVIDER: typeof GOAL_PROVIDER; 13 | } 14 | } 15 | declare module 'src/sender/types' { 16 | interface NameMap { 17 | [GOAL_PROVIDER]: SenderWatch; 18 | } 19 | } 20 | 21 | export const initProvider = () => { 22 | if (flags.GOAL_FEATURE) { 23 | providersSync.push(useGoal); 24 | providerMap[GOAL_PROVIDER] = useSenderWatch; 25 | nameMap[GOAL_PROVIDER] = fullList; 26 | providerMiddlewareList[GOAL_PROVIDER] = commonMiddlewares; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/providers/statusCheck/urlSearchParams.ts: -------------------------------------------------------------------------------- 1 | import { parse } from 'src/utils/querystring'; 2 | import { memo } from 'src/utils/function/memo'; 3 | import { getLocation } from 'src/utils/location/location'; 4 | import { parseDecimalInt } from 'src/utils/number/number'; 5 | 6 | export const CHECK_URL_PARAM = '_ym_status-check'; 7 | export const LANG_URL_PARAM = '_ym_lang'; 8 | 9 | export const DEFAULT_LANGUAGE = 'ru'; 10 | 11 | /** Search parameters values */ 12 | interface StatusCheckSearchParams { 13 | /** Status check */ 14 | id: number; 15 | /** Language */ 16 | lang: string; 17 | } 18 | 19 | export const getStatusCheckSearchParams = memo( 20 | (ctx: Window): StatusCheckSearchParams => { 21 | const location = getLocation(ctx); 22 | const searchParams = parse(location.search.substring(1)); 23 | 24 | return { 25 | id: parseDecimalInt(searchParams[CHECK_URL_PARAM] || ''), 26 | lang: searchParams[LANG_URL_PARAM] || DEFAULT_LANGUAGE, 27 | }; 28 | }, 29 | ); 30 | -------------------------------------------------------------------------------- /src/utils/mouseEvents/mouseEvents.ts: -------------------------------------------------------------------------------- 1 | import { getRootElement, getDocumentScroll } from '../dom/dom'; 2 | 3 | export type TMouseButton = 0 | 1 | 2 | 3 | 4; 4 | 5 | export const getPosition = (ctx: Window, evt: MouseEvent) => { 6 | const rootElement = getRootElement(ctx); 7 | const documentScroll = getDocumentScroll(ctx); 8 | 9 | return { 10 | x: 11 | evt.pageX || 12 | evt.clientX + documentScroll.x - (rootElement.clientLeft || 0) || 13 | 0, 14 | y: 15 | evt.pageY || 16 | evt.clientY + documentScroll.y - (rootElement.clientTop || 0) || 17 | 0, 18 | }; 19 | }; 20 | 21 | export const getMouseButton = (event: MouseEvent & Event): TMouseButton => { 22 | const { which, button } = event; 23 | if (!which && button !== undefined) { 24 | if (button === 1 || button === 3) return 1; 25 | if (button === 2) return 3; 26 | if (button === 4) return 2; 27 | return 0; 28 | } 29 | return which as TMouseButton; 30 | }; 31 | -------------------------------------------------------------------------------- /src/middleware/watchSyncFlags/telemetryFlags/numRequests.ts: -------------------------------------------------------------------------------- 1 | import { DEFER_KEY } from 'src/api/watch'; 2 | import type { SenderInfo } from 'src/sender/SenderInfo'; 3 | import { getCounterKey, CounterOptions } from 'src/utils/counterOptions'; 4 | import { memo } from 'src/utils/function/memo'; 5 | import { pipe } from 'src/utils/function/pipe'; 6 | import { call } from 'src/utils/function/utils'; 7 | 8 | const getNumRequestsIncrementor = memo((counterKey: string) => { 9 | let reqNum = 0; 10 | return () => { 11 | reqNum += 1; 12 | return reqNum; 13 | }; 14 | }); 15 | 16 | const runNumRequestsIncrementor = pipe( 17 | getCounterKey, 18 | getNumRequestsIncrementor, 19 | call, 20 | ); 21 | 22 | export const numRequestsTelemetry = ( 23 | ctx: Window, 24 | options: CounterOptions, 25 | senderParams: SenderInfo, 26 | ) => { 27 | const { urlParams } = senderParams; 28 | if (!urlParams || urlParams[DEFER_KEY]) { 29 | return null; 30 | } 31 | 32 | return runNumRequestsIncrementor(options); 33 | }; 34 | -------------------------------------------------------------------------------- /src/storage/cookie/isAllowed.ts: -------------------------------------------------------------------------------- 1 | import { includes } from 'src/utils/array/includes'; 2 | import { cReduce } from 'src/utils/array/reduce'; 3 | import { ENABLED_COOKIE_KEY } from './const'; 4 | import { CookieGetter } from './types'; 5 | 6 | type CookieCheckFunction = ( 7 | ctx: Window, 8 | getCookie: CookieGetter, 9 | name?: string, 10 | ) => boolean; 11 | 12 | export const COOKIES_WHITELIST: string[] = [ENABLED_COOKIE_KEY]; 13 | export const COOKIE_CHECK_CALLBACKS: CookieCheckFunction[] = []; 14 | 15 | export const isCookieAllowed: CookieCheckFunction = ( 16 | ctx: Window, 17 | getCookie: CookieGetter, 18 | name?: string, 19 | ) => { 20 | if (!COOKIE_CHECK_CALLBACKS.length) { 21 | return true; 22 | } 23 | 24 | if (includes(name, COOKIES_WHITELIST)) { 25 | return true; 26 | } 27 | 28 | return cReduce( 29 | (result: boolean, callback) => { 30 | return result && callback(ctx, getCookie, name); 31 | }, 32 | true, 33 | COOKIE_CHECK_CALLBACKS, 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/utils/array/includes.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { curry2SwapArgs, equal } from '../function/curry'; 3 | import { filterPoly } from './filter'; 4 | import { Includes } from './types'; 5 | import { toNativeOrFalse } from '../function/isNativeFunction/toNativeOrFalse'; 6 | 7 | const nativeIncludes = toNativeOrFalse(Array.prototype.includes, 'includes'); 8 | 9 | // TODO add `fromIndex` support 10 | export const includesPoly: Includes = (e, array) => { 11 | return filterPoly(equal(e), array).length >= 1; 12 | }; 13 | 14 | const callNativeOrPoly = nativeIncludes 15 | ? (searchElement: T, array: ArrayLike, fromIndex?: number) => 16 | nativeIncludes.call(array, searchElement, fromIndex) 17 | : includesPoly; 18 | 19 | export const includes: Includes = flags.POLYFILLS_ES6_FEATURE 20 | ? callNativeOrPoly 21 | : (searchElement: T, array: ArrayLike, fromIndex?: number) => 22 | Array.prototype.includes.call(array, searchElement, fromIndex); 23 | 24 | export const ctxIncludes = curry2SwapArgs(includes); 25 | -------------------------------------------------------------------------------- /src/utils/function/callUserCallback.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { setDeferBase } from 'src/utils/defer/base'; 3 | import { AnyFunc, CallUserCallback } from './types'; 4 | import { argsToArray } from './args'; 5 | import { isFunction, isNil } from '../object/assertions'; 6 | import { bindArg } from './bind/bind'; 7 | import { throwFunction } from '../errorLogger/throwFunction'; 8 | 9 | /** 10 | * запускает неизвестную функцию в try..catch 11 | * исключение бросает в следующем тике, чтобы не прервать текущий контекст выполнения 12 | */ 13 | export const callUserCallback: CallUserCallback = function z( 14 | ctx: Window, 15 | callback?: AnyFunc, 16 | userContext?: any, 17 | ) { 18 | try { 19 | if (isFunction(callback)) { 20 | // eslint-disable-next-line prefer-rest-params 21 | const [, , , ...args] = argsToArray(arguments); 22 | callback.apply(isNil(userContext) ? null : userContext, args); 23 | } 24 | } catch (error) { 25 | setDeferBase(ctx, bindArg(error, throwFunction), 0); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /scripts/build/utils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as fsp from 'fs/promises'; 3 | import * as path from 'path'; 4 | 5 | export const DEFAULT_BUNDLE_VERSION = '25'; 6 | 7 | const buildDirStr = '_build'; 8 | export const buildDir = (str: string) => { 9 | return path.join(buildDirStr, str); 10 | }; 11 | 12 | const UTF_8_BOM_SYMBOL = '\ufeff'; 13 | export const addBOMMark = (filePath: string) => { 14 | const content = fs.readFileSync(filePath, 'utf8'); 15 | fs.writeFileSync(filePath, UTF_8_BOM_SYMBOL + content, { 16 | encoding: 'utf8', 17 | }); 18 | }; 19 | 20 | export const emptyBuildDir = async (bundle: string, createOnly = false) => { 21 | const bundleDir = buildDir(bundle); 22 | await fsp.mkdir(bundleDir, { recursive: true }); 23 | if (createOnly) { 24 | return; 25 | } 26 | const files = await fsp.readdir(bundleDir); 27 | 28 | await Promise.all( 29 | files 30 | .filter((file) => file !== 'package.json') 31 | .map((file) => fsp.unlink(path.join(bundleDir, file))), 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/providers/firstPartyMethod/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { providersSync } from 'src/providersEntrypoint'; 3 | import { YM_LOG_WHITELIST_KEYS } from 'src/providers/params/const'; 4 | import { 5 | FIRST_PARTY_HASHED_PARAMS_KEY, 6 | FIRST_PARTY_PARAMS_KEY, 7 | METHOD_NAME_FIRST_PARTY, 8 | METHOD_NAME_FIRST_PARTY_HASHED, 9 | } from './const'; 10 | import { useFirstPartyMethod } from './firstPartyMethod'; 11 | import { useFirstPartyMethodHashed } from './firstPartyMethodHashed'; 12 | 13 | export const initProvider = () => { 14 | if (flags.FIRST_PARTY_METHOD_FEATURE) { 15 | providersSync.push((ctx, counterOptions) => ({ 16 | [METHOD_NAME_FIRST_PARTY]: useFirstPartyMethod(ctx, counterOptions), 17 | [METHOD_NAME_FIRST_PARTY_HASHED]: useFirstPartyMethodHashed( 18 | ctx, 19 | counterOptions, 20 | ), 21 | })); 22 | 23 | YM_LOG_WHITELIST_KEYS.push(FIRST_PARTY_PARAMS_KEY); 24 | YM_LOG_WHITELIST_KEYS.push(FIRST_PARTY_HASHED_PARAMS_KEY); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/providers/getClientID/getClientID.ts: -------------------------------------------------------------------------------- 1 | import { CounterOptions } from 'src/utils/counterOptions'; 2 | import { ctxErrorLogger } from 'src/utils/errorLogger/errorLogger'; 3 | import { getUid } from 'src/utils/uid'; 4 | import { callUserCallback } from 'src/utils/function/callUserCallback'; 5 | import { GetClientIDHandler, METHOD_NAME_GET_CLIENT_ID } from './const'; 6 | 7 | export const rawGetClientID = (ctx: Window, counterOptions: CounterOptions) => { 8 | return { 9 | [METHOD_NAME_GET_CLIENT_ID]: ((callback: (...args: any[]) => any) => { 10 | const uid = getUid(ctx, counterOptions); 11 | 12 | if (callback) { 13 | callUserCallback(ctx, callback, null, uid); 14 | } 15 | 16 | return uid; 17 | }) as GetClientIDHandler, 18 | }; 19 | }; 20 | 21 | /** 22 | * Constructs method for getting the user ID assigned by Metrica 23 | * @param ctx - Current window 24 | * @param counterOptions - Counter options on initialization 25 | */ 26 | export const getClientID = ctxErrorLogger('guid.int', rawGetClientID); 27 | -------------------------------------------------------------------------------- /src/utils/methodDecorators/decoratorPipe.ts: -------------------------------------------------------------------------------- 1 | import { cMap } from 'src/utils/array/map'; 2 | import type { CounterOptions } from 'src/utils/counterOptions'; 3 | import { pipe } from 'src/utils/function/pipe'; 4 | import type { AnyFunc } from 'src/utils/function/types'; 5 | import { bindArgs } from 'src/utils/function/bind'; 6 | import type { Decorator } from './types'; 7 | 8 | export function decoratorPipe< 9 | R, 10 | DecoratorList extends Decorator, 11 | CounterMethod extends (...args: unknown[]) => ReturnType, 12 | >( 13 | ctx: Window, 14 | counterOptions: CounterOptions, 15 | decorators: DecoratorList[], 16 | methodName: string, 17 | method: CounterMethod, 18 | ) { 19 | if (!decorators.length) { 20 | return method; 21 | } 22 | const fnList = cMap( 23 | (decorator): ((fn: CounterMethod) => AnyFunc) => 24 | bindArgs( 25 | [ctx, counterOptions, methodName], 26 | decorator as Decorator, 27 | ), 28 | decorators, 29 | ); 30 | return pipe(...fnList)(method); 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/dom/__tests__/nodeName.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { getNodeName } from '../dom'; 3 | 4 | describe('it gets node name', () => { 5 | it('works for empty node', () => { 6 | const result = getNodeName(null as any); 7 | expect(result).to.be.eq(undefined); 8 | }); 9 | 10 | it('works for non empty node', () => { 11 | let node: any = { 12 | tagName: '1', 13 | }; 14 | let result = getNodeName(node as any); 15 | expect(result).to.be.eq('1'); 16 | 17 | node = { 18 | nodeName: '1', 19 | }; 20 | result = getNodeName(node as any); 21 | expect(result).to.be.eq('1'); 22 | 23 | node = {}; 24 | result = getNodeName(node as any); 25 | expect(result).to.be.eq(undefined); 26 | }); 27 | 28 | it('works with non string values', () => { 29 | const node = { 30 | nodeName: {}, 31 | tagName: '1', 32 | }; 33 | const result = getNodeName(node as any); 34 | expect(result).to.be.eq('1'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/utils/flagsStorage/__test__/flagsStorage.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import * as chai from 'chai'; 3 | import * as sinon from 'sinon'; 4 | import { flagStorage } from '../flagsStorage'; 5 | 6 | describe('flagsStorage', () => { 7 | const serialize = sinon.stub(); 8 | beforeEach(() => { 9 | serialize.resetHistory(); 10 | }); 11 | it('inits', () => { 12 | const flags = { 13 | test: 1, 14 | }; 15 | const info = flagStorage(serialize)(flags); 16 | const emInfo = flagStorage(serialize)(null); 17 | chai.expect(emInfo.ctx()).to.be.deep.equal({}); 18 | chai.expect(info.ctx()).to.be.deep.equal(flags); 19 | chai.expect(info.setVal('q', 1).ctx()).to.be.deep.equal({ 20 | test: 1, 21 | q: 1, 22 | }); 23 | chai.expect(flags).to.be.deep.equal({ 24 | test: 1, 25 | q: 1, 26 | }); 27 | info.serialize(); 28 | const [infoSerializeFlags] = serialize.getCall(0).args; 29 | chai.expect(infoSerializeFlags).to.equal(flags); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/middleware/watchSyncFlags/brinfoFlags/numRequests.ts: -------------------------------------------------------------------------------- 1 | import { DEFER_KEY } from 'src/api/watch'; 2 | import type { SenderInfo } from 'src/sender/SenderInfo'; 3 | import { counterLocalStorage } from 'src/storage/localStorage/localStorage'; 4 | import { CounterOptions, getCounterKey } from 'src/utils/counterOptions'; 5 | import { REQUEST_NUMBER_KEY } from '../const'; 6 | 7 | export const numRequests = ( 8 | ctx: Window, 9 | options: CounterOptions, 10 | senderParams: SenderInfo, 11 | ) => { 12 | const { urlParams } = senderParams; 13 | if (!urlParams || urlParams[DEFER_KEY]) { 14 | return null; 15 | } 16 | 17 | const counterKey = getCounterKey(options); 18 | const ls = counterLocalStorage(ctx, counterKey); 19 | const lsKey = REQUEST_NUMBER_KEY; 20 | const reqNum = ls.getVal(lsKey, 0) || 0; 21 | 22 | const nextReq = reqNum + 1; 23 | ls.setVal(lsKey, nextReq); 24 | 25 | if (ls.getVal(lsKey) === nextReq) { 26 | return nextReq; 27 | } 28 | ls.delVal(lsKey); 29 | if (reqNum > 1) { 30 | return null; 31 | } 32 | 33 | return null; 34 | }; 35 | -------------------------------------------------------------------------------- /src/utils/dom/nodeText.ts: -------------------------------------------------------------------------------- 1 | import { getPath } from 'src/utils/object'; 2 | import { walkTree } from 'src/utils/treeWalker'; 3 | import { arrayJoin } from '../array/join'; 4 | import { trimText } from '../string/remove'; 5 | 6 | export const getNodeText = (ctx: Window, elem: Element | null) => { 7 | if (!elem) { 8 | return ''; 9 | } 10 | 11 | const res: string[] = []; 12 | const doc = getPath(ctx, 'document')!; 13 | walkTree(ctx, elem, (node: Node) => { 14 | let textInfo: string | null | undefined; 15 | 16 | if (node.nodeType === doc.TEXT_NODE) { 17 | textInfo = node.textContent; 18 | } else if (node instanceof ctx.HTMLImageElement) { 19 | textInfo = node.alt; 20 | } else if (node instanceof ctx.HTMLInputElement) { 21 | textInfo = node.value; 22 | } 23 | 24 | textInfo = textInfo && trimText(textInfo); 25 | if (textInfo) { 26 | res.push(textInfo); 27 | } 28 | }); 29 | 30 | if (res.length === 0) { 31 | return ''; 32 | } 33 | 34 | return arrayJoin(' ', res); 35 | }; 36 | -------------------------------------------------------------------------------- /src/utils/phones/const.ts: -------------------------------------------------------------------------------- 1 | import { CounterOptions } from 'src/utils/counterOptions'; 2 | 3 | export type PhoneTuple = [string, string]; 4 | 5 | export const ReplaceElementText = 'text'; 6 | export const ReplaceElementLink = 'href'; 7 | export type ReplaceElementType = 8 | | typeof ReplaceElementText 9 | | typeof ReplaceElementLink; 10 | 11 | export type ReplaceElement = { 12 | replaceElementType: ReplaceElementType; 13 | replaceHTMLNode: Node | HTMLAnchorElement; 14 | replaceFrom: string; 15 | replaceTo: string; 16 | textOrig: string; 17 | }; 18 | 19 | export type PhoneChangeMapItem = { 20 | replaceTo: string; 21 | tuple: PhoneTuple; 22 | }; 23 | 24 | export type PhoneChangeMap = { 25 | [key: string]: PhoneChangeMapItem; 26 | }; 27 | 28 | export type TransformPhoneFn = ( 29 | ctx: Window, 30 | counterOpt: CounterOptions | null, 31 | item: ReplaceElement, 32 | ) => boolean; 33 | 34 | export type ReplacerOptions = { 35 | transformer: TransformPhoneFn; 36 | needReplaceTypes?: Partial>; 37 | }; 38 | 39 | export const ANY_PHONE = '*'; 40 | -------------------------------------------------------------------------------- /src/utils/telemetry/telemetry.ts: -------------------------------------------------------------------------------- 1 | import { entries, isNull } from 'src/utils/object'; 2 | 3 | import { flagStorage, FlagStorage } from '../flagsStorage/flagsStorage'; 4 | import { arrayJoin } from '../array/join'; 5 | import { cMap } from '../array/map'; 6 | 7 | export const telemetry = flagStorage((flags) => { 8 | const flagEntries = entries(flags); 9 | return arrayJoin( 10 | '', 11 | cMap(([name, value]: [string, string | number | null]) => { 12 | if (!isNull(value)) { 13 | return `${name}(${value})`; 14 | } 15 | 16 | return ''; 17 | }, flagEntries), 18 | ); 19 | }); 20 | 21 | export type Telemetry = FlagStorage; 22 | 23 | export const addTelemetryToSenderParams = ( 24 | senderParams: { telemetry?: Telemetry }, 25 | key?: string, 26 | value: string | number | null = null, 27 | ) => { 28 | if (!senderParams.telemetry) { 29 | senderParams.telemetry = telemetry(); 30 | } 31 | if (key) { 32 | senderParams.telemetry.setOrNot(key, value); 33 | } 34 | 35 | return senderParams.telemetry; 36 | }; 37 | -------------------------------------------------------------------------------- /src/inject/inject.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Build time variables definition for tests. 3 | */ 4 | 5 | import { features } from 'generated/features'; 6 | import { Args, BuildFlags } from './types'; 7 | 8 | /* 9 | * This module is used only for tests and therefore sets up some variables not available during testing: 10 | * 1. window is not defined in Node; 11 | * 2. build flags, argOptions and resourceId are provided as env variables. 12 | */ 13 | if (!global.window) { 14 | // @ts-ignore 15 | global.window = {}; 16 | } 17 | 18 | // eslint-disable-next-line no-restricted-properties 19 | export const flags: Readonly = features.reduce((acc, feature) => { 20 | acc[feature] = true; 21 | return acc; 22 | }, {} as BuildFlags); 23 | 24 | let args = {}; 25 | try { 26 | args = JSON.parse(process.env.ARG_OPTIONS || ''); 27 | } catch (e) { 28 | // empty 29 | } 30 | export const argOptions = Object.assign(args, { 31 | version: 'unitTest', 32 | host: 'mc.yandex.ru', 33 | }) as Readonly; 34 | 35 | export const resourceId = 'test.js'; 36 | 37 | export const getVersion: () => string = () => '25'; 38 | -------------------------------------------------------------------------------- /src/utils/fletcher/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-bitwise */ 2 | /** 3 | * Вычисляет чексумму данных по алгоритму Флетчера. 4 | */ 5 | export function fletcher(data: number[] | string) { 6 | let { length } = data; 7 | let i = 0; 8 | let sum1 = 0xff; 9 | let sum2 = 0xff; 10 | let tlen; 11 | let ch; 12 | let ch2; 13 | while (length) { 14 | tlen = length > 21 ? 21 : length; 15 | length -= tlen; 16 | 17 | do { 18 | ch = typeof data === 'string' ? data.charCodeAt(i) : data[i]; 19 | i += 1; 20 | if (ch > 255) { 21 | ch2 = ch >> 8; 22 | ch &= 0xff; 23 | ch ^= ch2; 24 | } 25 | sum1 += ch; 26 | sum2 += sum1; 27 | // eslint-disable-next-line no-cond-assign 28 | } while ((tlen -= 1)); 29 | sum1 = (sum1 & 0xff) + (sum1 >> 8); 30 | sum2 = (sum2 & 0xff) + (sum2 >> 8); 31 | } 32 | const result = 33 | (((sum1 & 0xff) + (sum1 >> 8)) << 8) | ((sum2 & 0xff) + (sum2 >> 8)); 34 | return result === 0xffff ? 0 : result; 35 | } 36 | -------------------------------------------------------------------------------- /src/providers/debugConsole/debugEnabled.ts: -------------------------------------------------------------------------------- 1 | import { globalCookieStorage } from 'src/storage/cookie/cookie'; 2 | import { memo } from 'src/utils/function/memo'; 3 | import { getLocation } from 'src/utils/location/location'; 4 | import { stringIndexOf } from 'src/utils/string'; 5 | import { DEBUG_CTX_FLAG, DEBUG_STORAGE_FLAG, DEBUG_URL_PARAM } from './const'; 6 | 7 | export const isDebugUrlWithValue = (ctx: Window, value: string) => 8 | stringIndexOf(getLocation(ctx).href, `${DEBUG_URL_PARAM}=${value}`) > -1; 9 | 10 | export const debugEnabled = memo((ctx: Window) => { 11 | const cookie = globalCookieStorage(ctx); 12 | const hasCookieFlag = cookie.getVal(DEBUG_STORAGE_FLAG) === '1'; 13 | const hasUrlFlag = 14 | isDebugUrlWithValue(ctx, '1') || isDebugUrlWithValue(ctx, '2'); 15 | const hasCtxFlag = ctx[DEBUG_CTX_FLAG]; 16 | const isDebug = hasCtxFlag || hasUrlFlag; 17 | if (isDebug && !hasCookieFlag) { 18 | const location = getLocation(ctx); 19 | cookie.setVal(DEBUG_STORAGE_FLAG, '1', undefined, location.host); 20 | } 21 | return !!(hasCookieFlag || hasCtxFlag || hasUrlFlag); 22 | }); 23 | -------------------------------------------------------------------------------- /src/utils/console/console.ts: -------------------------------------------------------------------------------- 1 | import { memo } from 'src/utils/function/memo'; 2 | import { isNativeFn } from 'src/utils/function/isNativeFunction/isNativeFn'; 3 | import { getPath } from 'src/utils/object'; 4 | import { bind } from 'src/utils/function/bind/bind'; 5 | import { noop } from 'src/utils/function/noop'; 6 | 7 | export type LogFn = (...data: any[]) => void; 8 | export const createConsole = (ctx: Window) => { 9 | const consoleCtx = getPath(ctx, 'console') as unknown as Console; 10 | const logFn = getPath(consoleCtx, 'log')!; 11 | const log: LogFn = isNativeFn('log', logFn) 12 | ? bind(logFn, consoleCtx) 13 | : noop; 14 | const warnFn = getPath(consoleCtx, 'warn')!; 15 | const warn: LogFn = isNativeFn('warn', warnFn) 16 | ? bind(warnFn, consoleCtx) 17 | : log; 18 | const errorFn = getPath(consoleCtx, 'error')!; 19 | const error: LogFn = isNativeFn('error', errorFn) 20 | ? bind(errorFn, consoleCtx) 21 | : log; 22 | 23 | return { 24 | log, 25 | error, 26 | warn, 27 | }; 28 | }; 29 | 30 | export const getConsole = memo(createConsole); 31 | -------------------------------------------------------------------------------- /src/middleware/retransmit/const.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { CLICKMAP_RESOURCE } from 'src/providers/clickmap/const'; 3 | import { COLLECT_RESOURCE } from 'src/middleware/senderCollectInfo'; 4 | import { WATCH_RESOURCE } from 'src/middleware/senderWatchInfo'; 5 | import { startsWithString } from 'src/utils/string/startsWith'; 6 | import { equal } from 'src/utils/function/curry'; 7 | 8 | /** 9 | * A collection of callbacks verifying the counter is capable 10 | * of retransmitting requests with the specified resource. 11 | * 12 | * NOTE: The restriction is necessary to prevent attempts to retransmit 13 | * requests of a format a counter does not support. 14 | */ 15 | export const RETRANSMITTABLE_RESOURCE_CALLBACKS = flags.SENDER_COLLECT_FEATURE 16 | ? [equal(COLLECT_RESOURCE)] 17 | : [startsWithString(WATCH_RESOURCE), startsWithString(CLICKMAP_RESOURCE)]; 18 | 19 | /** 20 | * Max count of requests in the storage 21 | */ 22 | export const MAX_REQUESTS = flags.DEBUG_FEATURE ? 2 : 100; 23 | 24 | // 24 hours in ms 25 | export const RETRANSMIT_EXPIRE = 24 * 60 * 60 * 1000; 26 | 27 | export const RETRANSMIT_KEY = 'retryReqs'; 28 | -------------------------------------------------------------------------------- /src/providers/siteStatistics/__tests__/layout.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { boxStyles, getStyles } from '../layout/siteStatisticsLayout'; 3 | 4 | describe('siteStatistics layout', () => { 5 | describe('boxStyles', () => { 6 | it("should return {width: '2px', height: '2px'}", () => { 7 | const result = boxStyles('2px'); 8 | chai.expect(result).to.eql({ width: '2px', height: '2px' }); 9 | }); 10 | it("should return {width: '2px', height: '4px'}", () => { 11 | const result = boxStyles('2px', '4px'); 12 | chai.expect(result).to.eql({ width: '2px', height: '4px' }); 13 | }); 14 | }); 15 | describe('getStyles', () => { 16 | it('should return object with passed keys and value', () => { 17 | const result = getStyles(['width', 'height'], 'initial'); 18 | chai.expect(result).to.eql({ width: 'initial', height: 'initial' }); 19 | }); 20 | it('should return empty object', () => { 21 | const result = getStyles([], 'initial'); 22 | chai.expect(result).to.eql({}); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/utils/number/number.ts: -------------------------------------------------------------------------------- 1 | import { protoToString } from 'src/utils/string'; 2 | import { curry2SwapArgs } from '../function/curry'; 3 | 4 | export const MAX_INT_32 = 2147483647; 5 | 6 | export const isNumber = (ctx: Window, obj: any): obj is number => { 7 | return ( 8 | ctx.isFinite(obj) && 9 | !ctx.isNaN(obj) && 10 | protoToString(obj) === '[object Number]' 11 | ); 12 | }; 13 | 14 | /** 15 | * In case of monkey patching toString and indexOf methods 16 | * or passing an object without prototype, 17 | * isNumber throws a type error 18 | */ 19 | export const isNumberSafe = (ctx: Window, obj: any): obj is number => { 20 | try { 21 | return isNumber(ctx, obj); 22 | } catch { 23 | return false; 24 | } 25 | }; 26 | 27 | export const toNumber = (obj: any): number => +obj; 28 | 29 | export const parseIntSafe = (val: string) => { 30 | try { 31 | return parseInt(val, 10); 32 | } catch (e) { 33 | return null; 34 | } 35 | }; 36 | 37 | const parseIntSwap = curry2SwapArgs(parseInt); 38 | export const parseDecimalInt = parseIntSwap(10); 39 | export const parseBinaryInt = parseIntSwap(2); 40 | -------------------------------------------------------------------------------- /src/api/common/browserInfo.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | 3 | /** 4 | * Client time of a request 5 | * 6 | * Value Type: number 7 | */ 8 | export const SENDER_TIME_BR_KEY = 'st'; 9 | 10 | /** 11 | * Hashed combination of features in the script 12 | * 13 | * Value Type: string 14 | */ 15 | export const BUILD_FLAGS_BR_KEY = 'vf'; 16 | 17 | /** 18 | * Script version 19 | * 20 | * Value Type: string 21 | */ 22 | export const BUILD_VERSION_BR_KEY = 'v'; 23 | 24 | /** 25 | * User ID 26 | * 27 | * Value Type: string 28 | */ 29 | export const UID_BR_KEY = flags.SENDER_COLLECT_FEATURE ? 'cid' : 'u'; 30 | 31 | /** 32 | * Timezone 33 | * 34 | * Value Type: number 35 | */ 36 | export const TIMEZONE_BR_KEY = flags.SENDER_COLLECT_FEATURE ? 'tz' : 'z'; 37 | 38 | /** 39 | * Timestamp 40 | * 41 | * Value Type: string 42 | */ 43 | export const TIMESTAMP_BR_KEY = 'i'; 44 | 45 | /** 46 | * Event initialization time 47 | * 48 | * Value Type: number 49 | */ 50 | export const SECONDS_BR_KEY = 'et'; 51 | 52 | /** 53 | * Viewport size 54 | * 55 | * Value Type: string 56 | */ 57 | export const VIEWPORT_SIZE_BR_KEY = flags.SENDER_COLLECT_FEATURE ? 'vp' : 'w'; 58 | -------------------------------------------------------------------------------- /src/providers/trackHash/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { commonMiddlewares, providerMiddlewareList } from 'src/middleware'; 3 | import { providersSync } from 'src/providersEntrypoint'; 4 | import { providerMap } from 'src/sender'; 5 | import { useSenderWatch, SenderWatch } from 'src/sender/watch'; 6 | import { fullList, nameMap } from 'src/transport'; 7 | import { TRACK_HASH_PROVIDER } from './const'; 8 | import { useTrackHash } from './trackHash'; 9 | 10 | declare module 'src/providers/index' { 11 | interface PROVIDERS { 12 | /** Tracks URL hash change */ 13 | TRACK_HASH_PROVIDER: typeof TRACK_HASH_PROVIDER; 14 | } 15 | } 16 | declare module 'src/sender/types' { 17 | interface NameMap { 18 | /** Tracks URL hash change */ 19 | [TRACK_HASH_PROVIDER]: SenderWatch; 20 | } 21 | } 22 | 23 | export const initProvider = () => { 24 | if (flags.TRACK_HASH_FEATURE) { 25 | providersSync.push(useTrackHash); 26 | providerMiddlewareList[TRACK_HASH_PROVIDER] = commonMiddlewares; 27 | providerMap[TRACK_HASH_PROVIDER] = useSenderWatch; 28 | nameMap[TRACK_HASH_PROVIDER] = fullList; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/utils/events/asyncHandlerObserver.ts: -------------------------------------------------------------------------------- 1 | import { iterateTaskWithConstraints } from '../async/helpers'; 2 | import { asSideEffect } from '../function/curry'; 3 | import { pipe } from '../function/pipe'; 4 | import type { AnyFunc } from '../function/types'; 5 | import { Observer, observer } from './observer'; 6 | 7 | // разбивает поток выполнения 8 | // в зависимости от сложности обработки событий из потока 9 | // каждого обработчика 10 | 11 | export const asyncHandlerObserver = ( 12 | ctx: Window, 13 | sourceObserver: Observer, 14 | maxTime: number, 15 | ): Observer => { 16 | const resultObserver = observer(ctx); 17 | sourceObserver.listeners.push((data) => { 18 | return resultObserver.trigger(data) as any; 19 | }); 20 | resultObserver.on = pipe( 21 | asSideEffect(sourceObserver.on), 22 | resultObserver.on, 23 | ); 24 | resultObserver.trigger = ((data: T) => { 25 | iterateTaskWithConstraints( 26 | ctx, 27 | resultObserver.listeners, 28 | (fn: AnyFunc) => fn(data), 29 | maxTime, 30 | ); 31 | }) as any; 32 | return resultObserver; 33 | }; 34 | -------------------------------------------------------------------------------- /src/providers/getCounters/types.ts: -------------------------------------------------------------------------------- 1 | import type { AnyFunc } from 'src/utils/function/types'; 2 | 3 | /** 4 | * Intermediate state of counter parameters 5 | */ 6 | export interface CounterInfo { 7 | /** Counter id */ 8 | id: number; 9 | /** Advertising network or default */ 10 | type: number; 11 | /** Send not bounce event in strict timeout. Not bounce means that user not closed page */ 12 | accurateTrackBounce?: boolean; 13 | /** Clicks heat map */ 14 | clickmap?: 15 | | boolean 16 | | Partial<{ 17 | filter: AnyFunc; 18 | ignoreTags: string[]; 19 | quota: number; 20 | }>; 21 | /** Send URL hash changes */ 22 | trackHash: boolean; 23 | /** Allow the clicks provider to track clicks */ 24 | trackLinks?: boolean; 25 | } 26 | 27 | export type ExtraCounterInfo = { 28 | clickmap?: boolean; 29 | }; 30 | 31 | /** 32 | * Reduced set of counter parameters for Ya.Metrika.counters() 33 | */ 34 | export type ExportedCounterInfo = CounterInfo & ExtraCounterInfo; 35 | 36 | export type RawCounterInfo = Partial; 37 | 38 | export type GetCountersMethod = () => ExportedCounterInfo[]; 39 | -------------------------------------------------------------------------------- /src/api/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | The folder represents API for communication with the server. 3 | 4 | # Structure 5 | Each folder except `common` represents a route. The code outside the `api` folder shall import variables from dedicated route folder (not the common one). The `common` folder is meant for definition of common parameters shared by routes and shall be used to import variables only by the routes. An exception might be some very common code (e.g. `TRANSPORT_ID_BR_KEY`, which is set by a transport regardless of the route it sends to). 6 | 7 | ## urlParams 8 | The main definition of URL parameters sent within a route. 9 | 10 | ## browserInfo 11 | The browserInfo is an internal key-value storage which can be serialized into a single string an thus sent in a single URL parameter: `browser-info`. The file contains keys for the storage. 12 | 13 | The data stored by browserInfo comprises page, user, visit information. 14 | 15 | ## telemetry 16 | The telemetry is an internal key-value storage which can be serialized into a single string an thus sent in a single URL parameter: `t`. The file contains keys for the storage. 17 | 18 | The data stored by telemetry comprises logging, debugging information. 19 | -------------------------------------------------------------------------------- /src/utils/function/isNativeFunction/__tests__/isNative.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { isNativeFn } from '../isNativeFn'; 3 | 4 | describe('isNativeFunction', () => { 5 | it('checks native functions correctly', () => { 6 | const func = function a() { 7 | // do nothing 8 | }; 9 | let toString = ''; 10 | func.toString = () => toString; 11 | 12 | toString = 'function func () {[native-code]}'; 13 | chai.assert(isNativeFn('func', func)); 14 | 15 | toString = 'function func() {[native code]}'; 16 | chai.assert(isNativeFn('func', func)); 17 | 18 | toString = 'function () { a = 123; }'; 19 | chai.assert(!isNativeFn('func', func)); 20 | 21 | chai.assert(!isNativeFn('func', null as any)); 22 | 23 | chai.assert(!isNativeFn('func', {} as any)); 24 | }); 25 | 26 | it("Doesn't throw exception if something weird happens", () => { 27 | const func = function a() { 28 | // do nothing 29 | }; 30 | func.toString = () => { 31 | throw Error(); 32 | }; 33 | 34 | chai.assert(!isNativeFn('func', func)); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/api/watch/urlParams.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | 3 | export { 4 | URL_PARAM as WATCH_URL_PARAM, 5 | REQUEST_MODE_KEY, 6 | } from '../common/urlParams'; 7 | 8 | /** 9 | * Document referrer 10 | * 11 | * Value Type: string 12 | */ 13 | export const WATCH_REFERER_PARAM = flags.SENDER_COLLECT_FEATURE 14 | ? 'dr' 15 | : 'page-ref'; 16 | 17 | /** 18 | * Document encoding 19 | * 20 | * Value Type: string 21 | */ 22 | export const WATCH_ENCODING_PARAM = 'charset'; 23 | 24 | /** 25 | * Counter class 26 | * 27 | * Value Type: 0 | 1 28 | */ 29 | export const WATCH_CLASS_PARAM = 'cnt-class'; 30 | 31 | /** 32 | * A flag indicating that the hit shall not be marked as a pageview 33 | * 34 | * Value Type: string 35 | */ 36 | export const DEFER_KEY = 'nohit'; 37 | 38 | /** 39 | * Parameters send with a hit 40 | * 41 | * Value Type: string 42 | */ 43 | export const REQUEST_BODY_KEY = flags.SENDER_COLLECT_FEATURE 44 | ? '_pa' 45 | : 'site-info'; 46 | 47 | /** 48 | * Deprecated 49 | */ 50 | export const NOINDEX_PARAM = 'ut'; 51 | 52 | // Hit types 53 | export const HIT_TYPE_PAGEVIEW = 'pageview'; 54 | 55 | // Event actions 56 | export const EVENT_ACTION_GOAL = 'goal'; 57 | -------------------------------------------------------------------------------- /src/middleware/watchSyncFlags/brinfoFlags/uid.ts: -------------------------------------------------------------------------------- 1 | import { getUid } from 'src/utils/uid'; 2 | import { memo } from 'src/utils/function/memo'; 3 | import { CounterOptions } from 'src/utils/counterOptions'; 4 | import { isIframe, isTP } from 'src/utils/browser/browser'; 5 | import { 6 | counterIframeConnector, 7 | IFRAME_MESSAGE_DUID, 8 | } from 'src/utils/iframeConnector'; 9 | 10 | /** 11 | * For TP browsers in iframe context try to 12 | * use iframeConnector parent domain id, 13 | * that is written in counterFirstHit/waitParentDuid middleware. 14 | */ 15 | export const getSelfOrParentUid = (ctx: Window, opt: CounterOptions) => { 16 | if (!isTP(ctx) || !isIframe(ctx)) { 17 | return getUid(ctx, opt); 18 | } 19 | 20 | const iframeConnector = counterIframeConnector(ctx, opt); 21 | if (!iframeConnector || !iframeConnector.parents[opt.id]) { 22 | return getUid(ctx, opt); 23 | } 24 | 25 | return ( 26 | iframeConnector.parents[opt.id].info[IFRAME_MESSAGE_DUID] || 27 | getUid(ctx, opt) 28 | ); 29 | }; 30 | 31 | export const getUidFlag = memo( 32 | getSelfOrParentUid, 33 | (ctx: Window, opt: CounterOptions) => `${opt.ldc}${opt.noCookie}`, 34 | ); 35 | -------------------------------------------------------------------------------- /src/utils/string/remove.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { curry2 } from 'src/utils/function/curry'; 3 | import { toNativeOrFalse } from '../function/isNativeFunction/toNativeOrFalse'; 4 | 5 | export const trimRegexp = /^\s+|\s+$/g; 6 | 7 | const nativeTrim = toNativeOrFalse(String.prototype.trim, 'trim'); 8 | const callNativeOrPoly = (inputString: string) => 9 | nativeTrim 10 | ? nativeTrim.call(inputString) 11 | : `${inputString}`.replace(trimRegexp, ''); 12 | export const trimText = (text: string | null | undefined, length?: number) => { 13 | if (text) { 14 | const result = flags.POLYFILLS_FEATURE 15 | ? callNativeOrPoly(text) 16 | : String.prototype.trim.call(text); 17 | if (length && result.length > length) { 18 | return result.substring(0, length); 19 | } 20 | return result; 21 | } 22 | 23 | return ''; 24 | }; 25 | 26 | export const removeByRegexp = curry2((regexp: RegExp, str: string) => 27 | str.replace(regexp, ''), 28 | ); 29 | export const removeSpaces = removeByRegexp(/\s/g); 30 | export const removeNonDigits = removeByRegexp(/\D/g); 31 | export const removeDigits = removeByRegexp(/\d/g); 32 | -------------------------------------------------------------------------------- /src/providers/clicks/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { commonMiddlewares, providerMiddlewareList } from 'src/middleware'; 3 | import { providersSync } from 'src/providersEntrypoint'; 4 | import { providerMap } from 'src/sender'; 5 | import { SenderWatch, useSenderWatch } from 'src/sender/watch'; 6 | import { fullList, nameMap } from 'src/transport'; 7 | import { ctxErrorLogger } from 'src/utils/errorLogger/errorLogger'; 8 | import { useClicksProvider } from './clicks'; 9 | import { LINK_CLICK_HIT_PROVIDER } from './const'; 10 | 11 | declare module 'src/providers/index' { 12 | interface PROVIDERS { 13 | LINK_CLICK_HIT_PROVIDER: typeof LINK_CLICK_HIT_PROVIDER; 14 | } 15 | } 16 | declare module 'src/sender/types' { 17 | interface NameMap { 18 | [LINK_CLICK_HIT_PROVIDER]: SenderWatch; 19 | } 20 | } 21 | 22 | export const initProvider = () => { 23 | if (flags.EXTERNAL_LINK_FEATURE) { 24 | providersSync.push(ctxErrorLogger('cl.p', useClicksProvider)); 25 | providerMiddlewareList[LINK_CLICK_HIT_PROVIDER] = commonMiddlewares; 26 | providerMap[LINK_CLICK_HIT_PROVIDER] = useSenderWatch; 27 | nameMap[LINK_CLICK_HIT_PROVIDER] = fullList; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/utils/function/__tests__/isNativeFunction.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { isNativeFunction } from '../isNativeFunction'; 3 | 4 | describe('isNativeFunction', () => { 5 | it('checks native functions correctly', () => { 6 | const func = function a() { 7 | // do nothing 8 | }; 9 | let toString = ''; 10 | func.toString = () => toString; 11 | 12 | toString = 'function func () {[native-code]}'; 13 | chai.assert(isNativeFunction('func', func)); 14 | 15 | toString = 'function func() {[native code]}'; 16 | chai.assert(isNativeFunction('func', func)); 17 | 18 | toString = 'function () { a = 123; }'; 19 | chai.assert(!isNativeFunction('func', func)); 20 | 21 | chai.assert(!isNativeFunction('func', null as any)); 22 | 23 | chai.assert(!isNativeFunction('func', {} as any)); 24 | }); 25 | 26 | it("Doesn't throw exception if something weird happens", () => { 27 | const func = function a() { 28 | // do nothing 29 | }; 30 | func.toString = () => { 31 | throw Error(); 32 | }; 33 | 34 | chai.assert(!isNativeFunction('func', func)); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/utils/time/performance.ts: -------------------------------------------------------------------------------- 1 | import { bind } from 'src/utils/function/bind'; 2 | import { getPath, isFunction } from 'src/utils/object'; 3 | 4 | type PerformanceNow = typeof window.performance.now; 5 | type PerformanceInfo = [number, PerformanceNow]; 6 | 7 | export const getPerformance = (ctx: Window): Performance | null => { 8 | return getPath(ctx, `performance`) || getPath(ctx, `webkitPerformance`); 9 | }; 10 | 11 | export const performanceInfo = (ctx: Window): PerformanceInfo => { 12 | const performance = getPerformance(ctx)!; 13 | const ns = getPath(performance, `timing.navigationStart`)!; 14 | let now = getPath(performance, `now`)!; 15 | if (now) { 16 | now = bind(now, performance!); 17 | } 18 | return [ns, now]; 19 | }; 20 | 21 | export const getMsDate = (ctx: Window) => { 22 | return ctx.Date.now ? ctx.Date.now() : new ctx.Date().getTime(); 23 | }; 24 | 25 | export const getMsFromPerformance = (ctx: Window, info?: PerformanceInfo) => { 26 | const [ns, now] = info || performanceInfo(ctx); 27 | // eslint-disable-next-line no-restricted-globals 28 | if (!isNaN(ns) && isFunction(now)) { 29 | return Math.round(now() + ns); 30 | } 31 | 32 | return getMsDate(ctx); 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Наши утилиты для работы с типами в TypeScript 3 | */ 4 | import { List, Any } from 'ts-toolbelt'; 5 | 6 | /** 7 | * Делает ключи K опциональными у интерфейса I 8 | */ 9 | export type WithOptionalProperties = Omit & 10 | Record; 11 | 12 | /** 13 | * Делает ключи K required у интерфейса I 14 | */ 15 | export type WithRequiredProperties = Omit & 16 | Record>; 17 | 18 | /** 19 | * Делает ключи K1 required у интерфейса I и ключи K2 required у свойства K2 20 | */ 21 | export type WithRequiredPropertiesDeep< 22 | I, 23 | K1 extends keyof I, 24 | K2 extends keyof NonNullable, 25 | > = Omit & Record, K2>>; 26 | 27 | /** 28 | * Если ключ K принадлежит к интерфейсу I, вернуть I[K], 29 | * если нет - вернуть Else 30 | */ 31 | export type IsKeyOfObj = 32 | I extends Record ? I[K] : Else; 33 | 34 | /** 35 | * Тоже самое что l1.slice(l2.length) 36 | */ 37 | export type SliceFrom = List.Drop< 38 | L1, 39 | List.Length, 's'> 40 | >; 41 | -------------------------------------------------------------------------------- /src/providers/userParams/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { addCommonMiddleware, addMiddlewareForProvider } from 'src/middleware'; 3 | import { userParamsMiddleware } from 'src/middleware/userParams'; 4 | import { HIT_PROVIDER } from 'src/providers/index'; 5 | import { providersSync } from 'src/providersEntrypoint'; 6 | import { ARTIFICIAL_HIT_PROVIDER } from '../artificialHit/const'; 7 | import { METHOD_NAME_USER_PARAMS } from './const'; 8 | import type { UserParamsHandler } from './types'; 9 | import { userParams } from './userParams'; 10 | 11 | declare module 'src/utils/counter/type' { 12 | interface CounterObject { 13 | [METHOD_NAME_USER_PARAMS]?: UserParamsHandler; 14 | } 15 | } 16 | 17 | export const initProvider = () => { 18 | if (flags.USER_PARAMS_FEATURE) { 19 | providersSync.push(userParams); 20 | addCommonMiddleware(userParamsMiddleware, 0); 21 | addMiddlewareForProvider(HIT_PROVIDER, userParamsMiddleware, 0); 22 | if (flags.ARTIFICIAL_HIT_FEATURE) { 23 | addMiddlewareForProvider( 24 | ARTIFICIAL_HIT_PROVIDER, 25 | userParamsMiddleware, 26 | 0, 27 | ); 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "experimentalDecorators": true, 3 | "compilerOptions": { 4 | "incremental": true, 5 | "strict": true, 6 | "module": "node16", 7 | "moduleResolution": "node16", 8 | "resolveJsonModule": true, 9 | "keyofStringsOnly": true, 10 | "ignoreDeprecations": "5.0", 11 | "noUnusedLocals": true, 12 | "noEmit": true, 13 | "allowJs": true, 14 | "skipLibCheck": true, 15 | "esModuleInterop": true, 16 | "checkJs": false, 17 | "lib": [ "esnext", "dom" ], 18 | "types": [ 19 | "node", 20 | "mocha", 21 | "chai" 22 | ], 23 | "typeRoots": [ 24 | "./types", 25 | "./node_modules/@types" 26 | ], 27 | "baseUrl": ".", 28 | "paths": { 29 | "@inject": ["./src/inject"], 30 | } 31 | }, 32 | "include": [ 33 | "src/**/*", 34 | "scripts/**/*", 35 | "types/**/*" 36 | ], 37 | "ts-node": { 38 | "files": false, 39 | "swc": true, 40 | "require": ["tsconfig-paths/register"], 41 | "compilerOptions": { 42 | "module": "commonjs" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/middleware/userParams/userParams.ts: -------------------------------------------------------------------------------- 1 | import { MiddlewareGetter } from 'src/middleware/types'; 2 | import { getCounterInstance } from 'src/utils/counter/getInstance'; 3 | import { METHOD_NAME_USER_PARAMS } from 'src/providers/userParams/const'; 4 | import { CounterOptions } from 'src/utils/counterOptions'; 5 | 6 | declare module 'src/sender/SenderInfo' { 7 | interface MiddlewareInfo { 8 | /** User-specified visit parameters */ 9 | userParams?: Record; 10 | } 11 | } 12 | 13 | /** 14 | * Set user-specified visit parameters to request 15 | * @param ctx - Current window 16 | * @param counterOptions - Counter options on initialization 17 | */ 18 | export const userParamsMiddleware: MiddlewareGetter = ( 19 | ctx: Window, 20 | counterOptions: CounterOptions, 21 | ) => ({ 22 | afterRequest(senderParams, next) { 23 | const counterInstance = getCounterInstance(ctx, counterOptions); 24 | const userParamsFn = 25 | counterInstance && counterInstance[METHOD_NAME_USER_PARAMS]; 26 | 27 | const { userParams } = senderParams.middlewareInfo || {}; 28 | if (userParamsFn && userParams) { 29 | userParamsFn(userParams); 30 | } 31 | 32 | next(); 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /src/sender/middleware/__test__/returnFullHost.spec.ts: -------------------------------------------------------------------------------- 1 | import * as sinon from 'sinon'; 2 | import { expect } from 'chai'; 3 | import * as config from 'src/config'; 4 | import { returnFullHost } from '../returnFullHost'; 5 | 6 | describe('sender utils / middleware / returnFullHost', () => { 7 | const cProtocol = 'protocol'; 8 | const defaultHost = 'defaultHost'; 9 | const testHost = 'testHost'; 10 | const resource = 'resource'; 11 | 12 | const sandbox = sinon.createSandbox(); 13 | 14 | beforeEach(() => { 15 | sandbox.stub(config, 'config').value({ cProtocol }); 16 | sandbox.stub(config, 'host').value(defaultHost); 17 | }); 18 | 19 | afterEach(() => { 20 | sandbox.restore(); 21 | }); 22 | 23 | it('returns a combination of default protocol and host values appended with provided resource', () => { 24 | const result = returnFullHost(resource); 25 | expect(result).to.eq(`${cProtocol}//${defaultHost}/${resource}`); 26 | }); 27 | 28 | it('returns a combination of default protocol value appended with provided host and resource', () => { 29 | const result = returnFullHost(resource, testHost); 30 | expect(result).to.eq(`${cProtocol}//${testHost}/${resource}`); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/utils/counterSettings/types.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import { COUNTER_SETTINGS_SETTINGS_KEY } from './const'; 3 | 4 | export type ConditionRef = { 5 | track_id: string; // если не указан - остановить обработку, но замен не делать 6 | type: 'ref'; 7 | patterns: string[]; 8 | params?: string[]; 9 | }; 10 | 11 | export type ConditionAdv = { 12 | track_id: string; // если не указан - остановить обработку, но замен не делать 13 | type: 'adv'; 14 | RefererPattern?: string[]; 15 | ServiceNamePattern?: string[]; 16 | yandex_direct?: boolean; 17 | google_adwords?: boolean; 18 | direct_orders?: string[]; // только при yandex_direct = true 19 | direct_camp?: string[]; // только при yandex_direct = true 20 | }; 21 | 22 | export type Substitution = { 23 | selector: string; 24 | text: string; 25 | }; 26 | 27 | /** 28 | * Counter settings as received from backend on the first hit. 29 | */ 30 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 31 | export interface CounterSettingsParams {} 32 | 33 | export interface RawCounterSettings { 34 | [COUNTER_SETTINGS_SETTINGS_KEY]: CounterSettingsParams; 35 | } 36 | 37 | export type CounterSettings = RawCounterSettings & { 38 | firstHitClientTime: number; 39 | }; 40 | -------------------------------------------------------------------------------- /src/api/watch/telemetry.ts: -------------------------------------------------------------------------------- 1 | export { 2 | TRANSPORT_ID_BR_KEY, 3 | RETRANSMIT_BRINFO_KEY, 4 | } from '../common/telemetry'; 5 | 6 | /** 7 | * A flag indicating whether notBounce hit was send with experimental timeout 8 | * 9 | * Value Type: 0 | 1 10 | */ 11 | export const NOT_BOUNCE_TELEMETRY_EXP_BR_KEY = 'nbe'; 12 | 13 | /** 14 | * Debugging info for clickmaps 15 | * 16 | * Value Type: string 17 | */ 18 | export const CLMAP_CLICKS_TEL_KEY = 'clc'; 19 | 20 | /** 21 | * Call count metric for each counter method 22 | * 23 | * Value Type: number 24 | */ 25 | export const METHODS_CALLED_TEL_KEY = 'mc'; 26 | 27 | /** 28 | * A flag indicating whether uid value was recovered from localStorage 29 | * 30 | * Value Type: 1 31 | */ 32 | export const IS_RECOVERED_ID_KEY = 're'; 33 | 34 | /** 35 | * A flag indicating whether a hit is sent from active window or not 36 | * 37 | * Value Type: 0 | 1 38 | */ 39 | export const IS_ACTIVE_WINDOW_TEL_KEY = 'aw'; 40 | 41 | /** 42 | * Request count of a counter on a page 43 | * 44 | * Value Type: number 45 | */ 46 | export const REQUEST_NUMBER_TEL_KEY = 'rqnt'; 47 | 48 | /** 49 | * Main thread blocking time of the metrika script 50 | * 51 | * Value Type: number 52 | */ 53 | export const MAIN_THREAD_BLOCKING_TIME_TEL_FEATURE = 'mtb'; 54 | -------------------------------------------------------------------------------- /src/utils/object/assertions.ts: -------------------------------------------------------------------------------- 1 | import { protoToString } from 'src/utils/string'; 2 | import { equal } from '../function/curry'; 3 | 4 | export const isNull = equal(null) as (o: any) => o is null; 5 | 6 | export const isFunction = (fn: any): fn is (...args: any[]) => any => { 7 | return typeof fn === 'function'; 8 | }; 9 | 10 | export const isUndefined = equal(undefined) as (o: any) => o is undefined; 11 | 12 | export const isNil = (object: any): object is null | undefined => { 13 | return isUndefined(object) || isNull(object); 14 | }; 15 | 16 | /** 17 | * Полифилл для Object.is(1, 1) 18 | */ 19 | export const is = (v1: any, v2: any) => { 20 | if (v1 === 0 && v2 === 0) { 21 | return 1 / v1 === 1 / v2; 22 | } 23 | 24 | // eslint-disable-next-line no-self-compare 25 | if (v1 !== v1) { 26 | // eslint-disable-next-line no-self-compare 27 | return v2 !== v2; 28 | } 29 | 30 | return v1 === v2; 31 | }; 32 | 33 | // eslint-disable-next-line @typescript-eslint/ban-types 34 | export const isObject = = object>( 35 | object: any, 36 | ): object is T => { 37 | return ( 38 | !isNull(object) && 39 | !isUndefined(object) && 40 | protoToString(object) === '[object Object]' 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /src/utils/function/isNativeFunction/__tests__/isNativeFunction.spec.ts: -------------------------------------------------------------------------------- 1 | import * as sinon from 'sinon'; 2 | import * as chai from 'chai'; 3 | import * as report from 'src/providers/reportNonNativeFunctions/report'; 4 | import * as nf from '../isNativeFn'; 5 | import { isNativeFunction } from '../isNativeFunction'; 6 | 7 | describe('isNativeFunction', () => { 8 | const sandbox = sinon.createSandbox(); 9 | let reportStub: sinon.SinonStub; 10 | let isNativeStub: sinon.SinonStub; 11 | 12 | beforeEach(() => { 13 | reportStub = sandbox.stub(report, 'reportNonNativeFunction'); 14 | isNativeStub = sandbox.stub(nf, 'isNativeFn'); 15 | }); 16 | 17 | afterEach(() => { 18 | sandbox.restore(); 19 | }); 20 | 21 | it('Reports non-native functions', () => { 22 | const fn = () => {}; 23 | isNativeStub.returns(true); 24 | chai.assert(isNativeFunction('fn', fn)); 25 | chai.assert(reportStub.notCalled); 26 | 27 | isNativeStub.returns(false); 28 | chai.assert(!isNativeFunction('fn', null as any)); 29 | chai.assert(reportStub.notCalled); 30 | 31 | isNativeStub.returns(false); 32 | chai.assert(!isNativeFunction('fn', fn)); 33 | chai.assert(reportStub.calledWith(fn, 'fn')); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/middleware/utils.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from 'src/utils/object'; 2 | import type { MiddlewareWeightTuple, MiddlewareGetter } from './types'; 3 | 4 | export const addMiddlewareToTheList = ( 5 | list: MiddlewareWeightTuple[], 6 | middleware: MiddlewareGetter, 7 | weight: number, 8 | ) => { 9 | const resultTuple: MiddlewareWeightTuple = [middleware, weight]; 10 | let prevWeight = -10000; 11 | for (let i = 0; i < list.length; i += 1) { 12 | const [currentMW, currentWeight] = list[i]; 13 | if (weight === currentWeight && currentMW === middleware) { 14 | return; 15 | } 16 | 17 | if (weight < currentWeight && weight >= prevWeight) { 18 | list.splice(i, 0, resultTuple); 19 | return; 20 | } 21 | prevWeight = currentWeight; 22 | } 23 | list.push(resultTuple); 24 | }; 25 | 26 | export const addMiddlewareFor = ( 27 | middlewareList: Partial>, 28 | key: K, 29 | middleware?: MiddlewareGetter, 30 | weight?: number, 31 | ) => { 32 | if (!middlewareList[key]) { 33 | middlewareList[key] = []; 34 | } 35 | if (middleware && !isNil(weight)) { 36 | addMiddlewareToTheList(middlewareList[key]!, middleware, weight!); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/utils/dom/__tests__/matches.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import * as sinon from 'sinon'; 3 | import * as fn from 'src/utils/function/isNativeFunction/isNativeFunction'; 4 | import { getMatchesFunction } from '../dom'; 5 | 6 | describe('getMatchesFunction', () => { 7 | const sandbox = sinon.createSandbox(); 8 | let isNativeStub: sinon.SinonStub; 9 | beforeEach(() => { 10 | isNativeStub = sandbox.stub(fn, 'isNativeFunction'); 11 | isNativeStub.returns(true); 12 | }); 13 | afterEach(() => { 14 | sandbox.restore(); 15 | }); 16 | it('return null if function not in Prototype', () => { 17 | isNativeStub.returns(false); 18 | const res = getMatchesFunction({} as any); 19 | expect(res).to.be.null; 20 | }); 21 | it('return null if function not in context', () => { 22 | const res = getMatchesFunction({} as any); 23 | expect(res).to.be.null; 24 | }); 25 | it('find function if it exist', () => { 26 | const expectResult = {}; 27 | const res = getMatchesFunction({ 28 | Element: { 29 | prototype: { 30 | matches: expectResult, 31 | }, 32 | }, 33 | } as any); 34 | expect(res).to.be.eq(expectResult); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/providers/turboParams/index.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { IS_TURBO_PAGE_BR_KEY, TURBO_PAGE_ID_BR_KEY } from 'src/api/watch'; 3 | import { BRINFO_FLAG_GETTERS } from 'src/middleware/watchSyncFlags/brinfoFlags'; 4 | import { FlagGettersHash } from 'src/middleware/watchSyncFlags/const'; 5 | import { INTERNAL_PARAMS_KEY } from 'src/providers/params/const'; 6 | import { toOneOrNull } from 'src/utils/boolean'; 7 | import { secondArg } from 'src/utils/function/identity'; 8 | import { pipe } from 'src/utils/function/pipe'; 9 | import { mix } from 'src/utils/object'; 10 | import { getTurboPageId, isTurboPage } from 'src/utils/turboParams'; 11 | 12 | declare module 'src/utils/counterOptions/types' { 13 | interface Params { 14 | /** Turbo page settings */ 15 | [INTERNAL_PARAMS_KEY]?: Record; 16 | } 17 | } 18 | 19 | /** 20 | * Initialize the turbo params brInfo flags. 21 | */ 22 | export const initProvider = () => { 23 | if (flags.TURBO_PARAMS_FEATURE) { 24 | const TURBO_PARAMS_BRINFO_FLAG_GETTERS: FlagGettersHash = { 25 | [IS_TURBO_PAGE_BR_KEY]: pipe(secondArg, isTurboPage, toOneOrNull), 26 | [TURBO_PAGE_ID_BR_KEY]: pipe(secondArg, getTurboPageId), 27 | }; 28 | mix(BRINFO_FLAG_GETTERS, TURBO_PARAMS_BRINFO_FLAG_GETTERS); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/sender/default/query/watchAPI.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { 3 | TRANSPORT_ID_BR_KEY, 4 | BROWSERINFO_QUERY_KEY, 5 | TELEMETRY_QUERY_KEY, 6 | } from 'src/api/common'; 7 | import { SENDER_TIME_BR_KEY } from 'src/api/watch'; 8 | import { browserInfo } from 'src/utils/browserInfo/browserInfo'; 9 | import { mix } from 'src/utils/object'; 10 | import { TimeOne, getSec } from 'src/utils/time/time'; 11 | import type { SenderInfo } from '../../SenderInfo'; 12 | 13 | export const createWatchQuery = ( 14 | ctx: Window, 15 | senderInfo: SenderInfo, 16 | transportID: number, 17 | ) => { 18 | const query: Record = mix({}, senderInfo.urlParams); 19 | const time = TimeOne(ctx); 20 | if (senderInfo.brInfo) { 21 | query[BROWSERINFO_QUERY_KEY] = browserInfo(senderInfo.brInfo.ctx()) 22 | .setVal(SENDER_TIME_BR_KEY, time(getSec)) 23 | .serialize(); 24 | } 25 | 26 | if (flags.TELEMETRY_FEATURE) { 27 | if (!query[TELEMETRY_QUERY_KEY]) { 28 | const { telemetry } = senderInfo; 29 | if (telemetry) { 30 | telemetry.setVal(TRANSPORT_ID_BR_KEY, transportID); 31 | query[TELEMETRY_QUERY_KEY] = telemetry.serialize(); 32 | } 33 | } 34 | } 35 | 36 | return query; 37 | }; 38 | -------------------------------------------------------------------------------- /src/utils/phones/phonesSubscribe.ts: -------------------------------------------------------------------------------- 1 | import { cEvent } from 'src/utils/events/events'; 2 | import { waitForBodyTask } from 'src/utils/dom/waitForBody'; 3 | import { noop } from 'src/utils/function/noop'; 4 | import { isNativeFunction } from 'src/utils/function/isNativeFunction'; 5 | import { taskFork } from '../async/task'; 6 | import { Observer } from '../events/observer'; 7 | 8 | export const phoneSubscribeLoad = ( 9 | ctx: Window, 10 | observerObj: Observer, 11 | ) => { 12 | const eventSubscriber = cEvent(ctx); 13 | return eventSubscriber.on(ctx, ['load'], observerObj.trigger); 14 | }; 15 | 16 | export const phoneSubscribeMutation = ( 17 | ctx: Window, 18 | observerObj: Observer, 19 | ) => { 20 | waitForBodyTask(ctx)( 21 | taskFork(noop, () => { 22 | const target = ctx.document.body; 23 | const config = { 24 | ['attributes']: true, 25 | ['childList']: true, 26 | ['subtree']: true, 27 | }; 28 | if (!isNativeFunction('MutationObserver', ctx.MutationObserver)) { 29 | return; 30 | } 31 | const mutationObserver = new MutationObserver(observerObj.trigger); 32 | mutationObserver.observe(target, config); 33 | }), 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/utils/defer/defer.ts: -------------------------------------------------------------------------------- 1 | import { setDeferBase } from 'src/utils/defer/base'; 2 | import { errorLogger } from 'src/utils/errorLogger/errorLogger'; 3 | import { AnyFunc } from 'src/utils/function/types'; 4 | import { getNativeFunction } from '../function/isNativeFunction/getNativeFunction'; 5 | 6 | const SCOPE_KEY = 'def'; 7 | 8 | export const clearDefer = (ctx: Window, deferId: number) => { 9 | const clearTimeout: Window['clearTimeout'] = getNativeFunction( 10 | 'clearTimeout', 11 | ctx, 12 | ); 13 | // eslint-disable-next-line ban/ban 14 | return clearTimeout(deferId); 15 | }; 16 | 17 | export const setDefer = ( 18 | ctx: Window, 19 | fn: AnyFunc, 20 | timeOut: number, 21 | errorScope?: string, 22 | ) => { 23 | return setDeferBase( 24 | ctx, 25 | errorLogger(ctx, `d.err.${errorScope || SCOPE_KEY}`, fn), 26 | timeOut, 27 | ); 28 | }; 29 | 30 | export const setDeferInterval = ( 31 | ctx: Window, 32 | fn: AnyFunc, 33 | timeOut: number, 34 | errorScope?: string, 35 | ) => { 36 | return ctx.setInterval( 37 | errorLogger(ctx, `i.err.${errorScope || SCOPE_KEY}`, fn), 38 | timeOut, 39 | ); 40 | }; 41 | 42 | export const clearDeferInterval = (ctx: Window, deferIntervalId: number) => { 43 | return ctx.clearInterval(deferIntervalId); 44 | }; 45 | -------------------------------------------------------------------------------- /src/middleware/watchSyncFlags/brinfoFlags/falseUrl.ts: -------------------------------------------------------------------------------- 1 | import { WATCH_URL_PARAM, WATCH_REFERER_PARAM } from 'src/api/watch'; 2 | import type { SenderInfo } from 'src/sender/SenderInfo'; 3 | import { getLocation } from 'src/utils/location/location'; 4 | import { getPath } from 'src/utils/object'; 5 | import { CounterOptions } from 'src/utils/counterOptions'; 6 | 7 | const replaceRegex = /\/$/; 8 | 9 | export const isFalseURL = ( 10 | ctx: Window, 11 | opt: CounterOptions, 12 | senderParams: SenderInfo, 13 | ) => { 14 | const { urlParams } = senderParams; 15 | if (!urlParams) { 16 | return null; 17 | } 18 | const trueRef = (getPath(ctx, 'document.referrer') || '').replace( 19 | replaceRegex, 20 | '', 21 | ); 22 | const senderRef = (urlParams[WATCH_REFERER_PARAM] || '').replace( 23 | replaceRegex, 24 | '', 25 | ); 26 | const senderUrl = urlParams[WATCH_URL_PARAM]; 27 | const trueUrl = getLocation(ctx); 28 | const isFalseUrlBool = trueUrl.href !== senderUrl; 29 | const isFalseRefBool = trueRef !== senderRef; 30 | let result = 0; 31 | 32 | if (isFalseUrlBool && isFalseRefBool) { 33 | result = 3; 34 | } else if (isFalseRefBool) { 35 | result = 1; 36 | } else if (isFalseUrlBool) { 37 | result = 2; 38 | } 39 | return result; 40 | }; 41 | -------------------------------------------------------------------------------- /src/utils/errorLogger/knownError.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'src/config'; 2 | import { bindThisForMethodTest } from 'src/utils/function/bind'; 3 | import { isArray } from 'src/utils/array/isArray'; 4 | import { arrayJoin } from 'src/utils/array/join'; 5 | import { isString } from 'src/utils/string'; 6 | import { KNOWN_ERROR, DELIMITER } from './consts'; 7 | import { argsToArray } from '../function/args'; 8 | import { throwFunction } from './throwFunction'; 9 | import { createError } from './createError'; 10 | 11 | type Message = string | number; 12 | 13 | export const createKnownError = (moreInfo?: Message | Message[]) => { 14 | let data: Message | Message[] = ''; 15 | 16 | if (isArray(moreInfo)) { 17 | data = arrayJoin(DELIMITER, moreInfo); 18 | } else if (isString(moreInfo)) { 19 | data = moreInfo; 20 | } 21 | const errorMessage = `${KNOWN_ERROR}(${config.buildVersion})${data}`; 22 | 23 | return createError(errorMessage); 24 | }; 25 | 26 | export const throwKnownError = function throwKnownError() { 27 | // eslint-disable-next-line prefer-rest-params 28 | const args = argsToArray(arguments); 29 | return throwFunction(createKnownError(args)); 30 | } as (...a: Parameters) => never; 31 | 32 | export const isKnownError = bindThisForMethodTest( 33 | new RegExp(`^${KNOWN_ERROR}`), 34 | ); 35 | -------------------------------------------------------------------------------- /src/providers/callbackInit/callbackInit.ts: -------------------------------------------------------------------------------- 1 | import { cForEach } from 'src/utils/array/map'; 2 | import { ctxErrorLogger } from 'src/utils/errorLogger/errorLogger'; 3 | import { argOptions } from '@inject'; 4 | import { isFunction } from 'src/utils/object'; 5 | import { callUserCallback } from 'src/utils/function/callUserCallback'; 6 | import type { AnyFunc } from 'src/utils/function/types'; 7 | 8 | export const CALLBACK_ARRAY_NAME = [ 9 | `yandex_metrika_callback${argOptions['callbackPostfix']}`, 10 | `yandex_metrika_callbacks${argOptions['callbackPostfix']}`, 11 | ]; 12 | 13 | /** 14 | * Provider to run user defined callbacks 15 | * @param ctx - Current window 16 | */ 17 | export const callbackInit = ctxErrorLogger('cb.i', (ctx: Window) => { 18 | const [one, many] = CALLBACK_ARRAY_NAME; 19 | const anyCtx = ctx as any; 20 | if (isFunction(anyCtx[one])) { 21 | anyCtx[one](); 22 | } 23 | if (typeof anyCtx[many] === 'object') { 24 | cForEach((fn: AnyFunc, i) => { 25 | anyCtx[many][i] = null; 26 | callUserCallback(ctx, fn); 27 | }, anyCtx[many]); 28 | } 29 | cForEach((callbackName: string) => { 30 | try { 31 | delete anyCtx[callbackName]; 32 | } catch (e) { 33 | anyCtx[callbackName] = undefined; 34 | } 35 | }, CALLBACK_ARRAY_NAME); 36 | }); 37 | -------------------------------------------------------------------------------- /src/utils/methodDecorators/telCallCount/telCallCount.ts: -------------------------------------------------------------------------------- 1 | import { getGlobalStorage } from 'src/storage/global/getGlobal'; 2 | import { argsToArray } from 'src/utils/function/args'; 3 | import { 4 | METHODS_TELEMETRY_KEYS_MAP, 5 | METHODS_TELEMETRY_GLOBAL_STORAGE_KEY, 6 | } from './const'; 7 | import type { Decorator } from '../types'; 8 | 9 | export const telemetryCallCountDecorator: Decorator = ( 10 | ctx, 11 | counterOptions, 12 | methodName, 13 | fn, 14 | ) => { 15 | const methodKey = METHODS_TELEMETRY_KEYS_MAP[methodName]; 16 | if (methodKey) { 17 | return function telemetry() { 18 | // eslint-disable-next-line prefer-rest-params 19 | const fnArgs = argsToArray(arguments); 20 | const result = fn(...fnArgs); 21 | const globalStorage = getGlobalStorage(ctx); 22 | globalStorage.setSafe(METHODS_TELEMETRY_GLOBAL_STORAGE_KEY, {}); 23 | const methodsCallCounters: Record = 24 | globalStorage.getVal(METHODS_TELEMETRY_GLOBAL_STORAGE_KEY); 25 | const previouslyCalled = methodsCallCounters[methodKey]; 26 | methodsCallCounters[methodKey] = previouslyCalled 27 | ? previouslyCalled + 1 28 | : 1; 29 | return result; 30 | }; 31 | } 32 | 33 | return fn; 34 | }; 35 | -------------------------------------------------------------------------------- /src/providers/searchTLD/__test__/searchTLD.spec.ts: -------------------------------------------------------------------------------- 1 | import * as sinon from 'sinon'; 2 | import * as chai from 'chai'; 3 | import { host } from 'src/config'; 4 | import * as searchTLD from '../searchTLD'; 5 | 6 | describe('searchTLD', () => { 7 | const sandbox = sinon.createSandbox(); 8 | const ctx = {} as Window; 9 | afterEach(() => { 10 | sandbox.restore(); 11 | }); 12 | 13 | it('returns default host if no overrides are defined', () => { 14 | sandbox.stub(searchTLD, 'TLD_OVERRIDES').value([]); 15 | chai.expect(searchTLD.getDomainAndTLD(ctx, 'watch')).to.equal(host); 16 | }); 17 | 18 | it('returns default host if no overrides are fired', () => { 19 | const override = sinon.stub().returns(undefined); 20 | sandbox.stub(searchTLD, 'TLD_OVERRIDES').value([override]); 21 | chai.expect(searchTLD.getDomainAndTLD(ctx, 'watch')).to.equal(host); 22 | sinon.assert.calledWith(override, ctx, 'watch'); 23 | }); 24 | 25 | it('returns overridden host if override is defined', () => { 26 | const override = sinon.stub().returns('example.com'); 27 | sandbox.stub(searchTLD, 'TLD_OVERRIDES').value([override]); 28 | chai.expect(searchTLD.getDomainAndTLD(ctx, 'watch')).to.equal( 29 | 'example.com', 30 | ); 31 | sinon.assert.calledWith(override, ctx, 'watch'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/providers/sameSite/__tests__/sameSite.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import * as sinon from 'sinon'; 3 | import * as brow from 'src/utils/browser/browser'; 4 | import * as loc from 'src/utils/location/location'; 5 | import { getSameSiteCookieInfo } from '../sameSite'; 6 | 7 | describe('provider / sameSite', () => { 8 | const sandbox = sinon.createSandbox(); 9 | let browserStub: sinon.SinonStub<[Window], boolean>; 10 | let isHttpsStub: sinon.SinonStub<[Window], boolean>; 11 | beforeEach(() => { 12 | browserStub = sandbox.stub(brow, 'isSameSiteBrowser'); 13 | isHttpsStub = sandbox.stub(loc, 'isHttps'); 14 | }); 15 | afterEach(() => { 16 | sandbox.restore(); 17 | }); 18 | it('returns empty list if is not sameSite', () => { 19 | browserStub.returns(false); 20 | expect(getSameSiteCookieInfo({} as any)).to.equal(''); 21 | }); 22 | it('returns empty list if http', () => { 23 | browserStub.returns(true); 24 | isHttpsStub.returns(false); 25 | expect(getSameSiteCookieInfo({} as any)).to.equal(''); 26 | }); 27 | it('returns empty sameSite and secure if https', () => { 28 | browserStub.returns(true); 29 | isHttpsStub.returns(true); 30 | expect(getSameSiteCookieInfo({} as any)).to.equal( 31 | 'SameSite=None;Secure;', 32 | ); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/providers/clickmapMethod/clickmapMethod.ts: -------------------------------------------------------------------------------- 1 | import { ctxErrorLogger } from 'src/utils/errorLogger/errorLogger'; 2 | import { bindArg } from 'src/utils/function/bind'; 3 | import { counterStateSetter } from 'src/providers/getCounters/getCounters'; 4 | import { CounterOptions, getCounterKey } from 'src/utils/counterOptions'; 5 | import { COUNTER_STATE_CLICKMAP } from 'src/providers/getCounters/const'; 6 | import { isUndefined } from 'src/utils/object'; 7 | import { TClickMapParams } from '../clickmap/const'; 8 | import { METHOD_NAME_CLICK_MAP } from './const'; 9 | 10 | export const trackClicks = ( 11 | setVal: ReturnType, 12 | rawParams?: TClickMapParams, 13 | ) => { 14 | setVal({ 15 | [COUNTER_STATE_CLICKMAP]: isUndefined(rawParams) ? true : rawParams, 16 | }); 17 | }; 18 | 19 | /** 20 | * Provider for calling heat map of clicks as a function 21 | * @param ctx - Current window 22 | * @param counterOptions - Counter options on initialization 23 | */ 24 | export const useClickmapMethodProvider = ctxErrorLogger( 25 | 'c.m.p', 26 | (ctx: Window, counterOptions: CounterOptions) => { 27 | const counterKey = getCounterKey(counterOptions); 28 | return { 29 | [METHOD_NAME_CLICK_MAP]: bindArg( 30 | counterStateSetter(ctx, counterKey), 31 | trackClicks, 32 | ), 33 | }; 34 | }, 35 | ); 36 | -------------------------------------------------------------------------------- /src/utils/array/filter.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { Filter, FilterCallback, nullable } from './types'; 3 | import { curry2 } from '../function/curry'; 4 | import { reducePoly } from './reduce'; 5 | import { bindArg } from '../function/bind'; 6 | import { toNativeOrFalse } from '../function/isNativeFunction'; 7 | import { toBoolean } from '../boolean'; 8 | 9 | const nativeFilter = toNativeOrFalse(Array.prototype.filter, 'filter'); 10 | 11 | export const filterPoly = (fn: FilterCallback, array: ArrayLike) => { 12 | return reducePoly( 13 | (result, item, i) => { 14 | if (fn(item, i)) { 15 | result.push(item); 16 | } 17 | return result; 18 | }, 19 | [], 20 | array, 21 | ); 22 | }; 23 | const callNativeOrPoly: Filter = nativeFilter 24 | ? (predicate: FilterCallback, array: ArrayLike) => 25 | nativeFilter.call(array, predicate) 26 | : filterPoly; 27 | 28 | export const cFilter: Filter = flags.POLYFILLS_FEATURE 29 | ? callNativeOrPoly 30 | : (predicate: FilterCallback, array: ArrayLike) => 31 | Array.prototype.filter.call(array, predicate); 32 | 33 | type FilterFalsy = (array: ArrayLike) => Exclude[]; 34 | export const filterFalsy = bindArg(toBoolean, cFilter) as FilterFalsy; 35 | 36 | export const ctxFilter = curry2(cFilter); 37 | -------------------------------------------------------------------------------- /src/utils/object/mix.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { argsToArray } from '../function/args'; 3 | import { curry2 } from '../function/curry'; 4 | import { has } from './has'; 5 | 6 | export const assignPoly: typeof Object.assign = function assignPoly() { 7 | // eslint-disable-next-line prefer-rest-params 8 | const assignArgs = argsToArray(arguments); 9 | const [dst, ...args] = assignArgs; 10 | while (args.length) { 11 | const obj = args.shift(); 12 | // eslint-disable-next-line no-restricted-syntax 13 | for (const key in obj) { 14 | if (has(obj, key)) { 15 | dst[key] = obj[key]; 16 | } 17 | } 18 | /** 19 | * по всей видимости в каких-то браузерах проп toString не попадал в for..in 20 | * но попадал в obj.hasOwnProperty 21 | * поэтому приходится вручную проверять 22 | */ 23 | if (has(obj, 'toString')) { 24 | dst['toString'] = obj['toString']; 25 | } 26 | } 27 | return dst; 28 | }; 29 | 30 | export const mix = flags.POLYFILLS_ES6_FEATURE 31 | ? Object.assign || assignPoly 32 | : Object.assign; 33 | 34 | /** 35 | * @type function(...*): * 36 | * с curry2 не выйдет потому что нужен каждый раз новый экземпляр объекта 37 | */ 38 | export const ctxMix = curry2((obj1: T, obj2: R) => { 39 | return mix({}, obj1, obj2) as T & R; 40 | }); 41 | -------------------------------------------------------------------------------- /src/providers/notBounce/const.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_NOT_BOUNCE_TIMEOUT = 15000; 2 | export const EXPERIMENTAL_NOT_BOUNCE_TIMEOUT = 10000; 3 | export const METHOD_NAME_NOT_BOUNCE = 'notBounce'; 4 | export const LAST_NOT_BOUNCE_LS_KEY = 'lastHit'; 5 | export const APPROXIMATE_VISIT_DURATION = 30 * 60000; 6 | export const METHOD_NAME_ACCURATE_TRACK_BOUNCE = 'accurateTrackBounce'; 7 | 8 | export const NOT_BOUNCE_HIT_PROVIDER = 'n'; 9 | 10 | /** 11 | * Function for sending not bounce hit manually 12 | */ 13 | export type NotBounceHandler = (options?: { 14 | /** Context */ 15 | ctx: any; 16 | /** Function to be called after sending */ 17 | callback: (...args: any) => any; 18 | }) => T; 19 | 20 | /** 21 | * Function for manually setting timer for sending not bounce hit after specified period of time 22 | * @param time - Wait time before sending the hit 23 | */ 24 | export type AccurateTrackBounceHandler = ( 25 | time?: number | boolean, 26 | ) => T; 27 | 28 | declare module 'src/utils/counter/type' { 29 | interface CounterObject { 30 | /** Function for sending not bounce hit manually */ 31 | [METHOD_NAME_NOT_BOUNCE]?: NotBounceHandler; 32 | /** Function for manually setting timer for sending not bounce hit after specified period of time */ 33 | [METHOD_NAME_ACCURATE_TRACK_BOUNCE]?: AccurateTrackBounceHandler; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/storage/closureStorage/closureStorage.ts: -------------------------------------------------------------------------------- 1 | import { mix } from 'src/utils/object'; 2 | import { getGlobalStorage } from 'src/storage/global/getGlobal'; 3 | import { cont } from 'src/utils/function/curry'; 4 | import { StateManager } from './types'; 5 | 6 | export const GLOBAL_STORAGE_KEY = 'dsjf'; 7 | 8 | export const createStateManager = (): StateManager => { 9 | const state = {}; 10 | return cont(state); 11 | }; 12 | 13 | export const closureStorage = (ctx: Window): StateManager => { 14 | const globalStorage = getGlobalStorage(ctx); 15 | const stateManager = 16 | (globalStorage.getVal(GLOBAL_STORAGE_KEY) as StateManager) || 17 | createStateManager(); 18 | globalStorage.setSafe(GLOBAL_STORAGE_KEY, stateManager); 19 | return stateManager; 20 | }; 21 | 22 | export const getVal = (ctx: Window, key: string): Partial => { 23 | let value = {}; 24 | closureStorage(ctx)((state) => { 25 | value = state[key] || {}; 26 | }); 27 | return value; 28 | }; 29 | 30 | export const setVal = (ctx: Window, key: string, val: Partial) => { 31 | closureStorage(ctx)((state) => { 32 | const prevValue = state[key] || {}; 33 | state[key] = mix(prevValue, val); 34 | }); 35 | }; 36 | 37 | export const deleteVal = (ctx: Window, key: string) => { 38 | closureStorage(ctx)((state) => { 39 | delete state[key]; 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /src/utils/turboParams/turboParams.ts: -------------------------------------------------------------------------------- 1 | import { INTERNAL_PARAMS_KEY } from 'src/providers/params/const'; 2 | import { getCounterKey } from 'src/utils/counterOptions/getCounterKey'; 3 | import type { CounterOptions, Params } from 'src/utils/counterOptions/types'; 4 | import { getPath } from 'src/utils/object'; 5 | 6 | export type TurboInfo = { 7 | tp?: number; 8 | tpId?: number; 9 | }; 10 | 11 | const turboInfo: Record = {}; 12 | 13 | const TURBO_PARAMS_PATH = `${INTERNAL_PARAMS_KEY}.turbo_page`; 14 | 15 | export const setTurboInfo = (options: CounterOptions, params: Params) => { 16 | const counterId = getCounterKey(options); 17 | const tp = getPath(params, TURBO_PARAMS_PATH) as number | undefined; 18 | const tpId = getPath(params, `${TURBO_PARAMS_PATH}_id`) as 19 | | number 20 | | undefined; 21 | 22 | if (!turboInfo[counterId]) { 23 | turboInfo[counterId] = {}; 24 | } 25 | 26 | if (tp || tpId) { 27 | turboInfo[counterId].tp = tp; 28 | turboInfo[counterId].tpId = tpId; 29 | } 30 | }; 31 | 32 | export const isTurboPage = (options: CounterOptions) => { 33 | const id = getCounterKey(options); 34 | return turboInfo[id] && turboInfo[id].tp; 35 | }; 36 | 37 | export const getTurboPageId = (options: CounterOptions) => { 38 | const id = getCounterKey(options); 39 | return (turboInfo[id] && turboInfo[id].tpId) || null; 40 | }; 41 | -------------------------------------------------------------------------------- /src/utils/dom/targetLink.ts: -------------------------------------------------------------------------------- 1 | import { getPath } from 'src/utils/object'; 2 | import { getTagName } from './dom'; 3 | 4 | /** 5 | * Возвращает элемент ссылки из события 6 | */ 7 | export const getTargetLink = (event: MouseEvent): HTMLAnchorElement | null => { 8 | let target = null; 9 | try { 10 | // Выглядит так будто иногда мы можем поймать события из фреймов? 11 | // Соответственно мы можем словить ошибку пытаясь получить доступ к нему 12 | target = (event.target || event.srcElement) as HTMLElement; 13 | } catch (e) {} 14 | 15 | if (target) { 16 | if ((target as Node).nodeType === 3) { 17 | // Текстовая нода, Safari bug 18 | target = target.parentNode as HTMLElement; 19 | } 20 | 21 | let tag = getTagName(target); 22 | while ( 23 | getPath(target, 'parentNode.nodeName') && 24 | ((tag !== 'a' && tag !== 'area') || 25 | !( 26 | (target as HTMLAnchorElement).href || 27 | target.getAttribute('xlink:href') 28 | )) 29 | ) { 30 | target = target.parentNode as HTMLElement; 31 | tag = getTagName(target); 32 | } 33 | 34 | if (!(target as HTMLAnchorElement).href) { 35 | return null; 36 | } 37 | 38 | return target as HTMLAnchorElement; 39 | } 40 | 41 | return null; 42 | }; 43 | -------------------------------------------------------------------------------- /src/providers/clickmapMethod/__tests__/clickmapMethod.spec.ts: -------------------------------------------------------------------------------- 1 | import * as sinon from 'sinon'; 2 | import * as counterState from 'src/providers/getCounters/getCounters'; 3 | import { COUNTER_STATE_CLICKMAP } from 'src/providers/getCounters/const'; 4 | import { useClickmapMethodProvider } from '../clickmapMethod'; 5 | 6 | describe('clickMap method', () => { 7 | const sandbox = sinon.createSandbox(); 8 | const win = {} as Window; 9 | const counterOptions = { id: 123 }; 10 | 11 | let setSpy: sinon.SinonSpy; 12 | 13 | beforeEach(() => { 14 | setSpy = sandbox.spy(); 15 | sandbox.stub(counterState, 'counterStateSetter').returns(setSpy); 16 | }); 17 | afterEach(() => { 18 | sandbox.restore(); 19 | }); 20 | 21 | it('set counter state', () => { 22 | const value = true; 23 | const { clickmap } = useClickmapMethodProvider( 24 | win, 25 | counterOptions as any, 26 | ); 27 | clickmap(value); 28 | sinon.assert.calledWith(setSpy, { 29 | [COUNTER_STATE_CLICKMAP]: value, 30 | }); 31 | }); 32 | 33 | it('default true', () => { 34 | const { clickmap } = useClickmapMethodProvider( 35 | win, 36 | counterOptions as any, 37 | ); 38 | clickmap(); 39 | sinon.assert.calledWith(setSpy, { 40 | [COUNTER_STATE_CLICKMAP]: true, 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/utils/string/repeat.ts: -------------------------------------------------------------------------------- 1 | import { flags } from '@inject'; 2 | import { bindArg } from 'src/utils/function/bind'; 3 | import { toNativeOrFalse } from 'src/utils/function/isNativeFunction'; 4 | import { Repeat } from './types'; 5 | 6 | const nativeRepeat = toNativeOrFalse(String.prototype.repeat, 'repeat'); 7 | 8 | export const repeatPoly: Repeat = (inputString, count) => { 9 | let result = ''; 10 | for (let i = 0; i < count; i += 1) { 11 | result += inputString; 12 | } 13 | 14 | return result; 15 | }; 16 | 17 | const callNativeOrPoly = nativeRepeat 18 | ? (inputString: string, count: number) => 19 | nativeRepeat.call(inputString, count) 20 | : repeatPoly; 21 | 22 | export const repeat: Repeat = flags.POLYFILLS_FEATURE 23 | ? callNativeOrPoly 24 | : (inputString: string, count: number) => 25 | String.prototype.repeat.call(inputString, count); 26 | 27 | const pad = ( 28 | start: boolean, 29 | padString: string, 30 | targetLength: number, 31 | part: string, 32 | ) => { 33 | const repeatLength = 34 | padString.length && (targetLength - part.length) / padString.length; 35 | 36 | if (repeatLength <= 0) { 37 | return part; 38 | } 39 | 40 | const padding = repeat(padString, repeatLength); 41 | return start ? padding + part : part + padding; 42 | }; 43 | 44 | export const padStart = bindArg(true, pad); 45 | export const padEnd = bindArg(false, pad); 46 | -------------------------------------------------------------------------------- /src/utils/promise/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-bitwise */ 2 | // https://github.com/RubenVerborgh/promiscuous !?!? 3 | import * as polyPromise from 'promise-polyfill'; 4 | import { flags } from '@inject'; 5 | import { bind } from 'src/utils/function/bind'; 6 | import { toNativeOrFalse } from 'src/utils/function/isNativeFunction/toNativeOrFalse'; 7 | import { getPath } from 'src/utils/object'; 8 | 9 | /* eslint-disable-next-line import/no-mutable-exports */ 10 | export let PolyPromise: PromiseConstructor = window.Promise; 11 | 12 | if (flags.POLYFILLS_ES6_FEATURE) { 13 | const construct = toNativeOrFalse(PolyPromise as any, 'Promise'); 14 | const resolve = toNativeOrFalse( 15 | getPath(PolyPromise, 'resolve')!, 16 | 'resolve', 17 | ); 18 | const reject = toNativeOrFalse(getPath(PolyPromise, 'reject')!, 'reject'); 19 | const all = toNativeOrFalse(getPath(PolyPromise, 'all')!, 'all'); 20 | if (construct && resolve && reject && all) { 21 | const anyPromise = function promiseWrapper( 22 | a: ConstructorParameters[0], 23 | ) { 24 | return new Promise(a); 25 | } as any; 26 | anyPromise.resolve = bind(resolve, PolyPromise); 27 | anyPromise.reject = bind(reject, PolyPromise); 28 | anyPromise.all = bind(all, PolyPromise); 29 | PolyPromise = anyPromise; 30 | } else { 31 | PolyPromise = polyPromise.default; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/providers/statusCheck/statusCheckFn.ts: -------------------------------------------------------------------------------- 1 | import { cMap } from 'src/utils/array/map'; 2 | import { includes } from 'src/utils/array/includes'; 3 | import { constructArray } from 'src/utils/function/construct'; 4 | import { ctxPath } from 'src/utils/object'; 5 | import { getGlobalStorage } from 'src/storage/global/getGlobal'; 6 | 7 | import { GLOBAL_COUNTERS_METHOD_NAME } from 'src/providers/getCounters/const'; 8 | import { 9 | ExportedCounterInfo, 10 | GetCountersMethod, 11 | } from 'src/providers/getCounters/types'; 12 | import { getStatusCheckSearchParams } from './urlSearchParams'; 13 | 14 | /** Result of checking if counter exists on page */ 15 | interface CheckStatusResult { 16 | /** Counter id */ 17 | id: number; 18 | /** Is counter present on page */ 19 | counterFound: boolean; 20 | } 21 | 22 | export const checkStatusFn = (ctx: Window): CheckStatusResult => { 23 | const { id } = getStatusCheckSearchParams(ctx); 24 | 25 | const globalConfig = getGlobalStorage(ctx); 26 | const getCountersFn = globalConfig.getVal( 27 | GLOBAL_COUNTERS_METHOD_NAME, 28 | constructArray, 29 | ); 30 | 31 | const runningCounters = getCountersFn(); 32 | const counterIds = cMap( 33 | ctxPath('id'), 34 | runningCounters, 35 | ); 36 | 37 | return { 38 | id, 39 | ['counterFound']: !!id && includes(id, counterIds), 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /src/providers/artificialHit/type.ts: -------------------------------------------------------------------------------- 1 | import { METHOD_NAME_HIT } from './const'; 2 | 3 | /** 4 | * Params to be sent to backend 5 | */ 6 | export type ArtificialHitOptions = { 7 | /** Page referrer */ 8 | referrer?: string; 9 | /** 10 | * Alternative to `referrer` option. 11 | * In case if both options are provided, it will be overwritten. 12 | */ 13 | referer?: string; 14 | /** The function that will be called after sending the hit */ 15 | callback?: (...args: any[]) => any; 16 | /** Callback context */ 17 | ctx?: any; 18 | /** Visit parameters to be sent with a hit */ 19 | params?: Record; 20 | /** Page title */ 21 | title?: string; 22 | }; 23 | 24 | /** 25 | * Hit data handler 26 | */ 27 | export type ArtificialHandler = ( 28 | /** Artificial pseudo URL */ 29 | url?: string, 30 | /** Page title */ 31 | title?: string | ArtificialHitOptions, 32 | /** Page referrer */ 33 | referrer?: string, 34 | /** Visit parameters to be sent with a hit */ 35 | params?: Record, 36 | /** The function that will be called after sending the hit */ 37 | callback?: (...args: any[]) => any, 38 | /** Callback context */ 39 | fnCtx?: any, 40 | ) => T; 41 | 42 | declare module 'src/utils/counter/type' { 43 | interface CounterObject { 44 | /** Artificial hit */ 45 | [METHOD_NAME_HIT]?: ArtificialHandler; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/encoder/encoder.ts: -------------------------------------------------------------------------------- 1 | /* eslint no-bitwise: 0 */ 2 | // в этом файле битовые операции нужны 3 | 4 | import { arrayJoin } from '../array/join'; 5 | 6 | const Base64 = { 7 | abc: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 8 | tail: '+/=', 9 | tailSafe: '*-_', 10 | }; 11 | 12 | export const encode = (data: number[], safe = false) => { 13 | const abc = (Base64.abc + (safe ? Base64.tailSafe : Base64.tail)).split(''); 14 | const { length } = data; 15 | const result = []; 16 | const lPos = length - (length % 3); 17 | let t; 18 | 19 | for (let i = 0; i < lPos; i += 3) { 20 | t = (data[i] << 16) + (data[i + 1] << 8) + data[i + 2]; 21 | result.push( 22 | abc[(t >> 18) & 0x3f], 23 | abc[(t >> 12) & 0x3f], 24 | abc[(t >> 6) & 0x3f], 25 | abc[t & 0x3f], 26 | ); 27 | } 28 | switch (length - lPos) { 29 | case 1: 30 | t = data[lPos] << 4; 31 | result.push(abc[(t >> 6) & 0x3f], abc[t & 0x3f], abc[64], abc[64]); 32 | break; 33 | case 2: 34 | t = (data[lPos] << 10) + (data[lPos + 1] << 2); 35 | result.push( 36 | abc[(t >> 12) & 0x3f], 37 | abc[(t >> 6) & 0x3f], 38 | abc[t & 0x3f], 39 | abc[64], 40 | ); 41 | break; 42 | default: 43 | } 44 | return arrayJoin('', result); 45 | }; 46 | -------------------------------------------------------------------------------- /src/utils/dom/insertScript.ts: -------------------------------------------------------------------------------- 1 | import { mix } from '../object'; 2 | import { getElemCreateFunction } from './dom'; 3 | 4 | export type ScriptOptions = { 5 | src: string; 6 | type?: string; 7 | charset?: string; 8 | async?: boolean; 9 | dataAttributes?: Record; 10 | }; 11 | 12 | export const insertScript = ( 13 | ctx: Window, 14 | options: ScriptOptions, 15 | ): HTMLScriptElement | undefined => { 16 | const createFn = getElemCreateFunction(ctx); 17 | if (!createFn) { 18 | return undefined; 19 | } 20 | const { document: doc } = ctx; 21 | const scriptTag = createFn('script'); 22 | scriptTag.src = options.src; 23 | scriptTag.type = options.type || 'text/javascript'; 24 | scriptTag.charset = options.charset || 'utf-8'; 25 | scriptTag.async = options.async || true; 26 | 27 | if (options.dataAttributes) { 28 | mix(scriptTag.dataset, options.dataAttributes); 29 | } 30 | 31 | try { 32 | let head = doc.getElementsByTagName('head')[0]; 33 | // fix for Opera 34 | if (!head) { 35 | const html = doc.getElementsByTagName('html')[0]; 36 | head = createFn('head'); 37 | if (html) { 38 | html.appendChild(head); 39 | } 40 | } 41 | head.insertBefore(scriptTag, head.firstChild); 42 | return scriptTag; 43 | } catch (e) { 44 | // empty 45 | } 46 | return undefined; 47 | }; 48 | -------------------------------------------------------------------------------- /src/utils/string/__test__/string.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { padEnd, padStart } from '../repeat'; 3 | import { stringIndexOfPoly } from '../string'; 4 | 5 | describe('stringIndexOfPoly', () => { 6 | it('Gets indexes correctly', () => { 7 | expect(stringIndexOfPoly('abcde', 'a')).to.equal(0); 8 | expect(stringIndexOfPoly('abcde', 'bcde')).to.equal(1); 9 | expect(stringIndexOfPoly('abcde', 'cde')).to.equal(2); 10 | expect(stringIndexOfPoly('abcde', 'def')).to.equal(-1); 11 | expect(stringIndexOfPoly('abcde', 'z')).to.equal(-1); 12 | }); 13 | }); 14 | 15 | describe('padding', () => { 16 | it('padStart', () => { 17 | expect(padStart('0', 6, '111')).to.equal('000111'); 18 | expect(padStart('0', 6.5, '111')).to.equal('000111'); 19 | expect(padStart('', 8, '111')).to.equal('111'); 20 | expect(padStart('0', 3, '111')).to.equal('111'); 21 | expect(padStart('0', 2, '111')).to.equal('111'); 22 | expect(padStart('0', 6, '')).to.equal('000000'); 23 | }); 24 | 25 | it('padEnd', () => { 26 | expect(padEnd('0', 6, '111')).to.equal('111000'); 27 | expect(padEnd('0', 6.5, '111')).to.equal('111000'); 28 | expect(padEnd('', 8, '111')).to.equal('111'); 29 | expect(padEnd('0', 3, '111')).to.equal('111'); 30 | expect(padEnd('0', 2, '111')).to.equal('111'); 31 | expect(padEnd('0', 6, '')).to.equal('000000'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/providers/statusCheck/statusCheck.ts: -------------------------------------------------------------------------------- 1 | import { ctxErrorLogger } from 'src/utils/errorLogger/errorLogger'; 2 | 3 | import { 4 | getResourceUrl, 5 | setupUtilsAndLoadScript, 6 | } from 'src/providers/remoteControl/remoteControl'; 7 | import { getStatusCheckSearchParams } from 'src/providers/statusCheck/urlSearchParams'; 8 | import { CounterOptions } from 'src/utils/counterOptions'; 9 | import { setDefer } from 'src/utils/defer/defer'; 10 | import { bindArgs } from 'src/utils/function/bind'; 11 | import { DEFAULT_COUNTER_TYPE } from '../counterOptions'; 12 | 13 | export const CHK_STATUS_KEY = 'cs'; 14 | 15 | export const checkStatusRaw = (ctx: Window, counterOptions: CounterOptions) => { 16 | const { id, lang } = getStatusCheckSearchParams(ctx); 17 | 18 | if ( 19 | id && 20 | counterOptions.id === id && 21 | counterOptions.counterType === DEFAULT_COUNTER_TYPE 22 | ) { 23 | const src = getResourceUrl(ctx, { 24 | ['lang']: lang, 25 | ['fileId']: 'status', 26 | }); 27 | setDefer( 28 | ctx, 29 | bindArgs([ctx, src, `${id}`], setupUtilsAndLoadScript), 30 | 0, 31 | CHK_STATUS_KEY, 32 | ); 33 | } 34 | }; 35 | 36 | /** 37 | * Checks if counter with specified id found on page 38 | * @param ctx - Current window 39 | * @param counterOptions - Counter options on initialization 40 | */ 41 | export const checkStatus = ctxErrorLogger('cs.init', checkStatusRaw); 42 | -------------------------------------------------------------------------------- /.github/workflows/check-code.yml: -------------------------------------------------------------------------------- 1 | name: Check code 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: 9 | - opened 10 | - reopened 11 | - synchronize 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | check: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | - name: Setup node 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: 20.x 26 | cache: npm 27 | - name: Install 28 | run: npm ci 29 | - name: Typecheck/Prettier/Lint 30 | run: npm run lint 31 | test: 32 | needs: check 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v4 37 | - name: Setup node 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: 20.x 41 | cache: npm 42 | - name: Install 43 | run: npm ci 44 | - name: Tests 45 | run: npm run test:unit 46 | build: 47 | needs: 48 | - check 49 | - test 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Checkout 53 | uses: actions/checkout@v4 54 | - name: Setup node 55 | uses: actions/setup-node@v4 56 | with: 57 | node-version: 20.x 58 | cache: npm 59 | - name: Install 60 | run: npm ci 61 | - name: Build 62 | run: npm run build 63 | -------------------------------------------------------------------------------- /src/middleware/prerender/prerender.ts: -------------------------------------------------------------------------------- 1 | import { PRERENDER_MW_BR_KEY } from 'src/api/watch'; 2 | import type { SenderInfo } from 'src/sender/SenderInfo'; 3 | import { isPrerender } from 'src/utils/browser/browser'; 4 | import { cEvent } from 'src/utils/events/events'; 5 | import type { MiddlewareGetter } from '../types'; 6 | 7 | declare global { 8 | interface DocumentEventMap { 9 | webkitvisibilitychange: Event; 10 | prerenderingchange: Event; 11 | } 12 | } 13 | 14 | const EVENTS = [ 15 | 'webkitvisibilitychange', 16 | 'visibilitychange', 17 | 'prerenderingchange', 18 | ] as const; 19 | 20 | /** 21 | * If page is prerendered delays hit sending until it is visible 22 | * @param ctx - Current window 23 | */ 24 | export const prerender: MiddlewareGetter = (ctx: Window) => ({ 25 | beforeRequest(senderParams: SenderInfo, next) { 26 | const { document: doc } = ctx; 27 | const { brInfo } = senderParams; 28 | if (brInfo && isPrerender(ctx)) { 29 | const event = cEvent(ctx); 30 | const onVisibilityChange = (e: Event) => { 31 | if (!isPrerender(ctx)) { 32 | event.un(doc, EVENTS, onVisibilityChange); 33 | next(); 34 | } 35 | 36 | return e; 37 | }; 38 | 39 | event.on(doc, EVENTS, onVisibilityChange); 40 | brInfo.setVal(PRERENDER_MW_BR_KEY, '1'); 41 | } else { 42 | next(); 43 | } 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /src/utils/flagsStorage/flagsStorage.ts: -------------------------------------------------------------------------------- 1 | import { bindArg } from 'src/utils/function/bind'; 2 | import { curry2 } from 'src/utils/function/curry'; 3 | import { firstArg } from 'src/utils/function/identity'; 4 | import { isNil, isUndefined } from 'src/utils/object'; 5 | 6 | export type FlagData = Record; 7 | 8 | const storageFn = ( 9 | serialize: (data: FlagData) => string, 10 | initialData?: FlagData | null, 11 | ) => { 12 | const flags = initialData || {}; 13 | return { 14 | ctx: bindArg(flags, firstArg) as () => FlagData, 15 | getVal(flag: string, defVal?: E) { 16 | const out = flags[flag] as E; 17 | if (isUndefined(out) && !isUndefined(defVal)) { 18 | return defVal; 19 | } 20 | return out; 21 | }, 22 | setVal(flag: string, val: string | number) { 23 | flags[flag] = val; 24 | return this; 25 | }, 26 | setOrNot(flag: string, val?: string | number | null) { 27 | if (val === '' || isNil(val)) { 28 | return this; 29 | } 30 | return this.setVal(flag, val); 31 | }, 32 | serialize: bindArg(flags, serialize), 33 | }; 34 | }; 35 | 36 | export type FlagStorage = ReturnType; 37 | 38 | export const flagStorage = curry2(storageFn) as ( 39 | serialize: (data: FlagData) => string, 40 | ) => (initialData?: FlagData | null) => FlagStorage; 41 | -------------------------------------------------------------------------------- /src/utils/ecommerce/waitForDataLayer.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from 'src/utils/array/isArray'; 2 | import { 3 | dataLayerObserver, 4 | DataLayerObserverObject, 5 | } from 'src/utils/dataLayerObserver/dataLayerObserver'; 6 | import { clearDefer, setDefer } from 'src/utils/defer/defer'; 7 | import { ctxErrorLogger } from 'src/utils/errorLogger/errorLogger'; 8 | import { getPath } from 'src/utils/object'; 9 | 10 | const ECOMMERCE_WAIT_TIME = 1000; 11 | 12 | export const waitForDataLayer = ctxErrorLogger( 13 | 'dl.w', 14 | ( 15 | ctx: Window, 16 | name: string, 17 | cb: (e: DataLayerObserverObject) => void, 18 | ) => { 19 | let observerObject: 20 | | false 21 | | DataLayerObserverObject 22 | | undefined; 23 | let timeoutId = 0; 24 | 25 | const getObserver = () => { 26 | const dataLayer: unknown = getPath(ctx, name); 27 | observerObject = 28 | isArray(dataLayer) && 29 | dataLayerObserver(ctx, dataLayer, cb); 30 | 31 | if (observerObject) { 32 | return; 33 | } 34 | 35 | timeoutId = setDefer( 36 | ctx, 37 | getObserver, 38 | ECOMMERCE_WAIT_TIME, 39 | 'ec.dl', 40 | ); 41 | }; 42 | 43 | getObserver(); 44 | return () => clearDefer(ctx, timeoutId); 45 | }, 46 | ); 47 | -------------------------------------------------------------------------------- /src/utils/function/memo.ts: -------------------------------------------------------------------------------- 1 | import { cIndexOfWin } from 'src/utils/array/indexOf'; 2 | import { firstArg } from './identity'; 3 | import { argsToArray } from './args'; 4 | import { AnyFunc } from './types'; 5 | 6 | /** 7 | * Creates a memoized function. 8 | * 9 | * @param fn A function to memoize 10 | * @param rawKeyFn A function that determines the cache key 11 | * for storing the result based on the arguments provided to the function. 12 | * Defaults to the first argument. 13 | * The function is invoked only if no previous result is found for the key. 14 | */ 15 | export const memo = [0]>( 16 | fn: FN, 17 | rawKeyFn?: (...arg: Parameters) => K, 18 | ): FN => { 19 | let keyFn: (...arg: Parameters) => K; 20 | const resStorage: ReturnType[] = []; 21 | const keyStorage: K[] = []; 22 | if (!rawKeyFn) { 23 | // @ts-expect-error 24 | keyFn = firstArg; 25 | } else { 26 | keyFn = rawKeyFn; 27 | } 28 | // @ts-expect-error 29 | return function memoized() { 30 | // eslint-disable-next-line prefer-rest-params 31 | const fnArgs = argsToArray(arguments) as Parameters; 32 | const key = keyFn(...fnArgs); 33 | const keyIndex = cIndexOfWin(key, keyStorage); 34 | if (keyIndex !== -1) { 35 | return resStorage[keyIndex]; 36 | } 37 | const fnRes: ReturnType = fn(...fnArgs); 38 | resStorage.push(fnRes); 39 | keyStorage.push(key); 40 | return fnRes; 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /src/utils/asyncMap/__tests__/AsyncMap.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { AsyncMapFn, getAsync, setAsync } from '../AsyncMap'; 3 | 4 | describe('AsyncMap', () => { 5 | const defaultValues: Record = { 6 | a: 1, 7 | b: 2, 8 | c: 3, 9 | }; 10 | const keys = ['a', 'b', 'c']; 11 | let storage = AsyncMapFn(); 12 | 13 | beforeEach(() => { 14 | storage = AsyncMapFn(); 15 | }); 16 | 17 | it('Instantiates with params', async () => { 18 | for (let i = 0; i < keys.length; i += 1) { 19 | // eslint-disable-next-line no-await-in-loop 20 | storage(setAsync(keys[i], defaultValues[keys[i]])); 21 | } 22 | 23 | for (let i = 0; i < keys.length; i += 1) { 24 | // eslint-disable-next-line no-await-in-loop 25 | const result = await storage(getAsync(keys[i])); 26 | chai.expect(result).to.equal(i + 1); 27 | } 28 | }); 29 | 30 | it('Sets and then gets', async () => { 31 | const storageSet = AsyncMapFn(); 32 | storageSet(setAsync('10', 'settings')); 33 | const result = await storageSet(getAsync('10')); 34 | chai.expect(result).to.equal('settings'); 35 | }); 36 | 37 | it('Gets and then sets', async () => { 38 | const storageGet = AsyncMapFn(); 39 | storageGet(setAsync('10', 'settings')); 40 | const result = await storageGet(getAsync('10')); 41 | chai.expect(result).to.equal('settings'); 42 | }); 43 | }); 44 | --------------------------------------------------------------------------------