├── .editorconfig ├── .eslintrc ├── .github ├── CODEOWNERS ├── pull_request_template.md └── workflows │ ├── ci-cd.yml │ ├── sonar-scan.yml │ └── update-license-year.yml ├── .gitignore ├── .nvmrc ├── CHANGES.txt ├── CONTRIBUTORS-GUIDE.md ├── LICENSE ├── MIGRATION-GUIDE.md ├── README.md ├── client └── package.json ├── karma ├── config.debug.js ├── config.js ├── e2e.destroy.karma.conf.js ├── e2e.errorCatching.karma.conf.js ├── e2e.offline.karma.conf.js ├── e2e.online.karma.conf.js ├── e2e.push.karma.conf.js └── unit.karma.conf.js ├── package-lock.json ├── package.json ├── scripts ├── build_cjs_replace_imports.sh ├── build_esm_replace_imports.sh ├── clean_umd_build.sh ├── copy.packages.json.js └── sdk.unit.testing.setup ├── server └── package.json ├── src ├── .npmignore ├── __tests__ │ ├── browserSuites │ │ ├── evaluations-semver.spec.js │ │ ├── evaluations.spec.js │ │ ├── events.spec.js │ │ ├── fetch-specific-splits.spec.js │ │ ├── flag-sets.spec.js │ │ ├── ignore-ip-addresses-setting.spec.js │ │ ├── impressions-listener.spec.js │ │ ├── impressions.debug.spec.js │ │ ├── impressions.none.spec.js │ │ ├── impressions.spec.js │ │ ├── manager.spec.js │ │ ├── push-corner-cases.spec.js │ │ ├── push-fallback.spec.js │ │ ├── push-flag-sets.spec.js │ │ ├── push-initialization-nopush.spec.js │ │ ├── push-initialization-retries.spec.js │ │ ├── push-refresh-token.spec.js │ │ ├── push-synchronization-retries.spec.js │ │ ├── push-synchronization.spec.js │ │ ├── readiness.spec.js │ │ ├── ready-from-cache.spec.js │ │ ├── ready-promise.spec.js │ │ ├── shared-instantiation.spec.js │ │ ├── single-sync.spec.js │ │ ├── telemetry.spec.js │ │ ├── use-beacon-api.debug.spec.js │ │ ├── use-beacon-api.spec.js │ │ └── user-consent.spec.js │ ├── consumer │ │ └── node_redis.spec.js │ ├── destroy │ │ ├── browser.spec.js │ │ └── node.spec.js │ ├── errorCatching │ │ ├── browser.spec.js │ │ └── node.spec.js │ ├── mocks │ │ ├── auth.invalidCredentials.txt │ │ ├── auth.noUserSpecified.txt │ │ ├── auth.pushBadToken.json │ │ ├── auth.pushDisabled.json │ │ ├── auth.pushEnabled.nicolas@split.io.601secs.json │ │ ├── auth.pushEnabled.nicolas@split.io.json │ │ ├── auth.pushEnabled.nicolas@split.io.marcio@split.io.json │ │ ├── auth.pushEnabled.node.601secs.json │ │ ├── auth.pushEnabled.node.json │ │ ├── expected-treatments.csv │ │ ├── fetchSpecificSplits.js │ │ ├── impressions.json │ │ ├── memberships.emmanuel@split.io.json │ │ ├── memberships.facundo@split.io.json │ │ ├── memberships.marcio@split.io.json │ │ ├── memberships.nicolas@split.io.json │ │ ├── memberships.nicolas@split.io.mock2.json │ │ ├── membershipsEmpty.json │ │ ├── message.CONTROL.STREAMING_DISABLED.control_pri.1586987434950.json │ │ ├── message.CONTROL.STREAMING_PAUSED.control_pri.1586987434750.json │ │ ├── message.CONTROL.STREAMING_PAUSED.control_pri.1586987434900.json │ │ ├── message.CONTROL.STREAMING_RESUMED.control_pri.1586987434850.json │ │ ├── message.MEMBERSHIPS_LS_UPDATE.SEGMENT_REMOVAL.1457552653000.json │ │ ├── message.MEMBERSHIPS_LS_UPDATE.UNBOUNDED.DELAY.1457552650000.json │ │ ├── message.MEMBERSHIPS_MS_UPDATE.BOUNDED.GZIP.1457552651000.json │ │ ├── message.MEMBERSHIPS_MS_UPDATE.BOUNDED.ZLIB.1457552651000.json │ │ ├── message.MEMBERSHIPS_MS_UPDATE.KEYLIST.GZIP.1457552652000.json │ │ ├── message.MEMBERSHIPS_MS_UPDATE.SEGMENT_REMOVAL.1457552653000.json │ │ ├── message.MEMBERSHIPS_MS_UPDATE.UNBOUNDED.1457552640000.json │ │ ├── message.MEMBERSHIPS_MS_UPDATE.UNBOUNDED.1457552650000.json │ │ ├── message.OCCUPANCY.0.control_pri.1586987434550.json │ │ ├── message.OCCUPANCY.0.control_sec.1586987434451.json │ │ ├── message.OCCUPANCY.1.control_pri.1586987434450.json │ │ ├── message.OCCUPANCY.2.control_pri.1586987434650.json │ │ ├── message.RB_SEGMENT_UPDATE.1457552649999.json │ │ ├── message.RB_SEGMENT_UPDATE.C0.json │ │ ├── message.RB_SEGMENT_UPDATE.C1.json │ │ ├── message.RB_SEGMENT_UPDATE.C2.json │ │ ├── message.SEGMENT_UPDATE.1457552640000.json │ │ ├── message.SEGMENT_UPDATE.1457552650000.json │ │ ├── message.SPLIT_KILL.1457552650000.json │ │ ├── message.SPLIT_UPDATE.1457552620999.json │ │ ├── message.SPLIT_UPDATE.1457552649999.json │ │ ├── message.SPLIT_UPDATE.1457552650001.json │ │ ├── message.SPLIT_UPDATE.FS.1.json │ │ ├── message.SPLIT_UPDATE.FS.2.json │ │ ├── message.SPLIT_UPDATE.FS.3.json │ │ ├── message.SPLIT_UPDATE.FS.4.json │ │ ├── message.SPLIT_UPDATE.FS.4None.json │ │ ├── message.SPLIT_UPDATE.FS.5.json │ │ ├── message.SPLIT_UPDATE.FS.6.json │ │ ├── message.SPLIT_UPDATE.FS.kill.json │ │ ├── message.SPLIT_UPDATE.IFFU.1684265694505.json │ │ ├── message.SPLIT_UPDATE.IFFU.1684265694515.json │ │ ├── message.SPLIT_UPDATE.IFFU.1684265694525.json │ │ ├── message.SPLIT_UPDATE.IFFU.1684265694545.json │ │ ├── message.SPLIT_UPDATE.IFFU.1684265694555.json │ │ ├── message.STREAMING_RESET.json │ │ ├── redis-commands-flag-sets.txt │ │ ├── redis-commands.txt │ │ ├── splitChanges.since.-1.till.1500492097547.json │ │ ├── splitChanges.since.1500492097547.json │ │ ├── splitChanges.since.1500492097547.till.1500492297547.json │ │ ├── splitChanges.since.1500492297547.json │ │ ├── splitchanges.real.json │ │ ├── splitchanges.real.updateWithSegments.json │ │ ├── splitchanges.real.updateWithoutSegments.json │ │ ├── splitchanges.real.withSegments.json │ │ ├── splitchanges.since.-1.json │ │ ├── splitchanges.since.-1.semver.json │ │ ├── splitchanges.since.-1.till.1602796638344.json │ │ ├── splitchanges.since.100.till.1457552649999.RB_SEGMENT_UPDATE.json │ │ ├── splitchanges.since.1457552620999.json │ │ ├── splitchanges.since.1457552620999.till.1457552649999.SPLIT_UPDATE.json │ │ ├── splitchanges.since.1457552649999.till.1457552650000.SPLIT_KILL.json │ │ ├── splitchanges.since.1457552650000.till.1457552650001.SPLIT_UPDATE.json │ │ ├── splitchanges.since.1602796638344.till.1602797638344.json │ │ ├── splitchanges.since.1602797638344.till.1602798638344.json │ │ ├── splitchanges.since.1684265694505.till.1684265694506.SPLIT_UPDATE.json │ │ ├── splitchanges.since.1684265694506.till.1684265694526.SPLIT_UPDATE.json │ │ ├── splitchanges.since.1684265694526.till.1684265694546.SPLIT_UPDATE.json │ │ └── splitchanges.since.1684265694546.till.1684265694556.SPLIT_UPDATE.json │ ├── nodeSuites │ │ ├── evaluations-semver.spec.js │ │ ├── evaluations.spec.js │ │ ├── events.spec.js │ │ ├── expected-treatments.spec.js │ │ ├── fetch-specific-splits.spec.js │ │ ├── flag-sets.spec.js │ │ ├── impressions-listener.spec.js │ │ ├── impressions.debug.spec.js │ │ ├── impressions.none.spec.js │ │ ├── impressions.spec.js │ │ ├── ip-addresses-setting.debug.spec.js │ │ ├── ip-addresses-setting.spec.js │ │ ├── lazy-init.spec.js │ │ ├── manager.spec.js │ │ ├── proxy-fallback.spec.js │ │ ├── push-fallback.spec.js │ │ ├── push-flag-sets.spec.js │ │ ├── push-initialization-nopush.spec.js │ │ ├── push-initialization-retries.spec.js │ │ ├── push-refresh-token.spec.js │ │ ├── push-synchronization-retries.spec.js │ │ ├── push-synchronization.spec.js │ │ ├── readiness.spec.js │ │ ├── ready-promise.spec.js │ │ └── telemetry.spec.js │ ├── offline │ │ ├── .split │ │ ├── browser.spec.js │ │ ├── node.spec.js │ │ ├── split.yaml │ │ ├── split2.yml │ │ ├── update.split │ │ ├── update.split.yaml │ │ ├── update.split.yml │ │ ├── update.split2.yaml │ │ └── update.split2.yml │ ├── online │ │ ├── browser.spec.js │ │ └── node.spec.js │ ├── push │ │ ├── browser.spec.js │ │ └── node.spec.js │ └── testUtils │ │ ├── browser.js │ │ ├── eventSourceMock.js │ │ ├── fetchMock.js │ │ ├── index.js │ │ └── nodeFetchMock.js ├── factory │ ├── browser.js │ ├── node.js │ └── package.json ├── index.js ├── platform │ ├── EventEmitter.js │ ├── browser.js │ ├── filter │ │ ├── __tests__ │ │ │ └── bloomFilter.spec.js │ │ └── bloomFilter.js │ ├── getEventSource │ │ ├── __tests__ │ │ │ ├── browser.spec.js │ │ │ └── node.spec.js │ │ ├── browser.js │ │ ├── eventsource.js │ │ ├── node.js │ │ └── package.json │ ├── getFetch │ │ ├── __tests__ │ │ │ ├── browser.spec.js │ │ │ └── node.spec.js │ │ ├── browser.js │ │ ├── node.js │ │ └── package.json │ ├── getOptions │ │ ├── __tests__ │ │ │ └── node.spec.js │ │ └── node.js │ ├── node.js │ └── package.json ├── settings │ ├── __tests__ │ │ ├── browser.spec.js │ │ ├── defaults.spec.js │ │ └── node.spec.js │ ├── browser.js │ ├── defaults │ │ ├── browser.js │ │ ├── node.js │ │ └── version.js │ ├── integrations │ │ └── browser.js │ ├── node.js │ ├── package.json │ ├── runtime │ │ └── node.js │ └── storage │ │ ├── browser.js │ │ └── node.js ├── sync │ └── offline │ │ ├── LocalhostFromFile.js │ │ └── splitsParserFromFile.js ├── umd.js └── utils │ └── ip.js ├── ts-node.register.js ├── ts-tests ├── index.ts └── tsconfig.json ├── tsconfig.json ├── types ├── client │ └── index.d.ts ├── index.d.ts ├── server │ └── index.d.ts └── splitio.d.ts ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | end_of_line = lf 8 | 9 | [*.{js,json}] 10 | indent_style = space 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended" 4 | ], 5 | 6 | "plugins": [ 7 | "import" 8 | ], 9 | 10 | "env": { 11 | "browser": true, 12 | "node": true, 13 | "es6": true 14 | }, 15 | 16 | "rules": { 17 | "indent": ["error", 2, {"SwitchCase": 1}], 18 | "quotes": ["warn", "single", "avoid-escape"], 19 | "linebreak-style": ["error", "unix"], 20 | "semi": ["error", "always"], 21 | "no-underscore-dangle": "off", 22 | "eqeqeq": ["error", "smart"], 23 | "no-unused-expressions": "off", 24 | "new-cap" : "off", 25 | "no-mixed-requires": "off", 26 | "camelcase": ["error", {"properties": "never"}], 27 | "no-use-before-define": ["error", "nofunc"], 28 | "eol-last": ["error", "always"], 29 | "keyword-spacing": "error", 30 | "comma-style": "error", 31 | "no-trailing-spaces": "error", 32 | "space-before-function-paren": ["error", {"named": "never"}] 33 | }, 34 | 35 | "parserOptions": { 36 | "ecmaVersion": 2018, 37 | "sourceType": "module" 38 | }, 39 | 40 | "overrides": [ 41 | { 42 | "files": ["src/**/*.js"], 43 | "excludedFiles": ["src/**/__tests__/**"], 44 | "extends": [ 45 | "plugin:compat/recommended" 46 | ], 47 | "rules": { 48 | "no-restricted-syntax": ["error", "ForOfStatement", "ForInStatement", "ArrayPattern"], 49 | "compat/compat": ["error", "defaults, node >=14"], 50 | "no-throw-literal": "error", 51 | "import/no-default-export": "error", 52 | "import/no-self-import": "error" 53 | }, 54 | "parserOptions": { 55 | "ecmaVersion": 2015, 56 | "sourceType": "module" 57 | } 58 | } 59 | ], 60 | 61 | "settings": { 62 | "polyfills": [ 63 | "Promise" // required as a polyfill by the user 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @splitio/sdk 2 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # JS SDK 2 | 3 | ## What did you accomplish? 4 | 5 | ## How do we test the changes introduced in this PR? 6 | 7 | ## Extra Notes -------------------------------------------------------------------------------- /.github/workflows/ci-cd.yml: -------------------------------------------------------------------------------- 1 | name: ci-cd 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | - development 7 | push: 8 | branches: 9 | - '*' 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.run_number || github.event.pull_request.number }} 13 | cancel-in-progress: true 14 | 15 | permissions: 16 | contents: read 17 | id-token: write 18 | 19 | jobs: 20 | build: 21 | name: Build 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout code 25 | uses: actions/checkout@v4 26 | 27 | - name: Install Redis 28 | run: | 29 | sudo add-apt-repository ppa:redislabs/redis 30 | sudo apt-get install -y redis-tools redis-server 31 | 32 | - name: Check Redis 33 | run: redis-cli ping 34 | 35 | - name: Setup Node.js 36 | uses: actions/setup-node@v4 37 | with: 38 | node-version: 'lts/*' 39 | cache: 'npm' 40 | 41 | - name: npm ci 42 | run: npm ci 43 | 44 | - name: npm ts tests 45 | run: npm run test-ts-decls 46 | 47 | - name: npm check 48 | run: npm run check 49 | 50 | - name: npm test-browser 51 | run: npm run test-browser 52 | 53 | - name: npm test-node 54 | run: npm run test-node 55 | 56 | - name: npm build 57 | run: BUILD_BRANCH=$(echo "${GITHUB_REF#refs/heads/}") npm run build 58 | 59 | - name: Store assets 60 | if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/development' || github.ref == 'refs/heads/master') }} 61 | uses: actions/upload-artifact@v4 62 | with: 63 | name: assets 64 | path: umd/ 65 | retention-days: 1 66 | 67 | upload-stage: 68 | name: Upload assets 69 | runs-on: ubuntu-latest 70 | needs: build 71 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/development' }} 72 | strategy: 73 | matrix: 74 | environment: 75 | - stage 76 | include: 77 | - environment: stage 78 | account_id: "079419646996" 79 | bucket: split-public-stage 80 | 81 | steps: 82 | - name: Download assets 83 | uses: actions/download-artifact@v4 84 | with: 85 | name: assets 86 | path: umd 87 | 88 | - name: Display structure of assets 89 | run: ls -R 90 | working-directory: umd 91 | 92 | - name: Configure AWS credentials 93 | uses: aws-actions/configure-aws-credentials@v4 94 | with: 95 | role-to-assume: arn:aws:iam::${{ matrix.account_id }}:role/gha-public-assets-role 96 | aws-region: us-east-1 97 | 98 | - name: Upload to S3 99 | run: aws s3 sync $SOURCE_DIR s3://$BUCKET/$DEST_DIR $ARGS 100 | env: 101 | BUCKET: ${{ matrix.bucket }} 102 | SOURCE_DIR: ./umd 103 | DEST_DIR: sdk 104 | ARGS: --acl public-read --follow-symlinks --cache-control max-age=31536000,public 105 | 106 | upload-prod: 107 | name: Upload assets 108 | runs-on: ubuntu-latest 109 | needs: build 110 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} 111 | strategy: 112 | matrix: 113 | environment: 114 | - prod 115 | include: 116 | - environment: prod 117 | account_id: "825951051969" 118 | bucket: split-public 119 | 120 | steps: 121 | - name: Download assets 122 | uses: actions/download-artifact@v4 123 | with: 124 | name: assets 125 | path: umd 126 | 127 | - name: Display structure of assets 128 | run: ls -R 129 | working-directory: umd 130 | 131 | - name: Configure AWS credentials 132 | uses: aws-actions/configure-aws-credentials@v4 133 | with: 134 | role-to-assume: arn:aws:iam::${{ matrix.account_id }}:role/gha-public-assets-role 135 | aws-region: us-east-1 136 | 137 | - name: Upload to S3 138 | run: aws s3 sync $SOURCE_DIR s3://$BUCKET/$DEST_DIR $ARGS 139 | env: 140 | BUCKET: ${{ matrix.bucket }} 141 | SOURCE_DIR: ./umd 142 | DEST_DIR: sdk 143 | ARGS: --acl public-read --follow-symlinks --cache-control max-age=31536000,public 144 | -------------------------------------------------------------------------------- /.github/workflows/sonar-scan.yml: -------------------------------------------------------------------------------- 1 | name: sonar-scan 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | - development 7 | push: 8 | branches: 9 | - master 10 | - development 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Set up Node.js 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: 'lts/*' 26 | cache: 'npm' 27 | 28 | - name: npm CI 29 | run: npm ci 30 | 31 | - name: npm Check 32 | run: npm run check 33 | 34 | - name: npm Build 35 | run: BUILD_BRANCH=$(echo "${GITHUB_REF#refs/heads/}") npm run build 36 | 37 | - name: SonarQube Scan (Push) 38 | if: github.event_name == 'push' 39 | uses: SonarSource/sonarcloud-github-action@v1.6 40 | env: 41 | SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} 42 | with: 43 | projectBaseDir: . 44 | args: > 45 | -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} 46 | -Dsonar.projectName=${{ github.event.repository.name }} 47 | -Dsonar.projectKey=${{ github.event.repository.name }} 48 | -Dsonar.links.ci="https://github.com/splitio/${{ github.event.repository.name }}/actions" 49 | -Dsonar.links.scm="https://github.com/splitio/${{ github.event.repository.name }}" 50 | - name: SonarQube Scan (Pull Request) 51 | if: github.event_name == 'pull_request' 52 | uses: SonarSource/sonarcloud-github-action@v1.6 53 | env: 54 | SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} 55 | with: 56 | projectBaseDir: . 57 | args: > 58 | -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} 59 | -Dsonar.projectName=${{ github.event.repository.name }} 60 | -Dsonar.projectKey=${{ github.event.repository.name }} 61 | -Dsonar.links.ci="https://github.com/splitio/${{ github.event.repository.name }}/actions" 62 | -Dsonar.links.scm="https://github.com/splitio/${{ github.event.repository.name }}" 63 | -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} 64 | -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} 65 | -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }} 66 | -------------------------------------------------------------------------------- /.github/workflows/update-license-year.yml: -------------------------------------------------------------------------------- 1 | name: Update License Year 2 | 3 | on: 4 | schedule: 5 | - cron: "0 3 1 1 *" # 03:00 AM on January 1 6 | 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Set Current year 21 | run: "echo CURRENT=$(date +%Y) >> $GITHUB_ENV" 22 | 23 | - name: Set Previous Year 24 | run: "echo PREVIOUS=$(($CURRENT-1)) >> $GITHUB_ENV" 25 | 26 | - name: Update LICENSE 27 | uses: jacobtomlinson/gha-find-replace@v3 28 | with: 29 | find: ${{ env.PREVIOUS }} 30 | replace: ${{ env.CURRENT }} 31 | include: "LICENSE" 32 | regex: false 33 | 34 | - name: Commit files 35 | run: | 36 | git config user.name 'github-actions[bot]' 37 | git config user.email 'github-actions[bot]@users.noreply.github.com' 38 | git commit -m "Updated License Year" -a 39 | 40 | - name: Create Pull Request 41 | uses: peter-evans/create-pull-request@v5 42 | with: 43 | token: ${{ secrets.GITHUB_TOKEN }} 44 | title: Update License Year 45 | branch: update-license 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | **/npm-debug.log 4 | /.idea 5 | /.sonar 6 | *~ 7 | **/node_modules 8 | *.swp 9 | /.vscode 10 | dump.rdb 11 | 12 | ## local environment setup 13 | /startup.sh 14 | 15 | ## coverage info 16 | /karma/coverage 17 | 18 | ## stats info 19 | /stats 20 | 21 | ## transpiled code 22 | /cjs 23 | /esm 24 | /umd 25 | 26 | ## TS tests compilated files 27 | /ts-tests/*.js 28 | /ts-tests/package-lock.json 29 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS-GUIDE.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Split JavaScript SDK 2 | 3 | Split SDK is an open source project and we welcome feedback and contribution. The information below describes how to build the project with your changes, run the tests, and send the Pull Request(PR). 4 | 5 | ## Development 6 | 7 | ### Development process 8 | 9 | 1. Fork the repository and create a topic branch from `development` branch. Please use a descriptive name for your branch. 10 | 2. Run `nvm use` to ensure that you are using the right npm and Node.js version, and `npm install` to have the dependencies up to date. 11 | 3. While developing, use descriptive messages in your commits. Avoid short or meaningless sentences like: "fix bug". 12 | 4. Make sure to add tests for both positive and negative cases. 13 | 5. If your changes have any impact on the public API, make sure you update the TypeScript declarations as well as it's related test file. 14 | 6. Run the linter script of the project and fix any issues you find. 15 | 7. Run the build script and make sure it runs with no errors. 16 | 8. Run all tests and make sure there are no failures. 17 | 9. Run the TypeScript declarations tests and make sure it compiles correctly. 18 | 10. `git push` your changes to GitHub within your topic branch. 19 | 11. Open a Pull Request(PR) from your forked repo and into the `development` branch of the original repository. 20 | 12. When creating your PR, please fill out all the fields of the PR template, as applicable, for the project. 21 | 13. Check for conflicts once the pull request is created to make sure your PR can be merged cleanly into `development`. 22 | 14. Keep an eye out for any feedback or comments from Split's SDK team. 23 | 24 | ### Building the SDK 25 | 26 | For widespread use of the SDK with different environments and module formats, we have three different builds: 27 | * A bundled **UMD** file. 28 | * A **ES2015** modules compatible build. 29 | * A **CommonJS** modules compatible build. 30 | 31 | The different builds can be generated all at once with the command `npm run build`. Refer to [package.json](package.json) for more insight on the build scripts. 32 | 33 | ### Running tests 34 | 35 | The project includes unit as well as integration tests for both browser and Node.js environments. 36 | 37 | All tests can be run at once with the command `npm run test`. 38 | 39 | If you've updated the TypeScript declaration files (located in `/types` folder), you should add some lines verifying the updates in `/ts-tests/index.ts` and then run the TypeScript compilation test using the `npm run test-ts-decls` command. 40 | 41 | For additional testing scripts or to get more insight on how these work, please refer to our [package.json](package.json) file. 42 | 43 | ### Linting and other useful checks 44 | 45 | Consider running the linter script (`npm run check:lint`) and fixing any issues before pushing your changes. 46 | 47 | If you want to debug your changes consuming it from a test application, you could: 48 | - For browsers, import the **UMD** bundle from an HTML document. To debug you can use the browser dev tools. 49 | - For Node.js, you could use symlinks via [npm link command](https://docs.npmjs.com/cli/link.html) and then import the package as usual. To debug you could use the [Node.js inspector](https://nodejs.org/en/docs/guides/debugging-getting-started/). 50 | 51 | # Contact 52 | 53 | If you have any other questions or need to contact us directly in a private manner send us a note at sdks@split.io -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2025 Split Software, Inc. 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 | -------------------------------------------------------------------------------- /MIGRATION-GUIDE.md: -------------------------------------------------------------------------------- 1 | # Migrating to JavaScript SDK v11 2 | 3 | JavaScript SDK v11.0.0 has a few breaking changes that you should consider when migrating from version 10.x.x. 4 | 5 | ## Changes that affect server-side API (Node.js) 6 | 7 | While JavaScript SDK previously supported Node.js v6 and above, the SDK now requires Node.js v14 or above. 8 | 9 | ## Changes that affect client-side API (Browser) 10 | 11 | Below you will find a list of the changes: 12 | 13 | ### • Removed the `core.trafficType` configuration option (`SplitIO.IBrowserSettings['core']['trafficType]`) and the `trafficType` parameter from the SDK `client()` method in Browser (`SplitIO.IBrowserSDK['client']`). As a result, traffic types can no longer be bound to SDK clients, and the traffic type must be provided in the `track` method 14 | 15 | This change was made to align the SDK with the client-side APIs of the [Browser SDK](https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK) and [React Native SDK](https://help.split.io/hc/en-us/articles/4406066357901-React-Native-SDK). 16 | 17 | SDK clients cannot be bound to a traffic type anymore, and so the traffic type must be provided when calling the `client.track` method. For example: 18 | 19 | ```javascript 20 | // JS SDK v10.x.x 21 | const factory = SplitFactory({ 22 | core: { 23 | authorizationKey: '...', 24 | key: USER_KEY, 25 | trafficType: 'user' 26 | } 27 | }); 28 | 29 | const client = factory.client(); 30 | const accountClient = factory.client(ACCOUNT_ID, 'account'); 31 | 32 | client.track('my_event'); 33 | accountClient.track('my_event'); 34 | ``` 35 | 36 | should be replaced with: 37 | 38 | ```javascript 39 | // JS SDK v11.0.0 40 | const factory = SplitFactory({ 41 | core: { 42 | authorizationKey: '...', 43 | key: USER_KEY 44 | } 45 | }); 46 | 47 | const client = factory.client(); 48 | const accountClient = factory.client(ACCOUNT_ID); 49 | 50 | client.track('user', 'my_event'); 51 | accountClient.track('account', 'my_event'); 52 | ``` 53 | 54 | ### • Removed the deprecated `GOOGLE_ANALYTICS_TO_SPLIT` and `SPLIT_TO_GOOGLE_ANALYTICS` integrations. The `integrations` configuration option has been removed from the SDK factory configuration, along with the associated interfaces in the TypeScript definitions 55 | 56 | The Google Analytics integrations were removed since they integrate with the *Google Universal Analytics* library, which was shut down on July 1, 2024, and [replaced by *Google Analytics 4*](https://support.google.com/analytics/answer/11583528?hl=en). Go to Split's [Google Analytics integration guide](https://help.split.io/hc/en-us/articles/360040838752-Google-Analytics) for more information on how to integrate Split with Google Analytics 4. 57 | 58 | The integrations have stopped being used and maintained, and were removed from the SDK, together with the `integrations` configuration option. If you were using the `integrations` option, you should remove it from your SDK configuration object. 59 | 60 | ### • Removed internal polyfills for the `Map` and `Set` global objects, dropping support for IE and other outdated browsers 61 | 62 | The SDK no longer ships with internal implementations for the `Map` and `Set` global objects, which were used to support old browsers like Internet Explorer. 63 | 64 | If you need to target environments that do not support these features natively, you should provide a polyfill for them. For example, [es6-map](https://github.com/medikoo/es6-map) for `Map`, and [es6-set](https://github.com/medikoo/es6-set) for `Set`. 65 | 66 | In addition, the Split SDK depends on support for ES6 promises. Since v10.2.0, the SDK does not pollute any global variable to add the ES6 promise polyfill. If your environment does not support ES6 promises, you can [polyfill](https:/github.com/stefanpenner/es6-promise). 67 | 68 | ### • Dropped support for Split Proxy below version 5.9.0, when using in the browser (client-side API). The SDK now requires Split Proxy 5.9.0 or above 69 | 70 | If using the Split Proxy with the SDK in the browser, make sure to update it to version 5.9.0 or above. This is required due to the introduction of Large Segments matchers in the SDK on client-side, which uses a new HTTP endpoint to retrieve the segments data and is only supported by Split Proxy 5.9.0. 71 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../cjs/factory/browser.js", 3 | "module": "../esm/factory/browser.js", 4 | "types": "../types/client/index.d.ts" 5 | } 6 | -------------------------------------------------------------------------------- /karma/config.debug.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const merge = require('lodash/merge'); 4 | 5 | module.exports = merge({}, require('./config'), { 6 | customLaunchers: { 7 | ChromeNoSandbox: { 8 | base: 'Chrome', 9 | flags: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu'] 10 | } 11 | }, 12 | browsers: [ 13 | 'ChromeNoSandbox' 14 | ], 15 | webpack: { 16 | mode: 'development' 17 | }, 18 | singleRun: false 19 | }); 20 | -------------------------------------------------------------------------------- /karma/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Comment the next two lines if you want to run with Chrome instead of Chromium 3 | const puppeteer = require('puppeteer'); 4 | process.env.CHROME_BIN = puppeteer.executablePath(); 5 | 6 | const NodePolyfillPlugin = require('node-polyfill-webpack-plugin'); 7 | 8 | module.exports = { 9 | // base path, that will be used to resolve files and exclude 10 | basePath: '../src', 11 | 12 | // load tap integration 13 | frameworks: [ 14 | 'tap', 'webpack' 15 | ], 16 | 17 | // Run on Chrome Headless 18 | customLaunchers: { 19 | ChromeHeadlessNoSandbox: { 20 | base: 'ChromeHeadless', 21 | // Flags required to run in ubuntu-22.04 or above (https://chromium.googlesource.com/chromium/src/+/master/docs/linux/suid_sandbox_development.md) 22 | flags: ['--no-sandbox', '--disable-setuid-sandbox'] 23 | } 24 | }, 25 | browsers: ['ChromeHeadlessNoSandbox'], 26 | 27 | // list of files / patterns to load in the browser 28 | files: [ 29 | // Uncomment to run a particular UT: 30 | // '**/listeners/__tests__/browser.spec.js', 31 | // Run browser UTs. Commons and Node UTs run with `test-node` npm script 32 | '*/**/__tests__/**/browser.spec.js', 33 | { 34 | pattern: 'engine/__tests__/engine/mocks/murmur3*.csv', 35 | watched: false, 36 | included: false, 37 | served: true, 38 | nocache: true 39 | }, 40 | { 41 | pattern: 'engine/__tests__/matchers/mocks/regex.txt', 42 | watched: false, 43 | included: false, 44 | served: true, 45 | nocache: true 46 | } 47 | ], 48 | 49 | // list of files / patterns to exclude 50 | exclude: [ 51 | '*/**/__tests__/**/node.spec.js', 52 | '*/**/__tests__/**/node_redis.spec.js', 53 | '*/**/__tests__/**/*.node.spec.js', 54 | '*/**/__tests__/**/inputValidation/*.spec.js' 55 | ], 56 | 57 | // prepare code for the browser using webpack 58 | preprocessors: { 59 | '*/**/__tests__/**/*.spec.js': ['webpack'], 60 | }, 61 | 62 | webpack: { 63 | mode: 'production', // Use 'development' to debug with not minified bundle 64 | devtool: false, // Use 'inline-source-map' to debug with source map 65 | 66 | target: ['web', 'es5'], // 'web' => resolve.mainFields === ['browser', 'module', 'main'] 67 | module: { 68 | rules: [ 69 | { 70 | test: /\.(ts|js)$/, 71 | 72 | // Cannot exclude 'node_modules/@splitsoftware/splitio-commons/src', in order to process TS files 73 | exclude: /node_modules[/](?!@splitsoftware)/, 74 | 75 | use: { 76 | loader: 'ts-loader', 77 | options: { 78 | transpileOnly: true, // https://webpack.js.org/guides/build-performance/#typescript-loader 79 | allowTsInNodeModules: true, // https://github.com/TypeStrong/ts-loader#allowtsinnodemodules 80 | } 81 | } 82 | } 83 | ] 84 | }, 85 | plugins: [ 86 | new NodePolyfillPlugin() 87 | ], 88 | resolve: { 89 | extensions: ['.ts', '.js'], 90 | fallback: { 91 | fs: false 92 | } 93 | } 94 | }, 95 | 96 | webpackServer: { 97 | noInfo: true 98 | }, 99 | 100 | // web server port 101 | port: 9876, 102 | 103 | // make IE happy (in theory not required) 104 | // https://msdn.microsoft.com/en-us/library/ff955275(v=vs.85).aspx 105 | customHeaders: [{ 106 | match: 'html', 107 | name: 'X-UA-Compatible', 108 | value: 'IE=edge' 109 | }, { 110 | match: 'csv$', 111 | name: 'Content-Type', 112 | value: 'text/plain' 113 | }], 114 | 115 | // Which plugins to enable 116 | plugins: [ 117 | 'karma-webpack', 118 | 'karma-tap', 119 | 'karma-chrome-launcher' 120 | ], 121 | 122 | browserConsoleLogOptions: { 123 | terminal: false 124 | }, 125 | 126 | // Continuous Integration mode 127 | // if true, it capture browsers, run tests and exit 128 | singleRun: true, 129 | 130 | colors: true, 131 | 132 | /** 133 | * @WARNING in local mode, murmur verification takes forever (chrome tested), 134 | * so I keep this only to be used by Chrome Headless. 135 | */ 136 | browserDisconnectTolerance: 1, 137 | browserNoActivityTimeout: 60 * 60 * 1000, 138 | reportSlowerThan: 30, 139 | }; 140 | -------------------------------------------------------------------------------- /karma/e2e.destroy.karma.conf.js: -------------------------------------------------------------------------------- 1 | const assign = require('lodash/assign'); 2 | 3 | module.exports = function(config) { 4 | 'use strict'; 5 | 6 | config.set(assign({}, require('./config'), { 7 | // list of files / patterns to load in the browser 8 | files: [ 9 | '__tests__/destroy/browser.spec.js' 10 | ], 11 | // prepare code for the browser using webpack 12 | preprocessors: { 13 | '__tests__/destroy/browser.spec.js': ['webpack'] 14 | }, 15 | 16 | // level of logging 17 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 18 | logLevel: config.LOG_WARN 19 | })); 20 | }; 21 | -------------------------------------------------------------------------------- /karma/e2e.errorCatching.karma.conf.js: -------------------------------------------------------------------------------- 1 | const assign = require('lodash/assign'); 2 | 3 | module.exports = function(config) { 4 | 'use strict'; 5 | 6 | config.set(assign({}, require('./config'), { 7 | // list of files / patterns to load in the browser 8 | files: [ 9 | '__tests__/errorCatching/browser.spec.js' 10 | ], 11 | // prepare code for the browser using webpack 12 | preprocessors: { 13 | '__tests__/errorCatching/browser.spec.js': ['webpack'] 14 | }, 15 | 16 | // level of logging 17 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 18 | logLevel: config.LOG_WARN 19 | })); 20 | }; 21 | -------------------------------------------------------------------------------- /karma/e2e.offline.karma.conf.js: -------------------------------------------------------------------------------- 1 | const assign = require('lodash/assign'); 2 | 3 | module.exports = function(config) { 4 | 'use strict'; 5 | 6 | config.set(assign({}, require('./config'), { 7 | // list of files / patterns to load in the browser 8 | files: [ 9 | '__tests__/offline/browser.spec.js' 10 | ], 11 | // prepare code for the browser using webpack 12 | preprocessors: { 13 | '__tests__/offline/browser.spec.js': ['webpack'] 14 | }, 15 | 16 | // level of logging 17 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 18 | logLevel: config.LOG_WARN 19 | })); 20 | }; 21 | -------------------------------------------------------------------------------- /karma/e2e.online.karma.conf.js: -------------------------------------------------------------------------------- 1 | const assign = require('lodash/assign'); 2 | 3 | module.exports = function(config) { 4 | 'use strict'; 5 | 6 | config.set(assign({}, require('./config'), { 7 | // list of files / patterns to load in the browser 8 | files: [ 9 | '__tests__/online/browser.spec.js' 10 | ], 11 | // prepare code for the browser using webpack 12 | preprocessors: { 13 | '__tests__/online/browser.spec.js': ['webpack'] 14 | }, 15 | 16 | // level of logging 17 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 18 | logLevel: config.LOG_WARN 19 | })); 20 | }; 21 | -------------------------------------------------------------------------------- /karma/e2e.push.karma.conf.js: -------------------------------------------------------------------------------- 1 | const assign = require('lodash/assign'); 2 | 3 | module.exports = function(config) { 4 | 'use strict'; 5 | 6 | config.set(assign({}, require('./config'), { 7 | // list of files / patterns to load in the browser 8 | files: [ 9 | '__tests__/push/browser.spec.js' 10 | ], 11 | // prepare code for the browser using webpack 12 | preprocessors: { 13 | '__tests__/push/browser.spec.js': ['webpack'] 14 | }, 15 | 16 | // level of logging 17 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 18 | logLevel: config.LOG_WARN 19 | })); 20 | }; 21 | -------------------------------------------------------------------------------- /karma/unit.karma.conf.js: -------------------------------------------------------------------------------- 1 | const merge = require('lodash/merge'); 2 | 3 | module.exports = function(config) { 4 | 'use strict'; 5 | 6 | config.set(merge({}, require('./config'), { 7 | // level of logging 8 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 9 | logLevel: config.LOG_WARN 10 | })); 11 | }; 12 | -------------------------------------------------------------------------------- /scripts/build_cjs_replace_imports.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # replace splitio-commons imports to use CommonJS 4 | replace '@splitsoftware/splitio-commons/src' '@splitsoftware/splitio-commons/cjs' ./cjs -r 5 | 6 | if [ $? -eq 0 ] 7 | then 8 | exit 0 9 | else 10 | exit 1 11 | fi 12 | -------------------------------------------------------------------------------- /scripts/build_esm_replace_imports.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # replace splitio-commons imports to use EcmaScript Modules 4 | replace '@splitsoftware/splitio-commons/src' '@splitsoftware/splitio-commons/esm' ./esm -r 5 | 6 | if [ $? -eq 0 ] 7 | then 8 | exit 0 9 | else 10 | exit 1 11 | fi 12 | -------------------------------------------------------------------------------- /scripts/clean_umd_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # remove unfetch source mapping url from UMD development build 4 | replace '//# sourceMappingURL=unfetch.module.js.map' '' ./umd -r 5 | 6 | if [ $? -eq 0 ] 7 | then 8 | exit 0 9 | else 10 | exit 1 11 | fi 12 | -------------------------------------------------------------------------------- /scripts/copy.packages.json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const copyfiles = require('copyfiles'); 4 | 5 | const input = './src/**/package.json'; 6 | const outputCjsDir = './cjs'; 7 | const outputEsmDir = './esm'; 8 | 9 | copyfiles([input, process.env.NODE_ENV === 'cjs' ? outputCjsDir : outputEsmDir], { 10 | up: 1, 11 | exclude: './src/**/__tests__/**/package.json' 12 | }, (err) => { 13 | if (err) { 14 | console.log('Error copying package.json files: ' + err); 15 | process.exit(1); 16 | } else { 17 | console.log('All package.json files copied correctly.'); 18 | process.exit(0); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../cjs/factory/node.js", 3 | "module": "../esm/factory/node.js", 4 | "types": "../types/server/index.d.ts" 5 | } 6 | -------------------------------------------------------------------------------- /src/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | **/__tests__ 3 | .npmignore -------------------------------------------------------------------------------- /src/__tests__/browserSuites/fetch-specific-splits.spec.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | import { SplitFactory } from '../../'; 3 | import { splitFilters, queryStrings, groupedFilters } from '../mocks/fetchSpecificSplits'; 4 | 5 | const baseConfig = { 6 | core: { 7 | authorizationKey: '', 8 | key: 'nicolas@split.io' 9 | }, 10 | scheduler: { 11 | featuresRefreshRate: 0.01 12 | }, 13 | streamingEnabled: false, 14 | }; 15 | 16 | export function fetchSpecificSplits(fetchMock, assert) { 17 | 18 | assert.plan(splitFilters.length); 19 | 20 | for (let i = 0; i < splitFilters.length; i++) { 21 | const urls = { sdk: 'https://sdkurl' + i }; 22 | const config = { ...baseConfig, sync: { splitFilters: splitFilters[i] }, urls }; 23 | 24 | if (groupedFilters[i]) { // tests where validateSplitFilters executes normally 25 | const queryString = queryStrings[i] || ''; 26 | let factory; 27 | 28 | fetchMock.getOnce(urls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1' + queryString, { status: 200, body: { ff: { d: [], s: -1, t: 1457552620999 } } }); 29 | fetchMock.getOnce(urls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1' + queryString, { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }); 30 | fetchMock.getOnce(urls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1' + queryString, function () { 31 | factory.client().destroy().then(() => { 32 | assert.pass(`splitFilters #${i}`); 33 | }); 34 | return { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }; 35 | }); 36 | fetchMock.get(urls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: { 'ms': {} } }); 37 | 38 | factory = SplitFactory(config); 39 | 40 | } else { // tests where validateSplitFilters throws an exception 41 | try { 42 | SplitFactory(config); 43 | } catch (e) { 44 | assert.equal(e.message, queryStrings[i]); 45 | } 46 | } 47 | 48 | } 49 | } 50 | 51 | export function fetchSpecificSplitsForFlagSets(fetchMock, assert) { 52 | // Flag sets 53 | assert.test(async (t) => { 54 | 55 | const splitFilters = [{ type: 'bySet', values: ['set_x ', 'set_x', 'set_3', 'set_2', 'set_3', 'set_ww', 'invalid+', '_invalid', '4_valid'] }]; 56 | const baseUrls = { sdk: 'https://sdk.baseurl' }; 57 | 58 | const config = { 59 | ...baseConfig, 60 | urls: baseUrls, 61 | debug: 'WARN', 62 | sync: { 63 | splitFilters 64 | } 65 | }; 66 | 67 | const logSpy = sinon.spy(console, 'log'); 68 | 69 | let factory; 70 | const queryString = '&sets=4_valid,set_2,set_3,set_ww,set_x'; 71 | fetchMock.get(baseUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: { 'ms': {} } }); 72 | 73 | fetchMock.getOnce(baseUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1' + queryString, { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } }}); 74 | fetchMock.getOnce(baseUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1' + queryString, async function () { 75 | t.pass('flag set query correctly formed'); 76 | t.true(logSpy.calledWithExactly('[WARN] splitio => settings: bySet filter value "set_x " has extra whitespace, trimming.')); 77 | t.true(logSpy.calledWithExactly('[WARN] splitio => settings: you passed invalid+, flag set must adhere to the regular expressions /^[a-z0-9][_a-z0-9]{0,49}$/. This means a flag set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. invalid+ was discarded.')); 78 | t.true(logSpy.calledWithExactly('[WARN] splitio => settings: you passed _invalid, flag set must adhere to the regular expressions /^[a-z0-9][_a-z0-9]{0,49}$/. This means a flag set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. _invalid was discarded.')); 79 | logSpy.restore(); 80 | factory.client().destroy().then(() => { 81 | t.end(); 82 | }); 83 | }); 84 | factory = SplitFactory(config); 85 | }, 'FlagSets config'); 86 | } 87 | -------------------------------------------------------------------------------- /src/__tests__/browserSuites/impressions-listener.spec.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | 3 | import { SplitFactory } from '../../'; 4 | import { settingsFactory } from '../../settings'; 5 | 6 | const settings = settingsFactory({ 7 | core: { 8 | key: '' 9 | }, 10 | streamingEnabled: false 11 | }); 12 | 13 | const listener = { 14 | logImpression: sinon.stub() 15 | }; 16 | 17 | const config = { 18 | core: { 19 | authorizationKey: '', 20 | key: 'nicolas@split.io' 21 | }, 22 | scheduler: { 23 | featuresRefreshRate: 1, 24 | segmentsRefreshRate: 1, 25 | impressionsRefreshRate: 3000 26 | }, 27 | startup: { 28 | eventsFirstPushWindow: 3000 29 | }, 30 | impressionListener: listener, 31 | streamingEnabled: false 32 | }; 33 | 34 | export default function (assert) { 35 | const splitio = SplitFactory(config); 36 | const client = splitio.client(); 37 | const client2 = splitio.client({ matchingKey: 'marcio@split.io', bucketingKey: 'impr_bucketing_2' }); 38 | const client3 = splitio.client('facundo@split.io'); 39 | 40 | return client.ready().then(() => { 41 | const metaData = { 42 | ip: settings.runtime.ip, 43 | hostname: settings.runtime.hostname, 44 | sdkLanguageVersion: settings.version 45 | }; 46 | const testAttrs = { is_test: true }; 47 | 48 | // Impression listener is shared across all client instances and does not get affected by configurations. 49 | client.getTreatment('hierarchical_splits_test', undefined, { properties: { prop1: 'prop-value' } }); 50 | client2.getTreatment('qc_team'); 51 | client2.getTreatmentWithConfig('qc_team'); // Validate that the impression is the same. 52 | client3.getTreatment('qc_team', testAttrs); 53 | 54 | setTimeout(() => { 55 | const secondImpression = { 56 | feature: 'qc_team', 57 | keyName: 'marcio@split.io', 58 | treatment: 'no', 59 | bucketingKey: 'impr_bucketing_2', 60 | label: 'default rule', 61 | pt: undefined, 62 | properties: undefined 63 | }; 64 | 65 | assert.equal(listener.logImpression.callCount, 4, 'Impression listener logImpression method should be called after we call client.getTreatment, once per each impression generated.'); 66 | assert.true(listener.logImpression.getCall(0).calledWithExactly({ 67 | impression: { 68 | feature: 'hierarchical_splits_test', 69 | keyName: 'nicolas@split.io', 70 | treatment: 'on', 71 | time: listener.logImpression.getCall(0).args[0].impression.time, 72 | bucketingKey: undefined, 73 | label: 'expected label', 74 | changeNumber: 2828282828, 75 | properties: '{"prop1":"prop-value"}' 76 | }, 77 | attributes: undefined, 78 | ...metaData 79 | })); 80 | assert.true(listener.logImpression.getCall(1).calledWithMatch({ 81 | impression: secondImpression, 82 | attributes: undefined, 83 | ...metaData 84 | })); 85 | assert.true(listener.logImpression.getCall(2).calledWithMatch({ 86 | impression: Object.assign(secondImpression, { pt: listener.logImpression.getCall(1).lastArg.impression.time }), 87 | attributes: undefined, 88 | ...metaData 89 | })); 90 | assert.true(listener.logImpression.getCall(3).calledWithMatch({ 91 | impression: { 92 | feature: 'qc_team', 93 | keyName: 'facundo@split.io', 94 | treatment: 'no', 95 | bucketingKey: undefined, 96 | label: 'default rule', 97 | }, 98 | attributes: testAttrs, 99 | ...metaData 100 | })); 101 | 102 | client3.destroy(); 103 | client2.destroy(); 104 | client.destroy(); 105 | assert.end(); 106 | }, 0); 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /src/__tests__/browserSuites/impressions.none.spec.js: -------------------------------------------------------------------------------- 1 | import { SplitFactory } from '../..'; 2 | import { settingsFactory } from '../../settings/node'; 3 | import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; 4 | import membershipsFacundo from '../mocks/memberships.facundo@split.io.json'; 5 | import { NONE } from '@splitsoftware/splitio-commons/src/utils/constants'; 6 | import { truncateTimeFrame } from '@splitsoftware/splitio-commons/src/utils/time'; 7 | import { url } from '../testUtils'; 8 | 9 | const baseUrls = { 10 | sdk: 'https://sdk.baseurl/impressionsNoneSuite', 11 | events: 'https://events.baseurl/impressionsNoneSuite' 12 | }; 13 | 14 | const settings = settingsFactory({ 15 | core: { 16 | key: '' 17 | }, 18 | urls: baseUrls, 19 | streamingEnabled: false 20 | }); 21 | 22 | const config = { 23 | core: { 24 | authorizationKey: '', 25 | key: 'facundo@split.io' 26 | }, 27 | scheduler: { 28 | featuresRefreshRate: 1, 29 | segmentsRefreshRate: 1, 30 | }, 31 | urls: baseUrls, 32 | startup: { 33 | eventsFirstPushWindow: 3000 34 | }, 35 | sync: { 36 | impressionsMode: NONE 37 | }, 38 | streamingEnabled: false 39 | }; 40 | 41 | export default async function (fetchMock, assert) { 42 | // Mocking this specific route to make sure we only get the items we want to test from the handlers. 43 | fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); 44 | fetchMock.get(url(settings, '/memberships/facundo%40split.io'), { status: 200, body: membershipsFacundo }); 45 | fetchMock.get(url(settings, '/memberships/emma%40split.io'), { status: 200, body: membershipsFacundo }); 46 | 47 | const splitio = SplitFactory(config); 48 | const client = splitio.client(); 49 | const sharedClient = splitio.client('emma@split.io'); 50 | 51 | fetchMock.postOnce(baseUrls.events + '/testImpressions/count', (url, opts) => { 52 | const data = JSON.parse(opts.body); 53 | const truncatedTimeFrame = truncateTimeFrame(Date.now()); 54 | 55 | assert.deepEqual(data, { 56 | pf: [ 57 | { f: 'split_with_config', m: truncatedTimeFrame, rc: 2 }, 58 | { f: 'always_off', m: truncatedTimeFrame, rc: 4 }, 59 | { f: 'always_on', m: truncatedTimeFrame, rc: 2 }, 60 | { f: 'always_on_impressions_disabled_true', m: truncatedTimeFrame, rc: 1 } 61 | ] 62 | }); 63 | return 200; 64 | }); 65 | 66 | fetchMock.postOnce(url(settings, '/v1/keys/cs'), (url, opts) => { 67 | const data = JSON.parse(opts.body); 68 | 69 | assert.deepEqual(data, { 70 | keys: [ 71 | { 72 | k: 'facundo@split.io', 73 | fs: ['split_with_config', 'always_off', 'always_on'] 74 | }, 75 | { 76 | k: 'emma@split.io', 77 | fs: ['always_off', 'always_on', 'always_on_impressions_disabled_true'] 78 | } 79 | ] 80 | }, 'We performed evaluations for two keys, so we should have 2 item total.'); 81 | 82 | return 200; 83 | }); 84 | 85 | await client.ready(); 86 | 87 | client.getTreatment('split_with_config'); 88 | sharedClient.getTreatment('always_off'); 89 | client.getTreatment('always_off'); 90 | sharedClient.getTreatment('always_on'); 91 | sharedClient.getTreatment('always_off'); 92 | client.getTreatment('always_on'); 93 | client.getTreatment('always_off'); 94 | client.getTreatment('split_with_config'); 95 | sharedClient.getTreatment('always_on_impressions_disabled_true'); 96 | 97 | client.destroy().then(() => { 98 | assert.end(); 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /src/__tests__/browserSuites/manager.spec.js: -------------------------------------------------------------------------------- 1 | import { SplitFactory } from '../../'; 2 | import splitChangesMockReal from '../mocks/splitchanges.real.json'; 3 | import map from 'lodash/map'; 4 | import { url } from '../testUtils'; 5 | 6 | export default async function (settings, fetchMock, assert) { 7 | fetchMock.getOnce({ url: url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), overwriteRoutes: true }, { status: 200, body: splitChangesMockReal }); 8 | 9 | const mockSplits = splitChangesMockReal; 10 | 11 | const splitio = SplitFactory({ 12 | core: { 13 | authorizationKey: '', 14 | key: 'marcio@split.io' 15 | }, 16 | streamingEnabled: false 17 | }); 18 | const client = splitio.client(); 19 | const manager = splitio.manager(); 20 | const manager2 = splitio.manager(); 21 | 22 | assert.equal(manager, manager2, 'Does not matter how many times you call .manager(), you get the same instance for the same factory.'); 23 | assert.equal(manager.ready, client.ready, 'And it shares all readiness methods with the main client.'); 24 | assert.equal(manager.on, client.on, 'And it shares all readiness methods with the main client.'); 25 | assert.equal(manager.once, client.once, 'And it shares all readiness methods with the main client.'); 26 | assert.equal(manager.Event, client.Event, 'And it shares all readiness constants with the main client.'); 27 | 28 | await client.ready(); 29 | 30 | const splitNames = manager.names(); 31 | 32 | assert.equal(splitNames.length, mockSplits.ff.d.length, 'The manager.splits() method should return all split names on the factory storage.'); 33 | assert.deepEqual(splitNames, map(mockSplits.ff.d, split => split.name), 'The manager.splits() method should return all split names on the factory storage.'); 34 | 35 | const splitObj = manager.split(splitNames[0]); 36 | const expectedSplitObj = index => ({ 37 | 'trafficType': mockSplits.ff.d[index].trafficTypeName, 38 | 'name': mockSplits.ff.d[index].name, 39 | 'killed': mockSplits.ff.d[index].killed, 40 | 'changeNumber': mockSplits.ff.d[index].changeNumber, 41 | 'treatments': map(mockSplits.ff.d[index].conditions[0].partitions, partition => partition.treatment), 42 | 'configs': mockSplits.ff.d[index].configurations || {}, 43 | 'sets': mockSplits.ff.d[index].sets || [], 44 | 'defaultTreatment': mockSplits.ff.d[index].defaultTreatment, 45 | 'impressionsDisabled': false, 46 | 'prerequisites': [] 47 | }); 48 | 49 | assert.equal(manager.split('non_existent'), null, 'Trying to get a manager.split() of a Split that does not exist returns null.'); 50 | assert.deepEqual(splitObj, expectedSplitObj(0), 'If we ask for an existent one we receive the expected split view.'); 51 | 52 | const splitObjects = manager.splits(); 53 | assert.equal(splitObjects.length, mockSplits.ff.d.length, 'The manager.splits() returns the full collection of split views.'); 54 | assert.deepEqual(splitObjects[0], expectedSplitObj(0), 'And the split views should match the items of the collection in split view format.'); 55 | assert.deepEqual(splitObjects[1], expectedSplitObj(1), 'And the split views should match the items of the collection in split view format.'); 56 | 57 | client.destroy(); 58 | assert.end(); 59 | } 60 | -------------------------------------------------------------------------------- /src/__tests__/browserSuites/push-corner-cases.spec.js: -------------------------------------------------------------------------------- 1 | import { getStorageHash } from '@splitsoftware/splitio-commons/src/storages/KeyBuilder'; 2 | import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; 3 | import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; 4 | import splitKillMessage from '../mocks/message.SPLIT_KILL.1457552650000.json'; 5 | import authPushEnabledNicolas from '../mocks/auth.pushEnabled.nicolas@split.io.json'; 6 | import { nearlyEqual, url } from '../testUtils'; 7 | 8 | // Replace original EventSource with mock 9 | import EventSourceMock, { setMockListener } from '../testUtils/eventSourceMock'; 10 | window.EventSource = EventSourceMock; 11 | 12 | import { SplitFactory } from '../../'; 13 | import { settingsFactory } from '../../settings'; 14 | 15 | const userKey = 'nicolas@split.io'; 16 | 17 | const baseUrls = { 18 | sdk: 'https://sdk.push-corner-cases/api', 19 | events: 'https://events.push-corner-cases/api', 20 | auth: 'https://auth.push-corner-cases/api' 21 | }; 22 | const config = { 23 | core: { 24 | authorizationKey: '', 25 | key: userKey 26 | }, 27 | urls: baseUrls, 28 | storage: { 29 | type: 'LOCALSTORAGE', 30 | prefix: 'pushCornerCase' 31 | }, 32 | }; 33 | const settings = settingsFactory(config); 34 | 35 | const MILLIS_SSE_OPEN = 100; 36 | const MILLIS_SPLIT_KILL_EVENT = 200; 37 | const MILLIS_SPLIT_CHANGES_RESPONSE = 400; 38 | 39 | /** 40 | * Sequence of calls: 41 | * 0.0 secs: initial SyncAll (/splitChanges, /memberships/*), auth, SSE connection, SDK_READY_FROM_CACHE 42 | * 0.1 secs: SSE connection opened -> syncAll (/splitChanges, /memberships/*) 43 | * 0.2 secs: SPLIT_KILL event -> /splitChanges 44 | * 0.4 secs: /splitChanges response --> SDK_READY 45 | */ 46 | export function testSplitKillOnReadyFromCache(fetchMock, assert) { 47 | assert.plan(2); 48 | fetchMock.reset(); 49 | 50 | // prepare localstorage to allow SPLIT_KILL kill locally 51 | localStorage.clear(); 52 | localStorage.setItem('pushCornerCase.SPLITIO.hash', getStorageHash(settings)); 53 | localStorage.setItem('pushCornerCase.SPLITIO.splits.till', 25); 54 | localStorage.setItem('pushCornerCase.SPLITIO.split.whitelist', JSON.stringify({ 55 | 'name': 'whitelist', 56 | 'status': 'ACTIVE', 57 | 'killed': false, 58 | 'defaultTreatment': 'not_allowed', 59 | })); 60 | 61 | let start, splitio, client; 62 | 63 | // mock SSE open and message events 64 | setMockListener(function (eventSourceInstance) { 65 | setTimeout(() => { 66 | eventSourceInstance.emitOpen(); 67 | }, MILLIS_SSE_OPEN); // open SSE connection after 0.1 seconds 68 | 69 | setTimeout(() => { 70 | eventSourceInstance.emitMessage(splitKillMessage); 71 | }, MILLIS_SPLIT_KILL_EVENT); // send a SPLIT_KILL event with a new changeNumber after 0.2 seconds 72 | }); 73 | 74 | // 1 auth request 75 | fetchMock.getOnce(url(settings, `/v2/auth?s=1.3&users=${encodeURIComponent(userKey)}`), { status: 200, body: authPushEnabledNicolas }); 76 | // 2 memberships requests: initial sync and after SSE opened 77 | fetchMock.get({ url: url(settings, '/memberships/nicolas%40split.io'), repeat: 2 }, { status: 200, body: { ms: {} } }); 78 | 79 | // 2 splitChanges request: initial sync and after SSE opened. Sync after SPLIT_KILL is not performed because SplitsSyncTask is "executing" 80 | fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=25&rbSince=-1'), { status: 200, body: splitChangesMock1 }, { delay: MILLIS_SPLIT_CHANGES_RESPONSE, /* delay response */ }); 81 | fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }, { delay: MILLIS_SPLIT_CHANGES_RESPONSE - 100, /* delay response */ }); 82 | 83 | fetchMock.get(new RegExp('.*'), function (url) { 84 | assert.fail('unexpected GET request with url: ' + url); 85 | }); 86 | 87 | fetchMock.post('*', 200); 88 | 89 | start = Date.now(); 90 | splitio = SplitFactory(config); 91 | client = splitio.client(); 92 | client.on(client.Event.SDK_UPDATE, () => { 93 | assert.fail('SDK_UPDATE must no be emitted, even after SPLIT_KILL has changed the cached data'); 94 | }); 95 | client.on(client.Event.SDK_READY_FROM_CACHE, () => { 96 | assert.pass(); 97 | }); 98 | client.on(client.Event.SDK_READY, () => { 99 | const lapse = Date.now() - start; 100 | assert.true(nearlyEqual(lapse, MILLIS_SPLIT_CHANGES_RESPONSE), 'SDK_READY once split changes arrives'); 101 | 102 | client.destroy().then(() => { assert.end(); }); 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /src/__tests__/browserSuites/single-sync.spec.js: -------------------------------------------------------------------------------- 1 | import { SplitFactory } from '../../'; 2 | import { settingsFactory } from '../../settings'; 3 | import { url } from '../testUtils'; 4 | 5 | import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; 6 | import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; 7 | import membershipsNicolasMock2 from '../mocks/memberships.nicolas@split.io.json'; 8 | 9 | const baseUrls = { 10 | sdk: 'https://sdk.single-sync/api', 11 | events: 'https://events.single-sync/api', 12 | }; 13 | const userKey = 'nicolas@split.io'; 14 | const config = { 15 | core: { 16 | authorizationKey: '', 17 | key: userKey 18 | }, 19 | scheduler: { 20 | featuresRefreshRate: 0.2, 21 | segmentsRefreshRate: 0.2, 22 | impressionsRefreshRate: 3000, 23 | pushRetryBackoffBase: 0.1 24 | }, 25 | urls: baseUrls, 26 | userConsent: 'UNKNOWN', 27 | startup: { 28 | eventsFirstPushWindow: 3000 29 | }, 30 | sync: { 31 | enabled: false 32 | }, 33 | streamingEnabled: true, 34 | }; 35 | const settings = settingsFactory(config); 36 | 37 | export default function singleSync(fetchMock, assert) { 38 | 39 | fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), function () { 40 | assert.pass('first splitChanges fetch'); 41 | return { status: 200, body: splitChangesMock1 }; 42 | }); 43 | fetchMock.getOnce('begin:'+url(settings, '/splitChanges?'), function () { 44 | assert.fail('splitChanges should not be called again'); 45 | return { status: 200, body: splitChangesMock2 }; 46 | }); 47 | 48 | fetchMock.getOnce(url(settings, '/memberships/nicolas%40split.io'), function () { 49 | assert.pass('first memberships fetch'); 50 | return { status: 200, body: membershipsNicolasMock2 }; 51 | }); 52 | fetchMock.getOnce(url(settings, '/memberships/nicolas%40split.io'), function () { 53 | assert.fail('memberships should not be called again'); 54 | return { status: 200, body: membershipsNicolasMock2 }; 55 | }); 56 | 57 | let splitio, client = false; 58 | 59 | splitio = SplitFactory(config); 60 | client = splitio.client(); 61 | client.on(client.Event.SDK_READY, () => { 62 | setTimeout(() => client.destroy().then(() => assert.end()), 1000); 63 | }); 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/__tests__/errorCatching/browser.spec.js: -------------------------------------------------------------------------------- 1 | // Here we are testing exceptions and the handler should be ours, we need to avoid tape-catch 2 | import { getStorageHash } from '@splitsoftware/splitio-commons/src/storages/KeyBuilder'; 3 | import tape from 'tape'; 4 | import includes from 'lodash/includes'; 5 | import fetchMock from '../testUtils/fetchMock'; 6 | import { url } from '../testUtils'; 7 | import splitChangesMock1 from '../mocks/splitChanges.since.-1.till.1500492097547.json'; 8 | import membershipsMock from '../mocks/membershipsEmpty.json'; 9 | import splitChangesMock2 from '../mocks/splitChanges.since.1500492097547.till.1500492297547.json'; 10 | import splitChangesMock3 from '../mocks/splitChanges.since.1500492297547.json'; 11 | import { SplitFactory } from '../../'; 12 | import { settingsFactory } from '../../settings'; 13 | 14 | const settings = settingsFactory({ 15 | core: { 16 | authorizationKey: '' 17 | }, 18 | streamingEnabled: false 19 | }); 20 | 21 | // prepare localstorage to emit SDK_READY_FROM_CACHE 22 | localStorage.clear(); 23 | localStorage.setItem('SPLITIO.splits.till', 25); 24 | localStorage.setItem('SPLITIO.hash', getStorageHash({ core: { authorizationKey: '' }, sync: { __splitFiltersValidation: { queryString: null }, flagSpecVersion: '1.3' } })); 25 | 26 | fetchMock.get(url(settings, '/splitChanges?s=1.3&since=25&rbSince=-1'), function () { 27 | return new Promise((res) => { setTimeout(() => res({ status: 200, body: splitChangesMock1 }), 1000); }); 28 | }); 29 | fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1500492097547&rbSince=-1'), { status: 200, body: splitChangesMock2 }); 30 | fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1500492297547&rbSince=-1'), { status: 200, body: splitChangesMock3 }); 31 | fetchMock.get(url(settings, '/memberships/nico%40split.io'), { status: 200, body: membershipsMock }); 32 | fetchMock.post('*', 200); 33 | 34 | const assertionsPlanned = 4; 35 | let errCount = 0; 36 | 37 | tape('Error catching on callbacks - Browsers', assert => { 38 | let previousErrorHandler = window.onerror || null; 39 | 40 | const factory = SplitFactory({ 41 | core: { 42 | authorizationKey: '', 43 | key: 'nico@split.io' 44 | }, 45 | startup: { 46 | eventsFirstPushWindow: 100000, 47 | readyTimeout: 0.5, 48 | requestTimeoutBeforeReady: 100000 49 | }, 50 | scheduler: { 51 | featuresRefreshRate: 1.5, 52 | segmentsRefreshRate: 100000, 53 | telemetryRefreshRate: 100000, 54 | impressionsRefreshRate: 100000, 55 | eventsPushRate: 100000 56 | }, 57 | storage: { 58 | type: 'LOCALSTORAGE', 59 | // Using default prefix 'SPLITIO' 60 | }, 61 | streamingEnabled: false 62 | }); 63 | 64 | const client = factory.client(); 65 | 66 | const exceptionHandler = err => { 67 | if (includes(err, 'willThrowFor')) { 68 | errCount++; 69 | assert.pass(`But this should be loud, all should throw as Uncaught Exception: ${err}`); 70 | 71 | if (errCount === assertionsPlanned) { 72 | const wrapUp = () => { 73 | window.onerror = previousErrorHandler; 74 | fetchMock.restore(); 75 | assert.end(); 76 | }; 77 | 78 | client.destroy().then(wrapUp).catch(wrapUp); 79 | } 80 | return true; 81 | } 82 | assert.fail(err); 83 | return false; 84 | }; 85 | // Karma is missbehaving and overwriting our custom error handler on some scenarios. 86 | function attachErrorHandlerIfApplicable() { 87 | if (window.onerror !== exceptionHandler) { 88 | previousErrorHandler = window.onerror; 89 | window.onerror = exceptionHandler; 90 | } 91 | } 92 | 93 | client.on(client.Event.SDK_READY_TIMED_OUT, () => { 94 | assert.true(client.__getStatus().hasTimedout); // SDK status should be already updated 95 | attachErrorHandlerIfApplicable(); 96 | null.willThrowForTimedOut(); 97 | }); 98 | 99 | client.once(client.Event.SDK_READY, () => { 100 | assert.true(client.__getStatus().isReady); // SDK status should be already updated 101 | attachErrorHandlerIfApplicable(); 102 | null.willThrowForReady(); 103 | }); 104 | 105 | client.once(client.Event.SDK_UPDATE, () => { 106 | attachErrorHandlerIfApplicable(); 107 | null.willThrowForUpdate(); 108 | }); 109 | 110 | client.once(client.Event.SDK_READY_FROM_CACHE, () => { 111 | assert.true(client.__getStatus().isReadyFromCache); // SDK status should be already updated 112 | attachErrorHandlerIfApplicable(); 113 | null.willThrowForReadyFromCache(); 114 | }); 115 | 116 | attachErrorHandlerIfApplicable(); 117 | }); 118 | -------------------------------------------------------------------------------- /src/__tests__/errorCatching/node.spec.js: -------------------------------------------------------------------------------- 1 | // Here we are testing exceptions and the handler should be ours, we need to avoid tape-catch 2 | import tape from 'tape'; 3 | import includes from 'lodash/includes'; 4 | import fetchMock from '../testUtils/nodeFetchMock'; 5 | import { url } from '../testUtils'; 6 | 7 | import { SplitFactory } from '../../'; 8 | import { settingsFactory } from '../../settings'; 9 | 10 | import splitChangesMock1 from '../mocks/splitChanges.since.-1.till.1500492097547.json'; 11 | import splitChangesMock2 from '../mocks/splitChanges.since.1500492097547.till.1500492297547.json'; 12 | import splitChangesMock3 from '../mocks/splitChanges.since.1500492297547.json'; 13 | 14 | // Option object used to configure mocked routes with a delay of 1.5 seconds. 15 | const responseDelay = { delay: 1500 }; 16 | 17 | const settings = settingsFactory({ 18 | core: { 19 | authorizationKey: '' 20 | }, 21 | streamingEnabled: false 22 | }); 23 | 24 | fetchMock.get(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }, responseDelay); 25 | fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1500492097547&rbSince=-1'), { status: 200, body: splitChangesMock2 }, responseDelay); 26 | fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1500492297547&rbSince=-1'), { status: 200, body: splitChangesMock3 }, responseDelay); 27 | fetchMock.postOnce(url(settings, '/v1/metrics/config'), 200); // SDK_READY 28 | fetchMock.postOnce(url(settings, '/v1/metrics/usage'), 200); // SDK destroyed 29 | 30 | tape('Error catching on callbacks', assert => { 31 | const assertionsPlanned = 3; 32 | let errCount = 0; 33 | const factory = SplitFactory({ 34 | core: { 35 | authorizationKey: '' 36 | }, 37 | startup: { 38 | eventsFirstPushWindow: 10000, 39 | readyTimeout: 1 40 | }, 41 | scheduler: { 42 | featuresRefreshRate: 2, 43 | segmentsRefreshRate: 10000, 44 | telemetryRefreshRate: 10000, 45 | impressionsRefreshRate: 10000, 46 | eventsPushRate: 10000 47 | }, 48 | debug: false, 49 | streamingEnabled: false 50 | }); 51 | const client = factory.client(); 52 | 53 | client.once(client.Event.SDK_READY_TIMED_OUT, () => { 54 | null.willThrowForTimedOut(); 55 | }); 56 | 57 | client.once(client.Event.SDK_READY, () => { 58 | null.willThrowForReady(); 59 | }); 60 | 61 | client.once(client.Event.SDK_UPDATE, () => { 62 | null.willThrowForUpdate(); 63 | }); 64 | 65 | const exceptionHandler = err => { 66 | if (includes(err.message, 'willThrowFor')) { 67 | errCount++; 68 | assert.pass(`But this should be loud, all should throw as Uncaught Exception: ${err.message}`); 69 | 70 | if (errCount === assertionsPlanned) { 71 | process.off('uncaughException', exceptionHandler); 72 | client.destroy(); 73 | fetchMock.restore(); 74 | assert.end(); 75 | } 76 | } 77 | }; 78 | 79 | process.on('uncaughtException', exceptionHandler); 80 | }); 81 | -------------------------------------------------------------------------------- /src/__tests__/mocks/auth.invalidCredentials.txt: -------------------------------------------------------------------------------- 1 | "Invalid credentials" -------------------------------------------------------------------------------- /src/__tests__/mocks/auth.noUserSpecified.txt: -------------------------------------------------------------------------------- 1 | "no user specified" -------------------------------------------------------------------------------- /src/__tests__/mocks/auth.pushBadToken.json: -------------------------------------------------------------------------------- 1 | { 2 | "pushEnabled": true, 3 | "token": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjVZOU05US45QnJtR0EiLCJ0eXAiOiJKV1QifQ.eyJ4LWFibHktY2FwYWJpbGl0eSI6IntcIk56TTJNREk1TXpjMF9OREV6TWpRMU16QTBOdz09X3NlZ21lbnRzXCI6W1wic3Vic2NyaWJlXCJdLFwiTnpNMk1ESTVNemMwX05ERXpNalExTXpBME53PT1fc3BsaXRzXCI6W1wic3Vic2NyaWJlXCJdLFwiY29udHJvbF9wcmlcIjpbXCJzdWJzY3JpYmVcIixcImNoYW5uZWwtbWV0YWRhdGE6cHVibGlzaGVyc1wiXSxcImNvbnRyb2xfc2VjXCI6W1wic3Vic2NyaWJlXCIsXCJjaGFubmVsLW1ldGFkYXRhOnB1Ymxpc2hlcnNcIl19IiwieC1hYmx5LWNsaWVudElkIjoiY2xpZW50SWQiLCJleHAiOjE1ODY5MTYyNjYsImlhdCI6MTU4NjkxMjY2Nn0iq6k65WcCx8s-yqDj4FpIOUEP6-G3VdB-NLhR0fXQUw" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/auth.pushDisabled.json: -------------------------------------------------------------------------------- 1 | { 2 | "pushEnabled":false, 3 | "token":"" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/auth.pushEnabled.nicolas@split.io.601secs.json: -------------------------------------------------------------------------------- 1 | { 2 | "pushEnabled": true, 3 | "token": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjVZOU05US45QnJtR0EiLCJ0eXAiOiJKV1QifQ.eyJ4LWFibHktY2FwYWJpbGl0eSI6IntcIk56TTJNREk1TXpjMF9OREV6TWpRMU16QTBOdz09X05UY3dPVGMzTURReF9teVNlZ21lbnRzXCI6W1wic3Vic2NyaWJlXCJdLFwiTnpNMk1ESTVNemMwX05ERXpNalExTXpBME53PT1fc3BsaXRzXCI6W1wic3Vic2NyaWJlXCJdLFwiY29udHJvbF9wcmlcIjpbXCJzdWJzY3JpYmVcIixcImNoYW5uZWwtbWV0YWRhdGE6cHVibGlzaGVyc1wiXSxcImNvbnRyb2xfc2VjXCI6W1wic3Vic2NyaWJlXCIsXCJjaGFubmVsLW1ldGFkYXRhOnB1Ymxpc2hlcnNcIl19IiwieC1hYmx5LWNsaWVudElkIjoiY2xpZW50SWQiLCJleHAiOjE1ODY5MTI2MDEsImlhdCI6MTU4NjkxMjAwMH0.iq6k65WcCx8s-yqDj4FpIOUEP6-G3VdB-NLhR0fXQUw", 4 | "connDelay": 0 5 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/auth.pushEnabled.nicolas@split.io.json: -------------------------------------------------------------------------------- 1 | { 2 | "pushEnabled": true, 3 | "token": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjVZOU05US5MZzMtZWciLCJ0eXAiOiJKV1QifQ.eyJ4LWFibHktY2FwYWJpbGl0eSI6IntcIk56TTJNREk1TXpjMF9OREV6TWpRMU16QTBOdz09X2NvbnRyb2xcIjpbXCJzdWJzY3JpYmVcIl0sXCJOek0yTURJNU16YzBfTkRFek1qUTFNekEwTnc9PV9mbGFnc1wiOltcInN1YnNjcmliZVwiXSxcIk56TTJNREk1TXpjMF9OREV6TWpRMU16QTBOdz09X21lbWJlcnNoaXBzXCI6W1wic3Vic2NyaWJlXCJdLFwiY29udHJvbF9wcmlcIjpbXCJzdWJzY3JpYmVcIixcImNoYW5uZWwtbWV0YWRhdGE6cHVibGlzaGVyc1wiXSxcImNvbnRyb2xfc2VjXCI6W1wic3Vic2NyaWJlXCIsXCJjaGFubmVsLW1ldGFkYXRhOnB1Ymxpc2hlcnNcIl19IiwieC1hYmx5LWNsaWVudElkIjoiY2xpZW50SWQiLCJleHAiOjE3MjUzODM2NDEsImlhdCI6MTcyNTM4MDA0MX0.Qqyixo2ZG-2tAkxjad7O-iphK3DVK5_xICypbIDh3IM", 4 | "connDelay": 0 5 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/auth.pushEnabled.nicolas@split.io.marcio@split.io.json: -------------------------------------------------------------------------------- 1 | { 2 | "pushEnabled": true, 3 | "token": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjVZOU05US5MZzMtZWciLCJ0eXAiOiJKV1QifQ.eyJ4LWFibHktY2FwYWJpbGl0eSI6IntcIk56TTJNREk1TXpjMF9OREV6TWpRMU16QTBOdz09X2NvbnRyb2xcIjpbXCJzdWJzY3JpYmVcIl0sXCJOek0yTURJNU16YzBfTkRFek1qUTFNekEwTnc9PV9mbGFnc1wiOltcInN1YnNjcmliZVwiXSxcIk56TTJNREk1TXpjMF9OREV6TWpRMU16QTBOdz09X21lbWJlcnNoaXBzXCI6W1wic3Vic2NyaWJlXCJdLFwiY29udHJvbF9wcmlcIjpbXCJzdWJzY3JpYmVcIixcImNoYW5uZWwtbWV0YWRhdGE6cHVibGlzaGVyc1wiXSxcImNvbnRyb2xfc2VjXCI6W1wic3Vic2NyaWJlXCIsXCJjaGFubmVsLW1ldGFkYXRhOnB1Ymxpc2hlcnNcIl19IiwieC1hYmx5LWNsaWVudElkIjoiY2xpZW50SWQiLCJleHAiOjE3MjUzODk4MjgsImlhdCI6MTcyNTM4NjIyOH0.KaEa6CjNM489dLgHxDbL8RP1DUFCMtkGLI6W3JZcTTs", 4 | "connDelay": 0 5 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/auth.pushEnabled.node.601secs.json: -------------------------------------------------------------------------------- 1 | { 2 | "pushEnabled": true, 3 | "token": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjVZOU05US45QnJtR0EiLCJ0eXAiOiJKV1QifQ.eyJ4LWFibHktY2FwYWJpbGl0eSI6IntcIk56TTJNREk1TXpjMF9OREV6TWpRMU16QTBOdz09X3NlZ21lbnRzXCI6W1wic3Vic2NyaWJlXCJdLFwiTnpNMk1ESTVNemMwX05ERXpNalExTXpBME53PT1fc3BsaXRzXCI6W1wic3Vic2NyaWJlXCJdLFwiY29udHJvbF9wcmlcIjpbXCJzdWJzY3JpYmVcIixcImNoYW5uZWwtbWV0YWRhdGE6cHVibGlzaGVyc1wiXSxcImNvbnRyb2xfc2VjXCI6W1wic3Vic2NyaWJlXCIsXCJjaGFubmVsLW1ldGFkYXRhOnB1Ymxpc2hlcnNcIl19IiwieC1hYmx5LWNsaWVudElkIjoiY2xpZW50SWQiLCJleHAiOjE1ODY5MTI2MDEsImlhdCI6MTU4NjkxMjAwMH0.iq6k65WcCx8s-yqDj4FpIOUEP6-G3VdB-NLhR0fXQUw", 4 | "connDelay": 0 5 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/auth.pushEnabled.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "pushEnabled": true, 3 | "token": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjVZOU05US45QnJtR0EiLCJ0eXAiOiJKV1QifQ.eyJ4LWFibHktY2FwYWJpbGl0eSI6IntcIk56TTJNREk1TXpjMF9OREV6TWpRMU16QTBOdz09X3NlZ21lbnRzXCI6W1wic3Vic2NyaWJlXCJdLFwiTnpNMk1ESTVNemMwX05ERXpNalExTXpBME53PT1fc3BsaXRzXCI6W1wic3Vic2NyaWJlXCJdLFwiY29udHJvbF9wcmlcIjpbXCJzdWJzY3JpYmVcIixcImNoYW5uZWwtbWV0YWRhdGE6cHVibGlzaGVyc1wiXSxcImNvbnRyb2xfc2VjXCI6W1wic3Vic2NyaWJlXCIsXCJjaGFubmVsLW1ldGFkYXRhOnB1Ymxpc2hlcnNcIl19IiwieC1hYmx5LWNsaWVudElkIjoiY2xpZW50SWQiLCJleHAiOjE1ODY5MTYyNjYsImlhdCI6MTU4NjkxMjY2Nn0.iq6k65WcCx8s-yqDj4FpIOUEP6-G3VdB-NLhR0fXQUw", 4 | "connDelay": 0 5 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/impressions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "f": "Single_Test", 4 | "i": [ 5 | { 6 | "k": "ut1", 7 | "t": "on" 8 | }, 9 | { 10 | "k": "ut2", 11 | "t": "on" 12 | }, 13 | { 14 | "k": "ut3", 15 | "t": "on" 16 | } 17 | ] 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /src/__tests__/mocks/memberships.emmanuel@split.io.json: -------------------------------------------------------------------------------- 1 | { 2 | "ms": { 3 | "k": [ 4 | { 5 | "n": "developers" 6 | }, 7 | { 8 | "n": "engineers" 9 | }, 10 | { 11 | "n": "employees" 12 | } 13 | ] 14 | } 15 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/memberships.facundo@split.io.json: -------------------------------------------------------------------------------- 1 | { 2 | "ms": { 3 | "k": [ 4 | { 5 | "n": "splitters" 6 | } 7 | ] 8 | } 9 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/memberships.marcio@split.io.json: -------------------------------------------------------------------------------- 1 | { 2 | "ms": {} 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/mocks/memberships.nicolas@split.io.json: -------------------------------------------------------------------------------- 1 | { 2 | "ms": { 3 | "k": [ 4 | { 5 | "n": "developers" 6 | }, 7 | { 8 | "n": "engineers" 9 | }, 10 | { 11 | "n": "employees" 12 | } 13 | ] 14 | } 15 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/memberships.nicolas@split.io.mock2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ms": { 3 | "k": [ 4 | { 5 | "n": "developers" 6 | }, 7 | { 8 | "n": "engineers" 9 | }, 10 | { 11 | "n": "splitters" 12 | } 13 | ] 14 | } 15 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/membershipsEmpty.json: -------------------------------------------------------------------------------- 1 | { 2 | "ms": { 3 | "k": [] 4 | } 5 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.CONTROL.STREAMING_DISABLED.control_pri.1586987434950.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"itKIDa6b4b:0:0\",\"clientId\": \"EORI49J_FSJKA2\",\"timestamp\":1586987434950,\"encoding\":\"json\",\"channel\":\"control_pri\",\"data\":\"{\\\"type\\\":\\\"CONTROL\\\",\\\"controlType\\\":\\\"STREAMING_DISABLED\\\"}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.CONTROL.STREAMING_PAUSED.control_pri.1586987434750.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"itKIDa6b4b:0:0\",\"clientId\": \"EORI49J_FSJKA2\",\"timestamp\":1586987434750,\"encoding\":\"json\",\"channel\":\"control_pri\",\"data\":\"{\\\"type\\\":\\\"CONTROL\\\",\\\"controlType\\\":\\\"STREAMING_PAUSED\\\"}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.CONTROL.STREAMING_PAUSED.control_pri.1586987434900.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"itKIDa6b4b:0:0\",\"clientId\": \"EORI49J_FSJKA2\",\"timestamp\":1586987434900,\"encoding\":\"json\",\"channel\":\"control_pri\",\"data\":\"{\\\"type\\\":\\\"CONTROL\\\",\\\"controlType\\\":\\\"STREAMING_PAUSED\\\"}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.CONTROL.STREAMING_RESUMED.control_pri.1586987434850.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"itKIDa6b4b:0:0\",\"clientId\": \"EORI49J_FSJKA2\",\"timestamp\":1586987434850,\"encoding\":\"json\",\"channel\":\"control_pri\",\"data\":\"{\\\"type\\\":\\\"CONTROL\\\",\\\"controlType\\\":\\\"STREAMING_RESUMED\\\"}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.MEMBERSHIPS_LS_UPDATE.SEGMENT_REMOVAL.1457552653000.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"data\":\"{\\\"type\\\":\\\"MEMBERSHIPS_LS_UPDATE\\\",\\\"cn\\\":1457552653000,\\\"n\\\":[\\\"splitters\\\"],\\\"c\\\": 0,\\\"u\\\": 3,\\\"d\\\":\\\"\\\"}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.MEMBERSHIPS_LS_UPDATE.UNBOUNDED.DELAY.1457552650000.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"data\":\"{\\\"type\\\":\\\"MEMBERSHIPS_LS_UPDATE\\\",\\\"cn\\\":1457552650000,\\\"n\\\":[],\\\"c\\\": 0,\\\"u\\\": 0,\\\"d\\\":\\\"\\\",\\\"i\\\":300,\\\"h\\\":1,\\\"s\\\":0}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.MEMBERSHIPS_MS_UPDATE.BOUNDED.GZIP.1457552651000.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"data\":\"{\\\"type\\\":\\\"MEMBERSHIPS_MS_UPDATE\\\",\\\"cn\\\":1457552651000,\\\"n\\\":[],\\\"c\\\": 1,\\\"u\\\": 1,\\\"d\\\":\\\"H4sIAAAAAAAA/2JABxzYeIxQLguYFIBLN8Bl4EABjc+EzOnAsA4QAAD//8YBvWeAAAAA\\\",\\\"h\\\":0}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.MEMBERSHIPS_MS_UPDATE.BOUNDED.ZLIB.1457552651000.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"data\":\"{\\\"type\\\":\\\"MEMBERSHIPS_MS_UPDATE\\\",\\\"cn\\\":1457552651000,\\\"n\\\":[],\\\"c\\\": 2,\\\"u\\\": 1,\\\"d\\\":\\\"eJxiGAX4AMdAO2AU4AeMA+2AAQACA+0AuoORGMvDBDANtAPoDBQG2gGDGQz16pRloB0wCkbBKBgFo4As0EBYyZCqoojwDwEACAAA//+W/QFR\\\",\\\"h\\\":0}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.MEMBERSHIPS_MS_UPDATE.KEYLIST.GZIP.1457552652000.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"data\":\"{\\\"type\\\":\\\"MEMBERSHIPS_MS_UPDATE\\\",\\\"cn\\\":1457552652000,\\\"n\\\":[\\\"splitters\\\"],\\\"c\\\": 1,\\\"u\\\": 2,\\\"d\\\":\\\"H4sIAAAAAAAA/wTAsRHDUAgD0F2ofwEIkPAqPhdZIW0uu/v97GPXHU004ULuMGrYR6XUbIjlXULPPse+dt1yhJibBODjrTmj3GJ4emduuDDP/w0AAP//18WLsl0AAAA=\\\",\\\"h\\\":0}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.MEMBERSHIPS_MS_UPDATE.SEGMENT_REMOVAL.1457552653000.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"data\":\"{\\\"type\\\":\\\"MEMBERSHIPS_MS_UPDATE\\\",\\\"cn\\\":1457552653000,\\\"n\\\":[\\\"splitters\\\"],\\\"c\\\": 0,\\\"u\\\": 3,\\\"d\\\":\\\"\\\",\\\"h\\\":0}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.MEMBERSHIPS_MS_UPDATE.UNBOUNDED.1457552640000.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"data\":\"{\\\"type\\\":\\\"MEMBERSHIPS_MS_UPDATE\\\",\\\"cn\\\":1457552640000,\\\"u\\\": 0,\\\"h\\\":0}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.MEMBERSHIPS_MS_UPDATE.UNBOUNDED.1457552650000.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"data\":\"{\\\"type\\\":\\\"MEMBERSHIPS_MS_UPDATE\\\",\\\"cn\\\":1457552650000,\\\"n\\\":[],\\\"c\\\": 0,\\\"u\\\": 0,\\\"d\\\":\\\"\\\",\\\"h\\\":0}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.OCCUPANCY.0.control_pri.1586987434550.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"itKIDa6b4b:0:0\",\"timestamp\":1586987434550,\"encoding\":\"json\",\"channel\":\"[?occupancy=metrics.publishers]control_pri\",\"data\":\"{\\\"metrics\\\":{\\\"publishers\\\":0}}\",\"name\":\"[meta]occupancy\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.OCCUPANCY.0.control_sec.1586987434451.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"JdPcOgIxuH:0:0\",\"timestamp\":1586987434451,\"encoding\":\"json\",\"channel\":\"[?occupancy=metrics.publishers]control_sec\",\"data\":\"{\\\"metrics\\\":{\\\"publishers\\\":0}}\",\"name\":\"[meta]occupancy\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.OCCUPANCY.1.control_pri.1586987434450.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"itKIDa6b4b:0:0\",\"timestamp\":1586987434450,\"encoding\":\"json\",\"channel\":\"[?occupancy=metrics.publishers]control_pri\",\"data\":\"{\\\"metrics\\\":{\\\"publishers\\\":1}}\",\"name\":\"[meta]occupancy\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.OCCUPANCY.2.control_pri.1586987434650.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"itKIDa6b4b:0:0\",\"timestamp\":1586987434650,\"encoding\":\"json\",\"channel\":\"[?occupancy=metrics.publishers]control_pri\",\"data\":\"{\\\"metrics\\\":{\\\"publishers\\\":2}}\",\"name\":\"[meta]occupancy\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.RB_SEGMENT_UPDATE.1457552649999.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\": \"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1457552649999}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C0.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694505,\\\"pcn\\\":1457552649999,\\\"c\\\":0,\\\"d\\\":\\\"eyJuYW1lIjoicmJzX3Rlc3QiLCJzdGF0dXMiOiJBQ1RJVkUiLCJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiZXhjbHVkZWQiOnsia2V5cyI6W10sInNlZ21lbnRzIjpbXX0sImNvbmRpdGlvbnMiOlt7Im1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6InVzZXIifSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2V9XX19XX0=\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C1.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694506,\\\"pcn\\\":1684265694505,\\\"c\\\":1,\\\"d\\\":\\\"H4sIAAAAAAAA/0zOwWrDMBAE0H+Zs75At9CGUhpySSiUYoIij1MTSwraFdQY/XtRU5ccd3jDzoLoAmGRz3JSisJA1GkRWGyejq/vWxhodsMw+uN84/7OizDDgN9+Kj172AVXzgL72RkIL4FRf69q4FPsRx1TbMGC4NR/Mb/kVG6t51M4j5G5Pdw/w6zgrq+cD5zoNeWGH5asK+p/4y/d7Hant+3HAQaRF6eEHdwkrF2tXf0JAAD//9JucZnyAAAA\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C2.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694507,\\\"pcn\\\":1684265694506,\\\"c\\\":2,\\\"d\\\":\\\"eJxMzsFqwzAQBNB/mbO+QLfQhlIackkolGKCIo9TE0sK2hXUGP17UVOXHHd4w86C6AJhkc9yUorCQNRpEVhsno6v71sYaHbDMPrjfOP+zosww4Dffio9e9gFV84C+9kZCC+BUX+vauBT7EcdU2zBguDUfzG/5FRuredTOI+RuT3cP8Os4K6vnA+c6DXlhh+WrCvqf+Mv3ex2p7ftxwEGkRenhB3cJKxdrV39CQAA//8FrVMM\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SEGMENT_UPDATE.1457552640000.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"mc4i3NENoA:0:0\",\"clientId\":\"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\":1457552640900,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_segments\",\"data\":\"{\\\"type\\\":\\\"SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1457552640000,\\\"segmentName\\\":\\\"splitters\\\"}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SEGMENT_UPDATE.1457552650000.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"mc4i3NENoA:0:0\",\"clientId\":\"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\":1457552650900,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_segments\",\"data\":\"{\\\"type\\\":\\\"SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1457552650000,\\\"segmentName\\\":\\\"employees\\\"}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_KILL.1457552650000.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"mc4i3NENoA:0:0\",\"clientId\":\"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\":1457552650900,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_KILL\\\",\\\"changeNumber\\\":1457552650000,\\\"defaultTreatment\\\":\\\"not_allowed\\\",\\\"splitName\\\":\\\"whitelist\\\"}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.1457552620999.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"mc4i3NENoA:0:0\",\"clientId\":\"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\":1457552621899,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":1457552620999}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.1457552649999.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\": \"mc4i3NENoA:0:0\",\"clientId\": \"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\": 1457552649999,\"encoding\": \"json\",\"channel\": \"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_splits\",\"data\": \"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":1457552649999}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.1457552650001.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\": \"mc4i3NENoA:0:0\",\"clientId\": \"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\": 1457552650001,\"encoding\": \"json\",\"channel\": \"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_splits\",\"data\": \"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":1457552650001}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.FS.1.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"m2T85LA4fQ:0:0\",\"clientId\":\"pri:NzIyNjY1MzI4\",\"timestamp\":1694548198907,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":2,\\\"pcn\\\":1,\\\"c\\\":0,\\\"d\\\":\\\"eyJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiaWQiOiJkNDMxY2RkMC1iMGJlLTExZWEtOGE4MC0xNjYwYWRhOWNlMzkiLCJuYW1lIjoibWF1cm9famF2YSIsInRyYWZmaWNBbGxvY2F0aW9uIjoxMDAsInRyYWZmaWNBbGxvY2F0aW9uU2VlZCI6LTkyMzkxNDkxLCJzZWVkIjotMTc2OTM3NzYwNCwic3RhdHVzIjoiQUNUSVZFIiwia2lsbGVkIjpmYWxzZSwiZGVmYXVsdFRyZWF0bWVudCI6Im9mZiIsImNoYW5nZU51bWJlciI6MTYwMjc5OTYzODM0NCwiYWxnbyI6MiwiY29uZmlndXJhdGlvbnMiOnt9LCJzZXRzIjpbXSwiY29uZGl0aW9ucyI6W3siY29uZGl0aW9uVHlwZSI6IldISVRFTElTVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJtYXRjaGVyVHlwZSI6IldISVRFTElTVCIsIm5lZ2F0ZSI6ZmFsc2UsIndoaXRlbGlzdE1hdGNoZXJEYXRhIjp7IndoaXRlbGlzdCI6WyJhZG1pbiIsIm1hdXJvIiwibmljbyJdfX1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfV0sImxhYmVsIjoid2hpdGVsaXN0ZWQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoidXNlciJ9LCJtYXRjaGVyVHlwZSI6IklOX1NFR01FTlQiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6eyJzZWdtZW50TmFtZSI6Im1hdXItMiJ9fV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG1hdXItMiJ9LHsiY29uZGl0aW9uVHlwZSI6IlJPTExPVVQiLCJtYXRjaGVyR3JvdXAiOnsiY29tYmluZXIiOiJBTkQiLCJtYXRjaGVycyI6W3sia2V5U2VsZWN0b3IiOnsidHJhZmZpY1R5cGUiOiJ1c2VyIn0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6IlY0Iiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJ2NSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.FS.2.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"m2T85LA4fQ:0:0\",\"clientId\":\"pri:NzIyNjY1MzI4\",\"timestamp\":1694548363040,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":2,\\\"pcn\\\":1,\\\"c\\\":0,\\\"d\\\":\\\"eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjIsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMSIsInNldF8yIl0sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.FS.3.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"m2T85LA4fQ:0:0\",\"clientId\":\"pri:NzIyNjY1MzI4\",\"timestamp\":1694548363039,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":3,\\\"pcn\\\":2,\\\"c\\\":0,\\\"d\\\":\\\"eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjMsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMSJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.FS.4.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"m2T85LA4fQ:0:0\",\"clientId\":\"pri:NzIyNjY1MzI4\",\"timestamp\":1694548363039,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":4,\\\"pcn\\\":3,\\\"c\\\":0,\\\"d\\\":\\\"eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjQsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMyJdLCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibmV3X3NlZ21lbnQifSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjowfSx7InRyZWF0bWVudCI6Im9mZiIsInNpemUiOjB9LHsidHJlYXRtZW50IjoiZnJlZSIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJpbiBzZWdtZW50IG5ld19zZWdtZW50In0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6ImNsaWVudCIsImF0dHJpYnV0ZSI6bnVsbH0sIm1hdGNoZXJUeXBlIjoiQUxMX0tFWVMiLCJuZWdhdGUiOmZhbHNlLCJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6bnVsbCwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOm51bGwsInVuYXJ5TnVtZXJpY01hdGNoZXJEYXRhIjpudWxsLCJiZXR3ZWVuTWF0Y2hlckRhdGEiOm51bGwsImJvb2xlYW5NYXRjaGVyRGF0YSI6bnVsbCwiZGVwZW5kZW5jeU1hdGNoZXJEYXRhIjpudWxsLCJzdHJpbmdNYXRjaGVyRGF0YSI6bnVsbH1dfSwicGFydGl0aW9ucyI6W3sidHJlYXRtZW50Ijoib24iLCJzaXplIjoxMDB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJjb250YSIsInNpemUiOjB9XSwibGFiZWwiOiJkZWZhdWx0IHJ1bGUifV19\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.FS.4None.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"m2T85LA4fQ:0:0\",\"clientId\":\"pri:NzIyNjY1MzI4\",\"timestamp\":1694548363039,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":4,\\\"pcn\\\":3,\\\"c\\\":0,\\\"d\\\":\\\"eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjUsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6W10sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.FS.5.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"m2T85LA4fQ:0:0\",\"clientId\":\"pri:NzIyNjY1MzI4\",\"timestamp\":1694548665831,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":5,\\\"pcn\\\":4,\\\"c\\\":0,\\\"d\\\":\\\"eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjUsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMyIsInNldF80Il0sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.FS.6.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"m2T85LA4fQ:0:0\",\"clientId\":\"pri:NzIyNjY1MzI4\",\"timestamp\":1694548363040,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":6,\\\"pcn\\\":5,\\\"c\\\":0,\\\"d\\\":\\\"eyJ0cmFmZmljVHlwZU5hbWUiOiJjbGllbnQiLCJuYW1lIjoid29ya20iLCJ0cmFmZmljQWxsb2NhdGlvbiI6MTAwLCJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOjE0NzM5MjIyNCwic2VlZCI6NTI0NDE3MTA1LCJzdGF0dXMiOiJBQ1RJVkUiLCJraWxsZWQiOmZhbHNlLCJkZWZhdWx0VHJlYXRtZW50Ijoib24iLCJjaGFuZ2VOdW1iZXIiOjIsImFsZ28iOjIsImNvbmZpZ3VyYXRpb25zIjp7fSwic2V0cyI6WyJzZXRfMSIsInNldF8yIl0sImNvbmRpdGlvbnMiOlt7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJJTl9TRUdNRU5UIiwibmVnYXRlIjpmYWxzZSwidXNlckRlZmluZWRTZWdtZW50TWF0Y2hlckRhdGEiOnsic2VnbWVudE5hbWUiOiJuZXdfc2VnbWVudCJ9LCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjB9LHsidHJlYXRtZW50Ijoib2ZmIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJmcmVlIiwic2l6ZSI6MTAwfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbmV3X3NlZ21lbnQifSx7ImNvbmRpdGlvblR5cGUiOiJST0xMT1VUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7ImtleVNlbGVjdG9yIjp7InRyYWZmaWNUeXBlIjoiY2xpZW50IiwiYXR0cmlidXRlIjpudWxsfSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjpudWxsLCJ3aGl0ZWxpc3RNYXRjaGVyRGF0YSI6bnVsbCwidW5hcnlOdW1lcmljTWF0Y2hlckRhdGEiOm51bGwsImJldHdlZW5NYXRjaGVyRGF0YSI6bnVsbCwiYm9vbGVhbk1hdGNoZXJEYXRhIjpudWxsLCJkZXBlbmRlbmN5TWF0Y2hlckRhdGEiOm51bGwsInN0cmluZ01hdGNoZXJEYXRhIjpudWxsfV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvbiIsInNpemUiOjEwMH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImZyZWUiLCJzaXplIjowfSx7InRyZWF0bWVudCI6ImNvbnRhIiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.FS.kill.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\":\"-OT-rGuSwz:0:0\",\"clientId\":\"NDEzMTY5Mzg0MA==:NDIxNjU0NTUyNw==\",\"timestamp\":1694549324214,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_KILL\\\",\\\"changeNumber\\\":5,\\\"defaultTreatment\\\":\\\"off\\\",\\\"splitName\\\":\\\"workm\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.IFFU.1684265694505.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\": \"mc4i3NENoA:0:0\",\"clientId\": \"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\": 1684265694505,\"encoding\": \"json\",\"channel\": \"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_splits\",\"data\": \"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":1684265694505,\\\"pcn\\\":1457552650001,\\\"c\\\":1,\\\"d\\\":\\\"H4sIAAAAAAAA/8yT327aTBDFXyU612vJxoTgvUMfKB8qcaSapqoihAZ7DNusvWi9TpUiv3tl/pdQVb1qL+cwc3bOj/EGzlKeq3T6tuaYCoZEXbGFgMogkXXDIM0y31v4C/aCgMnrU9/3gl7Pp4yilMMIAuVusqDamvlXeiWIg/FAa5OSU6aEDHz/ip4wZ5Be1AmjoBsFAtVOCO56UXh31/O7ApUjV1eQGPw3HT+NIPCitG7bctIVC2ScU63d1DK5gksHCZPnEEhXVC45rosFW8ig1++GYej3g85tJEB6aSA7Aqkpc7Ws7XahCnLTbLVM7evnzalsUUHi8//j6WgyTqYQKMilK7b31tRryLa3WKiyfRCDeHhq2Dntiys+JS/J8THUt5VyrFXlHnYTQ3LU2h91yGdQVqhy+0RtTeuhUoNZ08wagTVZdxbBndF5vYVApb7z9m9pZgKaFqwhT+6coRHvg398nEweP/157Bd+S1hz6oxtm88O73B0jbhgM47nyej+YRRfgdNODDlXJWcJL9tUF5SqnRqfbtPr4LdcTHnk4rfp3buLOkG7+Pmp++vRM9w/wVblzX7Pm8OGfxf5YDKZfxh9SS6B/2Pc9t/7ja01o5k1PwIAAP//uTipVskEAAA=\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.IFFU.1684265694515.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\": \"mc4i3NENoA:0:0\",\"clientId\": \"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\": 1684265694515,\"encoding\": \"json\",\"channel\": \"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_splits\",\"data\": \"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":1684265694515,\\\"pcn\\\":1457552650001,\\\"c\\\":3,\\\"d\\\":\\\"eJzMk99u2kwQxV8lOtdryQZj8N6hD5QPlThSTVNVEUKDPYZt1jZar1OlyO9emf8lVFWv2ss5zJyd82O8hTWUZSqZvW04opwhUVdsIKBSSKR+10vS1HWW7pIdz2NyBjRwHS8IXEopTLgbQqDYT+ZUm3LxlV4J4mg81LpMyKqygPRc94YeM6eQTtjphp4fegLVXvD6Qdjt9wPXF6gs2bqCxPC/2eRpDIEXpXXblpGuWCDljGptZ4bJ5lxYSJRZBoFkTcWKozpfsoH0goHfCXpB6PfcngDpVQnZEUjKIlOr2uwWqiC3zU5L1aF+3p7LFhUkPv8/mY2nk3gGgZxssmZzb8p6A9n25ktVtA9iGI3ODXunQ3HDp+AVWT6F+rZWlrWq7MN+YkSWWvuTDvkMSnNV7J6oTdl6qKTEvGnmjcCGjL2IYC/ovPYgUKnvvPtbmrmApiVryLM7p2jE++AfH6fTx09/HvuF32LWnNjStM0Xh3c8ukZcsZlEi3h8/zCObsBpJ0acqYLTmFdtqitK1V6NzrfpdPBbLmVx4uK26e27izpDu/r5yf/16AXun2Cr4u6w591xw7+LfDidLj6Mv8TXwP8xbofv/c7UmtHMmx8BAAD//0fclvU=\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.IFFU.1684265694525.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\": \"mc4i3NENoA:0:0\",\"clientId\": \"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\": 1684265694525,\"encoding\": \"json\",\"channel\": \"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_splits\",\"data\": \"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":1684265694525,\\\"pcn\\\":0,\\\"c\\\":2,\\\"d\\\":\\\"eJzMk99u2kwQxV8lOtdryQZj8N6hD5QPlThSTVNVEUKDPYZt1jZar1OlyO9emf8lVFWv2ss5zJyd82O8hTWUZSqZvW04opwhUVdsIKBSSKR+10vS1HWW7pIdz2NyBjRwHS8IXEopTLgbQqDYT+ZUm3LxlV4J4mg81LpMyKqygPRc94YeM6eQTtjphp4fegLVXvD6Qdjt9wPXF6gs2bqCxPC/2eRpDIEXpXXblpGuWCDljGptZ4bJ5lxYSJRZBoFkTcWKozpfsoH0goHfCXpB6PfcngDpVQnZEUjKIlOr2uwWqiC3zU5L1aF+3p7LFhUkPv8/mY2nk3gGgZxssmZzb8p6A9n25ktVtA9iGI3ODXunQ3HDp+AVWT6F+rZWlrWq7MN+YkSWWvuTDvkMSnNV7J6oTdl6qKTEvGnmjcCGjL2IYC/ovPYgUKnvvPtbmrmApiVryLM7p2jE++AfH6fTx09/HvuF32LWnNjStM0Xh3c8ukZcsZlEi3h8/zCObsBpJ0acqYLTmFdtqitK1V6NzrfpdPBbLmVx4uK26e27izpDu/r5yf/16AXun2Cr4u6w591xw7+LfDidLj6Mv8TXwP8xbofv/c7UmtHMmx8BAAD//0fclvU=\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.IFFU.1684265694545.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\": \"mc4i3NENoA:0:0\",\"clientId\": \"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\": 1684265694545,\"encoding\": \"json\",\"channel\": \"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_splits\",\"data\": \"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":1684265694545,\\\"pcn\\\":1684265694535,\\\"c\\\":1,\\\"d\\\":\\\"eJzMk99u2kwQxV8lOtdryQZj8N6hD5QPlThSTVNVEUKDPYZt1jZar1OlyO9emf8lVFWv2ss5zJyd82O8hTWUZSqZvW04opwhUVdsIKBSSKR+10vS1HWW7pIdz2NyBjRwHS8IXEopTLgbQqDYT+ZUm3LxlV4J4mg81LpMyKqygPRc94YeM6eQTtjphp4fegLVXvD6Qdjt9wPXF6gs2bqCxPC/2eRpDIEXpXXblpGuWCDljGptZ4bJ5lxYSJRZBoFkTcWKozpfsoH0goHfCXpB6PfcngDpVQnZEUjKIlOr2uwWqiC3zU5L1aF+3p7LFhUkPv8/mY2nk3gGgZxssmZzb8p6A9n25ktVtA9iGI3ODXunQ3HDp+AVWT6F+rZWlrWq7MN+YkSWWvuTDvkMSnNV7J6oTdl6qKTEvGnmjcCGjL2IYC/ovPYgUKnvvPtbmrmApiVryLM7p2jE++AfH6fTx09/HvuF32LWnNjStM0Xh3c8ukZcsZlEi3h8/zCObsBpJ0acqYLTmFdtqitK1V6NzrfpdPBbLmVx4uK26e27izpDu/r5yf/16AXun2Cr4u6w591xw7+LfDidLj6Mv8TXwP8xbofv/c7UmtHMmx8BAAD//0fclvU=\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.SPLIT_UPDATE.IFFU.1684265694555.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"id\": \"mc4i3NENoA:0:0\",\"clientId\": \"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\": 1684265694555,\"encoding\": \"json\",\"channel\": \"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_splits\",\"data\": \"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":1684265694555,\\\"pcn\\\":1684265694535,\\\"c\\\":2,\\\"d\\\":\\\"eJxsUdFu4jAQ/JVqnx3JDjTh/JZCrj2JBh0EqtOBIuNswKqTIMeuxKH8+ykhiKrqiyXvzM7O7lzAGlEUSqbnEyaiRODgGjRAQOXAIQ/puPB96tHHIPQYQ/QmFNErxEgG44DKnI2AQHXtTOI0my6WcXZAmxoUtsTKvil7nNZVoQ5RYdFERh7VBwK5TY60rqWwqq6AM0q/qa8Qc+As/EHZ5HHMCDR9wQ/9kIajcEygscK6BjhEy+nLr008AwLvSuuOVgjdIIEcC+H03RZw2Hg/n88JEJBHUR0wceUeDXAWTAIWPAYsZEFAQOhDDdwnIPslnOk9NcAvNwEOly3IWtdmC3wLe+1wCy0Q2Hh/zNvTV9xg3sFtr5irQe3v5f7twgAOy8V8vlinQKAUVh7RPJvanbrBsi73qurMQpTM7oSrzjueV6hR2tp05E8J39MV1hq1d7YrWWxsZ2cQGYjzeLXK0pcoyRbLLP69juZZuuiyxoPo2oa7ukqYc+JKNEq+XgVmwopucC6sGMSS9etTvAQCH0I7BO7Ttt21BE7C2E8XsN+l06h/CJy25CveH/eGM0rbHQEt9qiHnR62jtKR7N/8wafQ7tr/AQAA//8S4fPB\\\"}\"}" 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/mocks/message.STREAMING_RESET.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "message", 3 | "data": "{\"timestamp\":1457552660000,\"data\":\"{\\\"type\\\":\\\"CONTROL\\\",\\\"controlType\\\":\\\"STREAMING_RESET\\\"}\"}" 4 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/splitChanges.since.-1.till.1500492097547.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [ 4 | { 5 | "trafficTypeName": "account", 6 | "name": "Single_Test", 7 | "trafficAllocation": 100, 8 | "trafficAllocationSeed": -1012499566, 9 | "seed": -88385793, 10 | "status": "ACTIVE", 11 | "killed": false, 12 | "defaultTreatment": "off", 13 | "changeNumber": 1492734008466, 14 | "algo": null, 15 | "conditions": [ 16 | { 17 | "conditionType": "ROLLOUT", 18 | "matcherGroup": { 19 | "combiner": "AND", 20 | "matchers": [ 21 | { 22 | "keySelector": { 23 | "trafficType": "account", 24 | "attribute": null 25 | }, 26 | "matcherType": "ALL_KEYS", 27 | "negate": false, 28 | "userDefinedSegmentMatcherData": null, 29 | "whitelistMatcherData": null, 30 | "unaryNumericMatcherData": null, 31 | "betweenMatcherData": null, 32 | "booleanMatcherData": null, 33 | "dependencyMatcherData": null 34 | } 35 | ] 36 | }, 37 | "partitions": [ 38 | { 39 | "treatment": "on", 40 | "size": 100 41 | }, 42 | { 43 | "treatment": "off", 44 | "size": 0 45 | } 46 | ], 47 | "label": "in segment all" 48 | } 49 | ] 50 | } 51 | ], 52 | "s": -1, 53 | "t": 1500492097547 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitChanges.since.1500492097547.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [], 4 | "s": 1500492097547, 5 | "t": 1500492097547 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitChanges.since.1500492097547.till.1500492297547.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [ 4 | { 5 | "trafficTypeName": "account", 6 | "name": "Single_Test", 7 | "trafficAllocation": 100, 8 | "trafficAllocationSeed": -1012499566, 9 | "seed": -88385793, 10 | "status": "ACTIVE", 11 | "killed": false, 12 | "defaultTreatment": "off", 13 | "changeNumber": 1492734008466, 14 | "algo": null, 15 | "conditions": [ 16 | { 17 | "conditionType": "ROLLOUT", 18 | "matcherGroup": { 19 | "combiner": "AND", 20 | "matchers": [ 21 | { 22 | "keySelector": { 23 | "trafficType": "account", 24 | "attribute": null 25 | }, 26 | "matcherType": "ALL_KEYS", 27 | "negate": false, 28 | "userDefinedSegmentMatcherData": null, 29 | "whitelistMatcherData": null, 30 | "unaryNumericMatcherData": null, 31 | "betweenMatcherData": null, 32 | "booleanMatcherData": null, 33 | "dependencyMatcherData": null 34 | } 35 | ] 36 | }, 37 | "partitions": [ 38 | { 39 | "treatment": "on", 40 | "size": 0 41 | }, 42 | { 43 | "treatment": "off", 44 | "size": 100 45 | } 46 | ], 47 | "label": "in segment all" 48 | } 49 | ] 50 | } 51 | ], 52 | "s": 1500492097547, 53 | "t": 1500492297547 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitChanges.since.1500492297547.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [], 4 | "s": 1500492297547, 5 | "t": 1500492297547 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitchanges.real.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [ 4 | { 5 | "trafficTypeName": "user", 6 | "name": "real_split", 7 | "trafficAllocation": 100, 8 | "trafficAllocationSeed": -1757484928, 9 | "seed": 764645059, 10 | "status": "ACTIVE", 11 | "killed": false, 12 | "defaultTreatment": "on", 13 | "changeNumber": 1550099287313, 14 | "algo": 2, 15 | "conditions": [ 16 | { 17 | "conditionType": "ROLLOUT", 18 | "matcherGroup": { 19 | "combiner": "AND", 20 | "matchers": [ 21 | { 22 | "keySelector": { 23 | "trafficType": "user", 24 | "attribute": null 25 | }, 26 | "matcherType": "ALL_KEYS", 27 | "negate": false, 28 | "userDefinedSegmentMatcherData": null, 29 | "whitelistMatcherData": null, 30 | "unaryNumericMatcherData": null, 31 | "betweenMatcherData": null, 32 | "booleanMatcherData": null, 33 | "dependencyMatcherData": null, 34 | "stringMatcherData": null 35 | } 36 | ] 37 | }, 38 | "partitions": [ 39 | { 40 | "treatment": "on", 41 | "size": 50 42 | }, 43 | { 44 | "treatment": "off", 45 | "size": 50 46 | } 47 | ], 48 | "label": "default rule" 49 | } 50 | ], 51 | "configurations": {} 52 | }, 53 | { 54 | "trafficTypeName": "user", 55 | "name": "real_split_2", 56 | "trafficAllocation": 100, 57 | "trafficAllocationSeed": -1427479928, 58 | "seed": 769174959, 59 | "status": "ACTIVE", 60 | "killed": false, 61 | "defaultTreatment": "on", 62 | "changeNumber": 1550099287990, 63 | "algo": 2, 64 | "conditions": [ 65 | { 66 | "conditionType": "ROLLOUT", 67 | "matcherGroup": { 68 | "combiner": "AND", 69 | "matchers": [ 70 | { 71 | "keySelector": { 72 | "trafficType": "user", 73 | "attribute": null 74 | }, 75 | "matcherType": "ALL_KEYS", 76 | "negate": false, 77 | "userDefinedSegmentMatcherData": null, 78 | "whitelistMatcherData": null, 79 | "unaryNumericMatcherData": null, 80 | "betweenMatcherData": null, 81 | "booleanMatcherData": null, 82 | "dependencyMatcherData": null, 83 | "stringMatcherData": null 84 | } 85 | ] 86 | }, 87 | "partitions": [ 88 | { 89 | "treatment": "on", 90 | "size": 100 91 | }, 92 | { 93 | "treatment": "off", 94 | "size": 0 95 | } 96 | ], 97 | "label": "default rule" 98 | } 99 | ], 100 | "configurations": { 101 | "on": "{\"color\":\"brown\",\"dimensions\":{\"height\":12,\"width\":14},\"text\":{\"inner\":\"click me\"}}" 102 | } 103 | } 104 | ], 105 | "s": -1, 106 | "t": 1457552620999 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitchanges.real.updateWithSegments.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [ 4 | { 5 | "trafficTypeName": "user", 6 | "name": "real_split", 7 | "trafficAllocation": 100, 8 | "trafficAllocationSeed": -1757484928, 9 | "seed": 764645059, 10 | "status": "ACTIVE", 11 | "killed": false, 12 | "defaultTreatment": "on", 13 | "changeNumber": 1550099287313, 14 | "algo": 2, 15 | "conditions": [ 16 | { 17 | "conditionType": "ROLLOUT", 18 | "matcherGroup": { 19 | "combiner": "AND", 20 | "matchers": [ 21 | { 22 | "keySelector": { 23 | "trafficType": "user", 24 | "attribute": null 25 | }, 26 | "matcherType": "IN_SEGMENT", 27 | "negate": false, 28 | "userDefinedSegmentMatcherData": { 29 | "segmentName": "employees" 30 | }, 31 | "whitelistMatcherData": null, 32 | "unaryNumericMatcherData": null, 33 | "betweenMatcherData": null, 34 | "booleanMatcherData": null, 35 | "dependencyMatcherData": null, 36 | "stringMatcherData": null 37 | }, 38 | { 39 | "keySelector": { 40 | "trafficType": "user", 41 | "attribute": null 42 | }, 43 | "matcherType": "ALL_KEYS", 44 | "negate": false, 45 | "userDefinedSegmentMatcherData": null, 46 | "whitelistMatcherData": null, 47 | "unaryNumericMatcherData": null, 48 | "betweenMatcherData": null, 49 | "booleanMatcherData": null, 50 | "dependencyMatcherData": null, 51 | "stringMatcherData": null 52 | } 53 | ] 54 | }, 55 | "partitions": [ 56 | { 57 | "treatment": "on", 58 | "size": 0 59 | }, 60 | { 61 | "treatment": "off", 62 | "size": 100 63 | } 64 | ], 65 | "label": "default rule" 66 | } 67 | ], 68 | "configurations": {} 69 | } 70 | ], 71 | "s": 1457552620999, 72 | "t": 1457552649999 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitchanges.real.updateWithoutSegments.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [ 4 | { 5 | "trafficTypeName": "user", 6 | "name": "real_split", 7 | "trafficAllocation": 100, 8 | "trafficAllocationSeed": -1757484928, 9 | "seed": 764645059, 10 | "status": "ARCHIVED", 11 | "killed": false, 12 | "defaultTreatment": "on", 13 | "changeNumber": 1550099287313, 14 | "algo": 2, 15 | "conditions": [ 16 | { 17 | "conditionType": "ROLLOUT", 18 | "matcherGroup": { 19 | "combiner": "AND", 20 | "matchers": [ 21 | { 22 | "keySelector": { 23 | "trafficType": "user", 24 | "attribute": null 25 | }, 26 | "matcherType": "ALL_KEYS", 27 | "negate": false, 28 | "userDefinedSegmentMatcherData": null, 29 | "whitelistMatcherData": null, 30 | "unaryNumericMatcherData": null, 31 | "betweenMatcherData": null, 32 | "booleanMatcherData": null, 33 | "dependencyMatcherData": null, 34 | "stringMatcherData": null 35 | } 36 | ] 37 | }, 38 | "partitions": [ 39 | { 40 | "treatment": "on", 41 | "size": 50 42 | }, 43 | { 44 | "treatment": "off", 45 | "size": 50 46 | } 47 | ], 48 | "label": "default rule" 49 | } 50 | ], 51 | "configurations": {} 52 | } 53 | ], 54 | "s": 1457552649999, 55 | "t": 1457552669999 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitchanges.real.withSegments.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [ 4 | { 5 | "trafficTypeName": "user", 6 | "name": "real_split", 7 | "trafficAllocation": 100, 8 | "trafficAllocationSeed": -1757484928, 9 | "seed": 764645059, 10 | "status": "ACTIVE", 11 | "killed": false, 12 | "defaultTreatment": "on", 13 | "changeNumber": 1550099287313, 14 | "algo": 2, 15 | "conditions": [ 16 | { 17 | "conditionType": "ROLLOUT", 18 | "matcherGroup": { 19 | "combiner": "AND", 20 | "matchers": [ 21 | { 22 | "keySelector": { 23 | "trafficType": "user", 24 | "attribute": null 25 | }, 26 | "matcherType": "IN_SEGMENT", 27 | "negate": false, 28 | "userDefinedSegmentMatcherData": { 29 | "segmentName": "employees" 30 | }, 31 | "whitelistMatcherData": null, 32 | "unaryNumericMatcherData": null, 33 | "betweenMatcherData": null, 34 | "booleanMatcherData": null, 35 | "dependencyMatcherData": null, 36 | "stringMatcherData": null 37 | }, 38 | { 39 | "keySelector": { 40 | "trafficType": "user", 41 | "attribute": null 42 | }, 43 | "matcherType": "ALL_KEYS", 44 | "negate": false, 45 | "userDefinedSegmentMatcherData": null, 46 | "whitelistMatcherData": null, 47 | "unaryNumericMatcherData": null, 48 | "betweenMatcherData": null, 49 | "booleanMatcherData": null, 50 | "dependencyMatcherData": null, 51 | "stringMatcherData": null 52 | } 53 | ] 54 | }, 55 | "partitions": [ 56 | { 57 | "treatment": "on", 58 | "size": 50 59 | }, 60 | { 61 | "treatment": "off", 62 | "size": 50 63 | } 64 | ], 65 | "label": "default rule" 66 | } 67 | ], 68 | "configurations": {} 69 | }, 70 | { 71 | "trafficTypeName": "user", 72 | "name": "real_split_2", 73 | "trafficAllocation": 100, 74 | "trafficAllocationSeed": -1427479928, 75 | "seed": 769174959, 76 | "status": "ACTIVE", 77 | "killed": false, 78 | "defaultTreatment": "on", 79 | "changeNumber": 1550099287990, 80 | "algo": 2, 81 | "conditions": [ 82 | { 83 | "conditionType": "ROLLOUT", 84 | "matcherGroup": { 85 | "combiner": "AND", 86 | "matchers": [ 87 | { 88 | "keySelector": { 89 | "trafficType": "user", 90 | "attribute": null 91 | }, 92 | "matcherType": "ALL_KEYS", 93 | "negate": false, 94 | "userDefinedSegmentMatcherData": null, 95 | "whitelistMatcherData": null, 96 | "unaryNumericMatcherData": null, 97 | "betweenMatcherData": null, 98 | "booleanMatcherData": null, 99 | "dependencyMatcherData": null, 100 | "stringMatcherData": null 101 | } 102 | ] 103 | }, 104 | "partitions": [ 105 | { 106 | "treatment": "on", 107 | "size": 100 108 | }, 109 | { 110 | "treatment": "off", 111 | "size": 0 112 | } 113 | ], 114 | "label": "default rule" 115 | } 116 | ], 117 | "configurations": { 118 | "on": "{\"color\":\"brown\",\"dimensions\":{\"height\":12,\"width\":14},\"text\":{\"inner\":\"click me\"}}" 119 | } 120 | } 121 | ], 122 | "s": -1, 123 | "t": 1457552620999 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitchanges.since.100.till.1457552649999.RB_SEGMENT_UPDATE.json: -------------------------------------------------------------------------------- 1 | { 2 | "rbs": { 3 | "s": 100, 4 | "t": 1457552649999, 5 | "d": [ 6 | { 7 | "changeNumber": 1457552649999, 8 | "name": "test_rule_based_segment", 9 | "status": "ACTIVE", 10 | "trafficTypeName": "user", 11 | "excluded": { 12 | "keys": [ 13 | "mauro@split.io" 14 | ], 15 | "segments": [ 16 | { 17 | "type": "standard", 18 | "name": "segment_excluded_by_rbs" 19 | } 20 | ] 21 | }, 22 | "conditions": [ 23 | { 24 | "matcherGroup": { 25 | "combiner": "AND", 26 | "matchers": [ 27 | { 28 | "keySelector": { 29 | "trafficType": "user" 30 | }, 31 | "matcherType": "ENDS_WITH", 32 | "negate": false, 33 | "whitelistMatcherData": { 34 | "whitelist": [ 35 | "@split.io" 36 | ] 37 | } 38 | } 39 | ] 40 | } 41 | } 42 | ] 43 | } 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitchanges.since.1457552620999.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [], 4 | "s": 1457552620999, 5 | "t": 1457552620999 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitchanges.since.1457552620999.till.1457552649999.SPLIT_UPDATE.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [ 4 | { 5 | "orgId": null, 6 | "environment": null, 7 | "trafficTypeId": null, 8 | "trafficTypeName": null, 9 | "name": "qc_team", 10 | "seed": -1984784937, 11 | "status": "ACTIVE", 12 | "killed": false, 13 | "defaultTreatment": "no", 14 | "conditions": [ 15 | { 16 | "matcherGroup": { 17 | "combiner": "AND", 18 | "matchers": [ 19 | { 20 | "keySelector": null, 21 | "matcherType": "WHITELIST", 22 | "negate": false, 23 | "userDefinedSegmentMatcherData": null, 24 | "whitelistMatcherData": { 25 | "whitelist": [ 26 | "tia@split.io", 27 | "trevor@split.io" 28 | ] 29 | }, 30 | "unaryNumericMatcherData": null, 31 | "betweenMatcherData": null 32 | } 33 | ] 34 | }, 35 | "partitions": [ 36 | { 37 | "treatment": "yes", 38 | "size": 100 39 | } 40 | ] 41 | }, 42 | { 43 | "matcherGroup": { 44 | "combiner": "AND", 45 | "matchers": [ 46 | { 47 | "keySelector": { 48 | "trafficType": "user", 49 | "attribute": null 50 | }, 51 | "matcherType": "IN_SEGMENT", 52 | "negate": false, 53 | "userDefinedSegmentMatcherData": { 54 | "segmentName": "employees" 55 | }, 56 | "whitelistMatcherData": null, 57 | "unaryNumericMatcherData": null, 58 | "betweenMatcherData": null, 59 | "unaryStringMatcherData": null 60 | } 61 | ] 62 | }, 63 | "partitions": [ 64 | { 65 | "treatment": "yes", 66 | "size": 100 67 | }, 68 | { 69 | "treatment": "no", 70 | "size": 0 71 | } 72 | ] 73 | } 74 | ], 75 | "configurations": {} 76 | }, 77 | { 78 | "orgId": null, 79 | "environment": null, 80 | "trafficTypeId": null, 81 | "trafficTypeName": null, 82 | "name": "whitelist", 83 | "seed": 104328192, 84 | "status": "ACTIVE", 85 | "killed": false, 86 | "defaultTreatment": "not_allowed", 87 | "conditions": [ 88 | { 89 | "matcherGroup": { 90 | "combiner": "AND", 91 | "matchers": [ 92 | { 93 | "keySelector": null, 94 | "matcherType": "WHITELIST", 95 | "negate": false, 96 | "userDefinedSegmentMatcherData": null, 97 | "whitelistMatcherData": { 98 | "whitelist": [ 99 | "facundo@split.io" 100 | ] 101 | }, 102 | "unaryNumericMatcherData": null, 103 | "betweenMatcherData": null 104 | } 105 | ] 106 | }, 107 | "partitions": [ 108 | { 109 | "treatment": "allowed", 110 | "size": 100 111 | } 112 | ] 113 | }, 114 | { 115 | "matcherGroup": { 116 | "combiner": "AND", 117 | "matchers": [ 118 | { 119 | "keySelector": { 120 | "trafficType": "user", 121 | "attribute": null 122 | }, 123 | "matcherType": "ALL_KEYS", 124 | "negate": false, 125 | "userDefinedSegmentMatcherData": null, 126 | "whitelistMatcherData": null, 127 | "unaryNumericMatcherData": null, 128 | "betweenMatcherData": null 129 | } 130 | ] 131 | }, 132 | "partitions": [ 133 | { 134 | "treatment": "allowed", 135 | "size": 100 136 | }, 137 | { 138 | "treatment": "not_allowed", 139 | "size": 0 140 | } 141 | ] 142 | } 143 | ] 144 | } 145 | ], 146 | "s": 1457552620999, 147 | "t": 1457552649999 148 | } 149 | } -------------------------------------------------------------------------------- /src/__tests__/mocks/splitchanges.since.1457552649999.till.1457552650000.SPLIT_KILL.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [ 4 | { 5 | "orgId": null, 6 | "environment": null, 7 | "trafficTypeId": null, 8 | "trafficTypeName": null, 9 | "name": "whitelist", 10 | "seed": 104328192, 11 | "status": "ACTIVE", 12 | "killed": true, 13 | "defaultTreatment": "not_allowed", 14 | "changeNumber": 1457552650000, 15 | "conditions": [ 16 | { 17 | "matcherGroup": { 18 | "combiner": "AND", 19 | "matchers": [ 20 | { 21 | "keySelector": null, 22 | "matcherType": "WHITELIST", 23 | "negate": false, 24 | "userDefinedSegmentMatcherData": null, 25 | "whitelistMatcherData": { 26 | "whitelist": [ 27 | "facundo@split.io" 28 | ] 29 | }, 30 | "unaryNumericMatcherData": null, 31 | "betweenMatcherData": null 32 | } 33 | ] 34 | }, 35 | "partitions": [ 36 | { 37 | "treatment": "allowed", 38 | "size": 100 39 | } 40 | ] 41 | }, 42 | { 43 | "matcherGroup": { 44 | "combiner": "AND", 45 | "matchers": [ 46 | { 47 | "keySelector": { 48 | "trafficType": "user", 49 | "attribute": null 50 | }, 51 | "matcherType": "ALL_KEYS", 52 | "negate": false, 53 | "userDefinedSegmentMatcherData": null, 54 | "whitelistMatcherData": null, 55 | "unaryNumericMatcherData": null, 56 | "betweenMatcherData": null 57 | } 58 | ] 59 | }, 60 | "partitions": [ 61 | { 62 | "treatment": "allowed", 63 | "size": 100 64 | }, 65 | { 66 | "treatment": "not_allowed", 67 | "size": 0 68 | } 69 | ] 70 | } 71 | ] 72 | } 73 | ], 74 | "s": 1457552649999, 75 | "t": 1457552650000 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitchanges.since.1457552650000.till.1457552650001.SPLIT_UPDATE.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [ 4 | { 5 | "orgId": null, 6 | "environment": null, 7 | "trafficTypeId": null, 8 | "trafficTypeName": null, 9 | "name": "qc_team", 10 | "seed": -1984784937, 11 | "status": "ACTIVE", 12 | "killed": false, 13 | "defaultTreatment": "no", 14 | "conditions": [ 15 | { 16 | "matcherGroup": { 17 | "combiner": "AND", 18 | "matchers": [ 19 | { 20 | "keySelector": null, 21 | "matcherType": "WHITELIST", 22 | "negate": false, 23 | "userDefinedSegmentMatcherData": null, 24 | "whitelistMatcherData": { 25 | "whitelist": [ 26 | "tia@split.io", 27 | "trevor@split.io" 28 | ] 29 | }, 30 | "unaryNumericMatcherData": null, 31 | "betweenMatcherData": null 32 | } 33 | ] 34 | }, 35 | "partitions": [ 36 | { 37 | "treatment": "yes", 38 | "size": 100 39 | } 40 | ] 41 | }, 42 | { 43 | "matcherGroup": { 44 | "combiner": "AND", 45 | "matchers": [ 46 | { 47 | "keySelector": { 48 | "trafficType": "user", 49 | "attribute": null 50 | }, 51 | "matcherType": "IN_SEGMENT", 52 | "negate": false, 53 | "userDefinedSegmentMatcherData": { 54 | "segmentName": "new_segment" 55 | }, 56 | "whitelistMatcherData": null, 57 | "unaryNumericMatcherData": null, 58 | "betweenMatcherData": null, 59 | "unaryStringMatcherData": null 60 | } 61 | ] 62 | }, 63 | "partitions": [ 64 | { 65 | "treatment": "yes", 66 | "size": 100 67 | }, 68 | { 69 | "treatment": "no", 70 | "size": 0 71 | } 72 | ] 73 | } 74 | ], 75 | "configurations": {} 76 | } 77 | ], 78 | "s": 1457552650000, 79 | "t": 1457552650001 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitchanges.since.1602796638344.till.1602797638344.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [ 4 | { 5 | "trafficTypeName": "client", 6 | "name": "workm", 7 | "trafficAllocation": 100, 8 | "trafficAllocationSeed": 147392224, 9 | "seed": 524417105, 10 | "status": "ACTIVE", 11 | "killed": false, 12 | "defaultTreatment": "on", 13 | "changeNumber": 1602797638344, 14 | "algo": 2, 15 | "configurations": {}, 16 | "sets": [ 17 | "set_1" 18 | ], 19 | "conditions": [ 20 | { 21 | "conditionType": "ROLLOUT", 22 | "matcherGroup": { 23 | "combiner": "AND", 24 | "matchers": [ 25 | { 26 | "keySelector": { 27 | "trafficType": "client", 28 | "attribute": null 29 | }, 30 | "matcherType": "IN_SEGMENT", 31 | "negate": false, 32 | "userDefinedSegmentMatcherData": { 33 | "segmentName": "new_segment" 34 | }, 35 | "whitelistMatcherData": null, 36 | "unaryNumericMatcherData": null, 37 | "betweenMatcherData": null, 38 | "booleanMatcherData": null, 39 | "dependencyMatcherData": null, 40 | "stringMatcherData": null 41 | } 42 | ] 43 | }, 44 | "partitions": [ 45 | { 46 | "treatment": "on", 47 | "size": 0 48 | }, 49 | { 50 | "treatment": "off", 51 | "size": 0 52 | }, 53 | { 54 | "treatment": "free", 55 | "size": 100 56 | }, 57 | { 58 | "treatment": "conta", 59 | "size": 0 60 | } 61 | ], 62 | "label": "in segment new_segment" 63 | }, 64 | { 65 | "conditionType": "ROLLOUT", 66 | "matcherGroup": { 67 | "combiner": "AND", 68 | "matchers": [ 69 | { 70 | "keySelector": { 71 | "trafficType": "client", 72 | "attribute": null 73 | }, 74 | "matcherType": "ALL_KEYS", 75 | "negate": false, 76 | "userDefinedSegmentMatcherData": null, 77 | "whitelistMatcherData": null, 78 | "unaryNumericMatcherData": null, 79 | "betweenMatcherData": null, 80 | "booleanMatcherData": null, 81 | "dependencyMatcherData": null, 82 | "stringMatcherData": null 83 | } 84 | ] 85 | }, 86 | "partitions": [ 87 | { 88 | "treatment": "on", 89 | "size": 100 90 | }, 91 | { 92 | "treatment": "off", 93 | "size": 0 94 | }, 95 | { 96 | "treatment": "free", 97 | "size": 0 98 | }, 99 | { 100 | "treatment": "conta", 101 | "size": 0 102 | } 103 | ], 104 | "label": "default rule" 105 | } 106 | ] 107 | } 108 | ], 109 | "s": 1602796638344, 110 | "t": 1602797638344 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/__tests__/mocks/splitchanges.since.1602797638344.till.1602798638344.json: -------------------------------------------------------------------------------- 1 | { 2 | "ff": { 3 | "d": [ 4 | { 5 | "trafficTypeName": "client", 6 | "name": "workm", 7 | "trafficAllocation": 100, 8 | "trafficAllocationSeed": 147392224, 9 | "seed": 524417105, 10 | "status": "ACTIVE", 11 | "killed": false, 12 | "defaultTreatment": "on", 13 | "changeNumber": 1602798638344, 14 | "algo": 2, 15 | "configurations": {}, 16 | "sets": [ 17 | "set_3" 18 | ], 19 | "conditions": [ 20 | { 21 | "conditionType": "ROLLOUT", 22 | "matcherGroup": { 23 | "combiner": "AND", 24 | "matchers": [ 25 | { 26 | "keySelector": { 27 | "trafficType": "client", 28 | "attribute": null 29 | }, 30 | "matcherType": "IN_SEGMENT", 31 | "negate": false, 32 | "userDefinedSegmentMatcherData": { 33 | "segmentName": "new_segment" 34 | }, 35 | "whitelistMatcherData": null, 36 | "unaryNumericMatcherData": null, 37 | "betweenMatcherData": null, 38 | "booleanMatcherData": null, 39 | "dependencyMatcherData": null, 40 | "stringMatcherData": null 41 | } 42 | ] 43 | }, 44 | "partitions": [ 45 | { 46 | "treatment": "on", 47 | "size": 0 48 | }, 49 | { 50 | "treatment": "off", 51 | "size": 0 52 | }, 53 | { 54 | "treatment": "free", 55 | "size": 100 56 | }, 57 | { 58 | "treatment": "conta", 59 | "size": 0 60 | } 61 | ], 62 | "label": "in segment new_segment" 63 | }, 64 | { 65 | "conditionType": "ROLLOUT", 66 | "matcherGroup": { 67 | "combiner": "AND", 68 | "matchers": [ 69 | { 70 | "keySelector": { 71 | "trafficType": "client", 72 | "attribute": null 73 | }, 74 | "matcherType": "ALL_KEYS", 75 | "negate": false, 76 | "userDefinedSegmentMatcherData": null, 77 | "whitelistMatcherData": null, 78 | "unaryNumericMatcherData": null, 79 | "betweenMatcherData": null, 80 | "booleanMatcherData": null, 81 | "dependencyMatcherData": null, 82 | "stringMatcherData": null 83 | } 84 | ] 85 | }, 86 | "partitions": [ 87 | { 88 | "treatment": "on", 89 | "size": 100 90 | }, 91 | { 92 | "treatment": "off", 93 | "size": 0 94 | }, 95 | { 96 | "treatment": "free", 97 | "size": 0 98 | }, 99 | { 100 | "treatment": "conta", 101 | "size": 0 102 | } 103 | ], 104 | "label": "default rule" 105 | } 106 | ] 107 | } 108 | ], 109 | "s": 1602797638344, 110 | "t": 1602798638344 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/__tests__/nodeSuites/expected-treatments.spec.js: -------------------------------------------------------------------------------- 1 | import { SplitFactory } from '../../'; 2 | import fs from 'fs'; 3 | import rl from 'readline'; 4 | import { url } from '../testUtils'; 5 | 6 | import splitChangesMockReal from '../mocks/splitchanges.real.json'; 7 | 8 | export default async function (config, settings, fetchMock, assert) { 9 | fetchMock.getOnce({ url: url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), overwriteRoutes: true }, { status: 200, body: splitChangesMockReal }); 10 | fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }); 11 | 12 | const splitio = SplitFactory({ 13 | ...config, 14 | scheduler: { 15 | ...config.scheduler, 16 | // This test generates more than 30000 impressions (the default impressionsQueueSize) 17 | // so we set the queue size unlimited, to avoid flushing impressions 18 | impressionsQueueSize: 0 19 | } 20 | }); 21 | const client = splitio.client(); 22 | 23 | await client.ready(); 24 | 25 | let parser = rl.createInterface({ 26 | terminal: false, 27 | input: fs.createReadStream(require.resolve('../mocks/expected-treatments.csv')) 28 | }); 29 | 30 | parser 31 | .on('line', line => { 32 | const parts = line.toString('utf8').split(','); 33 | 34 | if (parts.length === 2) { 35 | const key = parts[0]; 36 | const treatment = parts[1]; 37 | 38 | assert.equal(client.getTreatment(key, 'real_split'), treatment, `Checking expected treatment "${treatment}" for key: ${key}`); 39 | } 40 | }) 41 | .on('close', () => client.destroy().then(assert.end)); 42 | } 43 | -------------------------------------------------------------------------------- /src/__tests__/nodeSuites/fetch-specific-splits.spec.js: -------------------------------------------------------------------------------- 1 | import { SplitFactory } from '../../'; 2 | import { splitFilters, queryStrings, groupedFilters } from '../mocks/fetchSpecificSplits'; 3 | 4 | const baseConfig = { 5 | core: { 6 | authorizationKey: '', 7 | }, 8 | scheduler: { 9 | featuresRefreshRate: 0.01 10 | }, 11 | streamingEnabled: false, 12 | }; 13 | 14 | export function fetchSpecificSplits(fetchMock, assert) { 15 | 16 | assert.plan(splitFilters.length); 17 | 18 | for (let i = 0; i < splitFilters.length; i++) { 19 | const urls = { sdk: 'https://sdkurl' + i }; 20 | const config = { ...baseConfig, sync: { splitFilters: splitFilters[i] }, urls }; 21 | 22 | if (groupedFilters[i]) { // tests where validateSplitFilters executes normally 23 | const queryString = queryStrings[i] || ''; 24 | let factory; 25 | 26 | fetchMock.getOnce(urls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1' + queryString, { status: 200, body: { ff: { d: [], s: -1, t: 1457552620999 } } }); 27 | fetchMock.getOnce(urls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1' + queryString, { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }); 28 | fetchMock.getOnce(urls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1' + queryString, function () { 29 | factory.client().destroy().then(() => { 30 | assert.pass(`splitFilters #${i}`); 31 | }); 32 | return { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }; 33 | }); 34 | 35 | factory = SplitFactory(config); 36 | 37 | } else { // tests where validateSplitFilters throws an exception 38 | try { 39 | SplitFactory(config); 40 | } catch (e) { 41 | assert.equal(e.message, queryStrings[i]); 42 | } 43 | } 44 | 45 | } 46 | } 47 | 48 | export function fetchSpecificSplitsForFlagSets(fetchMock, assert) { 49 | 50 | // Flag sets 51 | assert.test(async (t) => { 52 | 53 | const splitFilters = [{ type: 'bySet', values: ['set_x ', 'set_x', 'set_3', 'set_2', 'set_3', 'set_ww', 'invalid+', '_invalid', '4_valid'] }]; 54 | const baseUrls = { sdk: 'https://sdk.baseurl' }; 55 | 56 | const config = { 57 | ...baseConfig, 58 | urls: baseUrls, 59 | sync: { 60 | splitFilters 61 | } 62 | }; 63 | 64 | let factory; 65 | const queryString = '&sets=4_valid,set_2,set_3,set_ww,set_x'; 66 | 67 | fetchMock.getOnce(baseUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1' + queryString, { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } }}); 68 | fetchMock.getOnce(baseUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1' + queryString, async function () { 69 | t.pass('flag set query correctly formed'); 70 | factory.client().destroy().then(() => { 71 | t.end(); 72 | }); 73 | }); 74 | factory = SplitFactory(config); 75 | }, 'FlagSets config'); 76 | } 77 | -------------------------------------------------------------------------------- /src/__tests__/nodeSuites/impressions-listener.spec.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | 3 | import { SplitFactory } from '../../'; 4 | import { settingsFactory } from '../../settings'; 5 | 6 | const settings = settingsFactory({ 7 | core: { 8 | key: '' 9 | }, 10 | streamingEnabled: false 11 | }); 12 | 13 | const listener = { 14 | logImpression: sinon.stub() 15 | }; 16 | 17 | const config = { 18 | core: { 19 | authorizationKey: '' 20 | }, 21 | scheduler: { 22 | featuresRefreshRate: 1, 23 | segmentsRefreshRate: 1, 24 | }, 25 | impressionListener: listener, 26 | streamingEnabled: false 27 | }; 28 | 29 | export default function (assert) { 30 | const splitio = SplitFactory(config); 31 | const client = splitio.client(); 32 | 33 | return client.ready().then(() => { 34 | const metaData = { 35 | ip: settings.runtime.ip, 36 | hostname: settings.runtime.hostname, 37 | sdkLanguageVersion: settings.version 38 | }; 39 | const testAttrs = { is_test: true }; 40 | 41 | // Generate one impression, depends on hierarchical_dep_hierarchical which depends on hierarchical_dep_always_on 42 | client.getTreatment('nicolas@split.io', 'hierarchical_splits_test', undefined, { properties: { prop1: 'prop-value' } }); 43 | client.getTreatment({ matchingKey: 'marcio@split.io', bucketingKey: 'impr_bucketing_2' }, 'qc_team'); 44 | client.getTreatment('facundo@split.io', 'qc_team', testAttrs); 45 | client.getTreatment('facundo@split.io', 'qc_team', testAttrs); 46 | 47 | setTimeout(() => { 48 | assert.equal(listener.logImpression.callCount, 4, 'Impression listener logImpression method should be called after we call client.getTreatment, once per each impression generated.'); 49 | assert.true(listener.logImpression.getCall(0).calledWithExactly({ 50 | impression: { 51 | feature: 'hierarchical_splits_test', 52 | keyName: 'nicolas@split.io', 53 | treatment: 'on', 54 | time: listener.logImpression.getCall(0).args[0].impression.time, 55 | bucketingKey: undefined, 56 | label: 'expected label', 57 | changeNumber: 2828282828, 58 | properties: '{"prop1":"prop-value"}' 59 | }, 60 | attributes: undefined, 61 | ...metaData 62 | })); 63 | assert.true(listener.logImpression.getCall(1).calledWithMatch({ 64 | impression: { 65 | feature: 'qc_team', 66 | keyName: 'marcio@split.io', 67 | treatment: 'no', 68 | bucketingKey: 'impr_bucketing_2', 69 | label: 'default rule', 70 | }, 71 | attributes: undefined, 72 | ...metaData 73 | })); 74 | assert.true(listener.logImpression.getCall(2).calledWithMatch({ 75 | impression: { 76 | feature: 'qc_team', 77 | keyName: 'facundo@split.io', 78 | treatment: 'no', 79 | bucketingKey: undefined, 80 | label: 'default rule', 81 | }, 82 | attributes: testAttrs, 83 | ...metaData 84 | })); 85 | assert.true(listener.logImpression.getCall(3).calledWithMatch({ 86 | impression: { 87 | feature: 'qc_team', 88 | keyName: 'facundo@split.io', 89 | treatment: 'no', 90 | bucketingKey: undefined, 91 | label: 'default rule', 92 | pt: listener.logImpression.getCall(2).lastArg.impression.time 93 | }, 94 | attributes: testAttrs, 95 | ...metaData 96 | })); 97 | 98 | client.destroy(); 99 | assert.end(); 100 | }, 0); 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /src/__tests__/nodeSuites/impressions.none.spec.js: -------------------------------------------------------------------------------- 1 | import { SplitFactory } from '../../'; 2 | import { settingsFactory } from '../../settings'; 3 | import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; 4 | import { NONE } from '@splitsoftware/splitio-commons/src/utils/constants'; 5 | import { truncateTimeFrame } from '@splitsoftware/splitio-commons/src/utils/time'; 6 | import { url } from '../testUtils'; 7 | 8 | const baseUrls = { 9 | sdk: 'https://sdk.baseurl/impressionsDebugSuite', 10 | events: 'https://events.baseurl/impressionsDebugSuite' 11 | }; 12 | 13 | const settings = settingsFactory({ 14 | core: { 15 | key: '' 16 | }, 17 | urls: baseUrls, 18 | streamingEnabled: false 19 | }); 20 | 21 | const config = { 22 | core: { 23 | authorizationKey: '' 24 | }, 25 | scheduler: { 26 | featuresRefreshRate: 1, 27 | segmentsRefreshRate: 1, 28 | }, 29 | urls: baseUrls, 30 | startup: { 31 | eventsFirstPushWindow: 3000 32 | }, 33 | sync: { 34 | impressionsMode: NONE 35 | }, 36 | streamingEnabled: false 37 | }; 38 | 39 | export default async function (key, fetchMock, assert) { 40 | // Mocking this specific route to make sure we only get the items we want to test from the handlers. 41 | fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); 42 | fetchMock.get(new RegExp(`${url(settings, '/segmentChanges/')}.*`), { status: 200, body: { since: 10, till: 10, name: 'segmentName', added: [], removed: [] } }); 43 | 44 | const splitio = SplitFactory(config); 45 | const client = splitio.client(); 46 | 47 | fetchMock.postOnce(baseUrls.events + '/testImpressions/count', (url, opts) => { 48 | const data = JSON.parse(opts.body); 49 | const truncatedTimeFrame = truncateTimeFrame(Date.now()); 50 | 51 | assert.deepEqual(data, { 52 | pf: [ 53 | { f: 'split_with_config', m: truncatedTimeFrame, rc: 3 }, 54 | { f: 'always_off', m: truncatedTimeFrame, rc: 3 }, 55 | { f: 'always_on', m: truncatedTimeFrame, rc: 5 }, 56 | { f: 'always_on_impressions_disabled_true', m: truncatedTimeFrame, rc: 1 } 57 | ] 58 | }); 59 | return 200; 60 | }); 61 | 62 | fetchMock.postOnce(url(settings, '/v1/keys/ss'), (url, opts) => { 63 | const data = JSON.parse(opts.body); 64 | 65 | assert.deepEqual(data, { 66 | keys: [ 67 | { 68 | f: 'split_with_config', 69 | ks: ['emma@split.io', 'emi@split.io'] 70 | }, 71 | { 72 | f: 'always_off', 73 | ks: ['emma@split.io', 'emi@split.io'] 74 | }, 75 | { 76 | f: 'always_on', 77 | ks: ['emma@split.io', 'emi@split.io', 'nico@split.io'] 78 | }, 79 | { 80 | f: 'always_on_impressions_disabled_true', 81 | ks: ['emi@split.io'] 82 | } 83 | ] 84 | }, 'We performed evaluations for 4 flags, so we should have 4 items total.'); 85 | return 200; 86 | }); 87 | 88 | await client.ready(); 89 | 90 | client.getTreatment('emma@split.io', 'split_with_config'); 91 | client.getTreatment('emma@split.io', 'always_off'); 92 | client.getTreatment('emma@split.io', 'always_on'); 93 | client.getTreatment('emi@split.io', 'always_on'); 94 | client.getTreatment('nico@split.io', 'always_on'); 95 | client.getTreatment('emma@split.io', 'always_off'); 96 | client.getTreatment('emma@split.io', 'always_on'); 97 | client.getTreatment('emi@split.io', 'always_off'); 98 | client.getTreatment('nico@split.io', 'always_on'); 99 | client.getTreatment('emi@split.io', 'split_with_config'); 100 | client.getTreatment('emma@split.io', 'split_with_config'); 101 | client.getTreatment('emi@split.io', 'always_on_impressions_disabled_true'); 102 | 103 | client.destroy().then(() => { 104 | assert.end(); 105 | }); 106 | } 107 | -------------------------------------------------------------------------------- /src/__tests__/nodeSuites/ip-addresses-setting.debug.spec.js: -------------------------------------------------------------------------------- 1 | import osFunction from 'os'; 2 | import * as ipFunction from '../../utils/ip'; 3 | import { SplitFactory } from '../../'; 4 | import { settingsFactory } from '../../settings'; 5 | import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; 6 | import { DEBUG } from '@splitsoftware/splitio-commons/src/utils/constants'; 7 | import { url } from '../testUtils'; 8 | 9 | // Header keys and expected values. Expected values are obtained with the runtime function evaluated with IPAddressesEnabled in true. 10 | const HEADER_SPLITSDKMACHINEIP = 'SplitSDKMachineIP'; 11 | const HEADER_SPLITSDKMACHINENAME = 'SplitSDKMachineName'; 12 | const IP_VALUE = ipFunction.address(); 13 | const HOSTNAME_VALUE = osFunction.hostname(); 14 | 15 | // Refresh rates are set to 1 second to finish the test quickly. Otherwise, it would finish in 1 minute (60 seconds is the default value) 16 | const baseConfig = { 17 | streamingEnabled: false, 18 | sync: { 19 | impressionsMode: DEBUG, 20 | }, 21 | core: { 22 | authorizationKey: '', 23 | IPAddressesEnabled: true 24 | }, 25 | urls: { 26 | sdk: 'https://sdk.split-debug.io/api', 27 | events: 'https://events.split-debug.io/api', 28 | telemetry: 'https://telemetry.split-debug.io/api' 29 | } 30 | }; 31 | 32 | const postEndpoints = [ 33 | '/events/bulk', 34 | '/testImpressions/bulk', 35 | '/v1/metrics/usage', 36 | '/v1/metrics/config' 37 | ]; 38 | 39 | export default function ipAddressesSettingAssertions(fetchMock, assert) { 40 | 41 | // Assert properties in impressions 42 | function assertImpression(impression) { 43 | assert.equal(impression.ip, IP_VALUE, 'Ip did not match'); 44 | assert.equal(impression.hostname, HOSTNAME_VALUE, 'Hostname did not match'); 45 | } 46 | 47 | // Assert request headers 48 | function assertHeaders(req) { 49 | assert.equal(req.headers[HEADER_SPLITSDKMACHINEIP], IP_VALUE, `${HEADER_SPLITSDKMACHINEIP} header must be equal to the machine ip.`); 50 | assert.equal(req.headers[HEADER_SPLITSDKMACHINENAME], HOSTNAME_VALUE, `${HEADER_SPLITSDKMACHINENAME} header must be equal to the machine name.`); 51 | } 52 | 53 | function mockAndAssertIPAddressesEnabled(config) { 54 | 55 | config.impressionListener = { 56 | logImpression: function (impression) { 57 | assertImpression(impression); 58 | } 59 | }; 60 | const splitio = SplitFactory(config, ({ settings }) => { 61 | // Refresh rates are set to 1 second (below minimum values) to finish the test quickly. Otherwise, it would finish in 1 minute (60 seconds is the default value) 62 | settings.scheduler.impressionsRefreshRate = 1000; 63 | settings.scheduler.eventsPushRate = 1000; 64 | settings.scheduler.telemetryRefreshRate = 1000; 65 | }); 66 | const client = splitio.client(); 67 | const settings = settingsFactory(config); 68 | 69 | // Generator to synchronize the destruction of the client when all the post endpoints where called once. 70 | const finishConfig = (function* () { 71 | const POST_ENDPOINTS_TO_TEST = postEndpoints.length; 72 | for (let i = 0; i < POST_ENDPOINTS_TO_TEST - 1; i++) { 73 | yield; 74 | } 75 | client.destroy(); 76 | assert.end(); 77 | })(); 78 | 79 | // Mock GET endpoints to run client normally 80 | fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); 81 | fetchMock.get(new RegExp(`${url(settings, '/segmentChanges/')}.*`), { status: 200, body: { since: 10, till: 10, name: 'segmentName', added: [], removed: [] } }); 82 | 83 | // Mock and assert POST endpoints 84 | postEndpoints.forEach(postEndpoint => { 85 | fetchMock.postOnce(url(settings, postEndpoint), (url, opts) => { 86 | assertHeaders(opts); 87 | finishConfig.next(); 88 | return 200; 89 | }); 90 | fetchMock.post(url(settings, postEndpoint), 200); 91 | }); 92 | 93 | // Run normal client flow 94 | client.on(client.Event.SDK_READY, () => { 95 | client.getTreatment('nicolas@split.io', 'hierarchical_splits_test'); 96 | client.track('nicolas@split.io', 'sometraffictype', 'someEvent', 10); 97 | }); 98 | 99 | } 100 | 101 | return mockAndAssertIPAddressesEnabled(baseConfig); 102 | } 103 | -------------------------------------------------------------------------------- /src/__tests__/nodeSuites/manager.spec.js: -------------------------------------------------------------------------------- 1 | import { SplitFactory } from '../../'; 2 | import splitChangesMockReal from '../mocks/splitchanges.real.json'; 3 | import map from 'lodash/map'; 4 | import { url } from '../testUtils'; 5 | 6 | export default async function (settings, fetchMock, assert) { 7 | fetchMock.get({ url: url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), overwriteRoutes: true }, { status: 200, body: splitChangesMockReal }); 8 | 9 | const mockSplits = splitChangesMockReal; 10 | 11 | const splitio = SplitFactory({ 12 | core: { 13 | authorizationKey: '' 14 | }, 15 | streamingEnabled: false 16 | }); 17 | const client = splitio.client(); 18 | const manager = splitio.manager(); 19 | const manager2 = splitio.manager(); 20 | 21 | assert.equal(manager, manager2, 'Does not matter how many times you call .manager(), you get the same instance for the same factory.'); 22 | assert.equal(manager.ready, client.ready, 'And it shares all readiness methods with the main client.'); 23 | assert.equal(manager.on, client.on, 'And it shares all readiness methods with the main client.'); 24 | assert.equal(manager.once, client.once, 'And it shares all readiness methods with the main client.'); 25 | assert.equal(manager.Event, client.Event, 'And it shares all readiness constants with the main client.'); 26 | 27 | await manager.ready(); 28 | 29 | const splitNames = manager.names(); 30 | 31 | assert.equal(splitNames.length, mockSplits.ff.d.length, 'The manager.splits() method should return all split names on the factory storage.'); 32 | assert.deepEqual(splitNames, map(mockSplits.ff.d, split => split.name), 'The manager.splits() method should return all split names on the factory storage.'); 33 | 34 | const splitObj = manager.split(splitNames[0]); 35 | const expectedSplitObj = index => ({ 36 | 'trafficType': mockSplits.ff.d[index].trafficTypeName, 37 | 'name': mockSplits.ff.d[index].name, 38 | 'killed': mockSplits.ff.d[index].killed, 39 | 'changeNumber': mockSplits.ff.d[index].changeNumber, 40 | 'treatments': map(mockSplits.ff.d[index].conditions[0].partitions, partition => partition.treatment), 41 | 'configs': mockSplits.ff.d[index].configurations || {}, 42 | 'sets': mockSplits.ff.d[index].sets || [], 43 | 'defaultTreatment': mockSplits.ff.d[index].defaultTreatment, 44 | 'impressionsDisabled': false, 45 | 'prerequisites': [] 46 | }); 47 | 48 | assert.equal(manager.split('non_existent'), null, 'Trying to get a manager.split() of a Split that does not exist returns null.'); 49 | assert.deepEqual(splitObj, expectedSplitObj(0), 'If we ask for an existent one we receive the expected split view.'); 50 | 51 | const splitObjects = manager.splits(); 52 | assert.equal(splitObjects.length, mockSplits.ff.d.length, 'The manager.splits() returns the full collection of split views.'); 53 | assert.deepEqual(splitObjects[0], expectedSplitObj(0), 'And the split views should match the items of the collection in split view format.'); 54 | assert.deepEqual(splitObjects[1], expectedSplitObj(1), 'And the split views should match the items of the collection in split view format.'); 55 | 56 | client.destroy(); 57 | assert.end(); 58 | } 59 | -------------------------------------------------------------------------------- /src/__tests__/nodeSuites/readiness.spec.js: -------------------------------------------------------------------------------- 1 | import { SplitFactory } from '../../'; 2 | 3 | import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; 4 | import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; 5 | 6 | const readyTimeout = 0.1; 7 | 8 | const baseConfig = { 9 | core: { 10 | authorizationKey: '', 11 | }, 12 | startup: { 13 | readyTimeout, 14 | }, 15 | streamingEnabled: false 16 | }; 17 | 18 | export default function (fetchMock, assert) { 19 | 20 | assert.test(t => { // Timeout test: we provide a client-side SDK key on server-side (403 error) 21 | const testUrls = { 22 | sdk: 'https://sdk.baseurl/readinessSuite1', 23 | events: 'https://events.baseurl/readinessSuite1' 24 | }; 25 | 26 | fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 }); 27 | fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); 28 | fetchMock.get(new RegExp(testUrls.sdk + '/segmentChanges/*'), 403); 29 | fetchMock.postOnce(testUrls.events + '/events/bulk', 200); 30 | 31 | const splitio = SplitFactory({ 32 | ...baseConfig, urls: testUrls 33 | }); 34 | const client = splitio.client(); 35 | 36 | t.true(client.track('some_key', 'some_tt', 'some_event_type'), 'since client is not destroyed, client.track returns true'); 37 | 38 | client.once(client.Event.SDK_READY, () => { 39 | t.fail('### IS READY - NOT TIMED OUT when it should.'); 40 | t.end(); 41 | }); 42 | client.once(client.Event.SDK_READY_TIMED_OUT, async () => { 43 | t.pass('### SDK TIMED OUT - SegmentChanges requests with client-side SDK key should fail with 403. Timed out.'); 44 | 45 | t.false(client.track('some_key', 'some_tt', 'some_event_type'), 'since client is flagged as destroyed, client.track returns false'); 46 | t.equal(client.getTreatment('hierarchical_splits_test'), 'control', 'since client is flagged as destroyed, client.getTreatment returns control'); 47 | 48 | // ready promise should reject 49 | try { 50 | await client.ready(); 51 | } catch (e) { 52 | await splitio.destroy(); 53 | t.end(); 54 | } 55 | }); 56 | }); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/__tests__/offline/.split: -------------------------------------------------------------------------------- 1 | #### 2 | # This file a simple mock for testing 3 | #### 4 | 5 | testing_split on 6 | testing_split2 off 7 | testing_split3 custom_treatment 8 | -------------------------------------------------------------------------------- /src/__tests__/offline/split.yaml: -------------------------------------------------------------------------------- 1 | # Always on 2 | - testing_split_on: 3 | treatment: "on" 4 | # This one will (or should) return control for non specified keys on whitelist 5 | - testing_split_only_wl: 6 | treatment: "whitelisted" 7 | keys: ["key_for_wl"] 8 | # Playing with whitelists 9 | - testing_split_with_wl: 10 | treatment: "not_in_whitelist" 11 | config: "{\"color\": \"green\"}" 12 | - testing_split_with_wl: 13 | treatment: "one_key_wl" 14 | keys: "key_for_wl" 15 | - testing_split_with_wl: 16 | treatment: "multi_key_wl" 17 | keys: ["key_for_wl_1", "key_for_wl_2"] 18 | config: "{\"color\": \"brown\"}" 19 | # All keys with config 20 | - testing_split_off_with_config: 21 | treatment: "off" 22 | config: "{\"color\": \"green\"}" 23 | -------------------------------------------------------------------------------- /src/__tests__/offline/split2.yml: -------------------------------------------------------------------------------- 1 | # Always on 2 | - testing_split_on: 3 | treatment: "on" 4 | # This one will (or should) return control for non specified keys on whitelist 5 | - testing_split_only_wl: 6 | treatment: "whitelisted" 7 | keys: ["key_for_wl"] 8 | # Playing with whitelists 9 | - testing_split_with_wl: 10 | treatment: "not_in_whitelist" 11 | config: "{\"color\": \"green\"}" 12 | - testing_split_with_wl: 13 | treatment: "one_key_wl" 14 | keys: "key_for_wl" 15 | - testing_split_with_wl: 16 | treatment: "multi_key_wl" 17 | keys: ["key_for_wl_1", "key_for_wl_2"] 18 | config: "{\"color\": \"brown\"}" 19 | # All keys with config 20 | - testing_split_off_with_config: 21 | treatment: "off" 22 | config: "{\"color\": \"green\"}" 23 | 24 | -------------------------------------------------------------------------------- /src/__tests__/offline/update.split: -------------------------------------------------------------------------------- 1 | #### 2 | # This file a simple mock for testing 3 | #### 4 | 5 | testing_split on 6 | testing_split2 off 7 | testing_split3 custom_treatment 8 | testing_split4 updated_treatment 9 | -------------------------------------------------------------------------------- /src/__tests__/offline/update.split.yaml: -------------------------------------------------------------------------------- 1 | # Always on 2 | - testing_split_on: 3 | treatment: "on" 4 | # This one will (or should) return control for non specified keys on whitelist 5 | - testing_split_only_wl: 6 | treatment: "whitelisted" 7 | keys: ["key_for_wl"] 8 | # Playing with whitelists 9 | - testing_split_with_wl: 10 | treatment: "not_in_whitelist" 11 | config: "{\"color\": \"green\"}" 12 | - testing_split_with_wl: 13 | treatment: "one_key_wl" 14 | keys: "key_for_wl" 15 | - testing_split_with_wl: 16 | treatment: "multi_key_wl" 17 | keys: ["key_for_wl_1", "key_for_wl_2"] 18 | config: "{\"color\": \"brown\"}" 19 | # Update test 20 | - testing_split_update: 21 | treatment: "updated_treatment" 22 | -------------------------------------------------------------------------------- /src/__tests__/offline/update.split.yml: -------------------------------------------------------------------------------- 1 | # Always on 2 | - testing_split_on: 3 | treatment: "on" 4 | # This one will (or should) return control for non specified keys on whitelist 5 | - testing_split_only_wl: 6 | treatment: "whitelisted" 7 | keys: ["key_for_wl"] 8 | # Playing with whitelists 9 | - testing_split_with_wl: 10 | treatment: "not_in_whitelist" 11 | config: "{\"color\": \"green\"}" 12 | - testing_split_with_wl: 13 | treatment: "one_key_wl" 14 | keys: "key_for_wl" 15 | - testing_split_with_wl: 16 | treatment: "multi_key_wl" 17 | keys: ["key_for_wl_1", "key_for_wl_2"] 18 | config: "{\"color\": \"brown\"}" 19 | # Update test 20 | - testing_split_update: 21 | treatment: "updated_treatment" 22 | -------------------------------------------------------------------------------- /src/__tests__/offline/update.split2.yaml: -------------------------------------------------------------------------------- 1 | # Always on 2 | - testing_split_on: 3 | treatment: "on" 4 | # This one will (or should) return control for non specified keys on whitelist 5 | - testing_split_only_wl: 6 | treatment: "whitelisted" 7 | keys: ["key_for_wl"] 8 | # Playing with whitelists 9 | - testing_split_with_wl: 10 | treatment: "not_in_whitelist" 11 | config: "{\"color\": \"green\"}" 12 | - testing_split_with_wl: 13 | treatment: "one_key_wl" 14 | keys: "key_for_wl" 15 | - testing_split_with_wl: 16 | treatment: "multi_key_wl" 17 | keys: ["key_for_wl_1", "key_for_wl_2"] 18 | config: "{\"color\": \"brown\"}" 19 | # Update test 20 | - testing_split_update: 21 | treatment: "updated_treatment" 22 | -------------------------------------------------------------------------------- /src/__tests__/offline/update.split2.yml: -------------------------------------------------------------------------------- 1 | # Always on 2 | - testing_split_on: 3 | treatment: "on" 4 | # This one will (or should) return control for non specified keys on whitelist 5 | - testing_split_only_wl: 6 | treatment: "whitelisted" 7 | keys: ["key_for_wl"] 8 | # Playing with whitelists 9 | - testing_split_with_wl: 10 | treatment: "not_in_whitelist" 11 | config: "{\"color\": \"green\"}" 12 | - testing_split_with_wl: 13 | treatment: "one_key_wl" 14 | keys: "key_for_wl" 15 | - testing_split_with_wl: 16 | treatment: "multi_key_wl" 17 | keys: ["key_for_wl_1", "key_for_wl_2"] 18 | config: "{\"color\": \"brown\"}" 19 | # Update test 20 | - testing_split_update: 21 | treatment: "updated_treatment" 22 | -------------------------------------------------------------------------------- /src/__tests__/push/browser.spec.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape-catch'; 2 | import fetchMock from '../testUtils/fetchMock'; 3 | import { testAuthWithPushDisabled, testAuthWith401, testNoEventSource, testSSEWithNonRetryableError } from '../browserSuites/push-initialization-nopush.spec'; 4 | import { testPushRetriesDueToAuthErrors, testPushRetriesDueToSseErrors, testSdkDestroyWhileAuthRetries, testSdkDestroyWhileAuthSuccess, testSdkDestroyWhileConnDelay } from '../browserSuites/push-initialization-retries.spec'; 5 | import { testSynchronization } from '../browserSuites/push-synchronization.spec'; 6 | import { testSynchronizationRetries } from '../browserSuites/push-synchronization-retries.spec'; 7 | import { testFallback } from '../browserSuites/push-fallback.spec'; 8 | import { testRefreshToken } from '../browserSuites/push-refresh-token.spec'; 9 | import { testSplitKillOnReadyFromCache } from '../browserSuites/push-corner-cases.spec'; 10 | import { testFlagSets } from '../browserSuites/push-flag-sets.spec'; 11 | 12 | fetchMock.config.overwriteRoutes = false; 13 | Math.random = () => 0.5; // SDKs without telemetry 14 | 15 | tape('## Browser JS - E2E CI Tests for PUSH ##', function (assert) { 16 | 17 | // Non-recoverable issues on initialization 18 | assert.test('E2E / PUSH initialization: auth with push disabled', testAuthWithPushDisabled.bind(null, fetchMock)); 19 | assert.test('E2E / PUSH initialization: auth with 401', testAuthWith401.bind(null, fetchMock)); 20 | assert.test('E2E / PUSH initialization: fallback to polling if EventSource is not available', testNoEventSource.bind(null, fetchMock)); 21 | assert.test('E2E / PUSH initialization: sse with non-recoverable Ably error', testSSEWithNonRetryableError.bind(null, fetchMock)); 22 | 23 | // Recoverable issues on initialization 24 | assert.test('E2E / PUSH initialization: auth failures and then success', testPushRetriesDueToAuthErrors.bind(null, fetchMock)); 25 | assert.test('E2E / PUSH initialization: SSE connection failures and then success', testPushRetriesDueToSseErrors.bind(null, fetchMock)); 26 | 27 | // Graceful shutdown 28 | assert.test('E2E / PUSH disconnection: SDK destroyed while authenticating', testSdkDestroyWhileAuthSuccess.bind(null, fetchMock)); 29 | assert.test('E2E / PUSH disconnection: SDK destroyed while connection delay', testSdkDestroyWhileConnDelay.bind(null, fetchMock)); 30 | assert.test('E2E / PUSH disconnection: SDK destroyed while auth was retrying', testSdkDestroyWhileAuthRetries.bind(null, fetchMock)); 31 | 32 | assert.test('E2E / PUSH synchronization: happy paths', testSynchronization.bind(null, fetchMock)); 33 | assert.test('E2E / PUSH synchronization: retries', testSynchronizationRetries.bind(null, fetchMock)); 34 | 35 | assert.test('E2E / PUSH fallback, CONTROL, OCCUPANCY and STREAMING_RESET messages', testFallback.bind(null, fetchMock)); 36 | 37 | assert.test('E2E / PUSH refresh token and connection delay', testRefreshToken.bind(null, fetchMock)); 38 | 39 | // Corner cases 40 | assert.test('E2E / PUSH corner case: SPLIT_KILL notification must not emit SDK_READY if the SDK is ready from cache', testSplitKillOnReadyFromCache.bind(null, fetchMock)); 41 | 42 | // Validate flag sets 43 | assert.test('E2E / PUSH flag sets', testFlagSets.bind(null, fetchMock)); 44 | 45 | assert.end(); 46 | }); 47 | -------------------------------------------------------------------------------- /src/__tests__/push/node.spec.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape-catch'; 2 | import fetchMock from '../testUtils/nodeFetchMock'; 3 | import { testAuthWithPushDisabled, testAuthWith401, testAuthWith400, testNoEventSource, testSSEWithNonRetryableError } from '../nodeSuites/push-initialization-nopush.spec'; 4 | import { testPushRetriesDueToAuthErrors, testPushRetriesDueToSseErrors, testSdkDestroyWhileAuthRetries, testSdkDestroyWhileAuthSuccess } from '../nodeSuites/push-initialization-retries.spec'; 5 | import { testSynchronization } from '../nodeSuites/push-synchronization.spec'; 6 | import { testSynchronizationRetries } from '../nodeSuites/push-synchronization-retries.spec'; 7 | import { testFallback } from '../nodeSuites/push-fallback.spec'; 8 | import { testRefreshToken } from '../nodeSuites/push-refresh-token.spec'; 9 | import { testFlagSets } from '../nodeSuites/push-flag-sets.spec'; 10 | 11 | fetchMock.config.overwriteRoutes = false; 12 | 13 | fetchMock.post('https://telemetry.split.io/api/v1/metrics/config', 200); 14 | fetchMock.post('https://telemetry.split.io/api/v1/metrics/usage', 200); 15 | 16 | tape('## Node.js - E2E CI Tests for PUSH ##', async function (assert) { 17 | 18 | // Non-recoverable issues on initialization 19 | assert.test('E2E / PUSH initialization: auth with push disabled', testAuthWithPushDisabled.bind(null, fetchMock)); 20 | assert.test('E2E / PUSH initialization: auth with 401', testAuthWith401.bind(null, fetchMock)); 21 | assert.test('E2E / PUSH initialization: auth with 400', testAuthWith400.bind(null, fetchMock)); 22 | assert.test('E2E / PUSH initialization: fallback to polling if EventSource is not available', testNoEventSource.bind(null, fetchMock)); 23 | assert.test('E2E / PUSH initialization: sse with non-recoverable Ably error', testSSEWithNonRetryableError.bind(null, fetchMock)); 24 | 25 | // Recoverable issues on initialization 26 | assert.test('E2E / PUSH initialization: auth failures and then success', testPushRetriesDueToAuthErrors.bind(null, fetchMock)); 27 | assert.test('E2E / PUSH initialization: SSE connection failures and then success', testPushRetriesDueToSseErrors.bind(null, fetchMock)); 28 | 29 | // Graceful shutdown 30 | assert.test('E2E / PUSH disconnection: SDK destroyed while authenticating', testSdkDestroyWhileAuthSuccess.bind(null, fetchMock)); 31 | assert.test('E2E / PUSH disconnection: SDK destroyed while auth was retrying', testSdkDestroyWhileAuthRetries.bind(null, fetchMock)); 32 | 33 | assert.test('E2E / PUSH synchronization: happy paths', testSynchronization.bind(null, fetchMock)); 34 | assert.test('E2E / PUSH synchronization: retries', testSynchronizationRetries.bind(null, fetchMock)); 35 | 36 | assert.test('E2E / PUSH fallback, CONTROL and OCCUPANCY messages', testFallback.bind(null, fetchMock)); 37 | 38 | assert.test('E2E / PUSH refresh token and connection delay', testRefreshToken.bind(null, fetchMock)); 39 | 40 | /* Validate flag sets */ 41 | assert.test('E2E / PUSH flag sets', testFlagSets.bind(null, fetchMock)); 42 | 43 | assert.end(); 44 | }); 45 | -------------------------------------------------------------------------------- /src/__tests__/testUtils/browser.js: -------------------------------------------------------------------------------- 1 | function triggerEvent(eventName) { 2 | const event = document.createEvent('HTMLEvents'); 3 | event.initEvent(eventName, true, true); 4 | event.eventName = eventName; 5 | window.dispatchEvent(event); 6 | } 7 | 8 | export function triggerUnloadEvent() { 9 | triggerEvent('unload'); 10 | } 11 | 12 | export function triggerPagehideEvent() { 13 | triggerEvent('pagehide'); 14 | } 15 | 16 | export function triggerVisibilitychange(state = 'hidden' /* 'hidden' | 'visible' */) { 17 | Object.defineProperty(document, 'visibilityState', { value: state, writable: true }); 18 | document.dispatchEvent(new Event('visibilitychange')); 19 | } 20 | -------------------------------------------------------------------------------- /src/__tests__/testUtils/eventSourceMock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * EventEmitter mock based on https://github.com/gcedo/eventsourcemock/blob/master/src/EventSource.js 3 | * 4 | * To setup the mock assign it to the window object. 5 | * ``` 6 | * import EventSource from 'eventsourcemock'; 7 | * Object.defineProperty(window, 'EventSource', { 8 | * value: EventSource, 9 | * }); 10 | * ``` 11 | * 12 | */ 13 | 14 | import { EventEmitter } from '@splitsoftware/splitio-commons/src/utils/MinEvents'; 15 | 16 | const defaultOptions = { 17 | withCredentials: false 18 | }; 19 | 20 | export const sources = {}; 21 | let __listener; 22 | export function setMockListener(listener) { 23 | __listener = listener; 24 | } 25 | 26 | // eslint-disable-next-line no-redeclare 27 | export default class EventSource { 28 | 29 | constructor( 30 | url, 31 | eventSourceInitDict = defaultOptions 32 | ) { 33 | this.url = url; 34 | this.withCredentials = eventSourceInitDict.withCredentials; 35 | this.readyState = 0; 36 | // eslint-disable-next-line no-undef 37 | this.__emitter = new EventEmitter(); 38 | this.__eventSourceInitDict = arguments[1]; 39 | sources[url] = this; 40 | if (__listener) setTimeout(__listener, 0, this); 41 | } 42 | 43 | addEventListener(eventName, listener) { 44 | this.__emitter.addListener(eventName, listener); 45 | } 46 | 47 | removeEventListener(eventName, listener) { 48 | this.__emitter.removeListener(eventName, listener); 49 | } 50 | 51 | close() { 52 | this.readyState = 2; 53 | } 54 | 55 | // The following methods can be used to mock EventSource behavior and events 56 | emit(eventName, messageEvent) { 57 | this.__emitter.emit(eventName, messageEvent); 58 | 59 | let listener; 60 | switch (eventName) { 61 | case 'error': listener = this.onerror; break; 62 | case 'open': listener = this.onopen; break; 63 | case 'message': listener = this.onmessage; break; 64 | } 65 | if (typeof listener === 'function') { 66 | listener(messageEvent); 67 | } 68 | } 69 | 70 | emitError(error) { 71 | this.emit('error', error); 72 | } 73 | 74 | emitOpen() { 75 | this.readyState = 1; 76 | this.emit('open'); 77 | } 78 | 79 | emitMessage(message) { 80 | this.emit('message', message); 81 | } 82 | } 83 | 84 | EventSource.CONNECTING = 0; 85 | EventSource.OPEN = 1; 86 | EventSource.CLOSED = 2; 87 | -------------------------------------------------------------------------------- /src/__tests__/testUtils/fetchMock.js: -------------------------------------------------------------------------------- 1 | import fetchMock from 'fetch-mock'; 2 | 3 | // config the fetch mock to chain routes (appends the new route to the list of routes) 4 | fetchMock.config.overwriteRoutes = false; 5 | 6 | export default fetchMock; 7 | -------------------------------------------------------------------------------- /src/__tests__/testUtils/index.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_ERROR_MARGIN = 75; // 0.075 secs 2 | 3 | /** 4 | * Assert if an `actual` and `expected` numeric values are nearly equal. 5 | * 6 | * @param {number} actual actual time lapse in millis 7 | * @param {number} expected expected time lapse in millis 8 | * @param {number} epsilon error margin in millis 9 | * @returns {boolean} whether the absolute difference is minor to epsilon value or not 10 | */ 11 | export function nearlyEqual(actual, expected, epsilon = DEFAULT_ERROR_MARGIN) { 12 | const diff = Math.abs(actual - expected); 13 | return diff <= epsilon; 14 | } 15 | 16 | /** 17 | * mock the basic behavior for `/segmentChanges` endpoint: 18 | * - when `?since=-1`, it returns the given segment `keys` in `added` list. 19 | * - otherwise, it returns empty `added` and `removed` lists, and the same since and till values. 20 | * 21 | * @param {Object} fetchMock see https://www.wheresrhys.co.uk/fetch-mock 22 | * @param {string|RegExp|...} matcher see https://www.wheresrhys.co.uk/fetch-mock/#api-mockingmock_matcher 23 | * @param {string[]} keys array of segment keys to fetch 24 | * @param {number} changeNumber optional changeNumber 25 | */ 26 | export function mockSegmentChanges(fetchMock, matcher, keys, changeNumber = 1457552620999) { 27 | fetchMock.get(matcher, function (url) { 28 | const since = parseInt(url.split('=').pop()); 29 | const name = url.split('?')[0].split('/').pop(); 30 | return { 31 | status: 200, body: { 32 | 'name': name, 33 | 'added': since === -1 ? keys : [], 34 | 'removed': [], 35 | 'since': since, 36 | 'till': since === -1 ? changeNumber : since, 37 | } 38 | }; 39 | }); 40 | } 41 | 42 | export function hasNoCacheHeader(fetchMockOpts) { 43 | return fetchMockOpts.headers['Cache-Control'] === 'no-cache'; 44 | } 45 | 46 | const telemetryEndpointMatcher = /^\/v1\/(metrics|keys)\/(config|usage|ss|cs)/; 47 | const eventsEndpointMatcher = /^\/(testImpressions|metrics|events)/; 48 | const authEndpointMatcher = /^\/v2\/auth/; 49 | const streamingEndpointMatcher = /^\/(sse|event-stream)/; 50 | 51 | /** 52 | * Switch URLs servers based on target. 53 | * Only used for testing purposes. 54 | * 55 | * @param {Object} settings settings object 56 | * @param {String} target url path 57 | * @return {String} completed url 58 | */ 59 | export function url(settings, target) { 60 | if (telemetryEndpointMatcher.test(target)) { 61 | return `${settings.urls.telemetry}${target}`; 62 | } 63 | if (eventsEndpointMatcher.test(target)) { 64 | return `${settings.urls.events}${target}`; 65 | } 66 | if (authEndpointMatcher.test(target)) { 67 | return `${settings.urls.auth}${target}`; 68 | } 69 | if (streamingEndpointMatcher.test(target)) { 70 | return `${settings.urls.streaming}${target}`; 71 | } 72 | return `${settings.urls.sdk}${target}`; 73 | } 74 | -------------------------------------------------------------------------------- /src/__tests__/testUtils/nodeFetchMock.js: -------------------------------------------------------------------------------- 1 | import fetchMock from 'fetch-mock'; 2 | import { __setFetch } from '../../platform/getFetch/node'; 3 | 4 | const sandboxFetchMock = fetchMock.sandbox(); 5 | 6 | // config the fetch mock to chain routes (appends the new route to the list of routes) 7 | sandboxFetchMock.config.overwriteRoutes = false; 8 | 9 | __setFetch(sandboxFetchMock); 10 | 11 | export default sandboxFetchMock; 12 | -------------------------------------------------------------------------------- /src/factory/browser.js: -------------------------------------------------------------------------------- 1 | import { splitApiFactory } from '@splitsoftware/splitio-commons/src/services/splitApi'; 2 | import { syncManagerOnlineFactory } from '@splitsoftware/splitio-commons/src/sync/syncManagerOnline'; 3 | import { pushManagerFactory } from '@splitsoftware/splitio-commons/src/sync/streaming/pushManager'; 4 | import { pollingManagerCSFactory } from '@splitsoftware/splitio-commons/src/sync/polling/pollingManagerCS'; 5 | import { InLocalStorage } from '@splitsoftware/splitio-commons/src/storages/inLocalStorage'; 6 | import { InMemoryStorageCSFactory } from '@splitsoftware/splitio-commons/src/storages/inMemory/InMemoryStorageCS'; 7 | import { sdkManagerFactory } from '@splitsoftware/splitio-commons/src/sdkManager'; 8 | import { sdkClientMethodCSFactory } from '@splitsoftware/splitio-commons/src/sdkClient/sdkClientMethodCS'; 9 | import { impressionObserverCSFactory } from '@splitsoftware/splitio-commons/src/trackers/impressionObserver/impressionObserverCS'; 10 | import { __InLocalStorageMockFactory } from '@splitsoftware/splitio-commons/src/utils/settingsValidation/storage/storageCS'; 11 | import { sdkFactory } from '@splitsoftware/splitio-commons/src/sdkFactory'; 12 | import { LOCALHOST_MODE, STORAGE_LOCALSTORAGE } from '@splitsoftware/splitio-commons/src/utils/constants'; 13 | import { createUserConsentAPI } from '@splitsoftware/splitio-commons/src/consent/sdkUserConsent'; 14 | import { localhostFromObjectFactory } from '@splitsoftware/splitio-commons/src/sync/offline/LocalhostFromObject'; 15 | 16 | import { settingsFactory } from '../settings/browser'; 17 | import { platform, SignalListener } from '../platform'; 18 | 19 | const syncManagerOnlineCSFactory = syncManagerOnlineFactory(pollingManagerCSFactory, pushManagerFactory); 20 | 21 | function getStorage(settings) { 22 | return settings.storage.type === STORAGE_LOCALSTORAGE ? 23 | InLocalStorage(settings.storage) : 24 | settings.storage.__originalType === STORAGE_LOCALSTORAGE ? 25 | __InLocalStorageMockFactory : 26 | InMemoryStorageCSFactory; 27 | } 28 | 29 | /** 30 | * 31 | * @param {import("@splitsoftware/splitio-commons/types/types").ISettings} settings 32 | */ 33 | function getModules(settings) { 34 | 35 | const modules = { 36 | settings, 37 | 38 | platform, 39 | 40 | storageFactory: getStorage(settings), 41 | 42 | splitApiFactory, 43 | 44 | syncManagerFactory: syncManagerOnlineCSFactory, 45 | 46 | sdkManagerFactory, 47 | 48 | sdkClientMethodFactory: sdkClientMethodCSFactory, 49 | 50 | SignalListener, 51 | 52 | impressionsObserverFactory: impressionObserverCSFactory, 53 | 54 | extraProps: (params) => { 55 | return { 56 | UserConsent: createUserConsentAPI(params) 57 | }; 58 | } 59 | }; 60 | 61 | switch (settings.mode) { 62 | case LOCALHOST_MODE: 63 | modules.splitApiFactory = undefined; 64 | modules.syncManagerFactory = localhostFromObjectFactory; 65 | modules.SignalListener = undefined; 66 | break; 67 | } 68 | 69 | return modules; 70 | } 71 | 72 | /** 73 | * SplitFactory for client-side. 74 | * 75 | * @param {SplitIO.IBrowserSettings} config configuration object used to instantiate the SDK 76 | * @param {Function=} __updateModules optional function that lets redefine internal SDK modules. Use with 77 | * caution since, unlike `config`, this param is not validated neither considered part of the public API. 78 | * @throws Will throw an error if the provided config is invalid. 79 | */ 80 | export function SplitFactory(config, __updateModules) { 81 | const settings = settingsFactory(config); 82 | const modules = getModules(settings); 83 | if (__updateModules) __updateModules(modules); 84 | return sdkFactory(modules); 85 | } 86 | -------------------------------------------------------------------------------- /src/factory/node.js: -------------------------------------------------------------------------------- 1 | import { splitApiFactory } from '@splitsoftware/splitio-commons/src/services/splitApi'; 2 | import { syncManagerOnlineFactory } from '@splitsoftware/splitio-commons/src/sync/syncManagerOnline'; 3 | import { pushManagerFactory } from '@splitsoftware/splitio-commons/src/sync/streaming/pushManager'; 4 | import { pollingManagerSSFactory } from '@splitsoftware/splitio-commons/src/sync/polling/pollingManagerSS'; 5 | import { InRedisStorage } from '@splitsoftware/splitio-commons/src/storages/inRedis'; 6 | import { InMemoryStorageFactory } from '@splitsoftware/splitio-commons/src/storages/inMemory/InMemoryStorage'; 7 | import { sdkManagerFactory } from '@splitsoftware/splitio-commons/src/sdkManager'; 8 | import { sdkClientMethodFactory } from '@splitsoftware/splitio-commons/src/sdkClient/sdkClientMethod'; 9 | import { impressionObserverSSFactory } from '@splitsoftware/splitio-commons/src/trackers/impressionObserver/impressionObserverSS'; 10 | import { sdkFactory } from '@splitsoftware/splitio-commons/src/sdkFactory'; 11 | import { CONSUMER_MODE, LOCALHOST_MODE } from '@splitsoftware/splitio-commons/src/utils/constants'; 12 | 13 | import { localhostFromFileFactory } from '../sync/offline/LocalhostFromFile'; 14 | import { settingsFactory } from '../settings/node'; 15 | import { platform, SignalListener } from '../platform'; 16 | import { bloomFilterFactory } from '../platform/filter/bloomFilter'; 17 | 18 | const syncManagerOnlineSSFactory = syncManagerOnlineFactory(pollingManagerSSFactory, pushManagerFactory); 19 | 20 | function getStorage(settings) { 21 | return settings.storage.type === 'REDIS' ? 22 | InRedisStorage(settings.storage) : 23 | InMemoryStorageFactory; 24 | } 25 | 26 | /** 27 | * 28 | * @param {import("@splitsoftware/splitio-commons/types/types").ISettings} settings 29 | */ 30 | function getModules(settings) { 31 | 32 | const modules = { 33 | settings, 34 | 35 | platform, 36 | 37 | storageFactory: getStorage(settings), 38 | 39 | splitApiFactory, 40 | 41 | syncManagerFactory: syncManagerOnlineSSFactory, 42 | 43 | sdkManagerFactory, 44 | 45 | sdkClientMethodFactory, 46 | 47 | SignalListener, 48 | 49 | impressionsObserverFactory: impressionObserverSSFactory, 50 | 51 | filterAdapterFactory: bloomFilterFactory 52 | }; 53 | 54 | switch (settings.mode) { 55 | case LOCALHOST_MODE: 56 | modules.splitApiFactory = undefined; 57 | modules.syncManagerFactory = localhostFromFileFactory; 58 | modules.SignalListener = undefined; 59 | break; 60 | case CONSUMER_MODE: 61 | modules.syncManagerFactory = undefined; 62 | break; 63 | } 64 | 65 | return modules; 66 | } 67 | 68 | /** 69 | * SplitFactory for server-side. 70 | * 71 | * @param {SplitIO.INodeSettings | SplitIO.INodeAsyncSettings} config configuration object used to instantiate the SDK 72 | * @param {Function=} __updateModules optional function that lets redefine internal SDK modules. Use with 73 | * caution since, unlike `config`, this param is not validated neither considered part of the public API. 74 | * @throws Will throw an error if the provided config is invalid. 75 | */ 76 | export function SplitFactory(config, __updateModules) { 77 | const settings = settingsFactory(config); 78 | const modules = getModules(settings); 79 | if (__updateModules) __updateModules(modules); 80 | return sdkFactory(modules); 81 | } 82 | -------------------------------------------------------------------------------- /src/factory/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./node.js", 3 | "browser": "./browser.js" 4 | } 5 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { SplitFactory } from './factory'; 2 | -------------------------------------------------------------------------------- /src/platform/browser.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from './EventEmitter'; 2 | import { getFetch } from '../platform/getFetch/browser'; 3 | import { getEventSource } from '../platform/getEventSource/browser'; 4 | import { BrowserSignalListener } from '@splitsoftware/splitio-commons/src/listeners/browser'; 5 | import { now } from '@splitsoftware/splitio-commons/src/utils/timeTracker/now/browser'; 6 | 7 | export const platform = { 8 | getFetch, 9 | getEventSource, 10 | EventEmitter, 11 | now 12 | }; 13 | 14 | export const SignalListener = BrowserSignalListener; 15 | -------------------------------------------------------------------------------- /src/platform/filter/__tests__/bloomFilter.spec.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape-catch'; 2 | import { bloomFilterFactory } from '../bloomFilter'; 3 | 4 | tape('Bloom filter', (assert) => { 5 | 6 | const bloomFilter = bloomFilterFactory(); 7 | 8 | assert.true(bloomFilter.add('feature','key')); 9 | assert.false(bloomFilter.contains('feature1','key')); 10 | assert.true(bloomFilter.contains('feature','key')); 11 | 12 | bloomFilter.clear(); 13 | 14 | assert.false(bloomFilter.contains('feature','key')); 15 | 16 | assert.true(bloomFilter.add('feature2','key')); 17 | assert.false(bloomFilter.contains('feature3','key')); 18 | assert.true(bloomFilter.contains('feature2','key')); 19 | 20 | assert.end(); 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /src/platform/filter/bloomFilter.js: -------------------------------------------------------------------------------- 1 | import { BloomFilter } from 'bloom-filters'; 2 | 3 | const EXPECTED_INSERTIONS = 10000000; 4 | const ERROR_RATE = 0.01; 5 | const REFRESH_RATE = 24 * 60 * 60000; // 24HS 6 | 7 | export function bloomFilterFactory(expectedInsertions = EXPECTED_INSERTIONS, errorRate = ERROR_RATE, refreshRate = REFRESH_RATE) { 8 | let filter = BloomFilter.create(expectedInsertions, errorRate); 9 | 10 | return { 11 | 12 | refreshRate: refreshRate, 13 | 14 | add(key, value) { 15 | const data = `${key}:${value}`; 16 | if (filter.has(data)) { 17 | return false; 18 | } 19 | filter.add(data); 20 | return true; 21 | }, 22 | 23 | contains(key, value) { 24 | const data = `${key}:${value}`; 25 | return filter.has(data); 26 | }, 27 | 28 | clear() { 29 | filter = BloomFilter.create(expectedInsertions, errorRate); 30 | } 31 | 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/platform/getEventSource/__tests__/browser.spec.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape-catch'; 2 | import { getEventSource } from '../browser'; 3 | 4 | tape('getEventSource returns global EventSource in Browser', assert => { 5 | assert.equal(getEventSource(), EventSource); 6 | 7 | assert.end(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/platform/getEventSource/__tests__/node.spec.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape-catch'; 2 | import { getEventSource } from '../node'; 3 | 4 | tape('getEventSource returns eventsource module in Node', assert => { 5 | assert.equal(getEventSource(), require('../eventsource')); 6 | 7 | assert.end(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/platform/getEventSource/browser.js: -------------------------------------------------------------------------------- 1 | export function getEventSource() { 2 | return typeof EventSource === 'function' ? EventSource : undefined; 3 | } 4 | -------------------------------------------------------------------------------- /src/platform/getEventSource/node.js: -------------------------------------------------------------------------------- 1 | let __isCustom = false; 2 | let __eventSource = undefined; 3 | 4 | // This function is only exposed for testing purposes. 5 | export function __setEventSource(eventSource) { 6 | __eventSource = eventSource; 7 | __isCustom = true; 8 | } 9 | export function __restore() { 10 | __isCustom = false; 11 | } 12 | 13 | export function getEventSource() { 14 | // returns EventSource at `eventsource` package. If not available, return global EventSource or undefined 15 | try { 16 | return __isCustom ? __eventSource : require('./eventsource'); 17 | } catch (error) { 18 | return typeof EventSource === 'function' ? EventSource : undefined; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/platform/getEventSource/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./node.js", 3 | "browser": "./browser.js" 4 | } 5 | -------------------------------------------------------------------------------- /src/platform/getFetch/__tests__/browser.spec.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape-catch'; 2 | import { getFetch } from '../browser'; 3 | 4 | tape('getFetch returns global fetch in Browser', assert => { 5 | assert.equal(getFetch(), fetch); 6 | 7 | assert.end(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/platform/getFetch/__tests__/node.spec.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape-catch'; 2 | import { getFetch } from '../node'; 3 | 4 | tape('getFetch returns node-fetch module in Node', assert => { 5 | assert.equal(getFetch(), require('node-fetch')); 6 | 7 | assert.end(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/platform/getFetch/browser.js: -------------------------------------------------------------------------------- 1 | import unfetch from 'unfetch'; 2 | 3 | export function getFetch() { 4 | return typeof fetch === 'function' ? fetch : unfetch; 5 | } 6 | -------------------------------------------------------------------------------- /src/platform/getFetch/node.js: -------------------------------------------------------------------------------- 1 | let nodeFetch; 2 | 3 | try { 4 | nodeFetch = require('node-fetch'); 5 | 6 | // Handle node-fetch issue https://github.com/node-fetch/node-fetch/issues/1037 7 | if (typeof nodeFetch !== 'function') nodeFetch = nodeFetch.default; 8 | 9 | } catch (error) { 10 | // Try to access global fetch if `node-fetch` package couldn't be imported (e.g., not in a Node environment) 11 | nodeFetch = typeof fetch === 'function' ? fetch : undefined; 12 | } 13 | 14 | // This function is only exposed for testing purposes. 15 | export function __setFetch(fetch) { 16 | nodeFetch = fetch; 17 | } 18 | 19 | /** 20 | * Retrieves 'node-fetch', a Fetch API polyfill for Node.js, with fallback to global 'fetch' if available. 21 | */ 22 | export function getFetch() { 23 | return nodeFetch; 24 | } 25 | -------------------------------------------------------------------------------- /src/platform/getFetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./node.js", 3 | "browser": "./browser.js" 4 | } 5 | -------------------------------------------------------------------------------- /src/platform/getOptions/__tests__/node.spec.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape-catch'; 2 | import { settingsFactory } from '../../../settings/node'; 3 | import { getOptions } from '../node'; 4 | 5 | tape('getOptions returns an object with a custom agent if all urls are https', assert => { 6 | const settings = settingsFactory({}); 7 | assert.true(typeof getOptions(settings).agent === 'object'); 8 | 9 | assert.end(); 10 | }); 11 | 12 | tape('getOptions returns undefined if some url is not https', assert => { 13 | const settings = settingsFactory({ urls: { sdk: 'http://sdk.split.io' } }); 14 | assert.equal(getOptions(settings), undefined); 15 | 16 | assert.end(); 17 | }); 18 | 19 | tape('getOptions returns the provided options from settings', assert => { 20 | const customRequestOptions = { agent: false }; 21 | const settings = settingsFactory({ sync: { requestOptions: customRequestOptions } }); 22 | assert.equal(getOptions(settings), customRequestOptions); 23 | 24 | assert.end(); 25 | }); 26 | -------------------------------------------------------------------------------- /src/platform/getOptions/node.js: -------------------------------------------------------------------------------- 1 | // @TODO 2 | // 1- handle multiple protocols automatically 3 | // 2- destroy it once the sdk is destroyed 4 | import https from 'https'; 5 | 6 | import { find } from '@splitsoftware/splitio-commons/src/utils/lang'; 7 | 8 | const agent = new https.Agent({ 9 | keepAlive: true, 10 | keepAliveMsecs: 1500 11 | }); 12 | 13 | export function getOptions(settings) { 14 | // User provided options take precedence 15 | if (settings.sync.requestOptions) return settings.sync.requestOptions; 16 | 17 | // If some URL is not HTTPS, we don't use the agent, to let the SDK connect to HTTP endpoints 18 | if (find(settings.urls, url => !url.startsWith('https:'))) return; 19 | 20 | return { 21 | agent 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/platform/node.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'events'; 2 | import { getFetch } from '../platform/getFetch/node'; 3 | import { getEventSource } from '../platform/getEventSource/node'; 4 | import { getOptions } from '../platform/getOptions/node'; 5 | import { NodeSignalListener } from '@splitsoftware/splitio-commons/src/listeners/node'; 6 | import { now } from '@splitsoftware/splitio-commons/src/utils/timeTracker/now/node'; 7 | 8 | export const platform = { 9 | getFetch, 10 | getEventSource, 11 | getOptions, 12 | EventEmitter, 13 | now 14 | }; 15 | 16 | export const SignalListener = NodeSignalListener; 17 | -------------------------------------------------------------------------------- /src/platform/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./node.js", 3 | "browser": "./browser.js" 4 | } 5 | -------------------------------------------------------------------------------- /src/settings/__tests__/browser.spec.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape-catch'; 2 | import { settingsFactory } from '../browser'; 3 | 4 | tape('SETTINGS / Consent is overwritable and "GRANTED" by default in client-side', assert => { 5 | let settings = settingsFactory({}); 6 | assert.equal(settings.userConsent, 'GRANTED', 'userConsent defaults to granted if not provided.'); 7 | 8 | settings = settingsFactory({ userConsent: 'INVALID-VALUE' }); 9 | assert.equal(settings.userConsent, 'GRANTED', 'userConsent defaults to granted if a wrong value is provided.'); 10 | 11 | settings = settingsFactory({ userConsent: 'UNKNOWN' }); 12 | assert.equal(settings.userConsent, 'UNKNOWN', 'userConsent can be overwritten.'); 13 | 14 | settings = settingsFactory({ userConsent: 'declined' }); 15 | assert.equal(settings.userConsent, 'DECLINED', 'userConsent can be overwritten.'); 16 | 17 | assert.end(); 18 | }); 19 | -------------------------------------------------------------------------------- /src/settings/__tests__/defaults.spec.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape-catch'; 2 | import { defaults as nodeDefaults } from '../defaults/node'; 3 | import { defaults as browserDefaults } from '../defaults/browser'; 4 | import { version } from '../../../package.json'; 5 | 6 | tape('sdk version should contain the package.json version', (assert) => { 7 | assert.equal(nodeDefaults.version, `nodejs-${version}`); 8 | assert.equal(browserDefaults.version, `javascript-${version}`); 9 | assert.true(version.length <= 16); // SDK version must not exceed 16 chars length'); 10 | 11 | assert.end(); 12 | }); 13 | -------------------------------------------------------------------------------- /src/settings/browser.js: -------------------------------------------------------------------------------- 1 | import { settingsValidation } from '@splitsoftware/splitio-commons/src/utils/settingsValidation'; 2 | import { validateRuntime } from '@splitsoftware/splitio-commons/src/utils/settingsValidation/runtime'; 3 | import { validateLogger } from '@splitsoftware/splitio-commons/src/utils/settingsValidation/logger/builtinLogger'; 4 | import { validateConsent } from '@splitsoftware/splitio-commons/src/utils/settingsValidation/consent'; 5 | 6 | import { defaults } from './defaults/browser'; 7 | import { validateStorage } from './storage/browser'; 8 | 9 | const params = { 10 | defaults, 11 | acceptKey: true, // Client with bound key 12 | runtime: validateRuntime, 13 | storage: validateStorage, 14 | logger: validateLogger, 15 | consent: validateConsent, 16 | }; 17 | 18 | export function settingsFactory(config) { 19 | return settingsValidation(config, params); 20 | } 21 | -------------------------------------------------------------------------------- /src/settings/defaults/browser.js: -------------------------------------------------------------------------------- 1 | import { packageVersion } from './version'; 2 | import { CONSENT_GRANTED } from '@splitsoftware/splitio-commons/src/utils/constants'; 3 | 4 | export const defaults = { 5 | startup: { 6 | // Stress the request time used while starting up the SDK. 7 | requestTimeoutBeforeReady: 5, 8 | // How many quick retries we will do while starting up the SDK. 9 | retriesOnFailureBeforeReady: 1, 10 | // Maximum amount of time used before notifies me a timeout. 11 | readyTimeout: 10, 12 | // Amount of time we will wait before the first push of events. 13 | eventsFirstPushWindow: 10 14 | }, 15 | 16 | // Consent is considered granted by default 17 | userConsent: CONSENT_GRANTED, 18 | 19 | // Instance version. 20 | version: `javascript-${packageVersion}`, 21 | }; 22 | -------------------------------------------------------------------------------- /src/settings/defaults/node.js: -------------------------------------------------------------------------------- 1 | import { packageVersion } from './version'; 2 | 3 | export const defaults = { 4 | core: { 5 | // Default is true. 6 | IPAddressesEnabled: true 7 | }, 8 | startup: { 9 | // Stress the request time used while starting up the SDK. 10 | requestTimeoutBeforeReady: 15, 11 | // How many quick retries we will do while starting up the SDK. 12 | retriesOnFailureBeforeReady: 1, 13 | // Maximum amount of time used before notifies me a timeout. 14 | readyTimeout: 15, 15 | // Don't wait a specific time for first flush on Node, no page load here. 16 | eventsFirstPushWindow: 0 17 | }, 18 | 19 | features: '.split', 20 | 21 | // Instance version. 22 | version: `nodejs-${packageVersion}`, 23 | }; 24 | -------------------------------------------------------------------------------- /src/settings/defaults/version.js: -------------------------------------------------------------------------------- 1 | export const packageVersion = '11.4.1'; 2 | -------------------------------------------------------------------------------- /src/settings/integrations/browser.js: -------------------------------------------------------------------------------- 1 | import { GOOGLE_ANALYTICS_TO_SPLIT, SPLIT_TO_GOOGLE_ANALYTICS } from '@splitsoftware/splitio-commons/src/utils/constants/browser'; 2 | import { validateConfigurableIntegrations } from '@splitsoftware/splitio-commons/src/utils/settingsValidation/integrations/configurable'; 3 | 4 | export function validateIntegrations(settings) { 5 | return validateConfigurableIntegrations(settings, [GOOGLE_ANALYTICS_TO_SPLIT, SPLIT_TO_GOOGLE_ANALYTICS]); 6 | } 7 | -------------------------------------------------------------------------------- /src/settings/node.js: -------------------------------------------------------------------------------- 1 | import { settingsValidation } from '@splitsoftware/splitio-commons/src/utils/settingsValidation'; 2 | import { validateLogger } from '@splitsoftware/splitio-commons/src/utils/settingsValidation/logger/builtinLogger'; 3 | 4 | import { defaults } from './defaults/node'; 5 | import { validateStorage } from './storage/node'; 6 | import { validateRuntime } from './runtime/node'; 7 | 8 | const params = { 9 | defaults, 10 | runtime: validateRuntime, 11 | storage: validateStorage, 12 | logger: validateLogger, 13 | }; 14 | 15 | export function settingsFactory(config) { 16 | const settings = settingsValidation(config, params); 17 | 18 | // if provided, keeps reference to the `requestOptions` object 19 | if (settings.sync.requestOptions) settings.sync.requestOptions = config.sync.requestOptions; 20 | return settings; 21 | } 22 | -------------------------------------------------------------------------------- /src/settings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./node.js", 3 | "browser": "./browser.js" 4 | } 5 | -------------------------------------------------------------------------------- /src/settings/runtime/node.js: -------------------------------------------------------------------------------- 1 | import osFunction from 'os'; 2 | import { address } from '../../utils/ip'; 3 | 4 | import { UNKNOWN, NA, CONSUMER_MODE } from '@splitsoftware/splitio-commons/src/utils/constants'; 5 | 6 | export function validateRuntime(settings) { 7 | const isIPAddressesEnabled = settings.core.IPAddressesEnabled === true; 8 | const isConsumerMode = settings.mode === CONSUMER_MODE; 9 | 10 | // If the values are not available, default to false (for standalone) or "unknown" (for consumer mode, to be used on Redis keys) 11 | let ip = address() || (isConsumerMode ? UNKNOWN : false); 12 | let hostname = osFunction.hostname() || (isConsumerMode ? UNKNOWN : false); 13 | 14 | if (!isIPAddressesEnabled) { // If IPAddresses setting is not enabled, set as false (for standalone) or "NA" (for consumer mode, to be used on Redis keys) 15 | ip = hostname = isConsumerMode ? NA : false; 16 | } 17 | 18 | return { 19 | ip, hostname 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/settings/storage/browser.js: -------------------------------------------------------------------------------- 1 | import { isLocalStorageAvailable } from '@splitsoftware/splitio-commons/src/utils/env/isLocalStorageAvailable'; 2 | import { LOCALHOST_MODE, STORAGE_MEMORY } from '@splitsoftware/splitio-commons/src/utils/constants'; 3 | 4 | const STORAGE_LOCALSTORAGE = 'LOCALSTORAGE'; 5 | 6 | export function validateStorage(settings) { 7 | let { 8 | log, 9 | mode, 10 | storage: { 11 | type, 12 | options = {}, 13 | prefix, 14 | expirationDays, 15 | clearOnInit 16 | } = { type: STORAGE_MEMORY }, 17 | } = settings; 18 | let __originalType; 19 | 20 | const fallbackToMemory = () => { 21 | __originalType = type; 22 | type = STORAGE_MEMORY; 23 | }; 24 | 25 | // In localhost mode, fallback to Memory storage and track original type to emit SDK_READY_FROM_CACHE if corresponds. 26 | // ATM, other mode settings (e.g., 'consumer') are ignored in client-side API, and so treated as standalone. 27 | if (mode === LOCALHOST_MODE && type === STORAGE_LOCALSTORAGE) { 28 | fallbackToMemory(); 29 | } 30 | 31 | // If an invalid storage type is provided OR we want to use LOCALSTORAGE and 32 | // it's not available, fallback into MEMORY 33 | if (type !== STORAGE_MEMORY && type !== STORAGE_LOCALSTORAGE || 34 | type === STORAGE_LOCALSTORAGE && !isLocalStorageAvailable()) { 35 | fallbackToMemory(); 36 | log.error('Invalid or unavailable storage. Fallback into MEMORY storage'); 37 | } 38 | 39 | return { 40 | type, 41 | options, 42 | prefix, 43 | expirationDays, 44 | clearOnInit, 45 | __originalType 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /src/settings/storage/node.js: -------------------------------------------------------------------------------- 1 | import { LOCALHOST_MODE, STORAGE_MEMORY, STORAGE_REDIS, CONSUMER_MODE, STANDALONE_MODE } from '@splitsoftware/splitio-commons/src/utils/constants'; 2 | 3 | export function validateStorage(settings) { 4 | const { 5 | log, 6 | mode, 7 | storage: { 8 | type, 9 | options = {}, 10 | prefix 11 | } = { type: STORAGE_MEMORY } 12 | } = settings; 13 | 14 | // We can have MEMORY, REDIS or an invalid storage type 15 | switch (type) { 16 | case STORAGE_REDIS: { 17 | // If passing REDIS storage in localhost or standalone mode, we log an error and fallback to MEMORY storage 18 | if (mode === STANDALONE_MODE || mode === LOCALHOST_MODE) { 19 | log.error('The provided REDIS storage is invalid for this mode. It requires consumer mode. Fallback into default MEMORY storage.'); 20 | return { 21 | type: STORAGE_MEMORY, 22 | prefix 23 | }; 24 | } 25 | let { 26 | host, 27 | port, 28 | db, 29 | pass, 30 | url, 31 | tls, 32 | connectionTimeout, 33 | operationTimeout 34 | } = options; 35 | 36 | if (process.env.REDIS_HOST) 37 | host = process.env.REDIS_HOST; 38 | if (process.env.REDIS_PORT) 39 | port = process.env.REDIS_PORT; 40 | if (process.env.REDIS_DB) 41 | db = process.env.REDIS_DB; 42 | if (process.env.REDIS_PASS) 43 | pass = process.env.REDIS_PASS; 44 | if (process.env.REDIS_URL) 45 | url = process.env.REDIS_URL; 46 | 47 | const newOpts = { 48 | connectionTimeout, operationTimeout 49 | }; 50 | 51 | if (url) { 52 | newOpts.url = url; 53 | } else { 54 | newOpts.host = host; 55 | newOpts.port = port; 56 | newOpts.db = db; 57 | newOpts.pass = pass; 58 | } 59 | 60 | if (tls) { 61 | newOpts.tls = tls; 62 | } 63 | 64 | return { 65 | type, 66 | prefix, 67 | options: newOpts 68 | }; 69 | } 70 | 71 | // For now, we don't have modifiers or settings for MEMORY in Node.js 72 | case STORAGE_MEMORY: 73 | default: { 74 | // If passing MEMORY storage in consumer mode, throw an error (no way to fallback to REDIS storage) 75 | if (mode === CONSUMER_MODE) throw new Error('A REDIS storage is required on consumer mode'); 76 | // If passing an invalid storage type, log an error 77 | if (type !== STORAGE_MEMORY) log.error(`The provided '${type}' storage type is invalid. Fallback into default MEMORY storage.`); 78 | return { 79 | type: STORAGE_MEMORY, 80 | prefix 81 | }; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/sync/offline/LocalhostFromFile.js: -------------------------------------------------------------------------------- 1 | import { splitsParserFromFileFactory } from './splitsParserFromFile'; 2 | import { syncManagerOfflineFactory } from '@splitsoftware/splitio-commons/src/sync/offline/syncManagerOffline'; 3 | 4 | // Singleton instance of the factory function for offline SyncManager from YAML file 5 | // It uses Node.js APIs. 6 | export const localhostFromFileFactory = syncManagerOfflineFactory(splitsParserFromFileFactory); 7 | -------------------------------------------------------------------------------- /src/umd.js: -------------------------------------------------------------------------------- 1 | import { SplitFactory } from './index'; 2 | 3 | // eslint-disable-next-line import/no-default-export 4 | export default SplitFactory; 5 | -------------------------------------------------------------------------------- /src/utils/ip.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-redeclare */ 2 | /* 3 | Trimmed version of "ip" package (https://www.npmjs.com/package/ip) that fixes an error when running in Node.js v18. 4 | 5 | This software is licensed under the MIT License. 6 | 7 | Copyright Fedor Indutny, 2012. 8 | 9 | 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: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | 13 | 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. 14 | */ 15 | import os from 'os'; 16 | 17 | function _resolveFamily(family) { 18 | return typeof family === 'number' ? 'ipv' + family : family.toLowerCase(); 19 | } 20 | 21 | function _normalizeFamily(family) { 22 | return family ? _resolveFamily(family) : 'ipv4'; 23 | } 24 | 25 | function isPrivate(addr) { 26 | return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i 27 | .test(addr) || 28 | /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || 29 | /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i 30 | .test(addr) || 31 | /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || 32 | /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || 33 | /^f[cd][0-9a-f]{2}:/i.test(addr) || 34 | /^fe80:/i.test(addr) || 35 | /^::1$/.test(addr) || 36 | /^::$/.test(addr); 37 | } 38 | 39 | function isPublic(addr) { 40 | return !isPrivate(addr); 41 | } 42 | 43 | function isLoopback(addr) { 44 | return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/ 45 | .test(addr) || 46 | /^fe80::1$/.test(addr) || 47 | /^::1$/.test(addr) || 48 | /^::$/.test(addr); 49 | } 50 | 51 | function loopback(family) { 52 | // 53 | // Default to `ipv4` 54 | // 55 | family = _normalizeFamily(family); 56 | 57 | if (family !== 'ipv4' && family !== 'ipv6') { 58 | throw new Error('family must be ipv4 or ipv6'); 59 | } 60 | 61 | return family === 'ipv4' ? '127.0.0.1' : 'fe80::1'; 62 | } 63 | 64 | // 65 | // ### function address (name, family) 66 | // #### @name {string|'public'|'private'} **Optional** Name or security 67 | // of the network interface. 68 | // #### @family {ipv4|ipv6} **Optional** IP family of the address (defaults 69 | // to ipv4). 70 | // 71 | // Returns the address for the network interface on the current system with 72 | // the specified `name`: 73 | // * String: First `family` address of the interface. 74 | // If not found see `undefined`. 75 | // * 'public': the first public ip address of family. 76 | // * 'private': the first private ip address of family. 77 | // * undefined: First address with `ipv4` or loopback address `127.0.0.1`. 78 | // 79 | export function address(name, family) { 80 | var interfaces = os.networkInterfaces(); 81 | var all; 82 | 83 | // 84 | // Default to `ipv4` 85 | // 86 | family = _normalizeFamily(family); 87 | 88 | // 89 | // If a specific network interface has been named, 90 | // return the address. 91 | // 92 | if (name && name !== 'private' && name !== 'public') { 93 | var res = interfaces[name].filter(function (details) { 94 | var itemFamily = _resolveFamily(details.family); 95 | return itemFamily === family; 96 | }); 97 | if (res.length === 0) 98 | return undefined; 99 | return res[0].address; 100 | } 101 | 102 | var all = Object.keys(interfaces).map(function (nic) { 103 | // 104 | // Note: name will only be `public` or `private` 105 | // when this is called. 106 | // 107 | var addresses = interfaces[nic].filter(function (details) { 108 | details.family = _resolveFamily(details.family); 109 | if (details.family !== family || isLoopback(details.address)) { 110 | return false; 111 | } else if (!name) { 112 | return true; 113 | } 114 | 115 | return name === 'public' ? isPrivate(details.address) : 116 | isPublic(details.address); 117 | }); 118 | 119 | return addresses.length ? addresses[0].address : undefined; 120 | }).filter(Boolean); 121 | 122 | return !all.length ? loopback(family) : all[0]; 123 | } 124 | -------------------------------------------------------------------------------- /ts-node.register.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run tests and scripts in Node.js while transpiling typescript files from `@splitsoftware/splitio-commons/src` 3 | * https://www.npmjs.com/package/ts-node 4 | * 5 | * NOTE: can be used with `npm link @splitsoftware/splitio-commons` or `"@splitsoftware/splitio-commons": "file:../javascript-commons" without extra steps 6 | */ 7 | require('ts-node').register({ 8 | transpileOnly: true, // https://www.npmjs.com/package/ts-node#make-it-fast 9 | ignore: ['(?:^|/)node_modules/(?!@splitsoftware)'], // ignore transpiling node_modules except @splitsoftware (`ts-node` ignores node_modules by default) 10 | compilerOptions: { 11 | module: 'commonjs', // https://www.npmjs.com/package/ts-node#commonjs-vs-native-ecmascript-modules 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /ts-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "target": "es5", 5 | "module": "commonjs", 6 | "noEmit": true, 7 | }, 8 | "files": [ 9 | "index" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /types/client/index.d.ts: -------------------------------------------------------------------------------- 1 | // Declaration file for JavaScript Split Software SDK 2 | // Project: https://www.split.io/ 3 | 4 | import '@splitsoftware/splitio-commons'; 5 | 6 | export = JsSdk; 7 | 8 | declare module JsSdk { 9 | /** 10 | * Split.io SDK factory function. 11 | * The settings parameter should be an object that complies with the SplitIO.IBrowserSettings. 12 | * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#configuration} 13 | */ 14 | export function SplitFactory(settings: SplitIO.IBrowserSettings): SplitIO.IBrowserSDK; 15 | } 16 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // Declaration file for JavaScript and Node.js Split Software SDK v8.1.0 2 | // Project: https://www.split.io/ 3 | // Definitions by: Nico Zelaya 4 | 5 | /// 6 | 7 | export = JsSdk; 8 | 9 | declare module JsSdk { 10 | /** 11 | * Split.io SDK factory function. 12 | * The settings parameter should be an object that complies with the SplitIO.INodeAsyncSettings. 13 | * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#configuration} 14 | */ 15 | export function SplitFactory(settings: SplitIO.INodeAsyncSettings): SplitIO.IAsyncSDK; 16 | /** 17 | * Split.io SDK factory function. 18 | * The settings parameter should be an object that complies with the SplitIO.INodeSettings. 19 | * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#configuration} 20 | */ 21 | export function SplitFactory(settings: SplitIO.INodeSettings): SplitIO.ISDK; 22 | /** 23 | * Split.io SDK factory function. 24 | * The settings parameter should be an object that complies with the SplitIO.IBrowserSettings. 25 | * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#configuration} 26 | */ 27 | export function SplitFactory(settings: SplitIO.IBrowserSettings): SplitIO.IBrowserSDK; 28 | } 29 | -------------------------------------------------------------------------------- /types/server/index.d.ts: -------------------------------------------------------------------------------- 1 | // Declaration file for JavaScript Split Software SDK 2 | // Project: https://www.split.io/ 3 | 4 | import '@splitsoftware/splitio-commons'; 5 | 6 | export = JsSdk; 7 | 8 | declare module JsSdk { 9 | /** 10 | * Split.io SDK factory function. 11 | * The settings parameter should be an object that complies with the SplitIO.INodeAsyncSettings. 12 | * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#configuration} 13 | */ 14 | export function SplitFactory(settings: SplitIO.INodeAsyncSettings): SplitIO.IAsyncSDK; 15 | /** 16 | * Split.io SDK factory function. 17 | * The settings parameter should be an object that complies with the SplitIO.INodeSettings. 18 | * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#configuration} 19 | */ 20 | export function SplitFactory(settings: SplitIO.INodeSettings): SplitIO.ISDK; 21 | } 22 | -------------------------------------------------------------------------------- /types/splitio.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for JavaScript and NodeJS Split Software SDK 2 | 3 | import '@splitsoftware/splitio-commons'; 4 | 5 | export as namespace SplitIO; 6 | export = SplitIO; 7 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: { 3 | split: ['./esm/umd.js'] 4 | }, 5 | 6 | output: { 7 | path: __dirname + '/umd', 8 | library: 'splitio', 9 | libraryTarget: 'umd', 10 | libraryExport: 'default' 11 | }, 12 | devtool: false, // Remove source mapping. 'eval' is used by default in Webpack 5 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.(ts|js)$/, 17 | 18 | exclude: /node_modules/, 19 | use: { 20 | loader: 'ts-loader' 21 | } 22 | 23 | /* 24 | // Use next configuration to bundle from entry './src/umd.js' 25 | 26 | exclude: /node_modules[/](?!@splitsoftware)/, // Cannot exclude 'node_modules/@splitsoftware/splitio-commons/src', in order to process TS files 27 | use: { 28 | loader: 'ts-loader', 29 | options: { allowTsInNodeModules: true } // https://github.com/TypeStrong/ts-loader#allowtsinnodemodules 30 | } 31 | */ 32 | } 33 | ] 34 | }, 35 | resolve: { 36 | extensions: ['.ts', '.js'] 37 | }, 38 | node: false, // Not include Node polyfills, https://webpack.js.org/configuration/node 39 | target: ['web', 'es5'], // target 'es5', since 'es2015' is the default in Webpack 5 40 | }; 41 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | const pkg = require('./package.json'); 4 | 5 | const VERSION = pkg.version; 6 | 7 | module.exports = env => merge(common, { 8 | mode: 'development', 9 | output: { 10 | filename: `[name]${env.branch !== 'master' ? `-dev-${VERSION}` : `-${VERSION}`}.js` 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | const pkg = require('./package.json'); 4 | 5 | const VERSION = pkg.version; 6 | 7 | module.exports = env => merge(common, { 8 | mode: 'production', 9 | output: { 10 | filename: `[name]${env.branch !== 'master' ? `-dev-${VERSION}` : `-${VERSION}`}.min.js` 11 | }, 12 | performance: { 13 | hints: 'error', // build fails if asset size exceeded 14 | maxAssetSize: 138249 // 135KiB size limit 15 | } 16 | }); 17 | --------------------------------------------------------------------------------