├── .nvmrc ├── .gitattributes ├── packages ├── test-helpers │ ├── src │ │ ├── utils │ │ │ ├── index.ts │ │ │ └── sleep.ts │ │ ├── analytics │ │ │ └── index.ts │ │ └── index.ts │ ├── .lintstagedrc.js │ ├── jest.config.js │ ├── .eslintrc.js │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── package.json ├── generic-utils │ ├── src │ │ ├── emitter │ │ │ └── index.ts │ │ ├── create-deferred │ │ │ ├── index.ts │ │ │ └── create-deferred.ts │ │ └── index.ts │ ├── .lintstagedrc.js │ ├── jest.config.js │ ├── .eslintrc.js │ ├── tsconfig.build.json │ ├── README.md │ ├── tsconfig.json │ └── LICENSE ├── consent │ ├── consent-tools-integration-tests │ │ ├── .gitignore │ │ ├── .lintstagedrc.js │ │ ├── .eslintrc.js │ │ ├── src │ │ │ ├── types │ │ │ │ └── analytics.d.ts │ │ │ ├── page-objects │ │ │ │ ├── consent-tools-vanilla.ts │ │ │ │ └── onetrust.ts │ │ │ ├── page-bundles │ │ │ │ └── consent-tools-vanilla │ │ │ │ │ └── index.ts │ │ │ └── tests │ │ │ │ ├── consent-tools-vanilla.test.ts │ │ │ │ └── onetrust.test.ts │ │ ├── public │ │ │ └── consent-tools-vanilla.html │ │ ├── tsconfig.json │ │ └── README.md │ ├── consent-tools │ │ ├── src │ │ │ ├── domain │ │ │ │ ├── validation │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── validation-error.test.ts │ │ │ │ │ ├── validation-error.ts │ │ │ │ │ └── common-validators.ts │ │ │ │ ├── analytics │ │ │ │ │ └── index.ts │ │ │ │ ├── disable-segment.ts │ │ │ │ ├── __tests__ │ │ │ │ │ ├── typedef-tests.ts │ │ │ │ │ └── assertions │ │ │ │ │ │ └── integrations-assertions.ts │ │ │ │ ├── consent-stamping.ts │ │ │ │ └── load-cancellation.ts │ │ │ ├── test-helpers │ │ │ │ └── mocks │ │ │ │ │ ├── index.ts │ │ │ │ │ └── analytics-mock.ts │ │ │ ├── types │ │ │ │ ├── index.ts │ │ │ │ └── errors.ts │ │ │ ├── utils │ │ │ │ ├── uniq.ts │ │ │ │ ├── index.ts │ │ │ │ ├── pick.ts │ │ │ │ ├── pipe.ts │ │ │ │ ├── ts-helpers.ts │ │ │ │ └── resolve-when.ts │ │ │ └── index.ts │ │ ├── .lintstagedrc.js │ │ ├── .eslintrc.js │ │ ├── jest.config.js │ │ ├── jest.setup.js │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ └── LICENSE │ ├── consent-wrapper-onetrust │ │ ├── src │ │ │ ├── lib │ │ │ │ └── validation │ │ │ │ │ ├── index.ts │ │ │ │ │ └── onetrust-api-error.ts │ │ │ ├── index.ts │ │ │ ├── test-helpers │ │ │ │ ├── onetrust-globals.d.ts │ │ │ │ └── utils.ts │ │ │ └── index.umd.ts │ │ ├── .lintstagedrc.js │ │ ├── img │ │ │ ├── onetrust-popup.jpg │ │ │ ├── consent-mgmt-ui.png │ │ │ └── onetrust-cat-id.jpg │ │ ├── .eslintrc.js │ │ ├── jest.config.js │ │ ├── jest.setup.js │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.config.js │ │ └── LICENSE │ └── README.md ├── node │ ├── src │ │ ├── lib │ │ │ ├── uuid.ts │ │ │ ├── base-64-encode.ts │ │ │ ├── create-url.ts │ │ │ ├── get-message-id.ts │ │ │ ├── fetch.ts │ │ │ ├── __tests__ │ │ │ │ └── get-message-id.test.ts │ │ │ └── env.ts │ │ ├── generated │ │ │ └── version.ts │ │ ├── app │ │ │ ├── types │ │ │ │ ├── index.ts │ │ │ │ ├── plugin.ts │ │ │ │ └── segment-event.ts │ │ │ ├── context.ts │ │ │ ├── event-queue.ts │ │ │ ├── emitter.ts │ │ │ └── event-factory.ts │ │ ├── __tests__ │ │ │ ├── test-helpers │ │ │ │ ├── assert-shape │ │ │ │ │ ├── index.ts │ │ │ │ │ └── http-request-event.ts │ │ │ │ ├── sleep.ts │ │ │ │ ├── is-valid-date.ts │ │ │ │ ├── resolve-emitter.ts │ │ │ │ ├── factories.ts │ │ │ │ ├── test-plugin.ts │ │ │ │ └── resolve-ctx.ts │ │ │ ├── settings.test.ts │ │ │ └── plugins.test.ts │ │ ├── index.ts │ │ └── index.common.ts │ ├── .lintstagedrc.js │ ├── jest.config.js │ ├── jest.setup.js │ ├── tsconfig.build.json │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── scripts │ │ └── version.sh │ └── LICENSE ├── core │ ├── .lintstagedrc.js │ ├── test-helpers │ │ ├── index.ts │ │ ├── test-event-queue.ts │ │ └── test-ctx.ts │ ├── jest.config.js │ ├── .eslintrc.js │ ├── src │ │ ├── user │ │ │ └── index.ts │ │ ├── validation │ │ │ ├── errors.ts │ │ │ └── helpers.ts │ │ ├── utils │ │ │ ├── has-properties.ts │ │ │ ├── pick.ts │ │ │ ├── is-thenable.ts │ │ │ ├── p-while.ts │ │ │ ├── ts-helpers.ts │ │ │ ├── get-global.ts │ │ │ ├── bind-all.ts │ │ │ ├── group-by.ts │ │ │ ├── is-plain-object.ts │ │ │ └── __tests__ │ │ │ │ ├── is-thenable.test.ts │ │ │ │ └── is-plain-object.test.ts │ │ ├── analytics │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── task │ │ │ ├── __tests__ │ │ │ │ └── task-group.test.ts │ │ │ └── task-group.ts │ │ ├── priority-queue │ │ │ ├── backoff.ts │ │ │ └── __tests__ │ │ │ │ └── backoff.test.ts │ │ └── emitter │ │ │ └── interface.ts │ ├── README.md │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── jest.setup.js │ └── LICENSE.MD ├── browser │ ├── .lintstagedrc.js │ ├── src │ │ ├── core │ │ │ ├── constants │ │ │ │ └── index.ts │ │ │ ├── callback │ │ │ │ └── index.ts │ │ │ ├── page │ │ │ │ └── index.ts │ │ │ ├── environment │ │ │ │ └── index.ts │ │ │ ├── connection │ │ │ │ ├── index.ts │ │ │ │ └── __tests__ │ │ │ │ │ └── index.test.ts │ │ │ ├── stats │ │ │ │ ├── __tests__ │ │ │ │ │ └── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ └── metric-helpers.ts │ │ │ ├── plugin │ │ │ │ └── index.ts │ │ │ ├── query-string │ │ │ │ ├── gracefulDecodeURIComponent.ts │ │ │ │ ├── __tests__ │ │ │ │ │ ├── gracefulDecodeURIComponent.test.ts │ │ │ │ │ └── pickPrefix.test.ts │ │ │ │ └── pickPrefix.ts │ │ │ ├── inspector │ │ │ │ └── index.ts │ │ │ ├── storage │ │ │ │ ├── memoryStorage.ts │ │ │ │ ├── settings.ts │ │ │ │ └── __tests__ │ │ │ │ │ └── test-helpers.ts │ │ │ ├── context │ │ │ │ └── index.ts │ │ │ ├── events │ │ │ │ └── interfaces.ts │ │ │ └── queue │ │ │ │ └── event-queue.ts │ │ ├── generated │ │ │ ├── version.ts │ │ │ └── __tests__ │ │ │ │ └── version.test.ts │ │ ├── lib │ │ │ ├── sleep.ts │ │ │ ├── priority-queue │ │ │ │ ├── index.ts │ │ │ │ ├── backoff.ts │ │ │ │ └── __tests__ │ │ │ │ │ └── backoff.test.ts │ │ │ ├── __tests__ │ │ │ │ ├── get-process-env.test.ts │ │ │ │ ├── embedded-write-key.test.ts │ │ │ │ ├── is-thenable.test.ts │ │ │ │ ├── pick.test.ts │ │ │ │ └── pick.typedef.ts │ │ │ ├── is-thenable.ts │ │ │ ├── version-type.ts │ │ │ ├── fetch.ts │ │ │ ├── p-while.ts │ │ │ ├── get-process-env.ts │ │ │ ├── get-global.ts │ │ │ ├── client-hints │ │ │ │ └── index.ts │ │ │ ├── bind-all.ts │ │ │ ├── embedded-write-key.ts │ │ │ ├── is-plan-event-enabled.ts │ │ │ ├── pick.ts │ │ │ ├── browser-polyfill.ts │ │ │ ├── group-by.ts │ │ │ ├── csp-detection.ts │ │ │ ├── global-analytics-helper.ts │ │ │ ├── to-facade.ts │ │ │ └── on-page-change.ts │ │ ├── test-helpers │ │ │ ├── fixtures │ │ │ │ ├── index.ts │ │ │ │ ├── page-context.ts │ │ │ │ ├── client-hints.ts │ │ │ │ ├── classic-destination.ts │ │ │ │ └── create-fetch-method.ts │ │ │ ├── fetch-parse.ts │ │ │ ├── test-writekeys.ts │ │ │ ├── type-assertions.ts │ │ │ └── factories.ts │ │ ├── tester │ │ │ ├── __fixtures__ │ │ │ │ └── index.html │ │ │ ├── server.js │ │ │ └── ajs-perf.ts │ │ ├── node │ │ │ ├── node.browser.ts │ │ │ └── __tests__ │ │ │ │ └── node-integration.test.ts │ │ ├── browser │ │ │ ├── standalone-interface.ts │ │ │ └── browser-umd.ts │ │ ├── index.ts │ │ └── plugins │ │ │ ├── legacy-video-plugins │ │ │ └── index.ts │ │ │ ├── segmentio │ │ │ └── fetch-dispatcher.ts │ │ │ ├── routing-middleware │ │ │ └── index.ts │ │ │ └── validation │ │ │ └── index.ts │ ├── scripts │ │ ├── build-prep.sh │ │ ├── run.sh │ │ ├── ci.sh │ │ └── release.sh │ ├── tsconfig.build.json │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── jest.config.js │ ├── jest.setup.js │ ├── qa │ │ ├── lib │ │ │ ├── browser.ts │ │ │ └── stats.ts │ │ └── README.md │ ├── e2e-tests │ │ └── local-server.ts │ └── LICENSE.MD ├── browser-integration-tests │ ├── .lintstagedrc.js │ ├── .eslintrc.js │ ├── jest.config.js │ ├── src │ │ ├── shims.d.ts │ │ └── helpers │ │ │ └── extract-writekey.ts │ ├── README.md │ ├── tsconfig.json │ └── package.json ├── core-integration-tests │ ├── .lintstagedrc.js │ ├── .eslintrc.js │ ├── jest.config.js │ ├── src │ │ ├── public-api.test.ts │ │ └── typedef-tests.ts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── node-integration-tests │ ├── .lintstagedrc.js │ ├── .eslintrc.js │ ├── jest.config.js │ ├── src │ │ ├── server │ │ │ ├── types.ts │ │ │ ├── fetch-polyfill.ts │ │ │ ├── fixtures.ts │ │ │ ├── nock.ts │ │ │ └── autocannon.ts │ │ ├── cloudflare-tests │ │ │ └── workers │ │ │ │ ├── README.md │ │ │ │ ├── forgot-close-and-flush.ts │ │ │ │ ├── send-single-event.ts │ │ │ │ ├── send-multiple-events.ts │ │ │ │ └── send-each-event-type.ts │ │ ├── perf-tests │ │ │ ├── server-start-no-analytics.ts │ │ │ ├── server-start-old-analytics.ts │ │ │ └── server-start-analytics.ts │ │ ├── smoke │ │ │ └── smoke.ts │ │ └── durability-tests │ │ │ ├── server-start-analytics.ts │ │ │ └── durability-tests.ts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── config │ ├── src │ │ ├── lint-staged │ │ │ └── config.js │ │ └── index.js │ └── package.json └── config-webpack │ ├── README.md │ └── package.json ├── playgrounds ├── with-vite │ ├── src │ │ ├── vite-env.d.ts │ │ ├── main.tsx │ │ ├── index.css │ │ └── App.css │ ├── vite.config.ts │ ├── tsconfig.node.json │ ├── .gitignore │ ├── index.html │ ├── tsconfig.json │ └── package.json ├── standalone-playground │ ├── README.md │ ├── index.html │ └── package.json ├── README.md └── next-playground │ ├── public │ ├── fira-code.webfont │ │ ├── fira-code_regular.eot │ │ ├── fira-code_regular.ttf │ │ ├── fira-code_regular.woff │ │ └── webfont.css │ └── demos │ │ ├── faulty-load.js │ │ ├── faulty-track.js │ │ ├── faulty-middleware.js │ │ ├── signals.js │ │ └── identity.js │ ├── .eslintrc.js │ ├── next-env.d.ts │ ├── pages │ ├── _app.tsx │ ├── iframe │ │ ├── childPage.tsx │ │ └── index.tsx │ └── vanilla │ │ ├── other-page.tsx │ │ └── index.tsx │ ├── utils │ └── hooks │ │ ├── useConfig.ts │ │ └── useDidMountEffect.ts │ ├── .lintstagedrc.js │ ├── styles │ ├── dracula │ │ ├── card.css │ │ ├── badge.css │ │ ├── dracula-ui.css │ │ ├── typography.css │ │ └── avatar.css │ ├── logs-table.css │ └── globals.css │ ├── README.md │ ├── .gitignore │ ├── next.config.js │ ├── tsconfig.json │ └── package.json ├── example.png ├── .husky ├── pre-commit └── pre-push ├── scripts ├── .lintstagedrc.js ├── env.d.ts ├── jest.config.js ├── .eslintrc.js ├── create-release-from-tags │ ├── run.ts │ └── __tests__ │ │ └── fixtures │ │ ├── first-release-example.md │ │ └── reg-example.md ├── clean.sh ├── utils │ └── exists.ts ├── tsconfig.json ├── update-lockfile.sh └── package.json ├── .github ├── CODEOWNERS ├── architecture.png ├── workflows │ ├── md-link-check.config.json │ ├── md-link-check.yml │ ├── create-github-release.yml │ └── release-creator.yml └── PULL_REQUEST_TEMPLATE ├── img └── twilio-segment-logo-2x.png ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── .editorconfig ├── typings ├── get-monorepo-packages.d.ts └── spawn.d.ts ├── .buildkite ├── Makefile └── Readme.md ├── .changeset ├── config.json └── README.md ├── .yarnrc.yml ├── tsconfig.json ├── .gitignore ├── .eslintrc.isomorphic.js ├── jest.config.js ├── CONTRIBUTING.md ├── turbo.json └── DEVELOPMENT.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.16 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /packages/test-helpers/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sleep' 2 | -------------------------------------------------------------------------------- /packages/generic-utils/src/emitter/index.ts: -------------------------------------------------------------------------------- 1 | export * from './emitter' 2 | -------------------------------------------------------------------------------- /packages/consent/consent-tools-integration-tests/.gitignore: -------------------------------------------------------------------------------- 1 | driver-logs 2 | -------------------------------------------------------------------------------- /packages/node/src/lib/uuid.ts: -------------------------------------------------------------------------------- 1 | export { v4 as uuid } from '@lukeed/uuid' 2 | -------------------------------------------------------------------------------- /playgrounds/with-vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATTRIBUTION/analytics-next/master/example.png -------------------------------------------------------------------------------- /packages/test-helpers/src/analytics/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cdn-settings-builder' 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /packages/generic-utils/src/create-deferred/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-deferred' 2 | -------------------------------------------------------------------------------- /scripts/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /packages/core/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /packages/node/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /packages/test-helpers/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils' 2 | export * from './analytics' 3 | -------------------------------------------------------------------------------- /packages/browser/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /packages/browser/src/core/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const SEGMENT_API_HOST = 'api.segment.io/v1' 2 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/domain/validation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './options-validators' 2 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/test-helpers/mocks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './analytics-mock' 2 | -------------------------------------------------------------------------------- /packages/generic-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-deferred' 2 | export * from './emitter' 3 | -------------------------------------------------------------------------------- /packages/browser/src/generated/version.ts: -------------------------------------------------------------------------------- 1 | // This file is generated. 2 | export const version = '1.66.0' 3 | -------------------------------------------------------------------------------- /packages/core/test-helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './test-ctx' 2 | export * from './test-event-queue' 3 | -------------------------------------------------------------------------------- /packages/generic-utils/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /packages/node/src/generated/version.ts: -------------------------------------------------------------------------------- 1 | // This file is generated. 2 | export const version = '2.1.0' 3 | -------------------------------------------------------------------------------- /packages/test-helpers/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | @libraries-web-team 2 | @nettofarah 3 | @juliofarah 4 | @danieljackins 5 | @pooyaj 6 | @dk1027 -------------------------------------------------------------------------------- /.github/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATTRIBUTION/analytics-next/master/.github/architecture.png -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | CI=true yarn test --colors --silent 5 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/src/lib/validation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './onetrust-api-error' 2 | -------------------------------------------------------------------------------- /packages/browser-integration-tests/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /packages/browser/src/core/callback/index.ts: -------------------------------------------------------------------------------- 1 | export { invokeCallback, pTimeout } from '@segment/analytics-core' 2 | -------------------------------------------------------------------------------- /packages/browser/src/core/page/index.ts: -------------------------------------------------------------------------------- 1 | export * from './get-page-context' 2 | export * from './add-page-context' 3 | -------------------------------------------------------------------------------- /packages/core-integration-tests/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /packages/node-integration-tests/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/domain/analytics/index.ts: -------------------------------------------------------------------------------- 1 | export { AnalyticsService } from './analytics-service' 2 | -------------------------------------------------------------------------------- /img/twilio-segment-logo-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATTRIBUTION/analytics-next/master/img/twilio-segment-logo-2x.png -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /scripts/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /packages/consent/consent-tools-integration-tests/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@internal/config").lintStagedConfig 2 | -------------------------------------------------------------------------------- /packages/node/src/app/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './params' 2 | export * from './segment-event' 3 | export * from './plugin' 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 80, 5 | "tabWidth": 2, 6 | "arrowParens": "always" 7 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "wix.vscode-import-cost"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './wrapper' 2 | export * from './settings' 3 | export * from './errors' 4 | -------------------------------------------------------------------------------- /packages/node/src/__tests__/test-helpers/assert-shape/index.ts: -------------------------------------------------------------------------------- 1 | export * from './http-request-event' 2 | export * from './segment-http-api' 3 | -------------------------------------------------------------------------------- /scripts/jest.config.js: -------------------------------------------------------------------------------- 1 | const { createJestTSConfig } = require('@internal/config') 2 | 3 | module.exports = createJestTSConfig(__dirname) 4 | -------------------------------------------------------------------------------- /packages/core/jest.config.js: -------------------------------------------------------------------------------- 1 | const { createJestTSConfig } = require('@internal/config') 2 | 3 | module.exports = createJestTSConfig(__dirname) 4 | -------------------------------------------------------------------------------- /packages/config/src/lint-staged/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{js,jsx,ts,tsx}': ['eslint --fix'], 3 | '*.json*': ['prettier --write'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/core/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../../.eslintrc.isomorphic'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/generic-utils/jest.config.js: -------------------------------------------------------------------------------- 1 | const { createJestTSConfig } = require('@internal/config') 2 | 3 | module.exports = createJestTSConfig(__dirname) 4 | -------------------------------------------------------------------------------- /packages/test-helpers/jest.config.js: -------------------------------------------------------------------------------- 1 | const { createJestTSConfig } = require('@internal/config') 2 | 3 | module.exports = createJestTSConfig(__dirname) 4 | -------------------------------------------------------------------------------- /packages/core-integration-tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../../.eslintrc'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/generic-utils/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../../.eslintrc.isomorphic'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/node-integration-tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../../.eslintrc'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/browser-integration-tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../../.eslintrc'], 4 | } 5 | -------------------------------------------------------------------------------- /packages/browser-integration-tests/jest.config.js: -------------------------------------------------------------------------------- 1 | const { createJestTSConfig } = require('@internal/config') 2 | 3 | module.exports = createJestTSConfig(__dirname) 4 | -------------------------------------------------------------------------------- /packages/browser/src/lib/sleep.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (time: number): Promise => 2 | new Promise((resolve) => { 3 | setTimeout(resolve, time) 4 | }) 5 | -------------------------------------------------------------------------------- /packages/core-integration-tests/jest.config.js: -------------------------------------------------------------------------------- 1 | const { createJestTSConfig } = require('@internal/config') 2 | 3 | module.exports = createJestTSConfig(__dirname) 4 | -------------------------------------------------------------------------------- /packages/node-integration-tests/jest.config.js: -------------------------------------------------------------------------------- 1 | const { createJestTSConfig } = require('@internal/config') 2 | 3 | module.exports = createJestTSConfig(__dirname) 4 | -------------------------------------------------------------------------------- /playgrounds/standalone-playground/README.md: -------------------------------------------------------------------------------- 1 | This is a testing playground for the standalone "snippet" version of the app. 2 | 3 | Run: 4 | ``` 5 | yarn start 6 | ``` -------------------------------------------------------------------------------- /packages/test-helpers/src/utils/sleep.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (time: number): Promise => 2 | new Promise((resolve) => { 3 | setTimeout(resolve, time) 4 | }) 5 | -------------------------------------------------------------------------------- /playgrounds/README.md: -------------------------------------------------------------------------------- 1 | # Application Playground 2 | 3 | These applications are not meant to be vanilla examples. Please refer to the individual READMEs for more information. 4 | -------------------------------------------------------------------------------- /scripts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../.eslintrc'], 4 | env: { 5 | node: true, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /packages/browser/src/lib/priority-queue/index.ts: -------------------------------------------------------------------------------- 1 | import { PriorityQueue, ON_REMOVE_FROM_FUTURE } from '@segment/analytics-core' 2 | 3 | export { PriorityQueue, ON_REMOVE_FROM_FUTURE } 4 | -------------------------------------------------------------------------------- /packages/config/src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | createJestTSConfig: require('./jest/config').createJestTSConfig, 3 | lintStagedConfig: require('./lint-staged/config'), 4 | } 5 | -------------------------------------------------------------------------------- /packages/node-integration-tests/src/server/types.ts: -------------------------------------------------------------------------------- 1 | export interface ServerReport { 2 | totalBatchEvents: number 3 | totalApiRequests: number 4 | averagePerBatch: number 5 | } 6 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/utils/uniq.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This function removes duplicates from an array 3 | */ 4 | export const uniq = (arr: T[]): T[] => Array.from(new Set(arr)) 5 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/img/onetrust-popup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATTRIBUTION/analytics-next/master/packages/consent/consent-wrapper-onetrust/img/onetrust-popup.jpg -------------------------------------------------------------------------------- /packages/core/src/user/index.ts: -------------------------------------------------------------------------------- 1 | export type ID = string | null | undefined 2 | 3 | // TODO: this is a base user 4 | export interface User { 5 | id(): ID 6 | anonymousId(): ID 7 | } 8 | -------------------------------------------------------------------------------- /packages/node/src/__tests__/test-helpers/sleep.ts: -------------------------------------------------------------------------------- 1 | export function sleep(timeoutInMs: number): Promise { 2 | return new Promise((resolve) => setTimeout(resolve, timeoutInMs)) 3 | } 4 | -------------------------------------------------------------------------------- /packages/test-helpers/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../../.eslintrc'], 4 | env: { 5 | node: true, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './pipe' 2 | export * from './resolve-when' 3 | export * from './uniq' 4 | export * from './pick' 5 | export * from './ts-helpers' 6 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/img/consent-mgmt-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATTRIBUTION/analytics-next/master/packages/consent/consent-wrapper-onetrust/img/consent-mgmt-ui.png -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/img/onetrust-cat-id.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATTRIBUTION/analytics-next/master/packages/consent/consent-wrapper-onetrust/img/onetrust-cat-id.jpg -------------------------------------------------------------------------------- /packages/node/jest.config.js: -------------------------------------------------------------------------------- 1 | const { createJestTSConfig } = require('@internal/config') 2 | 3 | module.exports = createJestTSConfig(__dirname, { 4 | setupFilesAfterEnv: ['./jest.setup.js'], 5 | }) 6 | -------------------------------------------------------------------------------- /packages/browser/src/test-helpers/fixtures/index.ts: -------------------------------------------------------------------------------- 1 | export * from './page-context' 2 | export * from './create-fetch-method' 3 | export * from './classic-destination' 4 | export * from './cdn-settings' 5 | -------------------------------------------------------------------------------- /packages/browser/src/tester/__fixtures__/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 🍻 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/config-webpack/README.md: -------------------------------------------------------------------------------- 1 | # @internal/config-webpack 2 | 3 | This package is for sharing basic webpack configuration / browser support between all of the analytics.js artifacts in this monorepo. 4 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../../../.eslintrc'], 4 | env: { 5 | browser: true, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /playgrounds/next-playground/public/fira-code.webfont/fira-code_regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATTRIBUTION/analytics-next/master/playgrounds/next-playground/public/fira-code.webfont/fira-code_regular.eot -------------------------------------------------------------------------------- /playgrounds/next-playground/public/fira-code.webfont/fira-code_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATTRIBUTION/analytics-next/master/playgrounds/next-playground/public/fira-code.webfont/fira-code_regular.ttf -------------------------------------------------------------------------------- /packages/browser-integration-tests/src/shims.d.ts: -------------------------------------------------------------------------------- 1 | import type { AnalyticsSnippet } from '@segment/analytics-next' 2 | 3 | declare global { 4 | interface Window { 5 | analytics: AnalyticsSnippet 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/browser/src/core/environment/index.ts: -------------------------------------------------------------------------------- 1 | export function isBrowser(): boolean { 2 | return typeof window !== 'undefined' 3 | } 4 | 5 | export function isServer(): boolean { 6 | return !isBrowser() 7 | } 8 | -------------------------------------------------------------------------------- /playgrounds/next-playground/public/fira-code.webfont/fira-code_regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATTRIBUTION/analytics-next/master/playgrounds/next-playground/public/fira-code.webfont/fira-code_regular.woff -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../../../.eslintrc'], 4 | env: { 5 | browser: true, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /packages/node/src/__tests__/test-helpers/is-valid-date.ts: -------------------------------------------------------------------------------- 1 | export const isValidDate = (date: string) => { 2 | if (!date) { 3 | throw new Error('no date found.') 4 | } 5 | return !isNaN(Date.parse(date)) 6 | } 7 | -------------------------------------------------------------------------------- /packages/consent/consent-tools-integration-tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../../../.eslintrc'], 4 | env: { 5 | browser: true, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /playgrounds/with-vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /packages/browser-integration-tests/README.md: -------------------------------------------------------------------------------- 1 | Core tests that require AnalyticsBrowser, etc. 2 | This exists because we can't create circular dependencies -- so, for example, installing AnalyticsBrowser as a dev dependency on core. -------------------------------------------------------------------------------- /packages/browser/src/lib/__tests__/get-process-env.test.ts: -------------------------------------------------------------------------------- 1 | import { getProcessEnv } from '../get-process-env' 2 | 3 | it('it matches the contents of process.env', () => { 4 | expect(getProcessEnv()).toBe(process.env) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/browser/src/node/node.browser.ts: -------------------------------------------------------------------------------- 1 | export class AnalyticsNode { 2 | static load(): Promise { 3 | return Promise.reject( 4 | new Error('AnalyticsNode is not available in browsers.') 5 | ) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /scripts/create-release-from-tags/run.ts: -------------------------------------------------------------------------------- 1 | import { createReleaseFromTags, getConfig } from '.' 2 | 3 | async function run() { 4 | const config = await getConfig() 5 | return createReleaseFromTags(config) 6 | } 7 | 8 | void run() 9 | -------------------------------------------------------------------------------- /packages/consent/consent-tools-integration-tests/src/types/analytics.d.ts: -------------------------------------------------------------------------------- 1 | import type { AnalyticsSnippet } from '@segment/analytics-next' 2 | 3 | declare global { 4 | interface Window { 5 | analytics: AnalyticsSnippet 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/src/validation/errors.ts: -------------------------------------------------------------------------------- 1 | export class ValidationError extends Error { 2 | field: string 3 | 4 | constructor(field: string, message: string) { 5 | super(`${field} ${message}`) 6 | this.field = field 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/test-helpers/test-event-queue.ts: -------------------------------------------------------------------------------- 1 | import { CoreEventQueue, PriorityQueue } from '../src' 2 | 3 | export class TestEventQueue extends CoreEventQueue { 4 | constructor() { 5 | super(new PriorityQueue(4, [])) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/node/jest.setup.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch') 2 | 3 | // eslint-disable-next-line no-undef 4 | globalThis.fetch = fetch // polyfill fetch so nock will work correctly on node 18 (https://github.com/nock/nock/issues/2336) 5 | -------------------------------------------------------------------------------- /packages/node/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './index.common' 2 | 3 | // export Analytics as both a named export and a default export (for backwards-compat. reasons) 4 | import { Analytics } from './index.common' 5 | export default Analytics 6 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # @segment/analytics-core 2 | 3 | This package represents core 'shared' functionality that is shared by analytics packages. This is not designed to be used directly, but internal to analytics-node and analytics-browser. 4 | -------------------------------------------------------------------------------- /packages/core/test-helpers/test-ctx.ts: -------------------------------------------------------------------------------- 1 | import { CoreContext } from '../src/context' 2 | 3 | export class TestCtx extends CoreContext { 4 | static override system() { 5 | return new this({ type: 'track', event: 'system' }) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/jest.config.js: -------------------------------------------------------------------------------- 1 | const { createJestTSConfig } = require('@internal/config') 2 | 3 | module.exports = createJestTSConfig(__dirname, { 4 | testEnvironment: 'jsdom', 5 | setupFilesAfterEnv: ['./jest.setup.js'], 6 | }) 7 | -------------------------------------------------------------------------------- /playgrounds/next-playground/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../../.eslintrc', 'plugin:@next/next/recommended'], 4 | env: { 5 | browser: true, 6 | node: true, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/jest.setup.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch') 2 | 3 | // eslint-disable-next-line no-undef 4 | globalThis.fetch = fetch // polyfill fetch so nock will work correctly on node 18 (https://github.com/nock/nock/issues/2336) 5 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/jest.config.js: -------------------------------------------------------------------------------- 1 | const { createJestTSConfig } = require('@internal/config') 2 | 3 | module.exports = createJestTSConfig(__dirname, { 4 | testEnvironment: 'jsdom', 5 | setupFilesAfterEnv: ['./jest.setup.js'], 6 | }) 7 | -------------------------------------------------------------------------------- /packages/node/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["**/__tests__/**"], 5 | "compilerOptions": { 6 | "outDir": "./dist/esm", 7 | "declarationDir": "./dist/types" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /playgrounds/next-playground/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /packages/browser/scripts/build-prep.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PKG_VERSION=$(node --eval="process.stdout.write(require('./package.json').version)") 3 | 4 | cat <src/generated/version.ts 5 | // This file is generated. 6 | export const version = '$PKG_VERSION' 7 | EOF 8 | -------------------------------------------------------------------------------- /packages/config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@internal/config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./src", 6 | "packageManager": "yarn@3.4.1", 7 | "devDependencies": { 8 | "app-root-path": "^3.1.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /playgrounds/with-vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "module": "esnext", 6 | "moduleResolution": "node" 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/jest.setup.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch') 2 | 3 | // eslint-disable-next-line no-undef 4 | globalThis.fetch = fetch // polyfill fetch so nock will work correctly on node 18 (https://github.com/nock/nock/issues/2336) 5 | -------------------------------------------------------------------------------- /typings/get-monorepo-packages.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'get-monorepo-packages' { 2 | export default function getPackages(pathToRoot: string): { 3 | location: string 4 | package: { 5 | name: string 6 | version: string 7 | } 8 | }[] 9 | } 10 | -------------------------------------------------------------------------------- /packages/browser/scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Allow the service to run without chamber (for ci, docker-compose, etc) 4 | if [ -z "$NO_CHAMBER" ];then 5 | exec chamber exec analytics-next -- node dist/src/boot.js 6 | else 7 | exec node dist/src/boot.js 8 | fi; 9 | -------------------------------------------------------------------------------- /packages/core/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["**/__tests__/**", "**/*.test.*"], 5 | "compilerOptions": { 6 | "outDir": "./dist/esm", 7 | "declarationDir": "./dist/types" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/node/src/app/types/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { CorePlugin } from '@segment/analytics-core' 2 | import type { Analytics } from '../analytics-node' 3 | import type { Context } from '../context' 4 | 5 | export interface Plugin extends CorePlugin {} 6 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["**/__tests__/**"], 5 | "compilerOptions": { 6 | "outDir": "./dist/esm", 7 | "declarationDir": "./dist/types" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/node-integration-tests/src/cloudflare-tests/workers/README.md: -------------------------------------------------------------------------------- 1 | These workers can also be ran directly via: 2 | 3 | ``` 4 | npx wrangler dev ./src/cloudflare/workers/ 5 | ``` 6 | 7 | This can be useful if you need to debug a worker with dev tools. 8 | -------------------------------------------------------------------------------- /.buildkite/Makefile: -------------------------------------------------------------------------------- 1 | ECR_REPOSITORY = 528451384384.dkr.ecr.us-west-2.amazonaws.com 2 | IMAGE = ${ECR_REPOSITORY}/analytics-next-ci-agent:latest 3 | 4 | agent: 5 | docker build --pull . -f Dockerfile.agent -t ${IMAGE} 6 | aws-okta exec ops-write -- docker push ${IMAGE} 7 | .PHONY: agent -------------------------------------------------------------------------------- /packages/generic-utils/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["**/__tests__/**", "**/*.test.*"], 5 | "compilerOptions": { 6 | "outDir": "./dist/esm", 7 | "declarationDir": "./dist/types" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/test-helpers/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["**/__tests__/**", "**/*.test.*"], 5 | "compilerOptions": { 6 | "outDir": "./dist/esm", 7 | "declarationDir": "./dist/types" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.buildkite/Readme.md: -------------------------------------------------------------------------------- 1 | # Buildkite setup 2 | 3 | ## Dockerfile.agent 4 | 5 | Builds the base image that is used by analytics-next in CI. 6 | 7 | ```bash 8 | $ robo docker.login-privileged 9 | $ make agent 10 | ``` 11 | 12 | ## .pipeline 13 | 14 | Full buildkite configuration. 15 | -------------------------------------------------------------------------------- /packages/node/src/app/types/segment-event.ts: -------------------------------------------------------------------------------- 1 | import type { CoreSegmentEvent } from '@segment/analytics-core' 2 | 3 | type SegmentEventType = 'track' | 'page' | 'identify' | 'alias' | 'screen' 4 | 5 | export interface SegmentEvent extends CoreSegmentEvent { 6 | type: SegmentEventType 7 | } 8 | -------------------------------------------------------------------------------- /packages/browser-integration-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "target": "ES5", 7 | "moduleResolution": "node", 8 | "lib": ["es2020"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/browser/scripts/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo '--- Build bundles' 5 | make build 6 | 7 | echo '--- Check Size' 8 | yarn run -T browser size-limit 9 | 10 | echo '--- Lint files' 11 | make lint 12 | 13 | echo '--- Run tests' 14 | make test-unit 15 | make test-integration 16 | -------------------------------------------------------------------------------- /packages/browser/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["**/__tests__/**", "**/test-helpers/**", "**/tester/**"], 5 | "compilerOptions": { 6 | "outDir": "./dist/pkg", 7 | "declarationDir": "./dist/types" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /scripts/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Clear build artifacts and build cache 3 | 4 | find . \( -name ".turbo" -o -name "dist" -o -name ".next" -o -name "tsconfig.tsbuildinfo" \) ! -path "*/node_modules/*" -print0 | xargs -0 rm -rf 5 | rm -rf node_modules/.cache 6 | 7 | echo "Build files and cache deleted." 8 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the public API for this package. 3 | * We avoid using splat (*) exports so that we can control what is exposed. 4 | */ 5 | export { withOneTrust } from './domain/wrapper' 6 | export type { OneTrustSettings } from './domain/wrapper' 7 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["**/__tests__/**", "**/test-helpers/**"], 5 | "compilerOptions": { 6 | "outDir": "./dist/esm", 7 | "declarationDir": "./dist/types" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/generic-utils/README.md: -------------------------------------------------------------------------------- 1 | # @segment/analytics-generic-utils 2 | 3 | This monorepo's version of "lodash". This package contains shared generic utilities that can be used within the ecosystem. This package should not have dependencies, and should not contain any references to the Analytics domain. 4 | -------------------------------------------------------------------------------- /scripts/utils/exists.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This type guard can be passed into a function such as native filter 3 | * in order to remove nullish values from a list in a type-safe way. 4 | */ 5 | export const exists = (value: T): value is NonNullable => { 6 | return value != null && value !== undefined 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/src/utils/has-properties.ts: -------------------------------------------------------------------------------- 1 | export function hasProperties( 2 | obj: T, 3 | ...keys: K[] 4 | ): obj is T & { [J in K]: unknown } { 5 | // eslint-disable-next-line no-prototype-builtins 6 | return !!obj && keys.every((key) => obj.hasOwnProperty(key)) 7 | } 8 | -------------------------------------------------------------------------------- /playgrounds/with-vite/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /packages/core/src/utils/pick.ts: -------------------------------------------------------------------------------- 1 | export const pickBy = ( 2 | obj: T, 3 | fn: (key: K, v: T[K]) => boolean 4 | ) => { 5 | return (Object.keys(obj) as K[]) 6 | .filter((k) => fn(k, obj[k])) 7 | .reduce((acc, key) => ((acc[key] = obj[key]), acc), {} as Partial) 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "target": "ES5", 7 | "moduleResolution": "node", 8 | "resolveJsonModule": true, 9 | "lib": ["es2020"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/next-playground/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import '../styles/dracula/dracula-ui.css' 3 | import '../styles/dracula/prism.css' 4 | import '../styles/logs-table.css' 5 | 6 | export default function ExampleApp({ Component, pageProps }) { 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /packages/browser-integration-tests/src/helpers/extract-writekey.ts: -------------------------------------------------------------------------------- 1 | export function extractWriteKeyFromUrl(url: string): string | undefined { 2 | const matches = url.match( 3 | /https:\/\/cdn.segment.com\/v1\/projects\/(.+)\/settings/ 4 | ) 5 | 6 | if (matches) { 7 | return matches[1] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/consent/consent-tools-integration-tests/src/page-objects/consent-tools-vanilla.ts: -------------------------------------------------------------------------------- 1 | import { BasePage } from './base-page' 2 | 3 | class ConsentToolsVanilla extends BasePage { 4 | constructor() { 5 | super('consent-tools-vanilla.html') 6 | } 7 | } 8 | 9 | export default new ConsentToolsVanilla() 10 | -------------------------------------------------------------------------------- /packages/generic-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "target": "ES5", 7 | "moduleResolution": "node", 8 | "resolveJsonModule": true, 9 | "lib": ["es2020"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/node/src/lib/base-64-encode.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-nodejs-modules 2 | import { Buffer } from 'buffer' 3 | /** 4 | * Base64 encoder that works in browser, worker, node runtimes. 5 | */ 6 | export const b64encode = (str: string): string => { 7 | return Buffer.from(str).toString('base64') 8 | } 9 | -------------------------------------------------------------------------------- /packages/test-helpers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "resolveJsonModule": true, 6 | "module": "esnext", 7 | "target": "ES5", 8 | "moduleResolution": "node", 9 | "lib": ["es2020"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/browser/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | ignorePatterns: ['e2e-tests', 'qa', '/*.tmp.*/'], 4 | extends: ['../../.eslintrc'], 5 | env: { 6 | node: true, // TODO: change to false when node is abstracted out 7 | browser: true, 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /packages/browser/src/core/connection/index.ts: -------------------------------------------------------------------------------- 1 | import { isBrowser } from '../environment' 2 | 3 | export function isOnline(): boolean { 4 | if (isBrowser()) { 5 | return window.navigator.onLine 6 | } 7 | 8 | return true 9 | } 10 | 11 | export function isOffline(): boolean { 12 | return !isOnline() 13 | } 14 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/test-helpers/mocks/analytics-mock.ts: -------------------------------------------------------------------------------- 1 | import { AnyAnalytics } from '../../types' 2 | 3 | export const analyticsMock: jest.Mocked = { 4 | addSourceMiddleware: jest.fn(), 5 | page: jest.fn(), 6 | load: jest.fn(), 7 | on: jest.fn(), 8 | track: jest.fn(), 9 | } 10 | -------------------------------------------------------------------------------- /packages/browser/src/lib/is-thenable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if thenable 3 | * (instanceof Promise doesn't respect realms) 4 | */ 5 | export const isThenable = (value: unknown): boolean => 6 | typeof value === 'object' && 7 | value !== null && 8 | 'then' in value && 9 | typeof (value as any).then === 'function' 10 | -------------------------------------------------------------------------------- /packages/core/src/utils/is-thenable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if thenable 3 | * (instanceof Promise doesn't respect realms) 4 | */ 5 | export const isThenable = (value: unknown): boolean => 6 | typeof value === 'object' && 7 | value !== null && 8 | 'then' in value && 9 | typeof (value as any).then === 'function' 10 | -------------------------------------------------------------------------------- /packages/node-integration-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "resolveJsonModule": true, 6 | "module": "esnext", 7 | "target": "ES5", 8 | "moduleResolution": "node", 9 | "lib": ["es2020"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "noEmit": true, 6 | "resolveJsonModule": true, 7 | "module": "esnext", 8 | "target": "es6", 9 | "moduleResolution": "node", 10 | "lib": ["es2020"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/node/src/__tests__/settings.test.ts: -------------------------------------------------------------------------------- 1 | import { validateSettings } from '../app/settings' 2 | 3 | describe('validateSettings', () => { 4 | it('should throw an error if no write key', () => { 5 | expect(() => validateSettings({ writeKey: undefined as any })).toThrowError( 6 | /writeKey/i 7 | ) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/node/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type { import('eslint').Linter.Config } */ 2 | module.exports = { 3 | extends: ['../../.eslintrc.isomorphic'], 4 | rules: { 5 | '@typescript-eslint/no-empty-interface': 'off', // since this is a lib, sometimes we want to use interfaces rather than types for the ease of declaration merging. 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /packages/browser/src/test-helpers/fetch-parse.ts: -------------------------------------------------------------------------------- 1 | export type FetchCall = [input: RequestInfo, init?: RequestInit | undefined] 2 | 3 | export const parseFetchCall = ([url, request]: FetchCall) => ({ 4 | url, 5 | method: request?.method, 6 | headers: request?.headers, 7 | body: request?.body ? JSON.parse(request.body as any) : undefined, 8 | }) 9 | -------------------------------------------------------------------------------- /packages/node-integration-tests/src/perf-tests/server-start-no-analytics.ts: -------------------------------------------------------------------------------- 1 | import { startServer } from '../server/server' 2 | 3 | startServer() 4 | .then((app) => { 5 | app.get('/', (_, res) => { 6 | res.sendStatus(200) 7 | }) 8 | }) 9 | .catch((err) => { 10 | console.error(err) 11 | process.exit(1) 12 | }) 13 | -------------------------------------------------------------------------------- /.github/workflows/md-link-check.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | { 4 | "pattern": "^http://127\.0\.0\.1.*" 5 | }, 6 | { 7 | "pattern": "^http://localhost.*" 8 | } 9 | ], 10 | "replacementPatterns": [ 11 | { 12 | "pattern": "^/", 13 | "replacement": "{{BASEURL}}/" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/browser/src/lib/version-type.ts: -------------------------------------------------------------------------------- 1 | // Default value will be updated to 'web' in `bundle-umd.ts` for web build. 2 | let _version: 'web' | 'npm' = 'npm' 3 | 4 | export function setVersionType(version: typeof _version) { 5 | _version = version 6 | } 7 | 8 | export function getVersionType(): typeof _version { 9 | return _version 10 | } 11 | -------------------------------------------------------------------------------- /packages/node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "module": "ESNext", 6 | "target": "es2022", // node 18 7 | "moduleResolution": "node", 8 | "lib": ["es2022"] // TODO: es2023 https://www.npmjs.com/package/@tsconfig/node18 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/core-integration-tests/src/public-api.test.ts: -------------------------------------------------------------------------------- 1 | import { CoreContext } from '@segment/analytics-core' 2 | 3 | class TestCtx extends CoreContext {} 4 | 5 | it('should be able to import and instantiate some module from core', () => { 6 | // Test the ability to do basic imports 7 | expect(typeof new TestCtx({ type: 'alias' })).toBe('object') 8 | }) 9 | -------------------------------------------------------------------------------- /playgrounds/next-playground/pages/iframe/childPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AnalyticsProvider } from '../../context/analytics' 3 | 4 | const ChildPage: React.FC = () => { 5 | return
Hello world!
6 | } 7 | 8 | export default () => ( 9 | 10 | 11 | 12 | ) 13 | -------------------------------------------------------------------------------- /packages/browser/src/lib/fetch.ts: -------------------------------------------------------------------------------- 1 | import unfetch from 'unfetch' 2 | import { getGlobal } from './get-global' 3 | 4 | /** 5 | * Wrapper around native `fetch` containing `unfetch` fallback. 6 | */ 7 | export const fetch: typeof global.fetch = (...args) => { 8 | const global = getGlobal() 9 | return ((global && global.fetch) || unfetch)(...args) 10 | } 11 | -------------------------------------------------------------------------------- /packages/core-integration-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "noUnusedLocals": false, 6 | "noUnusedParameters": false, 7 | "module": "esnext", 8 | "target": "ES5", 9 | "moduleResolution": "node", 10 | "lib": ["es2020"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /typings/spawn.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@npmcli/promise-spawn' { 2 | import { EventEmitter } from 'events' 3 | import { SpawnOptions } from 'child_process' 4 | 5 | export default function spawn( 6 | cmd: string, 7 | args?: string[], 8 | opts?: SpawnOptions 9 | ): Promise<{ stdout: Buffer; code: number; stderr: Buffer }> & EventEmitter 10 | } 11 | -------------------------------------------------------------------------------- /packages/node/scripts/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Generate a version.ts file from the version in the package.json 3 | 4 | PKG_VERSION=$(node --eval="process.stdout.write(require('./package.json').version)") 5 | 6 | cat <src/generated/version.ts 7 | // This file is generated. 8 | export const version = '$PKG_VERSION' 9 | EOF 10 | 11 | git add src/generated/version.ts 12 | -------------------------------------------------------------------------------- /playgrounds/next-playground/utils/hooks/useConfig.ts: -------------------------------------------------------------------------------- 1 | import { useLocalStorage } from './useLocalStorage' 2 | 3 | export const useWriteKey = () => 4 | useLocalStorage( 5 | 'segment_playground_write_key', 6 | process.env.NEXT_PUBLIC_WRITEKEY 7 | ) 8 | 9 | export const useCDNUrl = () => 10 | useLocalStorage('segment_playground_cdn_url', 'https://cdn.segment.com') 11 | -------------------------------------------------------------------------------- /packages/browser/scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | : "${SEGMENT_LIB_PATH:?"is a required environment variable"}" 6 | : "${AJS_PRIVATE_ASSETS_UPLOAD:?"is a required environment variable"}" 7 | 8 | source "${SEGMENT_LIB_PATH}/aws.bash" 9 | 10 | function main() { 11 | node scripts/release.js 12 | } 13 | 14 | run-with-role ${AJS_PRIVATE_ASSETS_UPLOAD} main 15 | -------------------------------------------------------------------------------- /packages/node-integration-tests/src/server/fetch-polyfill.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch' 2 | 3 | const majorVersion = parseInt( 4 | process.version.replace('v', '').split('.')[0], 5 | 10 6 | ) 7 | 8 | if (majorVersion >= 18) { 9 | ;(globalThis as any).fetch = fetch // polyfill fetch so nock will work on node >= 18 -- see: https://github.com/nock/nock/issues/2336 10 | } 11 | -------------------------------------------------------------------------------- /packages/node-integration-tests/src/smoke/smoke.ts: -------------------------------------------------------------------------------- 1 | import { default as AnalyticsDefaultImport } from '@segment/analytics-node' 2 | import { Analytics as AnalyticsNamedImport } from '@segment/analytics-node' 3 | 4 | { 5 | // test named imports vs default imports 6 | new AnalyticsNamedImport({ writeKey: 'hello world' }) 7 | new AnalyticsDefaultImport({ writeKey: 'hello world' }) 8 | } 9 | -------------------------------------------------------------------------------- /packages/node/src/lib/create-url.ts: -------------------------------------------------------------------------------- 1 | const stripTrailingSlash = (str: string) => str.replace(/\/$/, '') 2 | 3 | /** 4 | * 5 | * @param host e.g. "http://foo.com" 6 | * @param path e.g. "/bar" 7 | * @returns "e.g." "http://foo.com/bar" 8 | */ 9 | export const tryCreateFormattedUrl = (host: string, path?: string) => { 10 | return stripTrailingSlash(new URL(path || '', host).href) 11 | } 12 | -------------------------------------------------------------------------------- /packages/browser/src/lib/p-while.ts: -------------------------------------------------------------------------------- 1 | export const pWhile = async ( 2 | condition: (value: T | undefined) => boolean, 3 | action: () => T | PromiseLike 4 | ): Promise => { 5 | const loop = async (actionResult: T | undefined): Promise => { 6 | if (condition(actionResult)) { 7 | return loop(await action()) 8 | } 9 | } 10 | 11 | return loop(undefined) 12 | } 13 | -------------------------------------------------------------------------------- /packages/browser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "target": "ES5", 6 | "moduleResolution": "node", 7 | "resolveJsonModule": true, 8 | "lib": ["es2020", "DOM", "DOM.Iterable"], 9 | "baseUrl": "./src", 10 | "keyofStringsOnly": true 11 | }, 12 | "exclude": ["node_modules", "dist"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/utils/p-while.ts: -------------------------------------------------------------------------------- 1 | export const pWhile = async ( 2 | condition: (value: T | undefined) => boolean, 3 | action: () => T | PromiseLike 4 | ): Promise => { 5 | const loop = async (actionResult: T | undefined): Promise => { 6 | if (condition(actionResult)) { 7 | return loop(await action()) 8 | } 9 | } 10 | 11 | return loop(undefined) 12 | } 13 | -------------------------------------------------------------------------------- /playgrounds/next-playground/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | // https://nextjs.org/docs/basic-features/eslint#lint-staged 2 | const path = require('path') 3 | 4 | const buildEslintCommand = (filenames) => 5 | `next lint --fix --file ${filenames 6 | .map((f) => path.relative(process.cwd(), f)) 7 | .join(' --file ')}` 8 | 9 | module.exports = { 10 | '*.{js,jsx,ts,tsx}': [buildEslintCommand], 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/next-playground/utils/hooks/useDidMountEffect.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from 'react' 2 | 3 | // This function runs on dependency change but not on initial render. 4 | export const useDidMountEffect = (func, deps) => { 5 | const didMount = useRef(false) 6 | 7 | useEffect(() => { 8 | if (didMount.current) func() 9 | else didMount.current = true 10 | }, deps) 11 | } 12 | -------------------------------------------------------------------------------- /packages/browser/src/browser/standalone-interface.ts: -------------------------------------------------------------------------------- 1 | import { Analytics, InitOptions } from '../core/analytics' 2 | 3 | export interface AnalyticsSnippet extends AnalyticsStandalone { 4 | load: (writeKey: string, options?: InitOptions) => void 5 | } 6 | 7 | export interface AnalyticsStandalone extends Analytics { 8 | _loadOptions?: InitOptions 9 | _writeKey?: string 10 | _cdn?: string 11 | } 12 | -------------------------------------------------------------------------------- /packages/browser/src/lib/get-process-env.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns `process.env` if it is available in the environment. 3 | * Always returns an object make it similarly easy to use as `process.env`. 4 | */ 5 | export function getProcessEnv(): { [key: string]: string | undefined } { 6 | if (typeof process === 'undefined' || !process.env) { 7 | return {} 8 | } 9 | 10 | return process.env 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/utils/ts-helpers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove Index Signature 3 | */ 4 | export type RemoveIndexSignature = { 5 | [K in keyof T as {} extends Record ? never : K]: T[K] 6 | } 7 | 8 | /** 9 | * Recursively make all object properties nullable 10 | */ 11 | export type DeepNullable = { 12 | [K in keyof T]: T[K] extends object ? DeepNullable | null : T[K] | null 13 | } 14 | -------------------------------------------------------------------------------- /playgrounds/next-playground/styles/dracula/card.css: -------------------------------------------------------------------------------- 1 | .drac-card { 2 | border-radius: var(--corner-radius); 3 | box-shadow: 0px var(--spacing-md) var(--spacing-lg) rgba(255, 255, 255, 0.25); 4 | } 5 | 6 | .drac-card-portrait { 7 | max-width: 30rem; 8 | } 9 | 10 | .drac-card-subtle { 11 | box-shadow: none; 12 | background: none; 13 | border-width: 2px; 14 | border-style: solid; 15 | } 16 | -------------------------------------------------------------------------------- /packages/browser/src/test-helpers/test-writekeys.ts: -------------------------------------------------------------------------------- 1 | // Writekeys that are used in different unit/integration tests. These writekeys 2 | // were created for the purpose of testing AJS and are not used for anything else. 3 | 4 | export const TEST_WRITEKEY = 'D8frB7upBChqDN9PMWksNvZYDaKJIYo6' // Test segment writekey 5 | export const AMPLITUDE_WRITEKEY = 'c56fd8ca27d0f9adfe8ad78d846dfcc8' // Test amplitude writekey 6 | -------------------------------------------------------------------------------- /playgrounds/with-vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .yarn 27 | -------------------------------------------------------------------------------- /packages/browser/src/test-helpers/fixtures/page-context.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BufferedPageContext, 3 | getDefaultBufferedPageContext, 4 | getDefaultPageContext, 5 | PageContext, 6 | } from '../../core/page' 7 | 8 | export const getPageCtxFixture = (): PageContext => getDefaultPageContext() 9 | 10 | export const getBufferedPageCtxFixture = (): BufferedPageContext => 11 | getDefaultBufferedPageContext() 12 | -------------------------------------------------------------------------------- /playgrounds/next-playground/public/fira-code.webfont/webfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'fira-code-regular'; 3 | src: url('fira-code_regular.eot'); 4 | src: url('fira-code_regular.eot?#iefix') format('embedded-opentype'), 5 | url('fira-code_regular.woff') format('woff'), 6 | url('fira-code_regular.ttf') format('truetype'), 7 | url('fira-code_regular.svg#fira-code-regular') format('svg'); 8 | } 9 | -------------------------------------------------------------------------------- /packages/consent/consent-tools-integration-tests/public/consent-tools-vanilla.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Hello World - Serving Analytics

10 |

Please Check Network tab

11 |

This page can used as playground or run by webdriver.io

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /playgrounds/next-playground/public/demos/faulty-load.js: -------------------------------------------------------------------------------- 1 | const faultyLoad = { 2 | name: 'Fails to load', 3 | type: 'destination', 4 | version: '1.0', 5 | 6 | load: () => { 7 | return new Promise((_res, rej) => { 8 | setTimeout(() => { 9 | rej(new Error('aaay')) 10 | }, 2000) 11 | }) 12 | }, 13 | 14 | isLoaded: () => false, 15 | } 16 | 17 | window.analytics.register(faultyLoad) 18 | -------------------------------------------------------------------------------- /packages/node/src/lib/get-message-id.ts: -------------------------------------------------------------------------------- 1 | import { uuid } from './uuid' 2 | 3 | /** 4 | * get a unique messageId with a very low chance of collisions 5 | * using @lukeed/uuid/secure uses the node crypto module, which is the fastest 6 | * @example "node-next-1668208232027-743be593-7789-4b74-8078-cbcc8894c586" 7 | */ 8 | export const createMessageId = (): string => { 9 | return `node-next-${Date.now()}-${uuid()}` 10 | } 11 | -------------------------------------------------------------------------------- /playgrounds/next-playground/public/demos/faulty-track.js: -------------------------------------------------------------------------------- 1 | const faultyTrack = { 2 | name: 'Fails to send', 3 | type: 'destination', 4 | version: '1.0', 5 | 6 | load: async () => {}, 7 | isLoaded: () => true, 8 | 9 | track(ctx) { 10 | if (ctx.event.context?.attempts < 2) { 11 | throw new Error('aaay') 12 | } 13 | 14 | return ctx 15 | }, 16 | } 17 | 18 | window.analytics.register(faultyTrack) 19 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/src/test-helpers/onetrust-globals.d.ts: -------------------------------------------------------------------------------- 1 | import { OneTrustGlobal } from '../lib/onetrust-api' 2 | /** 3 | * ALERT: It's OK to declare ambient globals in test code, but __not__ in library code 4 | * This file should not be included in the final package 5 | */ 6 | export declare global { 7 | interface Window { 8 | OneTrust: OneTrustGlobal 9 | OnetrustActiveGroups: string 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/next-playground/public/demos/faulty-middleware.js: -------------------------------------------------------------------------------- 1 | const faultyMiddleware = { 2 | name: 'Fails to run before', 3 | type: 'before', 4 | version: '1.0', 5 | 6 | load: async () => {}, 7 | isLoaded: () => true, 8 | 9 | track(ctx) { 10 | if (ctx.event.context?.attempts < 4) { 11 | throw new Error('aaay') 12 | } 13 | 14 | return ctx 15 | }, 16 | } 17 | 18 | window.analytics.register(faultyMiddleware) 19 | -------------------------------------------------------------------------------- /packages/browser/src/lib/get-global.ts: -------------------------------------------------------------------------------- 1 | // This an imperfect polyfill for globalThis 2 | export const getGlobal = () => { 3 | if (typeof globalThis !== 'undefined') { 4 | return globalThis 5 | } 6 | if (typeof self !== 'undefined') { 7 | return self 8 | } 9 | if (typeof window !== 'undefined') { 10 | return window 11 | } 12 | if (typeof global !== 'undefined') { 13 | return global 14 | } 15 | return null 16 | } 17 | -------------------------------------------------------------------------------- /packages/node-integration-tests/src/server/fixtures.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto' 2 | 3 | export const trackEventSmall = { 4 | userId: '019mr8mf4r', 5 | event: 'Order Completed', 6 | properties: { userId: 'foo', event: 'click' }, 7 | } 8 | 9 | export const trackEventLarge = { 10 | ...trackEventSmall, 11 | properties: { 12 | ...trackEventSmall.properties, 13 | data: crypto.randomBytes(1024 * 6).toString(), 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /playgrounds/with-vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/consent/consent-tools-integration-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "moduleResolution": "node", 5 | "module": "ESNext", 6 | "target": "es6", 7 | "noEmit": true, 8 | "types": [ 9 | "@wdio/globals/types", 10 | "node", 11 | "expect-webdriverio", 12 | "@wdio/mocha-framework", 13 | "wdio-intercept-service" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/node/src/app/context.ts: -------------------------------------------------------------------------------- 1 | // create a derived class since we may want to add node specific things to Context later 2 | 3 | import { CoreContext } from '@segment/analytics-core' 4 | import { SegmentEvent } from './types' 5 | 6 | // While this is not a type, it is a definition 7 | export class Context extends CoreContext { 8 | static override system() { 9 | return new this({ type: 'track', event: 'system' }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/with-vite/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/src/index.umd.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is meant to be used to create a webpack bundle. 3 | */ 4 | import { withOneTrust } from './index' 5 | export { withOneTrust } 6 | 7 | // this will almost certainly be executed in the browser, but since this is UMD, 8 | // we are checking just for the sake of being thorough 9 | if (typeof window !== 'undefined') { 10 | ;(window as any).withOneTrust = withOneTrust 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/next-playground/public/demos/signals.js: -------------------------------------------------------------------------------- 1 | const signals = { 2 | name: 'Signals', 3 | type: 'utility', 4 | version: '0.1.0', 5 | 6 | load: async (_ctx, analytics) => { 7 | document.querySelector('#shuffle').addEventListener('click', async () => { 8 | const ctx = await analytics.track('Event Shuffled') 9 | ctx.flush() 10 | }) 11 | }, 12 | 13 | isLoaded: () => true, 14 | } 15 | 16 | window.analytics.register(signals) 17 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/utils/pick.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @example 3 | * pick({ a: 1, b: 2, c: 3 }, ['a', 'c']) => { a: 1, c: 3 } 4 | */ 5 | export const pick = < 6 | Obj extends Record, 7 | Key extends keyof Obj 8 | >( 9 | obj: Obj, 10 | keys: Key[] 11 | ): Pick => { 12 | return keys.reduce((acc, k) => { 13 | if (k in obj) { 14 | acc[k] = obj[k] 15 | } 16 | return acc 17 | }, {} as Pick) 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/analytics/index.ts: -------------------------------------------------------------------------------- 1 | export interface CoreAnalytics { 2 | track(...args: unknown[]): unknown 3 | page(...args: unknown[]): unknown 4 | identify(...args: unknown[]): unknown 5 | group(...args: unknown[]): unknown 6 | alias(...args: unknown[]): unknown 7 | screen(...args: unknown[]): unknown 8 | register(...plugins: unknown[]): Promise 9 | deregister(...plugins: unknown[]): Promise 10 | readonly VERSION: string 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/standalone-playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Standalone Playground 9 | 10 | 11 | 12 |

Standalone Playground

13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /scripts/update-lockfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # # Changesets does not support yarn, so if the resolutions change, we want to commit them as part of the version pipeline. 3 | 4 | echo "pwd: $(pwd)" 5 | echo "Checking if yarn.lock is up-to-date" 6 | 7 | yarn_path=$(grep "yarnPath:" .yarnrc.yml | awk 'NF>1{print $NF}') 8 | YARN_ENABLE_IMMUTABLE_INSTALLS=false node "$yarn_path" # https://github.com/changesets/action/issues/170 9 | git add yarn.lock 10 | git status --porcelain 11 | 12 | -------------------------------------------------------------------------------- /packages/browser/jest.config.js: -------------------------------------------------------------------------------- 1 | const { createJestTSConfig } = require('@internal/config') 2 | 3 | module.exports = createJestTSConfig(__dirname, { 4 | modulePathIgnorePatterns: ['/e2e-tests', '/qa'], 5 | setupFilesAfterEnv: ['./jest.setup.js'], 6 | testEnvironment: 'jsdom', 7 | coverageThreshold: { 8 | global: { 9 | branches: 80.91, 10 | functions: 87.25, 11 | lines: 91.03, 12 | statements: 87.25, 13 | }, 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/utils/pipe.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Everyday variadic pipe function (reverse of 'compose') 3 | * @example pipe(fn1, fn2, fn3)(value) // fn3(fn2(fn1(value))) 4 | */ 5 | export const pipe = ( 6 | fn: (...args: T) => U, 7 | ...fns: ((a: U) => U)[] 8 | ) => { 9 | const piped = fns.reduce( 10 | (prevFn, nextFn) => (value: U) => nextFn(prevFn(value)), 11 | (value) => value 12 | ) 13 | return (...args: T) => piped(fn(...args)) 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/src/utils/get-global.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-globals */ 2 | // This an imperfect polyfill for globalThis 3 | export const getGlobal = () => { 4 | if (typeof globalThis !== 'undefined') { 5 | return globalThis 6 | } 7 | if (typeof self !== 'undefined') { 8 | return self 9 | } 10 | if (typeof window !== 'undefined') { 11 | return window 12 | } 13 | if (typeof global !== 'undefined') { 14 | return global 15 | } 16 | return null 17 | } 18 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.6.0/schema.json", 3 | "ignore": ["@playground/*"], 4 | "changelog": [ 5 | "@changesets/changelog-github", 6 | { 7 | "repo": "segmentio/analytics-next" 8 | } 9 | ], 10 | "commit": false, 11 | "access": "public", 12 | "baseBranch": "master", 13 | "linked": [], 14 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { 15 | "onlyUpdatePeerDependentsWhenOutOfRange": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/browser/src/core/stats/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { RemoteMetrics } from '../remote-metrics' 2 | import { Stats } from '..' 3 | 4 | const spy = jest.spyOn(RemoteMetrics.prototype, 'increment') 5 | 6 | describe(Stats, () => { 7 | test('forwards increments to remote metrics endpoint', () => { 8 | Stats.initRemoteMetrics() 9 | 10 | const stats = new Stats() 11 | stats.increment('banana', 1, ['phone:1']) 12 | 13 | expect(spy).toHaveBeenCalledWith('banana', ['phone:1']) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | 10 | 11 | - [ ] I've included a changeset (psst. run `yarn changeset`. Read about changesets [here](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md)). 12 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/domain/validation/__tests__/validation-error.test.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from '../validation-error' 2 | 3 | describe(ValidationError, () => { 4 | it('should have the correct shape', () => { 5 | const err = new ValidationError('foo', 'bar') 6 | 7 | expect(err).toBeInstanceOf(Error) 8 | 9 | expect(err.name).toBe('ValidationError') 10 | 11 | expect(err.message).toMatchInlineSnapshot( 12 | `"[Validation] foo (Received: "bar")"` 13 | ) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /playgrounds/next-playground/styles/dracula/badge.css: -------------------------------------------------------------------------------- 1 | .drac-badge { 2 | padding: 1rem 1.5rem; 3 | border: none; 4 | border-radius: var(--corner-radius); 5 | text-transform: uppercase; 6 | } 7 | 8 | .drac-badge .drac-text { 9 | text-transform: uppercase; 10 | } 11 | 12 | .drac-badge-subtle { 13 | border-width: var(--border-size); 14 | border-style: solid; 15 | } 16 | 17 | .drac-badge-outline { 18 | border-width: var(--border-size); 19 | border-style: solid; 20 | background-color: transparent; 21 | } 22 | -------------------------------------------------------------------------------- /playgrounds/next-playground/styles/dracula/dracula-ui.css: -------------------------------------------------------------------------------- 1 | @import './sizes.css'; 2 | @import './colors.css'; 3 | @import './typography.css'; 4 | @import './button.css'; 5 | @import './badge.css'; 6 | @import './input.css'; 7 | @import './select.css'; 8 | @import './avatar.css'; 9 | @import './radio-checkbox-switch.css'; 10 | @import './card.css'; 11 | 12 | .drac { 13 | /* 1rem = 10px */ 14 | font-size: 62.5%; 15 | } 16 | 17 | .drac *, 18 | .drac *::before, 19 | .drac *::after { 20 | box-sizing: border-box; 21 | } 22 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/domain/validation/validation-error.ts: -------------------------------------------------------------------------------- 1 | import { AnalyticsConsentError } from '../../types/errors' 2 | 3 | export class ValidationError extends AnalyticsConsentError { 4 | constructor(message: string, received?: any) { 5 | if (arguments.length === 2) { 6 | // to ensure that explicitly passing undefined as second argument still works 7 | message += ` (Received: ${JSON.stringify(received)})` 8 | } 9 | super('ValidationError', `[Validation] ${message}`) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/node/src/__tests__/test-helpers/resolve-emitter.ts: -------------------------------------------------------------------------------- 1 | import { NodeEmitter, NodeEmitterEvents } from '../../app/emitter' 2 | 3 | /** Tester helper that resolves args from emitter event */ 4 | export const resolveEmitterEvent = ( 5 | emitter: NodeEmitter, 6 | eventName: EventName 7 | ): Promise => { 8 | return new Promise((resolve) => { 9 | emitter.once(eventName, (...args) => resolve(args)) 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/next-playground/styles/logs-table.css: -------------------------------------------------------------------------------- 1 | @import './dracula/colors.css'; 2 | @import './dracula/sizes.css'; 3 | 4 | td { 5 | vertical-align: baseline; 6 | } 7 | 8 | .rc-table-thead { 9 | color: var(--purple); 10 | text-align: left; 11 | } 12 | 13 | .rc-table-cell { 14 | padding-right: var(--spacing-sm); 15 | padding-left: var(--spacing-sm); 16 | } 17 | 18 | .rc-table-cell:first-child { 19 | color: var(--pink); 20 | } 21 | 22 | .rc-table-row:hover { 23 | background: var(--purple-transparent); 24 | } 25 | -------------------------------------------------------------------------------- /packages/browser/src/tester/server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const express = require('express') 3 | const port = 3001 4 | 5 | const app = express() 6 | 7 | app.get('*.js', function (req, res, next) { 8 | req.url = req.url + '.gz' 9 | res.set('Content-Encoding', 'gzip') 10 | res.set('Content-Type', 'text/javascript') 11 | next() 12 | }) 13 | app.use('/', express.static(path.join(__dirname, '__fixtures__'))) 14 | app.use('/dist/umd', express.static(path.join(__dirname, '../../dist/umd'))) 15 | 16 | app.listen(port) 17 | -------------------------------------------------------------------------------- /.github/workflows/md-link-check.yml: -------------------------------------------------------------------------------- 1 | name: Markdown Links Check 2 | on: 3 | schedule: 4 | # Runs once every 3 days 5 | - cron: "0 0 */3 * *" 6 | jobs: 7 | check-links: 8 | name: Check Markdown Links 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@master 12 | - uses: gaurav-nelson/github-action-markdown-link-check@v1 13 | with: 14 | use-quiet-mode: "yes" 15 | use-verbose-mode: "yes" 16 | config-file: ".github/workflows/md-link-check.config.json" 17 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the public API for this package. 3 | * We avoid using splat (*) exports so that we can control what is exposed. 4 | */ 5 | export { createWrapper } from './domain/create-wrapper' 6 | export { resolveWhen } from './utils' 7 | 8 | export type { 9 | Wrapper, 10 | CreateWrapper, 11 | CreateWrapperSettings, 12 | IntegrationCategoryMappings, 13 | Categories, 14 | GetCategoriesFunction, 15 | RegisterOnConsentChangedFunction, 16 | AnyAnalytics, 17 | } from './types' 18 | -------------------------------------------------------------------------------- /packages/core-integration-tests/README.md: -------------------------------------------------------------------------------- 1 | # Core Integration Tests 2 | This can contain a mix of tests which cover the public API of the package. This can range anywhere from typical integration tests that might stub out the API (which may or may not also be in the package itself), to tests around the specific npm packaged artifact. Examples include: 3 | - Is a license included in npm pack? 4 | - can you import a module (e.g. is the package.json path correctly to allow consumers to import)? 5 | - are there missing depenndencies in package.json? 6 | -------------------------------------------------------------------------------- /packages/node-integration-tests/src/cloudflare-tests/workers/forgot-close-and-flush.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Analytics } from '@segment/analytics-node' 3 | 4 | export default { 5 | fetch(_request: Request, _env: {}, _ctx: ExecutionContext) { 6 | const analytics = new Analytics({ 7 | writeKey: '__TEST__', 8 | host: 'http://localhost:3000', 9 | }) 10 | 11 | analytics.track({ userId: 'some-user', event: 'some-event' }) 12 | return new Response('ok') 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /packages/browser/src/test-helpers/type-assertions.ts: -------------------------------------------------------------------------------- 1 | type IsAny = unknown extends T ? (T extends {} ? T : never) : never 2 | type NotAny = T extends IsAny ? never : T 3 | type NotUnknown = unknown extends T ? never : T 4 | 5 | type NotTopType = NotAny & NotUnknown 6 | 7 | // this is not meant to be run, just for type tests 8 | export function assertNotAny(_val: NotTopType) {} 9 | 10 | // this is not meant to be run, just for type tests 11 | export function assertIs(_val: T) {} 12 | -------------------------------------------------------------------------------- /packages/browser/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core/analytics' 2 | export * from './browser' 3 | export * from './node' 4 | 5 | export * from './core/context' 6 | export * from './core/events' 7 | export * from './core/plugin' 8 | export * from './core/user' 9 | 10 | export type { AnalyticsSnippet } from './browser/standalone-interface' 11 | export type { MiddlewareFunction } from './plugins/middleware' 12 | export { getGlobalAnalytics } from './lib/global-analytics-helper' 13 | export { UniversalStorage, Store, StorageObject } from './core/storage' 14 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/types/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base Consent Error 3 | */ 4 | export abstract class AnalyticsConsentError extends Error { 5 | /** 6 | * 7 | * @param name - Pass the name explicitly to work around the limitation that 'name' is automatically set to the parent class. 8 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends#using_extends 9 | * @param message - Error message 10 | */ 11 | constructor(public name: string, message: string) { 12 | super(message) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/browser/src/core/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import type { CorePlugin } from '@segment/analytics-core' 2 | import type { DestinationMiddlewareFunction } from '../../plugins/middleware' 3 | import type { Analytics } from '../analytics' 4 | import type { Context } from '../context' 5 | 6 | export interface Plugin extends CorePlugin {} 7 | 8 | export interface DestinationPlugin extends Plugin { 9 | addMiddleware: (...fns: DestinationMiddlewareFunction[]) => void 10 | } 11 | 12 | export type AnyBrowserPlugin = Plugin | DestinationPlugin 13 | -------------------------------------------------------------------------------- /packages/core/jest.setup.js: -------------------------------------------------------------------------------- 1 | const { TextEncoder, TextDecoder } = require('util') 2 | const { setImmediate } = require('timers') 3 | 4 | // fix: "ReferenceError: TextEncoder is not defined" after upgrading JSDOM 5 | global.TextEncoder = TextEncoder 6 | global.TextDecoder = TextDecoder 7 | // fix: jsdom uses setImmediate under the hood for preflight XHR requests, 8 | // and jest removed setImmediate, so we need to provide it to prevent console 9 | // logging ReferenceErrors made by integration tests that call Amplitude. 10 | global.setImmediate = setImmediate 11 | -------------------------------------------------------------------------------- /playgrounds/next-playground/README.md: -------------------------------------------------------------------------------- 1 | ### ⚠️ This is not a vanilla analytics.js-next.js example. 2 | If you're looking for how to implement Analytics.js with Next.js, see: 3 | - https://github.com/vercel/next.js/tree/canary/examples/with-segment-analytics 4 | - https://github.com/vercel/next.js/tree/canary/examples/with-segment-analytics-pages-router 5 | 6 | ### Getting Started 7 | First, run the development server: 8 | 9 | ```bash 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | -------------------------------------------------------------------------------- /packages/browser/jest.setup.js: -------------------------------------------------------------------------------- 1 | const { TextEncoder, TextDecoder } = require('util') 2 | const { setImmediate } = require('timers') 3 | 4 | // fix: "ReferenceError: TextEncoder is not defined" after upgrading JSDOM 5 | global.TextEncoder = TextEncoder 6 | global.TextDecoder = TextDecoder 7 | // fix: jsdom uses setImmediate under the hood for preflight XHR requests, 8 | // and jest removed setImmediate, so we need to provide it to prevent console 9 | // logging ReferenceErrors made by integration tests that call Amplitude. 10 | global.setImmediate = setImmediate 11 | -------------------------------------------------------------------------------- /packages/browser/src/test-helpers/factories.ts: -------------------------------------------------------------------------------- 1 | export const createSuccess = (body: any, overrides: Partial = {}) => { 2 | return Promise.resolve({ 3 | json: () => Promise.resolve(body), 4 | ok: true, 5 | status: 200, 6 | statusText: 'OK', 7 | ...overrides, 8 | }) as Promise 9 | } 10 | 11 | export const createError = (overrides: Partial = {}) => { 12 | return Promise.resolve({ 13 | ok: false, 14 | status: 404, 15 | statusText: 'Not Found', 16 | ...overrides, 17 | }) as Promise 18 | } 19 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/src/lib/validation/onetrust-api-error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An Errot that represents that the OneTrust API is not in the expected format. 3 | * This is not something that could happen unless our API types are wrong and something is very wonky. 4 | * Not a recoverable error. 5 | */ 6 | export class OneTrustApiValidationError extends Error { 7 | name = 'OtConsentWrapperValidationError' 8 | constructor(message: string, received: any) { 9 | super(`Invariant: ${message} (Received: ${JSON.stringify(received)})`) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/node-integration-tests/src/perf-tests/server-start-old-analytics.ts: -------------------------------------------------------------------------------- 1 | import Analytics from 'analytics-node' 2 | import { startServer } from '../server/server' 3 | import { trackEventSmall } from '../server/fixtures' 4 | 5 | startServer() 6 | .then((app) => { 7 | const analytics = new Analytics('foo', { flushInterval: 1000, flushAt: 15 }) 8 | app.get('/', (_, res) => { 9 | analytics.track(trackEventSmall) 10 | res.sendStatus(200) 11 | }) 12 | }) 13 | .catch((err) => { 14 | console.error(err) 15 | process.exit(1) 16 | }) 17 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | plugins: 4 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 5 | spec: "@yarnpkg/plugin-workspace-tools" 6 | - path: .yarn/plugins/@yarnpkg/plugin-constraints.cjs 7 | spec: "@yarnpkg/plugin-constraints" 8 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 9 | spec: "@yarnpkg/plugin-interactive-tools" 10 | - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs 11 | spec: "@yarnpkg/plugin-typescript" 12 | 13 | preferInteractive: true 14 | 15 | yarnPath: .yarn/releases/yarn-3.4.1.cjs 16 | -------------------------------------------------------------------------------- /packages/browser/src/lib/client-hints/index.ts: -------------------------------------------------------------------------------- 1 | import { HighEntropyHint, NavigatorUAData, UADataValues } from './interfaces' 2 | 3 | export async function clientHints( 4 | hints?: HighEntropyHint[] 5 | ): Promise { 6 | const userAgentData = (navigator as any).userAgentData as 7 | | NavigatorUAData 8 | | undefined 9 | 10 | if (!userAgentData) return undefined 11 | 12 | if (!hints) return userAgentData.toJSON() 13 | return userAgentData 14 | .getHighEntropyValues(hints) 15 | .catch(() => userAgentData.toJSON()) 16 | } 17 | -------------------------------------------------------------------------------- /packages/browser/src/core/stats/index.ts: -------------------------------------------------------------------------------- 1 | import { CoreStats } from '@segment/analytics-core' 2 | import { MetricsOptions, RemoteMetrics } from './remote-metrics' 3 | 4 | let remoteMetrics: RemoteMetrics | undefined 5 | 6 | export class Stats extends CoreStats { 7 | static initRemoteMetrics(options?: MetricsOptions) { 8 | remoteMetrics = new RemoteMetrics(options) 9 | } 10 | 11 | override increment(metric: string, by?: number, tags?: string[]): void { 12 | super.increment(metric, by, tags) 13 | remoteMetrics?.increment(metric, tags ?? []) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/consent/consent-tools-integration-tests/src/page-bundles/consent-tools-vanilla/index.ts: -------------------------------------------------------------------------------- 1 | import { AnalyticsBrowser } from '@segment/analytics-next' 2 | import { createWrapper } from '@segment/analytics-consent-tools' 3 | 4 | const fakeCategories = { FooCategory1: true, FooCategory2: true } 5 | 6 | const withCMP = createWrapper({ 7 | getCategories: () => fakeCategories, 8 | }) 9 | 10 | const analytics = new AnalyticsBrowser() 11 | 12 | withCMP(analytics).load({ 13 | writeKey: 'foo', 14 | }) 15 | 16 | // for testing 17 | ;(window as any).analytics = analytics 18 | -------------------------------------------------------------------------------- /packages/node/src/__tests__/test-helpers/factories.ts: -------------------------------------------------------------------------------- 1 | export const createSuccess = (body?: any) => { 2 | return Promise.resolve({ 3 | json: () => Promise.resolve(body), 4 | text: () => Promise.resolve(JSON.stringify(body)), 5 | ok: true, 6 | status: 200, 7 | statusText: 'OK', 8 | }) as Promise 9 | } 10 | 11 | export const createError = (overrides: Partial = {}) => { 12 | return Promise.resolve({ 13 | ok: false, 14 | status: 404, 15 | statusText: 'Not Found', 16 | ...overrides, 17 | }) as Promise 18 | } 19 | -------------------------------------------------------------------------------- /packages/browser/src/core/query-string/gracefulDecodeURIComponent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tries to gets the unencoded version of an encoded component of a 3 | * Uniform Resource Identifier (URI). If input string is malformed, 4 | * returns it back as-is. 5 | * 6 | * Note: All occurences of the `+` character become ` ` (spaces). 7 | **/ 8 | export function gracefulDecodeURIComponent( 9 | encodedURIComponent: string 10 | ): string { 11 | try { 12 | return decodeURIComponent(encodedURIComponent.replace(/\+/g, ' ')) 13 | } catch { 14 | return encodedURIComponent 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/node/src/__tests__/plugins.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestAnalytics } from './test-helpers/create-test-analytics' 2 | 3 | describe('Plugins', () => { 4 | describe('Initialize', () => { 5 | it('loads analytics-node-next plugin', async () => { 6 | const analytics = createTestAnalytics() 7 | await analytics.ready 8 | 9 | const ajsNodeXt = analytics['_queue'].plugins.find( 10 | (xt) => xt.name === 'Segment.io' 11 | ) 12 | expect(ajsNodeXt).toBeDefined() 13 | expect(ajsNodeXt?.isLoaded()).toBeTruthy() 14 | }) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/node-integration-tests/src/cloudflare-tests/workers/send-single-event.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Analytics } from '@segment/analytics-node' 3 | 4 | export default { 5 | async fetch(_request: Request, _env: {}, _ctx: ExecutionContext) { 6 | const analytics = new Analytics({ 7 | writeKey: '__TEST__', 8 | host: 'http://localhost:3000', 9 | }) 10 | 11 | analytics.track({ userId: 'some-user', event: 'some-event' }) 12 | 13 | await analytics.closeAndFlush() 14 | return new Response('ok') 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /packages/node/src/__tests__/test-helpers/assert-shape/http-request-event.ts: -------------------------------------------------------------------------------- 1 | import { NodeEmitterEvents } from '../../../app/emitter' 2 | type HttpRequestEmitterEvent = NodeEmitterEvents['http_request'][0] 3 | 4 | export const assertHttpRequestEmittedEvent = ( 5 | event: HttpRequestEmitterEvent 6 | ) => { 7 | const body = JSON.parse(event.body) 8 | expect(Array.isArray(body.batch)).toBeTruthy() 9 | expect(body.batch.length).toBe(1) 10 | expect(typeof event.headers).toBe('object') 11 | expect(typeof event.method).toBe('string') 12 | expect(typeof event.url).toBe('string') 13 | } 14 | -------------------------------------------------------------------------------- /packages/browser/src/generated/__tests__/version.test.ts: -------------------------------------------------------------------------------- 1 | import { version } from '../version' 2 | import { readFileSync } from 'fs' 3 | import { resolve as resolvePath } from 'path' 4 | 5 | function getPackageJsonVersion(): string { 6 | const packageJson = JSON.parse( 7 | readFileSync( 8 | resolvePath(__dirname, '..', '..', '..', 'package.json') 9 | ).toString() 10 | ) 11 | return packageJson.version 12 | } 13 | 14 | describe('version', () => { 15 | it('matches version in package.json', async () => { 16 | expect(version).toBe(getPackageJsonVersion()) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "module": "ESNext", // es6 modules 6 | "target": "ES2020", // don't down-compile *too much* -- if users are using webpack, they can always transpile this library themselves 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], // assume that consumers will be polyfilling at least down to es2020 8 | "moduleResolution": "node", 9 | "isolatedModules": true // ensure we are friendly to build systems 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /scripts/create-release-from-tags/__tests__/fixtures/first-release-example.md: -------------------------------------------------------------------------------- 1 | # @segment/analytics-core 2 | 3 | ## 1.99.0 4 | 5 | ### Minor Changes 6 | 7 | * [#606](https://github.com/segmentio/analytics-next/pull/606) [\`b9c6356\`](https://github.com/segmentio/analytics-next/commit/b9c6356b7d35ee8acb6ecbd1eebc468d18d63958) Thanks [@silesky] - foo!) 8 | 9 | ### Patch Changes 10 | 11 | * [#404](https://github.com/segmentio/analytics-next/pull/404) [\`b9abc6\`](https://github.com/segmentio/analytics-next/commit/b9c6356b7d35ee8acb6ecbd1eebc468d18d63958) Thanks [@silesky] - bar!) -------------------------------------------------------------------------------- /packages/browser/src/lib/__tests__/embedded-write-key.test.ts: -------------------------------------------------------------------------------- 1 | import { embeddedWriteKey } from '../embedded-write-key' 2 | 3 | it('it guards against undefined', () => { 4 | expect(embeddedWriteKey()).toBe(undefined) 5 | }) 6 | 7 | it('it returns undefined when default parameter is set', () => { 8 | window.analyticsWriteKey = '__WRITE_KEY__' 9 | expect(embeddedWriteKey()).toBe(undefined) 10 | }) 11 | 12 | it('it returns the write key when the key is set properly', () => { 13 | window.analyticsWriteKey = 'abc_123_write_key' 14 | expect(embeddedWriteKey()).toBe('abc_123_write_key') 15 | }) 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "esModuleInterop": true, 5 | "skipLibCheck": true, 6 | "noUnusedLocals": true, 7 | "noUnusedParameters": true, 8 | "preserveWatchOutput": true, 9 | "sourceMap": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "importHelpers": true 13 | }, 14 | "ts-node": { 15 | "transpileOnly": true, 16 | "files": true, 17 | "compilerOptions": { 18 | "module": "commonjs" // get rid of "Cannot use import statement outside a module" for local scripts 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/browser/src/node/__tests__/node-integration.test.ts: -------------------------------------------------------------------------------- 1 | import { AnalyticsNode } from '../..' 2 | 3 | const writeKey = 'foo' 4 | 5 | describe('Initialization', () => { 6 | it('loads analytics-node-next plugin', async () => { 7 | const [analytics] = await AnalyticsNode.load({ 8 | writeKey, 9 | }) 10 | 11 | expect(analytics.queue.plugins.length).toBe(2) 12 | 13 | const ajsNodeXt = analytics.queue.plugins.find( 14 | (xt) => xt.name === 'analytics-node-next' 15 | ) 16 | expect(ajsNodeXt).toBeDefined() 17 | expect(ajsNodeXt?.isLoaded()).toBeTruthy() 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/node/src/lib/fetch.ts: -------------------------------------------------------------------------------- 1 | import type { HTTPFetchFn } from './http-client' 2 | 3 | export const fetch: HTTPFetchFn = async (...args) => { 4 | if (globalThis.fetch) { 5 | return globalThis.fetch(...args) 6 | } 7 | // This guard causes is important, as it causes dead-code elimination to be enabled inside this block. 8 | // @ts-ignore 9 | else if (typeof EdgeRuntime !== 'string') { 10 | return (await import('node-fetch')).default(...args) 11 | } else { 12 | throw new Error( 13 | 'Invariant: an edge runtime that does not support fetch should not exist' 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /playgrounds/next-playground/styles/globals.css: -------------------------------------------------------------------------------- 1 | @import '../public/fira-code.webfont/webfont.css'; 2 | 3 | html, 4 | body { 5 | padding: 0; 6 | margin: 0; 7 | font-family: 'fira-code-regular', -apple-system, BlinkMacSystemFont, Segoe UI, 8 | Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, 9 | sans-serif; 10 | color: white; 11 | background-color: #22212c; 12 | } 13 | 14 | a { 15 | color: inherit; 16 | text-decoration: underline; 17 | } 18 | 19 | * { 20 | box-sizing: border-box; 21 | } 22 | 23 | label > input, 24 | label > button { 25 | margin: 1px 5px; 26 | } 27 | -------------------------------------------------------------------------------- /packages/browser/src/browser/browser-umd.ts: -------------------------------------------------------------------------------- 1 | import { getCDN, setGlobalCDNUrl } from '../lib/parse-cdn' 2 | import { setVersionType } from '../lib/version-type' 3 | 4 | if (process.env.ASSET_PATH) { 5 | if (process.env.ASSET_PATH === '/dist/umd/') { 6 | // @ts-ignore 7 | __webpack_public_path__ = '/dist/umd/' 8 | } else { 9 | const cdn = getCDN() 10 | setGlobalCDNUrl(cdn) // preserving original behavior -- TODO: neccessary? 11 | 12 | // @ts-ignore 13 | __webpack_public_path__ = cdn + '/analytics-next/bundles/' 14 | } 15 | } 16 | 17 | setVersionType('web') 18 | 19 | export * from '.' 20 | -------------------------------------------------------------------------------- /packages/browser/src/plugins/legacy-video-plugins/index.ts: -------------------------------------------------------------------------------- 1 | import { Analytics } from '../../core/analytics' 2 | 3 | export async function loadLegacyVideoPlugins( 4 | analytics: Analytics 5 | ): Promise { 6 | const plugins = await import( 7 | // @ts-expect-error 8 | '@segment/analytics.js-video-plugins/dist/index.umd.js' 9 | ) 10 | 11 | // This is super gross, but we need to support the `window.analytics.plugins` namespace 12 | // that is linked in the segment docs in order to be backwards compatible with ajs-classic 13 | 14 | // @ts-expect-error 15 | analytics._plugins = plugins 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/src/utils/bind-all.ts: -------------------------------------------------------------------------------- 1 | export function bindAll< 2 | ObjType extends { [key: string]: any }, 3 | KeyType extends keyof ObjType 4 | >(obj: ObjType): ObjType { 5 | const proto = obj.constructor.prototype 6 | for (const key of Object.getOwnPropertyNames(proto)) { 7 | if (key !== 'constructor') { 8 | const desc = Object.getOwnPropertyDescriptor( 9 | obj.constructor.prototype, 10 | key 11 | ) 12 | if (!!desc && typeof desc.value === 'function') { 13 | obj[key as KeyType] = obj[key].bind(obj) 14 | } 15 | } 16 | } 17 | 18 | return obj 19 | } 20 | -------------------------------------------------------------------------------- /packages/node/src/index.common.ts: -------------------------------------------------------------------------------- 1 | export { Analytics } from './app/analytics-node' 2 | export { Context } from './app/context' 3 | export { 4 | HTTPClient, 5 | FetchHTTPClient, 6 | HTTPFetchRequest, 7 | HTTPResponse, 8 | HTTPFetchFn, 9 | HTTPClientRequest, 10 | } from './lib/http-client' 11 | 12 | export { OAuthSettings } from './lib/types' 13 | 14 | export type { 15 | Plugin, 16 | GroupTraits, 17 | UserTraits, 18 | TrackParams, 19 | IdentifyParams, 20 | AliasParams, 21 | GroupParams, 22 | PageParams, 23 | } from './app/types' 24 | export type { AnalyticsSettings } from './app/settings' 25 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "module": "ESNext", // es6 modules 6 | "target": "ES2020", // don't down-compile *too much* -- if users are using webpack, they can always transpile this library themselves 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], // assume that consumers will be polyfilling at least down to es2020 8 | "moduleResolution": "node", 9 | "resolveJsonModule": true, 10 | "isolatedModules": true // ensure we are friendly to build systems 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/browser/src/lib/bind-all.ts: -------------------------------------------------------------------------------- 1 | export default function bindAll< 2 | ObjType extends { [key: string]: any }, 3 | KeyType extends keyof ObjType 4 | >(obj: ObjType): ObjType { 5 | const proto = obj.constructor.prototype 6 | for (const key of Object.getOwnPropertyNames(proto)) { 7 | if (key !== 'constructor') { 8 | const desc = Object.getOwnPropertyDescriptor( 9 | obj.constructor.prototype, 10 | key 11 | ) 12 | if (!!desc && typeof desc.value === 'function') { 13 | obj[key as KeyType] = obj[key].bind(obj) 14 | } 15 | } 16 | } 17 | 18 | return obj 19 | } 20 | -------------------------------------------------------------------------------- /packages/consent/consent-tools-integration-tests/src/tests/consent-tools-vanilla.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests targeting @segment/analytics-consent-tools 3 | */ 4 | 5 | import page from '../page-objects/consent-tools-vanilla' 6 | import { expect } from 'expect' 7 | 8 | it('should stamp each event', async () => { 9 | await page.load() 10 | 11 | const ctx = await browser.execute(() => { 12 | return window.analytics.track('hello') 13 | }) 14 | 15 | expect((ctx.event.context as any).consent).toEqual({ 16 | categoryPreferences: { 17 | FooCategory1: true, 18 | FooCategory2: true, 19 | }, 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/consent/consent-wrapper-onetrust/src/test-helpers/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Allows mocked objects to throw a helpful error message when a method is called without an implementation 3 | */ 4 | export const addDebugMockImplementation = (mock: jest.Mocked) => { 5 | Object.entries(mock).forEach(([method, value]) => { 6 | // automatically add mock implementation for debugging purposes 7 | if (typeof value === 'function') { 8 | mock[method] = mock[method].mockImplementation((...args: any[]) => { 9 | throw new Error(`Not Implemented: ${method}(${JSON.stringify(args)})`) 10 | }) 11 | } 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /packages/browser/src/core/inspector/index.ts: -------------------------------------------------------------------------------- 1 | import { getGlobal } from '../../lib/get-global' 2 | import type { Analytics } from '../analytics' 3 | 4 | const env = getGlobal() 5 | 6 | // The code below assumes the inspector extension will use Object.assign 7 | // to add the inspect interface on to this object reference (unless the 8 | // extension code ran first and has already set up the variable) 9 | const inspectorHost: { 10 | attach: (analytics: Analytics) => void 11 | } = ((env as any)['__SEGMENT_INSPECTOR__'] ??= {}) 12 | 13 | export const attachInspector = (analytics: Analytics) => 14 | inspectorHost.attach?.(analytics as any) 15 | -------------------------------------------------------------------------------- /packages/consent/consent-tools/src/domain/validation/common-validators.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from './validation-error' 2 | 3 | export function assertIsFunction( 4 | val: unknown, 5 | variableName: string 6 | ): asserts val is Function { 7 | if (typeof val !== 'function') { 8 | throw new ValidationError(`${variableName} is not a function`, val) 9 | } 10 | } 11 | 12 | export function assertIsObject( 13 | val: unknown, 14 | variableName: string 15 | ): asserts val is object { 16 | if (val === null || typeof val !== 'object') { 17 | throw new ValidationError(`${variableName} is not an object`, val) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/node-integration-tests/src/perf-tests/server-start-analytics.ts: -------------------------------------------------------------------------------- 1 | import { Analytics } from '@segment/analytics-node' 2 | import { startServer } from '../server/server' 3 | import { trackEventSmall } from '../server/fixtures' 4 | 5 | const analytics = new Analytics({ 6 | writeKey: 'foo', 7 | flushInterval: 1000, 8 | flushAt: 15, 9 | }) 10 | 11 | startServer({ onClose: analytics.closeAndFlush }) 12 | .then((app) => { 13 | app.get('/', (_, res) => { 14 | analytics.track(trackEventSmall) 15 | res.sendStatus(200) 16 | }) 17 | }) 18 | .catch((err) => { 19 | console.error(err) 20 | process.exit(1) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/browser/src/core/connection/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { isOffline, isOnline } from '..' 2 | 3 | describe('connection', () => { 4 | let online = true 5 | 6 | Object.defineProperty(window.navigator, 'onLine', { 7 | get() { 8 | return online 9 | }, 10 | }) 11 | 12 | test('checks that the browser is online', () => { 13 | online = true 14 | 15 | expect(isOnline()).toBe(true) 16 | expect(isOffline()).toBe(false) 17 | }) 18 | 19 | test('checks that the browser is offline', () => { 20 | online = false 21 | 22 | expect(isOnline()).toBe(false) 23 | expect(isOffline()).toBe(true) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /packages/core-integration-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@internal/core-integration-tests", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "test": "yarn jest", 7 | "lint": "yarn concurrently 'yarn:eslint .' 'yarn:tsc --noEmit'", 8 | "watch:test": "yarn test --watch", 9 | "tsc": "yarn run -T tsc", 10 | "eslint": "yarn run -T eslint", 11 | "concurrently": "yarn run -T concurrently", 12 | "jest": "yarn run -T jest" 13 | }, 14 | "packageManager": "yarn@3.4.1", 15 | "devDependencies": { 16 | "@internal/config": "workspace:^", 17 | "@segment/analytics-core": "workspace:^" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/node-integration-tests/src/durability-tests/server-start-analytics.ts: -------------------------------------------------------------------------------- 1 | import { Analytics } from '@segment/analytics-node' 2 | import { startServer } from '../server/server' 3 | import { trackEventSmall } from '../server/fixtures' 4 | 5 | const analytics = new Analytics({ 6 | writeKey: 'foo', 7 | flushInterval: 1000, 8 | flushAt: 15, 9 | }) 10 | 11 | startServer({ onClose: analytics.closeAndFlush }) 12 | .then((app) => { 13 | app.get('/', (_, res) => { 14 | analytics.track(trackEventSmall) 15 | res.sendStatus(200) 16 | }) 17 | }) 18 | .catch((err) => { 19 | console.error(err) 20 | process.exit(1) 21 | }) 22 | -------------------------------------------------------------------------------- /playgrounds/next-playground/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | *.tsbuildinfo 34 | 35 | # vercel 36 | .vercel 37 | 38 | # @builder.io/partytown 39 | public/~partytown 40 | -------------------------------------------------------------------------------- /packages/browser/src/test-helpers/fixtures/client-hints.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UADataValues, 3 | UALowEntropyJSON, 4 | } from '../../lib/client-hints/interfaces' 5 | 6 | export const lowEntropyTestData: UALowEntropyJSON = { 7 | brands: [ 8 | { 9 | brand: 'Google Chrome', 10 | version: '113', 11 | }, 12 | { 13 | brand: 'Chromium', 14 | version: '113', 15 | }, 16 | { 17 | brand: 'Not-A.Brand', 18 | version: '24', 19 | }, 20 | ], 21 | mobile: false, 22 | platform: 'macOS', 23 | } 24 | 25 | export const highEntropyTestData: UADataValues = { 26 | architecture: 'x86', 27 | bitness: '64', 28 | } 29 | -------------------------------------------------------------------------------- /playgrounds/next-playground/next.config.js: -------------------------------------------------------------------------------- 1 | const withBundleAnalyzer = require('@next/bundle-analyzer')({ 2 | enabled: process.env.ANALYZE === 'true', 3 | }) 4 | 5 | module.exports = withBundleAnalyzer({ 6 | webpack: (config) => { 7 | if (config.mode === 'development') { 8 | config.module.rules.push({ 9 | test: /\.js$/, 10 | use: ['source-map-loader'], 11 | enforce: 'pre', 12 | }) 13 | if (!Array.isArray(config.ignoreWarnings)) { 14 | config.ignoreWarnings = [] 15 | } 16 | 17 | config.ignoreWarnings.push(/Failed to parse source map/) 18 | } 19 | 20 | return config 21 | }, 22 | }) 23 | -------------------------------------------------------------------------------- /packages/browser/src/core/query-string/__tests__/gracefulDecodeURIComponent.test.ts: -------------------------------------------------------------------------------- 1 | import { gracefulDecodeURIComponent } from '../gracefulDecodeURIComponent' 2 | 3 | describe('gracefulDecodeURIComponent', () => { 4 | it('decodes a properly encoded URI component', () => { 5 | const output = gracefulDecodeURIComponent( 6 | 'brown+fox+jumped+%40+the+fence%3F' 7 | ) 8 | 9 | expect(output).toEqual('brown fox jumped @ the fence?') 10 | }) 11 | 12 | it('returns the input string back as-is when input is malformed', () => { 13 | const output = gracefulDecodeURIComponent('25%%2F35%') 14 | 15 | expect(output).toEqual('25%%2F35%') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /packages/browser/src/core/storage/memoryStorage.ts: -------------------------------------------------------------------------------- 1 | import { Store, StorageObject } from './types' 2 | 3 | /** 4 | * Data Storage using in memory object 5 | */ 6 | export class MemoryStorage 7 | implements Store 8 | { 9 | private cache: Record = {} 10 | 11 | get(key: K): Data[K] | null { 12 | return (this.cache[key] ?? null) as Data[K] | null 13 | } 14 | 15 | set(key: K, value: Data[K] | null): void { 16 | this.cache[key] = value 17 | } 18 | 19 | remove(key: K): void { 20 | delete this.cache[key] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /playgrounds/with-vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /packages/browser/src/core/query-string/pickPrefix.ts: -------------------------------------------------------------------------------- 1 | import { QueryStringParams } from '.' 2 | 3 | /** 4 | * Returns an object containing only the properties prefixed by the input 5 | * string. 6 | * Ex: prefix('ajs_traits_', { ajs_traits_address: '123 St' }) 7 | * will return { address: '123 St' } 8 | **/ 9 | export function pickPrefix( 10 | prefix: string, 11 | object: QueryStringParams 12 | ): QueryStringParams { 13 | return Object.keys(object).reduce((acc: QueryStringParams, key: string) => { 14 | if (key.startsWith(prefix)) { 15 | const field = key.substr(prefix.length) 16 | acc[field] = object[key]! 17 | } 18 | return acc 19 | }, {}) 20 | } 21 | -------------------------------------------------------------------------------- /packages/generic-utils/src/create-deferred/create-deferred.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Return a promise that can be externally resolved 3 | */ 4 | export const createDeferred = () => { 5 | let resolve!: (value: T | PromiseLike) => void 6 | let reject!: (reason: any) => void 7 | let settled = false 8 | const promise = new Promise((_resolve, _reject) => { 9 | resolve = (...args) => { 10 | settled = true 11 | _resolve(...args) 12 | } 13 | reject = (...args) => { 14 | settled = true 15 | _reject(...args) 16 | } 17 | }) 18 | 19 | return { 20 | resolve, 21 | reject, 22 | promise, 23 | isSettled: () => settled, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/config-webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@internal/config-webpack", 3 | "version": "0.0.0", 4 | "private": true, 5 | "packageManager": "yarn@3.4.1", 6 | "devDependencies": { 7 | "@babel/cli": "^7.22.10", 8 | "@babel/core": "^7.22.11", 9 | "@babel/preset-env": "^7.22.10", 10 | "@babel/preset-typescript": "^7.22.11", 11 | "@types/circular-dependency-plugin": "^5", 12 | "babel-loader": "^8.0.0", 13 | "circular-dependency-plugin": "^5.2.2", 14 | "ecma-version-validator-webpack-plugin": "^1.2.1", 15 | "terser-webpack-plugin": "^5.1.4", 16 | "webpack": "^5.76.0", 17 | "webpack-cli": "^4.8.0", 18 | "webpack-merge": "^5.9.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './emitter/interface' 2 | export * from './plugins' 3 | export * from './events/interfaces' 4 | export * from './events' 5 | export * from './callback' 6 | export * from './priority-queue' 7 | export { backoff } from './priority-queue/backoff' 8 | export * from './context' 9 | export * from './queue/event-queue' 10 | export * from './analytics' 11 | export * from './analytics/dispatch' 12 | export * from './validation/helpers' 13 | export * from './validation/errors' 14 | export * from './validation/assertions' 15 | export * from './utils/bind-all' 16 | export * from './stats' 17 | export { CoreLogger } from './logger' 18 | export * from './queue/delivery' 19 | -------------------------------------------------------------------------------- /packages/node/src/__tests__/test-helpers/test-plugin.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from '../../app/types' 2 | 3 | export const testPlugin: Plugin = { 4 | isLoaded: jest.fn().mockReturnValue(true), 5 | load: jest.fn().mockResolvedValue(undefined), 6 | unload: jest.fn().mockResolvedValue(undefined), 7 | name: 'Test Plugin', 8 | type: 'destination', 9 | version: '0.1.0', 10 | alias: jest.fn((ctx) => Promise.resolve(ctx)), 11 | group: jest.fn((ctx) => Promise.resolve(ctx)), 12 | identify: jest.fn((ctx) => Promise.resolve(ctx)), 13 | page: jest.fn((ctx) => Promise.resolve(ctx)), 14 | screen: jest.fn((ctx) => Promise.resolve(ctx)), 15 | track: jest.fn((ctx) => Promise.resolve(ctx)), 16 | } 17 | -------------------------------------------------------------------------------- /packages/browser/src/core/context/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CoreContext, 3 | ContextCancelation, 4 | ContextFailedDelivery, 5 | SerializedContext, 6 | CancelationOptions, 7 | } from '@segment/analytics-core' 8 | import { SegmentEvent } from '../events/interfaces' 9 | import { Stats } from '../stats' 10 | 11 | export class Context extends CoreContext { 12 | static override system() { 13 | return new this({ type: 'track', event: 'system' }) 14 | } 15 | constructor(event: SegmentEvent, id?: string) { 16 | super(event, id, new Stats()) 17 | } 18 | } 19 | 20 | export { ContextCancelation } 21 | export type { ContextFailedDelivery, SerializedContext, CancelationOptions } 22 | -------------------------------------------------------------------------------- /packages/browser/src/plugins/segmentio/fetch-dispatcher.ts: -------------------------------------------------------------------------------- 1 | import { fetch } from '../../lib/fetch' 2 | 3 | export type Dispatcher = (url: string, body: object) => Promise 4 | 5 | export type StandardDispatcherConfig = { 6 | keepalive?: boolean 7 | } 8 | 9 | export default function (config?: StandardDispatcherConfig): { 10 | dispatch: Dispatcher 11 | } { 12 | function dispatch(url: string, body: object): Promise { 13 | return fetch(url, { 14 | keepalive: config?.keepalive, 15 | headers: { 'Content-Type': 'text/plain' }, 16 | method: 'post', 17 | body: JSON.stringify(body), 18 | }) 19 | } 20 | 21 | return { 22 | dispatch, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/task/__tests__/task-group.test.ts: -------------------------------------------------------------------------------- 1 | import { createTaskGroup } from '../task-group' 2 | 3 | function sleep(ms: number) { 4 | return new Promise((resolve) => setTimeout(resolve, ms)) 5 | } 6 | 7 | describe('TaskGroup', () => { 8 | it('works with concurrent operations', async () => { 9 | const group = createTaskGroup() 10 | const a = jest.fn() 11 | const b = jest.fn() 12 | 13 | void group.run(async () => { 14 | await sleep(100) 15 | a() 16 | }) 17 | void group.run(async () => { 18 | await sleep(1000) 19 | b() 20 | }) 21 | 22 | await group.done() 23 | expect(a).toBeCalled() 24 | expect(b).toBeCalled() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /playgrounds/next-playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "esnext" 9 | ], 10 | "allowJs": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "preserve", 19 | "incremental": true 20 | }, 21 | "include": [ 22 | "next-env.d.ts", 23 | "**/*.ts", 24 | "**/*.tsx" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # yarn artifacts - https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored 2 | .pnp.* 3 | .yarn/* 4 | !.yarn/patches 5 | !.yarn/plugins 6 | !.yarn/releases 7 | !.yarn/sdks 8 | !.yarn/versions 9 | 10 | # ignore all .vscode folders unless they're in the root directory 11 | .vscode 12 | !.vscode/extensions.json 13 | !.vscode/launch.json 14 | !.vscode/settings.json 15 | 16 | node_modules 17 | dist 18 | package-lock.json 19 | .DS_Store 20 | *.log 21 | stats.json 22 | .tmp 23 | *.tsbuildinfo 24 | coverage 25 | reports/* 26 | 27 | # ignore archives 28 | *.tgz 29 | *.gz 30 | 31 | .changelog 32 | .turbo 33 | /test-results/ 34 | playwright-report/ 35 | playwright/.cache/ 36 | tmp.tsconfig.json 37 | -------------------------------------------------------------------------------- /playgrounds/next-playground/pages/iframe/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AnalyticsProvider } from '../../context/analytics' 3 | 4 | function Iframe(): React.ReactElement { 5 | return ( 6 | 13 | ) 14 | } 15 | 16 | export default () => ( 17 | 18 |