diff --git a/.github/workflows/tracker-tests.yaml b/.github/workflows/tracker-tests.yaml index 5e9064e16..d5c4caeaf 100644 --- a/.github/workflows/tracker-tests.yaml +++ b/.github/workflows/tracker-tests.yaml @@ -17,8 +17,11 @@ jobs: name: Build and test Tracker strategy: matrix: - node-version: [ 16.x ] + node-version: [ 18.x ] steps: + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 @@ -28,7 +31,7 @@ jobs: uses: actions/cache@v3 with: path: tracker/tracker/node_modules - key: ${{ runner.OS }}-test_tracker_build-${{ hashFiles('**/yarn.lock') }} + key: ${{ runner.OS }}-test_tracker_build-${{ hashFiles('**/bun.lockb') }} restore-keys: | test_tracker_build{{ runner.OS }}-build- test_tracker_build{{ runner.OS }}- @@ -36,27 +39,34 @@ jobs: uses: actions/cache@v3 with: path: tracker/tracker-assist/node_modules - key: ${{ runner.OS }}-test_tracker_build-${{ hashFiles('**/yarn.lock') }} + key: ${{ runner.OS }}-test_tracker_build-${{ hashFiles('**/bun.lockb') }} restore-keys: | test_tracker_build{{ runner.OS }}-build- test_tracker_build{{ runner.OS }}- - name: Setup Testing packages run: | cd tracker/tracker - npm i -g yarn - yarn - - name: Setup Testing packages + bun install + - name: (TA) Setup Testing packages run: | cd tracker/tracker-assist - yarn + bun install - name: Jest tests run: | cd tracker/tracker - yarn test:ci - - name: Jest tests + bun run test:ci + - name: Building test + run: | + cd tracker/tracker + bun run build + - name: (TA) Jest tests run: | cd tracker/tracker-assist - yarn test:ci + bun run test:ci + - name: (TA) Building test + run: | + cd tracker/tracker-assist + bun run build - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/ui-tests.js.yml b/.github/workflows/ui-tests.js.yml index a65423c30..317c21b9d 100644 --- a/.github/workflows/ui-tests.js.yml +++ b/.github/workflows/ui-tests.js.yml @@ -27,8 +27,11 @@ jobs: name: Build and test Tracker plus Replayer strategy: matrix: - node-version: [ 16.x ] + node-version: [ 18.x ] steps: + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 @@ -38,18 +41,18 @@ jobs: uses: actions/cache@v3 with: path: tracker/tracker/node_modules - key: ${{ runner.OS }}-test_tracker_build-${{ hashFiles('tracker/tracker/yarn.lock') }} + key: ${{ runner.OS }}-test_tracker_build-${{ hashFiles('tracker/tracker/bun.lockb') }} restore-keys: | test_tracker_build-{{ runner.OS }}-build- test_tracker_build-{{ runner.OS }}- - name: Setup Testing packages run: | cd tracker/tracker - yarn + bun install - name: Build tracker inst run: | cd tracker/tracker - yarn build + bun run build - name: Setup Testing UI Env run: | cd tracker/tracker-testing-playground @@ -67,10 +70,6 @@ jobs: run: | cd tracker/tracker-testing-playground yarn - - name: Run testing frontend - run: | - cd tracker/tracker-testing-playground - yarn start &> ui.log & - name: Cache node modules uses: actions/cache@v3 with: @@ -96,39 +95,50 @@ jobs: echo "MINIO_USE_SSL = ''" >> .env echo "MINIO_ACCESS_KEY = ''" >> .env echo "MINIO_SECRET_KEY = ''" >> .env - echo "VERSION = '1.9.0'" >> .env - echo "TRACKER_VERSION = '4.0.0'" >> .env + echo "VERSION = '1.15.0'" >> .env + echo "TRACKER_VERSION = '10.0.0'" >> .env echo "COMMIT_HASH = 'dev'" >> .env echo "{ \"account\": \"$CY_ACC\", \"password\": \"$CY_PASS\" }" >> cypress.env.json - name: Setup packages run: | cd frontend - yarn + npm install --legacy-peer-deps - name: Run unit tests run: | cd frontend - yarn test + npm run test:ci - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} flags: ui name: ui - - name: Wait for Testing Frontend + - name: Run testing frontend run: | + cd tracker/tracker-testing-playground + yarn start &> testing.log & + echo "Started" + npm i -g wait-on + echo "Got wait on" + sleep 30 + cat testing.log npx wait-on http://localhost:3000 + echo "Done" + timeout-minutes: 4 - name: Run Frontend run: | cd frontend - yarn start &> frontend.log & - - name: Wait for frontend - run: | - cd frontend + bun start &> frontend.log & + echo "Started" + sleep 30 + cat frontend.log npx wait-on http://0.0.0.0:3333 + echo "Done" + timeout-minutes: 4 - name: (Chrome) Run visual tests run: | cd frontend - yarn cy:test + npm run cy:test # firefox have different viewport somehow # - name: (Firefox) Run visual tests # run: yarn cy:test-firefox diff --git a/frontend/app/player/player/_LSCache.ts b/frontend/app/player/player/_LSCache.ts index beb48af46..70425bdda 100644 --- a/frontend/app/player/player/_LSCache.ts +++ b/frontend/app/player/player/_LSCache.ts @@ -38,7 +38,7 @@ type Entries = { [K in keyof T]: [K, T[K]]; }[keyof T][]; -export default class LSCache { +export default class LSCache> { constructor(private state: SimpleState, private keyMap: Record, string>) { } update(newState: Partial) { diff --git a/frontend/cypress/e2e/generalStability.cy.ts b/frontend/cypress/e2e/generalStability.cy.ts deleted file mode 100644 index 4c8365070..000000000 --- a/frontend/cypress/e2e/generalStability.cy.ts +++ /dev/null @@ -1,44 +0,0 @@ -describe('Testing general stability', { - viewportHeight: 900, - viewportWidth: 1400, -}, () => { - it('Checking if app will crash', () => { - cy.clearCookies() - cy.clearAllSessionStorage() - cy.clearAllCookies() - cy.clearAllLocalStorage() - - cy.intercept('**/api/account').as('getAccount'); - - cy.visit('/') - cy.get('[data-test-id=login]').type(Cypress.env('account').replaceAll('"', '')); - cy.get('[data-test-id=password]').type(Cypress.env('password').replaceAll('"', '')); - cy.get('[data-test-id=log-button]').click(); - cy.wait('@getAccount') - - Cypress.on('uncaught:exception', (err, runnable) => { - return false - }) - - cy.get(':nth-child(1) > .ant-menu-item-group-title > .ant-typography').should('be.visible') - - cy.visit('/5/dashboard') - cy.wait(500) - cy.get('input[name="dashboardsSearch"]').should('be.visible') - - cy.visit('/client/account') - - cy.get(':nth-child(2) > .profileSettings-module__left--D4pCi > .profileSettings-module__info--DhVpL').should('be.visible') - - cy.get('.ant-menu-item-group-list > :nth-child(2)').click() - cy.wait(250) - cy.get('.text-2xl > div').should('be.visible') - cy.get('.ant-menu-item-group-list > :nth-child(3)').click() - cy.get('.ant-menu-item-group-list > :nth-child(4)').click() - cy.get('.text-base').should('be.visible') - - cy.get('.ant-menu-item-group-title').should('be.visible') - - // if test has not failed, we assume that app is not crashed (so far) - }) -}) \ No newline at end of file diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 4434b2055..eeb711dc2 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -146,6 +146,16 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.22.13": + version: 7.22.13 + resolution: "@babel/code-frame@npm:7.22.13" + dependencies: + "@babel/highlight": ^7.22.13 + chalk: ^2.4.2 + checksum: f4cc8ae1000265677daf4845083b72f88d00d311adb1a93c94eb4b07bf0ed6828a81ae4ac43ee7d476775000b93a28a9cddec18fbdc5796212d8dcccd5de72bd + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.17.7, @babel/compat-data@npm:^7.20.1, @babel/compat-data@npm:^7.20.5": version: 7.20.10 resolution: "@babel/compat-data@npm:7.20.10" @@ -220,6 +230,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-annotate-as-pure@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-annotate-as-pure@npm:7.22.5" + dependencies: + "@babel/types": ^7.22.5 + checksum: 5a80dc364ddda26b334bbbc0f6426cab647381555ef7d0cd32eb284e35b867c012ce6ce7d52a64672ed71383099c99d32765b3d260626527bb0e3470b0f58e45 + languageName: node + linkType: hard + "@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.18.6": version: 7.18.9 resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.18.9" @@ -263,6 +282,25 @@ __metadata: languageName: node linkType: hard +"@babel/helper-create-class-features-plugin@npm:^7.21.0": + version: 7.22.15 + resolution: "@babel/helper-create-class-features-plugin@npm:7.22.15" + dependencies: + "@babel/helper-annotate-as-pure": ^7.22.5 + "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-function-name": ^7.22.5 + "@babel/helper-member-expression-to-functions": ^7.22.15 + "@babel/helper-optimise-call-expression": ^7.22.5 + "@babel/helper-replace-supers": ^7.22.9 + "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + semver: ^6.3.1 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 2ae5759fe8845fda99b34f2ba6cd0794fc860213d14c93a87aa9180960252bce621157a79c373b7fbb423b25a55fb0e20eae0d5f8e4ad5ef22dc70e7c2af3805 + languageName: node + linkType: hard + "@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.20.5": version: 7.20.5 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.20.5" @@ -316,6 +354,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-environment-visitor@npm:^7.22.20, @babel/helper-environment-visitor@npm:^7.22.5": + version: 7.22.20 + resolution: "@babel/helper-environment-visitor@npm:7.22.20" + checksum: e762c2d8f5d423af89bd7ae9abe35bd4836d2eb401af868a63bbb63220c513c783e25ef001019418560b3fdc6d9a6fb67e6c0b650bcdeb3a2ac44b5c3d2bdd94 + languageName: node + linkType: hard + "@babel/helper-explode-assignable-expression@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-explode-assignable-expression@npm:7.18.6" @@ -335,6 +380,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-function-name@npm:^7.22.5": + version: 7.23.0 + resolution: "@babel/helper-function-name@npm:7.23.0" + dependencies: + "@babel/template": ^7.22.15 + "@babel/types": ^7.23.0 + checksum: d771dd1f3222b120518176733c52b7cadac1c256ff49b1889dbbe5e3fed81db855b8cc4e40d949c9d3eae0e795e8229c1c8c24c0e83f27cfa6ee3766696c6428 + languageName: node + linkType: hard + "@babel/helper-hoist-variables@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-hoist-variables@npm:7.18.6" @@ -353,6 +408,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-member-expression-to-functions@npm:^7.22.15": + version: 7.23.0 + resolution: "@babel/helper-member-expression-to-functions@npm:7.23.0" + dependencies: + "@babel/types": ^7.23.0 + checksum: b810daddf093ffd0802f1429052349ed9ea08ef7d0c56da34ffbcdecbdafac86f95bdea2fe30e0e0e629febc7dd41b56cb5eacc10d1a44336d37b755dac31fa4 + languageName: node + linkType: hard + "@babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-module-imports@npm:7.18.6" @@ -387,6 +451,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-optimise-call-expression@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-optimise-call-expression@npm:7.22.5" + dependencies: + "@babel/types": ^7.22.5 + checksum: 31b41a764fc3c585196cf5b776b70cf4705c132e4ce9723f39871f215f2ddbfb2e28a62f9917610f67c8216c1080482b9b05f65dd195dae2a52cef461f2ac7b8 + languageName: node + linkType: hard + "@babel/helper-plugin-utils@npm:7.10.4": version: 7.10.4 resolution: "@babel/helper-plugin-utils@npm:7.10.4" @@ -429,6 +502,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-replace-supers@npm:^7.22.9": + version: 7.22.20 + resolution: "@babel/helper-replace-supers@npm:7.22.20" + dependencies: + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-member-expression-to-functions": ^7.22.15 + "@babel/helper-optimise-call-expression": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 6b0858811ad46873817c90c805015d63300e003c5a85c147a17d9845fa2558a02047c3cc1f07767af59014b2dd0fa75b503e5bc36e917f360e9b67bb6f1e79f4 + languageName: node + linkType: hard + "@babel/helper-simple-access@npm:^7.20.2": version: 7.20.2 resolution: "@babel/helper-simple-access@npm:7.20.2" @@ -447,6 +533,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.22.5" + dependencies: + "@babel/types": ^7.22.5 + checksum: ab7fa2aa709ab49bb8cd86515a1e715a3108c4bb9a616965ba76b43dc346dee66d1004ccf4d222b596b6224e43e04cbc5c3a34459501b388451f8c589fbc3691 + languageName: node + linkType: hard + "@babel/helper-split-export-declaration@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-split-export-declaration@npm:7.18.6" @@ -456,6 +551,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-split-export-declaration@npm:^7.22.6": + version: 7.22.6 + resolution: "@babel/helper-split-export-declaration@npm:7.22.6" + dependencies: + "@babel/types": ^7.22.5 + checksum: d83e4b623eaa9622c267d3c83583b72f3aac567dc393dda18e559d79187961cb29ae9c57b2664137fc3d19508370b12ec6a81d28af73a50e0846819cb21c6e44 + languageName: node + linkType: hard + "@babel/helper-string-parser@npm:^7.19.4": version: 7.19.4 resolution: "@babel/helper-string-parser@npm:7.19.4" @@ -463,6 +567,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 6b0ff8af724377ec41e5587fffa7605198da74cb8e7d8d48a36826df0c0ba210eb9fedb3d9bef4d541156e0bd11040f021945a6cbb731ccec4aefb4affa17aa4 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": version: 7.19.1 resolution: "@babel/helper-validator-identifier@npm:7.19.1" @@ -470,6 +581,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-validator-identifier@npm:7.22.20" + checksum: dcad63db345fb110e032de46c3688384b0008a42a4845180ce7cd62b1a9c0507a1bed727c4d1060ed1a03ae57b4d918570259f81724aaac1a5b776056f37504e + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-validator-option@npm:7.18.6" @@ -511,6 +629,17 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.22.13": + version: 7.22.20 + resolution: "@babel/highlight@npm:7.22.20" + dependencies: + "@babel/helper-validator-identifier": ^7.22.20 + chalk: ^2.4.2 + js-tokens: ^4.0.0 + checksum: f3c3a193afad23434297d88e81d1d6c0c2cf02423de2139ada7ce0a7fc62d8559abf4cc996533c1a9beca7fc990010eb8d544097f75e818ac113bf39ed810aa2 + languageName: node + linkType: hard + "@babel/node@npm:^7.16.8": version: 7.20.7 resolution: "@babel/node@npm:7.20.7" @@ -538,6 +667,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.22.15": + version: 7.23.0 + resolution: "@babel/parser@npm:7.23.0" + bin: + parser: ./bin/babel-parser.js + checksum: ab4ea9360ed4ba3c728c5a9bf33035103ebde20a7e943c4ae1d42becb02a313d731d12a93c795c5a19777031e4022e64b92a52262eda902522a1a18649826283 + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.18.6" @@ -779,6 +917,20 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-private-property-in-object@npm:^7.21.11": + version: 7.21.11 + resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.11" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-create-class-features-plugin": ^7.21.0 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-private-property-in-object": ^7.14.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3c8c9ea175101b1cbb2b0e8fee20fcbdd03eb0700d3581aa826ac3573c9b002f39b1512c2af9fd1903ff921bcc864da95ad3cdeba53c9fbcfb3dc23916eacf47 + languageName: node + linkType: hard + "@babel/plugin-proposal-unicode-property-regex@npm:^7.18.6, @babel/plugin-proposal-unicode-property-regex@npm:^7.4.4": version: 7.18.6 resolution: "@babel/plugin-proposal-unicode-property-regex@npm:7.18.6" @@ -1737,6 +1889,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.22.15": + version: 7.22.15 + resolution: "@babel/template@npm:7.22.15" + dependencies: + "@babel/code-frame": ^7.22.13 + "@babel/parser": ^7.22.15 + "@babel/types": ^7.22.15 + checksum: 9312edd37cf1311d738907003f2aa321a88a42ba223c69209abe4d7111db019d321805504f606c7fd75f21c6cf9d24d0a8223104cd21ebd207e241b6c551f454 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.1.0, @babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.20.10, @babel/traverse@npm:^7.20.12, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.7.2": version: 7.20.12 resolution: "@babel/traverse@npm:7.20.12" @@ -1766,6 +1929,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.22.15, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/types@npm:7.23.0" + dependencies: + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.20 + to-fast-properties: ^2.0.0 + checksum: 70e4db41acb6793d0eb8d81a2fa88f19ee661219b84bd5f703dbdb54eb3a4d3c0dfc55e69034c945b479df9f43fd4b1376480aaccfc19797ce5af1c5d2576b36 + languageName: node + linkType: hard + "@base2/pretty-print-object@npm:1.0.1": version: 1.0.1 resolution: "@base2/pretty-print-object@npm:1.0.1" @@ -8044,7 +8218,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.0.0, chalk@npm:^2.0.1, chalk@npm:^2.4.1": +"chalk@npm:^2.0.0, chalk@npm:^2.0.1, chalk@npm:^2.4.1, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: @@ -17558,6 +17732,7 @@ __metadata: "@babel/plugin-proposal-class-properties": ^7.17.12 "@babel/plugin-proposal-decorators": ^7.17.12 "@babel/plugin-proposal-private-methods": ^7.10.4 + "@babel/plugin-proposal-private-property-in-object": ^7.21.11 "@babel/plugin-syntax-bigint": ^7.8.3 "@babel/plugin-transform-runtime": ^7.17.12 "@babel/preset-env": ^7.17.12 @@ -21852,6 +22027,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + "semver@npm:^7.5.3": version: 7.5.4 resolution: "semver@npm:7.5.4" diff --git a/tracker/.husky/pre-commit b/tracker/.husky/pre-commit index 9471928e2..0200dbbae 100755 --- a/tracker/.husky/pre-commit +++ b/tracker/.husky/pre-commit @@ -7,7 +7,7 @@ then pwd cd tracker/tracker - npm run lint-front + bun run lint-front cd ../../ fi @@ -17,7 +17,7 @@ then echo "tracker-assist" cd tracker/tracker-assist - npm run lint-front + bun run lint-front cd ../../ fi diff --git a/tracker/tracker-assist/bun.lockb b/tracker/tracker-assist/bun.lockb new file mode 100755 index 000000000..8475ceade Binary files /dev/null and b/tracker/tracker-assist/bun.lockb differ diff --git a/tracker/tracker-testing-playground/package.json b/tracker/tracker-testing-playground/package.json index 1ca00c7b0..a8386d93a 100644 --- a/tracker/tracker-testing-playground/package.json +++ b/tracker/tracker-testing-playground/package.json @@ -22,7 +22,7 @@ "zustand": "^4.1.1" }, "scripts": { - "start": "HOST=0.0.0.0 react-scripts start", + "start": "HOST=0.0.0.0 react-scripts --openssl-legacy-provider start", "build": "NODE_OPTIONS=--openssl-legacy-provider react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/tracker/tracker/README.md b/tracker/tracker/README.md index b1daf6d4d..2dd9ea755 100644 --- a/tracker/tracker/README.md +++ b/tracker/tracker/README.md @@ -2,6 +2,10 @@ The main package of the [OpenReplay](https://openreplay.com/) tracker. +## Development & Contribution + +Please use [bun](https://bun.sh/) to install and build this library. Any submitted pull request must pass **all tests** and should have positive test coverage diff %. + ## Documentation For launch options and available public methods, [refer to the documentation](https://docs.openreplay.com/installation/javascript-sdk#options) diff --git a/tracker/tracker/bun.lockb b/tracker/tracker/bun.lockb new file mode 100755 index 000000000..b4c48d4f8 Binary files /dev/null and b/tracker/tracker/bun.lockb differ diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index d83c103a0..8c9ce88e6 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "10.0.0", + "version": "10.0.1-1", "keywords": [ "logging", "replay" @@ -19,13 +19,13 @@ "tscRun": "tsc -b src/main && tsc -b src/webworker && tsc --project src/main/tsconfig-cjs.json", "rollup": "rollup --config rollup.config.js", "compile": "node --experimental-modules --experimental-json-modules scripts/compile.cjs", - "build": "npm run clean && npm run tscRun && npm run rollup && npm run compile", + "build": "bun run clean && bun run tscRun && bun run rollup && bun run compile", "prepare": "cd ../../ && husky install tracker/.husky/", "lint-front": "lint-staged", "test": "jest --coverage=false", "test:ci": "jest --coverage=true", - "postversion": "npm run build", - "prepublishOnly": "npm run build" + "postversion": "bun run build", + "prepublishOnly": "bun run build" }, "devDependencies": { "@babel/core": "^7.10.2", diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index f524393da..75e13055e 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -1,6 +1,14 @@ import type Message from './messages.gen.js' import { Timestamp, Metadata, UserID, Type as MType, TabChange, TabData } from './messages.gen.js' -import { now, adjustTimeOrigin, deprecationWarn, inIframe } from '../utils.js' +import { + now, + adjustTimeOrigin, + deprecationWarn, + inIframe, + createEventListener, + deleteEventListener, + requestIdleCb, +} from '../utils.js' import Nodes from './nodes.js' import Observer from './observer/top_observer.js' import Sanitizer from './sanitizer.js' @@ -338,12 +346,15 @@ export default class App { } private commit(): void { - if (this.worker && this.messages.length) { - this.messages.unshift(TabData(this.session.getTabId())) - this.messages.unshift(Timestamp(this.timestamp())) - this.worker.postMessage(this.messages) - this.commitCallbacks.forEach((cb) => cb(this.messages)) - this.messages.length = 0 + if (this.worker !== undefined && this.messages.length) { + requestIdleCb(() => { + this.messages.unshift(TabData(this.session.getTabId())) + this.messages.unshift(Timestamp(this.timestamp())) + // ? why I need to do this? + this.worker!.postMessage(this.messages) + this.commitCallbacks.forEach((cb) => cb(this.messages)) + this.messages.length = 0 + }) } } @@ -397,8 +408,14 @@ export default class App { if (useSafe) { listener = this.safe(listener) } - this.attachStartCallback(() => target?.addEventListener(type, listener, useCapture), useSafe) - this.attachStopCallback(() => target?.removeEventListener(type, listener, useCapture), useSafe) + this.attachStartCallback( + () => (target ? createEventListener(target, type, listener, useCapture) : null), + useSafe, + ) + this.attachStopCallback( + () => (target ? deleteEventListener(target, type, listener, useCapture) : null), + useSafe, + ) } // TODO: full correct semantic diff --git a/tracker/tracker/src/main/app/logger.ts b/tracker/tracker/src/main/app/logger.ts index 80012ba5f..b91c7d4ea 100644 --- a/tracker/tracker/src/main/app/logger.ts +++ b/tracker/tracker/src/main/app/logger.ts @@ -5,7 +5,7 @@ export const LogLevel = { Errors: 2, Silent: 0, } as const -type LogLevel = typeof LogLevel[keyof typeof LogLevel] +type LogLevel = (typeof LogLevel)[keyof typeof LogLevel] type CustomLevel = { error: boolean diff --git a/tracker/tracker/src/main/app/nodes.ts b/tracker/tracker/src/main/app/nodes.ts index 0e9eb3ed4..3901be780 100644 --- a/tracker/tracker/src/main/app/nodes.ts +++ b/tracker/tracker/src/main/app/nodes.ts @@ -1,3 +1,5 @@ +import { createEventListener, deleteEventListener } from '../utils.js' + type NodeCallback = (node: Node, isStart: boolean) => void type ElementListener = [string, EventListener, boolean] @@ -19,7 +21,7 @@ export default class Nodes { if (id === undefined) { return } - node.addEventListener(type, listener, useCapture) + createEventListener(node, type, listener, useCapture) let listeners = this.elementListeners.get(id) if (listeners === undefined) { listeners = [] @@ -49,7 +51,7 @@ export default class Nodes { if (listeners !== undefined) { this.elementListeners.delete(id) listeners.forEach((listener) => - node.removeEventListener(listener[0], listener[1], listener[2]), + deleteEventListener(node, listener[0], listener[1], listener[2]), ) } this.totalNodeAmount-- diff --git a/tracker/tracker/src/main/app/observer/observer.ts b/tracker/tracker/src/main/app/observer/observer.ts index 77d66e6a7..da02138ab 100644 --- a/tracker/tracker/src/main/app/observer/observer.ts +++ b/tracker/tracker/src/main/app/observer/observer.ts @@ -1,3 +1,4 @@ +import { createMutationObserver, ngSafeBrowserMethod } from '../../utils.js' import { RemoveNodeAttribute, SetNodeAttributeURLBased, @@ -66,8 +67,11 @@ export default abstract class Observer { private readonly indexes: Array = [] private readonly attributesMap: Map> = new Map() private readonly textSet: Set = new Set() - constructor(protected readonly app: App, protected readonly isTopContext = false) { - this.observer = new MutationObserver( + constructor( + protected readonly app: App, + protected readonly isTopContext = false, + ) { + this.observer = createMutationObserver( this.app.safe((mutations) => { for (const mutation of mutations) { // mutations order is sequential @@ -114,7 +118,7 @@ export default abstract class Observer { } } this.commitNodes() - }), + }) as MutationCallback, ) } private clear(): void { diff --git a/tracker/tracker/src/main/app/sanitizer.ts b/tracker/tracker/src/main/app/sanitizer.ts index faeda2702..8c13510c2 100644 --- a/tracker/tracker/src/main/app/sanitizer.ts +++ b/tracker/tracker/src/main/app/sanitizer.ts @@ -24,7 +24,10 @@ export default class Sanitizer { private readonly hidden: Set = new Set() private readonly options: Options - constructor(private readonly app: App, options: Partial) { + constructor( + private readonly app: App, + options: Partial, + ) { this.options = Object.assign( { obscureTextEmails: true, diff --git a/tracker/tracker/src/main/app/session.ts b/tracker/tracker/src/main/app/session.ts index 656bbae71..2580843b9 100644 --- a/tracker/tracker/src/main/app/session.ts +++ b/tracker/tracker/src/main/app/session.ts @@ -35,7 +35,10 @@ export default class Session { private tabId: string public userInfo: UserInfo - constructor(private readonly app: App, private readonly options: Options) { + constructor( + private readonly app: App, + private readonly options: Options, + ) { this.createTabId() } diff --git a/tracker/tracker/src/main/modules/attributeSender.ts b/tracker/tracker/src/main/modules/attributeSender.ts index c7ddb2a59..8d240dd1d 100644 --- a/tracker/tracker/src/main/modules/attributeSender.ts +++ b/tracker/tracker/src/main/modules/attributeSender.ts @@ -18,7 +18,10 @@ export class StringDictionary { export default class AttributeSender { private dict = new StringDictionary() - constructor(private readonly app: App, private readonly isDictDisabled: boolean) {} + constructor( + private readonly app: App, + private readonly isDictDisabled: boolean, + ) {} public sendSetAttribute(id: number, name: string, value: string) { if (this.isDictDisabled) { diff --git a/tracker/tracker/src/main/modules/img.ts b/tracker/tracker/src/main/modules/img.ts index d9ac6cd1c..eb6c6fa7f 100644 --- a/tracker/tracker/src/main/modules/img.ts +++ b/tracker/tracker/src/main/modules/img.ts @@ -1,5 +1,5 @@ import type App from '../app/index.js' -import { isURL, IS_FIREFOX, MAX_STR_LEN } from '../utils.js' +import { isURL, IS_FIREFOX, MAX_STR_LEN, createMutationObserver } from '../utils.js' import { ResourceTiming, SetNodeAttributeURLBased } from '../app/messages.gen.js' import { hasTag } from '../app/guards.js' @@ -81,24 +81,25 @@ export default function (app: App): void { sendSrcset(id, img) } }) - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'attributes') { - const target = mutation.target as HTMLImageElement - const id = app.nodes.getID(target) - if (id === undefined) { - return - } - if (mutation.attributeName === 'src') { - sendSrc(id, target) - } - if (mutation.attributeName === 'srcset') { - sendSrcset(id, target) + const observer = createMutationObserver( + app.safe((mutations) => { + for (const mutation of mutations) { + if (mutation.type === 'attributes') { + const target = mutation.target as HTMLImageElement + const id = app.nodes.getID(target) + if (id === undefined) { + return + } + if (mutation.attributeName === 'src') { + sendSrc(id, target) + } + if (mutation.attributeName === 'srcset') { + sendSrcset(id, target) + } } } - } - }) + }) as MutationCallback, + ) app.attachStopCallback(() => { observer.disconnect() diff --git a/tracker/tracker/src/main/utils.ts b/tracker/tracker/src/main/utils.ts index ef8c8c30f..96dee8f17 100644 --- a/tracker/tracker/src/main/utils.ts +++ b/tracker/tracker/src/main/utils.ts @@ -113,3 +113,67 @@ export function inIframe() { return true } } + +/** + * Because angular devs decided that its a good idea to override a browser apis + * we need to use this to achieve safe behavior + * */ +export function ngSafeBrowserMethod(method: string): string { + // @ts-ignore + return window.Zone && '__symbol__' in window.Zone + ? // @ts-ignore + window['Zone']['__symbol__'](method) + : method +} + +export function createMutationObserver(cb: MutationCallback) { + const mObserver = ngSafeBrowserMethod('MutationObserver') as 'MutationObserver' + return new window[mObserver](cb) +} + +export function createEventListener( + target: EventTarget, + event: string, + cb: EventListenerOrEventListenerObject, + capture?: boolean, +) { + const safeAddEventListener = ngSafeBrowserMethod('addEventListener') as 'addEventListener' + target[safeAddEventListener](event, cb, capture) +} + +export function deleteEventListener( + target: EventTarget, + event: string, + cb: EventListenerOrEventListenerObject, + capture?: boolean, +) { + const safeRemoveEventListener = ngSafeBrowserMethod( + 'removeEventListener', + ) as 'removeEventListener' + target[safeRemoveEventListener](event, cb, capture) +} + +/** + * This is a brief polyfill that suits our needs + * I took inspiration from Microsoft Clarity polyfill on this one + * then adapted it a little bit + * + * I'm very grateful for their bright idea + * */ +export function requestIdleCb(callback: () => void) { + const taskTimeout = 3000 + if (window.requestIdleCallback) { + return window.requestIdleCallback(callback, { timeout: taskTimeout }) + } else { + const channel = new MessageChannel() + const incoming = channel.port1 + const outgoing = channel.port2 + + incoming.onmessage = (): void => { + callback() + } + requestAnimationFrame((): void => { + outgoing.postMessage(1) + }) + } +} diff --git a/tracker/tracker/src/tests/utils.unit.test.ts b/tracker/tracker/src/tests/utils.unit.test.ts index 2b7836a70..8aef77110 100644 --- a/tracker/tracker/src/tests/utils.unit.test.ts +++ b/tracker/tracker/src/tests/utils.unit.test.ts @@ -11,6 +11,8 @@ import { hasOpenreplayAttribute, canAccessIframe, generateRandomId, + ngSafeBrowserMethod, + requestIdleCb, } from '../main/utils.js' describe('adjustTimeOrigin', () => { @@ -184,3 +186,59 @@ describe('generateRandomId', () => { expect(/^[0-9a-f]+$/.test(id)).toBe(true) }) }) + +describe('ngSafeBrowserMethod', () => { + test('returns the method as-is if Zone and __symbol__ are not in window.Zone', () => { + //@ts-ignore + window.Zone = undefined // Ensure Zone is not in the window object + expect(ngSafeBrowserMethod('someMethod')).toBe('someMethod') + }) + + test('returns the __symbol__ of the method if Zone and __symbol__ are in window.Zone', () => { + //@ts-ignore + window.Zone = { + __symbol__: (method: string) => `__${method}__`, + } + expect(ngSafeBrowserMethod('someMethod')).toBe('__someMethod__') + }) +}) + +describe('requestIdleCb', () => { + test('uses window.requestIdleCallback when available', () => { + const callback = jest.fn() + // @ts-ignore + window.requestIdleCallback = callback + + requestIdleCb(callback) + + expect(callback).toBeCalled() + }) + + test('falls back to using a MessageChannel if requestIdleCallback is not available', () => { + const callback = jest.fn() + class MessageChannelMock { + port1 = { + // @ts-ignore + postMessage: (v: any) => this.port2.onmessage(v), + onmessage: null, + } + port2 = { + onmessage: null, + // @ts-ignore + postMessage: (v: any) => this.port1.onmessage(v), + } + } + // @ts-ignore + globalThis.MessageChannel = MessageChannelMock + // @ts-ignore + globalThis.requestAnimationFrame = (cb: () => void) => cb() + // @ts-ignore + window.requestIdleCallback = undefined + + requestIdleCb(callback) + + // You can assert that the callback was called using the MessageChannel approach. + // This is more challenging to test, so it's recommended to mock MessageChannel. + expect(callback).toBeCalled() + }) +})