diff --git a/tracker/tracker/src/main/vendors/finder/LICENSE b/tracker/tracker/src/main/vendors/finder/LICENSE
deleted file mode 100644
index 1c1d11619..000000000
--- a/tracker/tracker/src/main/vendors/finder/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2018–2020 Anton Medvedev
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/tracker/tracker/src/main/vendors/finder/README.md b/tracker/tracker/src/main/vendors/finder/README.md
deleted file mode 100644
index 391bfee9a..000000000
--- a/tracker/tracker/src/main/vendors/finder/README.md
+++ /dev/null
@@ -1,132 +0,0 @@
-
-
-# finder
-
-[](https://www.npmjs.com/package/@medv/finder)
-[](https://travis-ci.org/antonmedv/finder)
-[](https://bundlephobia.com/result?p=@medv/finder)
-
-> CSS Selector Generator
-
-## Features
-
-* Generates **shortest** selectors
-* **Unique** selectors per page
-* Stable and **robust** selectors
-* **2.1 kB** gzip and minify size
-
-## Install
-
-```bash
-npm install @medv/finder
-```
-
-Finder can be used via modules:
-
-```html
-
-```
-
-## Usage
-
-```js
-import {finder} from '@medv/finder'
-
-document.addEventListener('click', event => {
- const selector = finder(event.target)
- console.log(selector)
-})
-```
-
-## Example
-
-Example of generated selector:
-
-```css
-.blog > article:nth-child(3) .add-comment
-```
-
-## Configuration
-
-`finder` takes configuration object as second parameters. Here is example of all params with default values:
-
-```js
-const selector = finder(event.target, {
- root: document.body,
- className: (name) => true,
- tagName: (name) => true,
- attr: (name, value) => false,
- seedMinLength: 1,
- optimizedMinLength: 2,
- threshold: 1000,
- maxNumberOfTries: 10_000,
-})
-```
-
-#### `root: Element`
-
-Root of search, defaults to `document.body`.
-
-#### `idName: (name: string) => boolean`
-
-Check if this ID can be used. For example you can restrict using framework specific IDs:
-
-```js
-const selector = finder(event.target, {
- idName: name => !name.startsWith('ember')
-})
-```
-
-#### `className: (name: string) => boolean`
-
-Check if this class name can be used. For example you can restrict using _is-*_ class names:
-
-```js
-const selector = finder(event.target, {
- className: name => !name.startsWith('is-')
-})
-```
-
-#### `tagName: (name: string) => boolean`
-
-Check if tag name can be used, same as `className`.
-
-#### `attr: (name: string, value: string) => boolean`
-
-Check if attr name can be used.
-
-#### `seedMinLength: number`
-
-Minimum length of levels in fining selector. Starts from `1`.
-For more robust selectors give this param value around 4-5 depending on depth of you DOM tree.
-If `finder` hits `root` this param is ignored.
-
-#### `optimizedMinLength: number`
-
-Minimum length for optimising selector. Starts from `2`.
-For example selector `body > div > div > p` can be optimized to `body p`.
-
-#### `threshold: number`
-
-Max number of selectors to check before falling into `nth-child` usage.
-Checking for uniqueness of selector is very costs operation, if you have DOM tree depth of 5, with 5 classes on each level,
-that gives you more than 3k selectors to check.
-`finder` uses two step approach so it's reaching this threshold in some cases twice.
-Default `1000` is good enough in most cases.
-
-#### `maxNumberOfTries: number`
-
-Max number of tries when we do the optimization. It is a trade-off between optimization and efficiency.
-Default `10_000` is good enough in most cases.
-
-### Google Chrome Extension
-
-
-
-Generate the unique selectors in your browser by using [Chrome Extension](https://chrome.google.com/webstore/detail/get-unique-css-selector/lkfaghhbdebclkklgjhhonadomejckai)
-
-## License
-
-[MIT](LICENSE)
diff --git a/tracker/tracker/src/main/vendors/finder/finder.ts b/tracker/tracker/src/main/vendors/finder/finder.ts
deleted file mode 100644
index 431956595..000000000
--- a/tracker/tracker/src/main/vendors/finder/finder.ts
+++ /dev/null
@@ -1,432 +0,0 @@
-type Node = {
- name: string
- penalty: number
- level?: number
-}
-
-type Path = Node[]
-
-enum Limit {
- All,
- Two,
- One,
-}
-
-export type Options = {
- root: Element
- idName: (name: string) => boolean
- className: (name: string) => boolean
- tagName: (name: string) => boolean
- attr: (name: string, value: string) => boolean
- seedMinLength: number
- optimizedMinLength: number
- threshold: number
- maxNumberOfTries: number
-}
-
-let config: Options
-
-let rootDocument: Document | Element
-export function finder(input: Element, options?: Partial) {
- if (input.nodeType !== Node.ELEMENT_NODE) {
- throw new Error("Can't generate CSS selector for non-element node type.")
- }
-
- if ('html' === input.tagName.toLowerCase()) {
- return 'html'
- }
-
- const defaults: Options = {
- root: document.body,
- idName: (name: string) => true,
- className: (name: string) => true,
- tagName: (name: string) => true,
- attr: (name: string, value: string) => false,
- seedMinLength: 1,
- optimizedMinLength: 2,
- threshold: 1000,
- maxNumberOfTries: 10000,
- }
-
- config = { ...defaults, ...options }
-
- rootDocument = findRootDocument(config.root, defaults)
-
- let path = bottomUpSearch(input, Limit.All, () =>
- bottomUpSearch(input, Limit.Two, () => bottomUpSearch(input, Limit.One)),
- )
-
- if (path) {
- const optimized = sort(optimize(path, input))
-
- if (optimized.length > 0) {
- path = optimized[0]
- }
-
- return selector(path)
- } else {
- throw new Error('Selector was not found.')
- }
-}
-
-function findRootDocument(rootNode: Element | Document, defaults: Options) {
- if (rootNode.nodeType === Node.DOCUMENT_NODE) {
- return rootNode
- }
- if (rootNode === defaults.root) {
- return rootNode.ownerDocument
- }
- return rootNode
-}
-
-function bottomUpSearch(input: Element, limit: Limit, fallback?: () => Path | null): Path | null {
- let path: Path | null = null
- const stack: Node[][] = []
- let current: Element | null = input
- let i = 0
-
- while (current && current !== config.root.parentElement) {
- let level: Node[] = maybe(id(current)) ||
- maybe(...attr(current)) ||
- maybe(...classNames(current)) ||
- maybe(tagName(current)) || [any()]
-
- const nth = index(current)
-
- if (limit === Limit.All) {
- if (nth) {
- level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth)))
- }
- } else if (limit === Limit.Two) {
- level = level.slice(0, 1)
-
- if (nth) {
- level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth)))
- }
- } else if (limit === Limit.One) {
- const [node] = (level = level.slice(0, 1))
-
- if (nth && dispensableNth(node)) {
- level = [nthChild(node, nth)]
- }
- }
-
- for (const node of level) {
- node.level = i
- }
-
- stack.push(level)
-
- if (stack.length >= config.seedMinLength) {
- path = findUniquePath(stack, fallback)
- if (path) {
- break
- }
- }
-
- current = current.parentElement
- i++
- }
-
- if (!path) {
- path = findUniquePath(stack, fallback)
- }
-
- return path
-}
-
-function findUniquePath(stack: Node[][], fallback?: () => Path | null): Path | null {
- const paths = sort(combinations(stack))
-
- if (paths.length > config.threshold) {
- return fallback ? fallback() : null
- }
-
- for (const candidate of paths) {
- if (unique(candidate)) {
- return candidate
- }
- }
-
- return null
-}
-
-function selector(path: Path): string {
- let node = path[0]
- let query = node.name
- for (let i = 1; i < path.length; i++) {
- const level = path[i].level || 0
-
- if (node.level === level - 1) {
- query = `${path[i].name} > ${query}`
- } else {
- query = `${path[i].name} ${query}`
- }
-
- node = path[i]
- }
- return query
-}
-
-function penalty(path: Path): number {
- return path.map((node) => node.penalty).reduce((acc, i) => acc + i, 0)
-}
-
-function unique(path: Path) {
- switch (rootDocument.querySelectorAll(selector(path)).length) {
- case 0:
- throw new Error(`Can't select any node with this selector: ${selector(path)}`)
- case 1:
- return true
- default:
- return false
- }
-}
-
-function id(input: Element): Node | null {
- const elementId = input.getAttribute('id')
- if (elementId && config.idName(elementId)) {
- return {
- name: '#' + cssesc(elementId, { isIdentifier: true }),
- penalty: 0,
- }
- }
- return null
-}
-
-function attr(input: Element): Node[] {
- const attrs = Array.from(input.attributes).filter((attr) => config.attr(attr.name, attr.value))
-
- return attrs.map(
- (attr): Node => ({
- name: '[' + cssesc(attr.name, { isIdentifier: true }) + '="' + cssesc(attr.value) + '"]',
- penalty: 0.5,
- }),
- )
-}
-
-function classNames(input: Element): Node[] {
- const names = Array.from(input.classList).filter(config.className)
-
- return names.map(
- (name): Node => ({
- name: '.' + cssesc(name, { isIdentifier: true }),
- penalty: 1,
- }),
- )
-}
-
-function tagName(input: Element): Node | null {
- const name = input.tagName.toLowerCase()
- if (config.tagName(name)) {
- return {
- name,
- penalty: 2,
- }
- }
- return null
-}
-
-function any(): Node {
- return {
- name: '*',
- penalty: 3,
- }
-}
-
-function index(input: Element): number | null {
- const parent = input.parentNode
- if (!parent) {
- return null
- }
-
- let child = parent.firstChild
- if (!child) {
- return null
- }
-
- let i = 0
- while (child) {
- if (child.nodeType === Node.ELEMENT_NODE) {
- i++
- }
-
- if (child === input) {
- break
- }
-
- child = child.nextSibling
- }
-
- return i
-}
-
-function nthChild(node: Node, i: number): Node {
- return {
- name: node.name + `:nth-child(${i})`,
- penalty: node.penalty + 1,
- }
-}
-
-function dispensableNth(node: Node) {
- return node.name !== 'html' && !node.name.startsWith('#')
-}
-
-function maybe(...level: (Node | null)[]): Node[] | null {
- const list = level.filter(notEmpty)
- if (list.length > 0) {
- return list
- }
- return null
-}
-
-function notEmpty(value: T | null | undefined): value is T {
- return value !== null && value !== undefined
-}
-
-function combinations(stack: Node[][], path: Node[] = []): Node[][] {
- const paths: Node[][] = []
- if (stack.length > 0) {
- for (const node of stack[0]) {
- paths.push(...combinations(stack.slice(1, stack.length), path.concat(node)))
- }
- } else {
- paths.push(path)
- }
- return paths
-}
-
-function sort(paths: Iterable): Path[] {
- return Array.from(paths).sort((a, b) => penalty(a) - penalty(b))
-}
-
-type Scope = {
- counter: number
- visited: Map
-}
-
-function optimize(
- path: Path,
- input: Element,
- scope: Scope = {
- counter: 0,
- visited: new Map(),
- },
-): Node[][] {
- const paths: Node[][] = []
- if (path.length > 2 && path.length > config.optimizedMinLength) {
- for (let i = 1; i < path.length - 1; i++) {
- if (scope.counter > config.maxNumberOfTries) {
- return paths // Okay At least I tried!
- }
- scope.counter += 1
- const newPath = [...path]
- newPath.splice(i, 1)
- const newPathKey = selector(newPath)
- if (scope.visited.has(newPathKey)) {
- return paths
- }
- if (unique(newPath) && same(newPath, input)) {
- paths.push(newPath)
- scope.visited.set(newPathKey, true)
- paths.push(...optimize(newPath, input, scope))
- }
- }
- }
- return paths
-}
-
-function same(path: Path, input: Element) {
- return rootDocument.querySelector(selector(path)) === input
-}
-
-const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/
-const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/
-const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g
-
-const defaultOptions = {
- escapeEverything: false,
- isIdentifier: false,
- quotes: 'single',
- wrap: false,
-}
-
-function cssesc(string: string, opt: Partial = {}) {
- const options = { ...defaultOptions, ...opt }
- if (options.quotes != 'single' && options.quotes != 'double') {
- options.quotes = 'single'
- }
- const quote = options.quotes == 'double' ? '"' : "'"
- const isIdentifier = options.isIdentifier
-
- const firstChar = string.charAt(0)
- let output = ''
- let counter = 0
- const length = string.length
- while (counter < length) {
- const character = string.charAt(counter++)
- let codePoint = character.charCodeAt(0)
- let value: string | undefined = void 0
- // If it’s not a printable ASCII character…
- if (codePoint < 0x20 || codePoint > 0x7e) {
- if (codePoint >= 0xd800 && codePoint <= 0xdbff && counter < length) {
- // It’s a high surrogate, and there is a next character.
- const extra = string.charCodeAt(counter++)
- if ((extra & 0xfc00) == 0xdc00) {
- // next character is low surrogate
- codePoint = ((codePoint & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000
- } else {
- // It’s an unmatched surrogate; only append this code unit, in case
- // the next code unit is the high surrogate of a surrogate pair.
- counter--
- }
- }
- value = '\\' + codePoint.toString(16).toUpperCase() + ' '
- } else {
- if (options.escapeEverything) {
- if (regexAnySingleEscape.test(character)) {
- value = '\\' + character
- } else {
- value = '\\' + codePoint.toString(16).toUpperCase() + ' '
- }
- } else if (/[\t\n\f\r\x0B]/.test(character)) {
- value = '\\' + codePoint.toString(16).toUpperCase() + ' '
- } else if (
- character == '\\' ||
- (!isIdentifier &&
- ((character == '"' && quote == character) || (character == "'" && quote == character))) ||
- (isIdentifier && regexSingleEscape.test(character))
- ) {
- value = '\\' + character
- } else {
- value = character
- }
- }
- output += value
- }
-
- if (isIdentifier) {
- if (/^-[-\d]/.test(output)) {
- output = '\\-' + output.slice(1)
- } else if (/\d/.test(firstChar)) {
- output = '\\3' + firstChar + ' ' + output.slice(1)
- }
- }
-
- // Remove spaces after `\HEX` escapes that are not followed by a hex digit,
- // since they’re redundant. Note that this is only possible if the escape
- // sequence isn’t preceded by an odd number of backslashes.
- output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) {
- if ($1 && $1.length % 2) {
- // It’s not safe to remove the space, so don’t.
- return $0
- }
- // Strip the space.
- return ($1 || '') + $2
- })
-
- if (!isIdentifier && options.wrap) {
- return quote + output + quote
- }
- return output
-}
diff --git a/tracker/tracker/src/main/vendors/finder/package.json b/tracker/tracker/src/main/vendors/finder/package.json
deleted file mode 100644
index 685be35f4..000000000
--- a/tracker/tracker/src/main/vendors/finder/package.json
+++ /dev/null
@@ -1,78 +0,0 @@
-{
- "_from": "@medv/finder",
- "_id": "@medv/finder@2.0.0",
- "_inBundle": false,
- "_integrity": "sha512-gV4jOsGpiWNDGd8Dw7tod1Fc9Gc7StaOT4oZ/6srHRWtsHU+HYWzmkYsa3Qy/z0e9tY1WpJ9wWdBFGskfbzoug==",
- "_location": "/@medv/finder",
- "_phantomChildren": {},
- "_requested": {
- "type": "tag",
- "registry": true,
- "raw": "@medv/finder",
- "name": "@medv/finder",
- "escapedName": "@medv%2ffinder",
- "scope": "@medv",
- "rawSpec": "",
- "saveSpec": null,
- "fetchSpec": "latest"
- },
- "_requiredBy": [
- "#USER",
- "/"
- ],
- "_resolved": "https://registry.npmjs.org/@medv/finder/-/finder-2.0.0.tgz",
- "_shasum": "699b7141393aa815f120b38f54f92ad212225902",
- "_spec": "@medv/finder",
- "_where": "/Users/shikhu/work/openreplay/tracker/tracker",
- "author": {
- "name": "Anton Medvedev",
- "email": "anton@medv.io"
- },
- "ava": {
- "require": [
- "esm",
- "./test/helpers/setup-browser-env.js"
- ]
- },
- "bugs": {
- "url": "https://github.com/antonmedv/finder/issues"
- },
- "bundleDependencies": false,
- "deprecated": false,
- "description": "CSS Selector Generator",
- "devDependencies": {
- "ava": "^3.8.2",
- "babel-minify": "*",
- "browser-env": "^3.3.0",
- "esm": "^3.2.25",
- "gzip-size-cli": "*",
- "release-it": "^13.6.1",
- "typescript": "3.9.3"
- },
- "files": [
- "*.ts",
- "*.js"
- ],
- "homepage": "https://github.com/antonmedv/finder",
- "keywords": [
- "css",
- "selector",
- "generator"
- ],
- "license": "MIT",
- "main": "finder.js",
- "name": "@medv/finder",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/antonmedv/finder.git"
- },
- "scripts": {
- "prepare": "tsc",
- "release": "release-it --access public",
- "size": "minify finder.js --sourceType module | gzip-size",
- "start": "tsc -w",
- "test": "tsc && ava"
- },
- "types": "finder.d.ts",
- "version": "2.0.0"
-}
diff --git a/tracker/tracker/src/webworker/QueueSender.unit.test.ts b/tracker/tracker/src/webworker/QueueSender.unit.test.ts
index 0f76c29dd..79fba9e71 100644
--- a/tracker/tracker/src/webworker/QueueSender.unit.test.ts
+++ b/tracker/tracker/src/webworker/QueueSender.unit.test.ts
@@ -3,20 +3,38 @@ import QueueSender from './QueueSender.js'
global.fetch = () => Promise.resolve(new Response()) // jsdom does not have it
-function mockFetch(status: number) {
+function mockFetch(status: number, headers?: Record) {
return jest
.spyOn(global, 'fetch')
- .mockImplementation(() => Promise.resolve({ status } as Response))
+ .mockImplementation((request) =>
+ Promise.resolve({ status, headers, request } as unknown as Response & {
+ request: RequestInfo
+ }),
+ )
}
const baseURL = 'MYBASEURL'
const sampleArray = new Uint8Array(1)
const randomToken = 'abc'
+
+const requestMock = {
+ body: sampleArray,
+ headers: { Authorization: 'Bearer abc' },
+ keepalive: true,
+ method: 'POST',
+}
+
+const gzipRequestMock = {
+ ...requestMock,
+ headers: { ...requestMock.headers, 'Content-Encoding': 'gzip' },
+}
+
function defaultQueueSender({
url = baseURL,
onUnauthorised = () => {},
onFailed = () => {},
-} = {}) {
- return new QueueSender(baseURL, onUnauthorised, onFailed, 10, 1000)
+ onCompress = undefined,
+}: Record = {}) {
+ return new QueueSender(baseURL, onUnauthorised, onFailed, 10, 1000, onCompress)
}
describe('QueueSender', () => {
@@ -42,6 +60,25 @@ describe('QueueSender', () => {
expect(fetchMock).toBeCalledTimes(0)
queueSender.push(sampleArray)
expect(fetchMock).toBeCalledTimes(1)
+ expect(fetchMock.mock.calls[0][1]).toMatchObject(requestMock)
+ })
+ test('Sends compressed request if onCompress is provided and compressed batch is included', () => {
+ const queueSender = defaultQueueSender({ onCompress: () => true })
+ const fetchMock = mockFetch(200)
+
+ // @ts-ignore
+ const spyOnCompress = jest.spyOn(queueSender, 'onCompress')
+ // @ts-ignore
+ const spyOnSendNext = jest.spyOn(queueSender, 'sendNext')
+
+ queueSender.authorise(randomToken)
+ queueSender.push(sampleArray)
+ expect(spyOnCompress).toBeCalledTimes(1)
+ queueSender.sendCompressed(sampleArray)
+ expect(fetchMock).toBeCalledTimes(1)
+ expect(spyOnSendNext).toBeCalledTimes(1)
+ expect(spyOnCompress).toBeCalledTimes(1)
+ expect(fetchMock.mock.calls[0][1]).toMatchObject(gzipRequestMock)
})
test('Calls fetch on authorisation if there was a push() call before', () => {
const queueSender = defaultQueueSender()