├── .dockerignore ├── .env.example ├── .github ├── ISSUE_TEMPLATE │ └── issue-template.md └── workflows │ ├── close-stale-issues.yml │ ├── docker-build.yml │ └── test-and-build.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── .readme-assets ├── github-hero-dark-mode.png ├── github-hero-light-mode.png └── overview-screenshot.png ├── .versions.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── all.Dockerfile ├── builder.Dockerfile ├── cli ├── build-scripts │ ├── babel.config.cjs │ ├── bin │ │ └── jitsu-build-scripts │ ├── package.json │ ├── src │ │ ├── box.ts │ │ ├── colors.ts │ │ ├── commands │ │ │ └── docker.ts │ │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js └── jitsu-cli │ ├── babel.config.cjs │ ├── bin │ └── jitsu-cli │ ├── data │ └── page.json │ ├── package.json │ ├── src │ ├── commands │ │ ├── build.ts │ │ ├── deploy.ts │ │ ├── init.ts │ │ ├── login.ts │ │ ├── shared.ts │ │ ├── test.ts │ │ └── whoami.ts │ ├── index.ts │ ├── lib │ │ ├── chalk-code-highlight.ts │ │ ├── compiled-function.ts │ │ ├── indent.ts │ │ ├── template.ts │ │ └── version.ts │ └── templates │ │ └── functions.ts │ ├── tsconfig.json │ └── webpack.config.js ├── console.cron ├── consolebuild.sh ├── devenv ├── .gitignore ├── docker-compose.yml └── utils │ └── wait-for-it.sh ├── docker-start-console.sh ├── docker ├── .env.example ├── .gitignore ├── README.md ├── SYNCS_README.md └── docker-compose.yml ├── e2e ├── jest.config.js ├── package.json ├── src │ ├── env.ts │ └── rotor.test.ts └── tsconfig.json ├── examples └── react-app │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.tsx │ ├── ConfigurationProvider.tsx │ ├── Page.css │ ├── Page.tsx │ └── index.tsx │ ├── tailwind.config.js │ └── tsconfig.json ├── libs ├── core-functions │ ├── .gitignore │ ├── __tests__ │ │ ├── datalayout-segment.test.ts │ │ ├── facebook-conversions.test.ts │ │ ├── hubspot-destination.test.ts │ │ ├── intercom-destination.test.ts │ │ ├── lib │ │ │ ├── datalayout-test-data.ts │ │ │ ├── mem-store.ts │ │ │ ├── test-data.ts │ │ │ └── testing-lib.ts │ │ ├── mixpanel.test.ts │ │ ├── posthog.test.ts │ │ ├── strings.test.ts │ │ ├── udf.test.ts │ │ └── user-recognition.test.ts │ ├── jest.config.js │ ├── jest.setup.js │ ├── package.json │ ├── src │ │ ├── functions │ │ │ ├── amplitude-destination.ts │ │ │ ├── braze-destination.ts │ │ │ ├── bulker-destination.ts │ │ │ ├── facebook-conversions.ts │ │ │ ├── ga4-destination.ts │ │ │ ├── hubspot-destination.ts │ │ │ ├── intercom-destination.ts │ │ │ ├── june-destination.ts │ │ │ ├── lib │ │ │ │ ├── browser.ts │ │ │ │ ├── clickhouse-logger.ts │ │ │ │ ├── crypto-code.ts │ │ │ │ ├── crypto-code.txtjs │ │ │ │ ├── http-agent.ts │ │ │ │ ├── index.ts │ │ │ │ ├── json-fetch.ts │ │ │ │ ├── mongodb.ts │ │ │ │ ├── profiles-udf-wrapper-code.ts │ │ │ │ ├── profiles-udf-wrapper-code.txtjs │ │ │ │ ├── profiles-udf-wrapper.ts │ │ │ │ ├── store.ts │ │ │ │ ├── strings.ts │ │ │ │ ├── ua.ts │ │ │ │ ├── udf-wrapper-code.ts │ │ │ │ ├── udf-wrapper-code.txtjs │ │ │ │ ├── udf_wrapper.ts │ │ │ │ └── warehouse-store.ts │ │ │ ├── mixpanel-destination.ts │ │ │ ├── mongodb-destination.ts │ │ │ ├── posthog-destination.ts │ │ │ ├── profiles-functions.ts │ │ │ ├── segment-destination.ts │ │ │ ├── user-recognition.ts │ │ │ └── webhook-destination.ts │ │ ├── index.ts │ │ ├── lib │ │ │ ├── config-types.ts │ │ │ ├── entity-store.ts │ │ │ └── inmem-store.ts │ │ └── meta.ts │ └── tsconfig.json ├── functions │ ├── __tests__ │ │ ├── classic-mapping.test.ts │ │ └── data │ │ │ └── classic-events.ts │ ├── jest.config.js │ ├── jest.setup.js │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── functions.ts │ │ │ ├── objects.ts │ │ │ └── strings.ts │ └── tsconfig.json ├── jitsu-js │ ├── .gitignore │ ├── README.md │ ├── __tests__ │ │ ├── node │ │ │ ├── method-queue.test.ts │ │ │ └── nodejs.test.ts │ │ ├── playwright │ │ │ ├── cases │ │ │ │ ├── anonymous-id-bug.html │ │ │ │ ├── basic.html │ │ │ │ ├── callbacks.html │ │ │ │ ├── cookie-names.html │ │ │ │ ├── disable-user-ids.html │ │ │ │ ├── dont-send.html │ │ │ │ ├── ip-policy.html │ │ │ │ ├── reset.html │ │ │ │ ├── segment-reference.html │ │ │ │ └── url-bug.html │ │ │ └── integration.test.ts │ │ └── simple-syrup.ts │ ├── jest.config.js │ ├── package.json │ ├── playwrite.config.ts │ ├── rollup.config.js │ ├── src │ │ ├── analytics-plugin.ts │ │ ├── browser.ts │ │ ├── destination-plugins │ │ │ ├── ga4.ts │ │ │ ├── gtm.ts │ │ │ ├── index.ts │ │ │ ├── logrocket.ts │ │ │ ├── no-destination-plugins.ts │ │ │ └── tag.ts │ │ ├── index.ts │ │ ├── method-queue.ts │ │ ├── script-loader.ts │ │ ├── tlds.ts │ │ └── version.ts │ ├── tsconfig.json │ └── tsconfig.test.json ├── jitsu-react │ ├── README.md │ ├── package.json │ ├── src │ │ ├── JitsuContext.tsx │ │ ├── JitsuProvider.tsx │ │ ├── index.ts │ │ └── useJitsu.ts │ ├── tsconfig.json │ └── tsconfig.test.json ├── jsondiffpatch │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── jest.config.cjs │ ├── package.json │ ├── src │ │ ├── contexts │ │ │ ├── context.ts │ │ │ ├── diff.ts │ │ │ └── patch.ts │ │ ├── diffpatcher.ts │ │ ├── filters │ │ │ ├── dates.ts │ │ │ ├── nested.ts │ │ │ └── trivial.ts │ │ ├── index.ts │ │ ├── pipe.ts │ │ ├── processor.ts │ │ └── types.ts │ ├── test │ │ ├── examples │ │ │ └── diffpatch.ts │ │ ├── index.spec.ts │ │ └── tsconfig.json │ └── tsconfig.json └── juava │ ├── .gitignore │ ├── __tests__ │ ├── id.test.ts │ ├── security.test.ts │ └── sql-params.test.ts │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── asserts.ts │ ├── boolean.ts │ ├── cache.ts │ ├── collections.ts │ ├── color.ts │ ├── debounce.ts │ ├── error.ts │ ├── id.ts │ ├── index.ts │ ├── log.ts │ ├── number.ts │ ├── objects.ts │ ├── rpc.ts │ ├── security.ts │ ├── shorts.ts │ ├── singleton.ts │ ├── sql-params.ts │ ├── stopwatch.ts │ ├── strings.ts │ └── throttle.ts │ └── tsconfig.json ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── release.sh ├── services ├── profiles │ ├── .gitignore │ ├── babel.config.js │ ├── dist_package.json │ ├── jest.config.js │ ├── jest.setup.js │ ├── package.json │ ├── src │ │ ├── builder.ts │ │ ├── index.ts │ │ └── lib │ │ │ ├── db.ts │ │ │ ├── functions-chain.ts │ │ │ ├── kafka.ts │ │ │ ├── metrics.ts │ │ │ ├── priority-consumer.ts │ │ │ └── repositories.ts │ ├── tsconfig.json │ └── webpack.config.js └── rotor │ ├── .gitignore │ ├── __tests__ │ ├── functions-chain-data.ts │ ├── functions-chain.test.ts │ ├── inmem-store.test.ts │ ├── kafka.test.ts │ └── simple-syrup.ts │ ├── babel.config.js │ ├── dist_package.json │ ├── jest.config.js │ ├── jest.setup.js │ ├── package.json │ ├── src │ ├── global.d.ts │ ├── http │ │ ├── functions.ts │ │ ├── profiles-udf.ts │ │ └── udf.ts │ ├── index.ts │ └── lib │ │ ├── functions-chain.ts │ │ ├── kafka-config.ts │ │ ├── maxmind.ts │ │ ├── message-handler.ts │ │ ├── metrics.ts │ │ ├── redis.ts │ │ ├── repositories.ts │ │ ├── retries.ts │ │ ├── rotor.ts │ │ └── version.ts │ ├── tsconfig.json │ └── webpack.config.js ├── tsconfig.base.json ├── turbo.json ├── types └── protocols │ ├── analytics.d.ts │ ├── async-request.d.ts │ ├── functions.d.ts │ ├── iso8601.d.ts │ ├── package.json │ ├── profile.d.ts │ └── tsconfig.json ├── vercel.json └── webapps ├── console ├── .eslintrc.json ├── .gitignore ├── .storybook │ ├── main.ts │ └── preview.ts ├── components │ ├── AntdTheme │ │ └── AntdTheme.tsx │ ├── ApiKeyEditor │ │ └── ApiKeyEditor.tsx │ ├── AsyncButton │ │ └── AsyncButton.tsx │ ├── BackButton │ │ └── BackButton.tsx │ ├── Billing │ │ ├── BillingBlockingDialog.tsx │ │ ├── BillingManager.module.css │ │ ├── BillingManager.tsx │ │ ├── BillingProvider.tsx │ │ ├── UpgradeDialog.tsx │ │ ├── copy.tsx │ │ └── use-events-usage.ts │ ├── BillingDetails │ │ └── BillingDetails.tsx │ ├── ButtonGroup │ │ ├── ButtonGroup.module.css │ │ └── ButtonGroup.tsx │ ├── ButtonLabel │ │ ├── ButtonLabel.module.css │ │ └── ButtonLabel.tsx │ ├── Callout │ │ └── Callout.tsx │ ├── Center │ │ └── Center.tsx │ ├── CodeBlock │ │ ├── CodeBlock.module.css │ │ ├── CodeBlock.tsx │ │ └── CodeBlockLight.tsx │ ├── CodeEditor │ │ ├── CodeEditor.module.css │ │ ├── CodeEditor.tsx │ │ └── SnippedEditor.tsx │ ├── ConfigObjectEditor │ │ ├── ConfigEditor.module.css │ │ ├── ConfigEditor.tsx │ │ ├── EditorBase.tsx │ │ ├── EditorButtons.tsx │ │ ├── EditorField.tsx │ │ ├── EditorTitle.tsx │ │ ├── Editors.tsx │ │ └── SchemaForm.tsx │ ├── ConnectionEditorPage │ │ ├── ConnectionEditorPage.module.css │ │ └── ConnectionEditorPage.tsx │ ├── ConnectionsDiagram │ │ └── ConnectionsDiagram.tsx │ ├── CopyButton │ │ └── CopyButton.tsx │ ├── DataRentionEditor │ │ └── DataRentionEditor.tsx │ ├── DataView │ │ ├── DataView.tsx │ │ ├── EventsBrowser.tsx │ │ ├── JSONView.tsx │ │ └── TableWithDrawer.tsx │ ├── DestinationsCatalog │ │ ├── DestinationsCatalog.module.css │ │ └── DestinationsCatalog.tsx │ ├── Disable │ │ └── Disable.tsx │ ├── DocumentedLabel │ │ └── DocumentedLabel.tsx │ ├── DomainsEditor │ │ └── DomainsEditor.tsx │ ├── EditorToolbar │ │ └── EditorToolbar.tsx │ ├── EventStat │ │ └── EventStatPage.tsx │ ├── Expandable │ │ └── Expandable.tsx │ ├── ExpandableButton │ │ ├── ExpandableButton.module.css │ │ └── ExpandableButton.tsx │ ├── FieldListEditorLayout │ │ ├── FieldListEditorLayout.module.css │ │ └── FieldListEditorLayout.tsx │ ├── FunctionsDebugger │ │ ├── CodeViewer.tsx │ │ ├── FunctionLogs.tsx │ │ ├── FunctionResult.tsx │ │ ├── FunctionVariables.tsx │ │ ├── FunctionsDebugger.module.css │ │ ├── FunctionsDebugger.tsx │ │ ├── code_templates.ts │ │ └── example_events.ts │ ├── FunctionsSelector │ │ └── FunctionsSelector.tsx │ ├── GlobalError │ │ └── GlobalError.tsx │ ├── GlobalLoader │ │ └── GlobalLoader.tsx │ ├── Htmlizer │ │ └── Htmlizer.tsx │ ├── Icons │ │ ├── ExternalLink.tsx │ │ └── FileText.tsx │ ├── JitsuButton │ │ └── JitsuButton.tsx │ ├── JsonAsTable │ │ ├── JsonAsTable.module.css │ │ └── JsonAsTable.tsx │ ├── LabelEllipsis │ │ └── LabelEllipsis.tsx │ ├── MultiSelectWithCustomOptions │ │ └── MultiSelectWithCustomOptions.tsx │ ├── ObjectTitle │ │ └── ObjectTitle.tsx │ ├── Overlay │ │ ├── Overlay.module.css │ │ └── Overlay.tsx │ ├── PageLayout │ │ ├── WorkspacePageLayout.module.css │ │ └── WorkspacePageLayout.tsx │ ├── PriorityQueueBar │ │ └── PriorityQueueBar.tsx │ ├── ProfileBuilderPage │ │ ├── ProfileBuilderPage.module.css │ │ ├── ProfileBuilderPage.tsx │ │ └── example.ts │ ├── ProvisionDatabaseButton │ │ └── ProvisionDatabaseButton.tsx │ ├── QueryResponse │ │ └── QueryResponse.tsx │ ├── Redirect │ │ └── Redirect.tsx │ ├── SQLViewer │ │ ├── SQLViewer.module.css │ │ └── SQLViewer.tsx │ ├── Selectors │ │ ├── DestinationSelector.tsx │ │ └── SourceSelector.tsx │ ├── ServiceEditor │ │ └── ServiceEditor.tsx │ ├── ServicesCatalog │ │ ├── ServicesCatalog.module.css │ │ └── ServicesCatalog.tsx │ ├── SignInOrUp │ │ ├── GoogleLogo.tsx │ │ ├── SignIn.tsx │ │ ├── SignUp.tsx │ │ └── use-query-string-copy.ts │ ├── SyncEditorPage │ │ └── SyncEditorPage.tsx │ ├── TrackingIntegrationDocumentation │ │ ├── HTTPApi.tsx │ │ ├── Html.tsx │ │ ├── NPMPackage.tsx │ │ ├── Other.tsx │ │ ├── React.tsx │ │ ├── TrackingIntegrationDocumentation.module.css │ │ ├── TrackingIntegrationDocumentation.tsx │ │ └── params.tsx │ ├── UserNotificationSettings │ │ └── UserNotificationSettings.tsx │ ├── Workspace │ │ └── WLink.tsx │ └── WorkspaceNameAndSlugEditor │ │ └── WorkspaceNameAndSlugEditor.tsx ├── emails │ ├── connection-status-failed.tsx │ ├── connection-status-firstrun.tsx │ ├── connection-status-flapping.tsx │ ├── connection-status-ongoing.tsx │ ├── connection-status-partial.tsx │ ├── connection-status-recovered.tsx │ ├── shared.tsx │ └── styles.ts ├── lib │ ├── api.ts │ ├── auth.ts │ ├── branding.tsx │ ├── code.ts │ ├── context.tsx │ ├── domains.ts │ ├── firebase-client.tsx │ ├── modal.tsx │ ├── nextauth.config.ts │ ├── oidc.ts │ ├── openapi.ts │ ├── previous-route.tsx │ ├── recovery-log.ts │ ├── schema │ │ ├── clickhouse-connection-credentials.ts │ │ ├── config-objects.ts │ │ ├── destinations.tsx │ │ ├── icons │ │ │ ├── amplitude.tsx │ │ │ ├── bigquery.tsx │ │ │ ├── blaze.tsx │ │ │ ├── clickhouse.tsx │ │ │ ├── devnull.tsx │ │ │ ├── facebook.tsx │ │ │ ├── ga4.tsx │ │ │ ├── gcs.tsx │ │ │ ├── gtm.tsx │ │ │ ├── hubspot.tsx │ │ │ ├── intercom.tsx │ │ │ ├── june.tsx │ │ │ ├── logrocket.tsx │ │ │ ├── mixpanel.tsx │ │ │ ├── mongodb.tsx │ │ │ ├── motherduck.tsx │ │ │ ├── mysql.tsx │ │ │ ├── postgres.tsx │ │ │ ├── posthog.tsx │ │ │ ├── redshift.tsx │ │ │ ├── s3.tsx │ │ │ ├── segment.tsx │ │ │ ├── snowflake.tsx │ │ │ ├── tag.tsx │ │ │ └── webhook.tsx │ │ └── index.ts │ ├── server │ │ ├── audit-log.ts │ │ ├── clickhouse.ts │ │ ├── custom-domains.ts │ │ ├── data-domains.ts │ │ ├── db.ts │ │ ├── ee.ts │ │ ├── events-log.ts │ │ ├── firebase-server.ts │ │ ├── http-agent.ts │ │ ├── log.ts │ │ ├── mail.tsx │ │ ├── oauth │ │ │ ├── nango-config.ts │ │ │ └── services.ts │ │ ├── origin.ts │ │ ├── read-only-mode.ts │ │ ├── sync.ts │ │ ├── syncs │ │ │ └── mixpanel.ts │ │ ├── telemetry.ts │ │ ├── templates.tsx │ │ ├── user-preferences.ts │ │ └── whoami.ts │ ├── shared │ │ ├── arrays.ts │ │ ├── chores.ts │ │ ├── countries.ts │ │ ├── data-retention.ts │ │ ├── domain-check-response.ts │ │ ├── email-domains.ts │ │ ├── errors.ts │ │ ├── json.ts │ │ ├── reporting.ts │ │ ├── url.ts │ │ └── zod.ts │ ├── sources.ts │ ├── store │ │ └── index.tsx │ ├── ui.tsx │ ├── useApi.ts │ ├── useQueryStringState.ts │ ├── version.ts │ └── zod.ts ├── middleware.ts_ ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages │ ├── 403.tsx │ ├── 404.tsx │ ├── 405.tsx │ ├── 500.tsx │ ├── [workspaceId] │ │ ├── connections │ │ │ ├── edit.tsx │ │ │ └── index.tsx │ │ ├── custom-images.tsx │ │ ├── data.tsx │ │ ├── destinations.tsx │ │ ├── event-stat.tsx │ │ ├── functions.tsx │ │ ├── index.tsx │ │ ├── miscs.tsx │ │ ├── profile-builder.tsx │ │ ├── services.tsx │ │ ├── settings │ │ │ ├── billing │ │ │ │ ├── details.tsx │ │ │ │ └── index.tsx │ │ │ ├── data-retention.tsx │ │ │ ├── domains.tsx │ │ │ ├── index.tsx │ │ │ └── notifications.tsx │ │ ├── sql.tsx │ │ ├── streams.tsx │ │ ├── support.tsx │ │ └── syncs │ │ │ ├── edit.tsx │ │ │ ├── index.tsx │ │ │ ├── logs.tsx │ │ │ ├── state.tsx │ │ │ └── tasks.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── _error.tsx │ ├── accept.tsx │ ├── admin │ │ ├── [workspaceId] │ │ │ └── email.tsx │ │ ├── email-preview.tsx │ │ ├── events-debug.tsx │ │ ├── oauth-test.tsx │ │ ├── overage-billing.tsx │ │ ├── users.tsx │ │ └── workspaces.tsx │ ├── api │ │ ├── [...not-found-404].ts │ │ ├── [workspaceId] │ │ │ ├── config │ │ │ │ ├── [type] │ │ │ │ │ ├── [id].ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── test.ts │ │ │ │ ├── link.ts │ │ │ │ └── profile-builder.ts │ │ │ ├── domain-check.ts │ │ │ ├── ee │ │ │ │ ├── [...proxyPath].ts │ │ │ │ └── provision-db │ │ │ │ │ ├── credentials.ts │ │ │ │ │ └── index.ts │ │ │ ├── function │ │ │ │ └── run.ts │ │ │ ├── listen.ts │ │ │ ├── log │ │ │ │ └── [type] │ │ │ │ │ └── [actorId].ts │ │ │ ├── metrics │ │ │ │ └── index.ts │ │ │ ├── notification-test.ts │ │ │ ├── profile-builder │ │ │ │ ├── events.ts │ │ │ │ ├── run.ts │ │ │ │ └── state.ts │ │ │ ├── reports │ │ │ │ ├── active-events.ts │ │ │ │ ├── event-stat.ts │ │ │ │ └── sync-stat.ts │ │ │ ├── sources │ │ │ │ ├── cancel.ts │ │ │ │ ├── check.ts │ │ │ │ ├── discover.ts │ │ │ │ ├── logs.ts │ │ │ │ ├── run.ts │ │ │ │ ├── spec.ts │ │ │ │ ├── state.ts │ │ │ │ └── tasks.ts │ │ │ └── sql │ │ │ │ ├── query.ts │ │ │ │ └── schema.ts │ │ ├── admin │ │ │ ├── become.ts │ │ │ ├── catalog-refresh.ts │ │ │ ├── domains-report.ts │ │ │ ├── domains.ts │ │ │ ├── email-templates │ │ │ │ ├── [template].tsx │ │ │ │ └── index.ts │ │ │ ├── env.ts │ │ │ ├── events-log-init.ts │ │ │ ├── events-log-trim.ts │ │ │ ├── export │ │ │ │ └── [name] │ │ │ │ │ └── index.ts │ │ │ ├── migrate-data.ts │ │ │ ├── migrate-mappings.ts │ │ │ ├── migrate.ts │ │ │ ├── notifications.ts │ │ │ ├── sync-logs-trim.ts │ │ │ └── users.ts │ │ ├── app-config.ts │ │ ├── auth │ │ │ └── [...nextauth].ts │ │ ├── destinations.ts │ │ ├── ee │ │ │ └── jwt.ts │ │ ├── fb-auth │ │ │ ├── create-session.ts │ │ │ ├── create-user.ts │ │ │ └── revoke-session.ts │ │ ├── healthcheck │ │ │ ├── db.ts │ │ │ └── index.ts │ │ ├── id.ts │ │ ├── init-user.ts │ │ ├── make-error.ts │ │ ├── me.ts │ │ ├── oauth │ │ │ ├── catalog.ts │ │ │ ├── init.ts │ │ │ └── service.ts │ │ ├── ping.ts │ │ ├── s │ │ │ └── javascript-library.ts │ │ ├── schema │ │ │ ├── [...type].ts │ │ │ └── index.ts │ │ ├── sources │ │ │ ├── [packageType] │ │ │ │ └── [...packageId].ts │ │ │ ├── index.ts │ │ │ ├── logo.ts │ │ │ └── versions.ts │ │ ├── user │ │ │ ├── accept.ts │ │ │ ├── change-password.ts │ │ │ ├── cli-key.ts │ │ │ ├── keys.ts │ │ │ ├── notifications-settings.ts │ │ │ └── properties.ts │ │ ├── version.ts │ │ └── workspace │ │ │ ├── [workspaceIdOrSlug] │ │ │ ├── [section] │ │ │ │ └── index.tsx │ │ │ ├── index.ts │ │ │ └── users │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ └── slug-check.ts │ ├── cli.tsx │ ├── custom-plan.tsx │ ├── index.tsx │ ├── reset-password.tsx │ ├── signin.tsx │ ├── signup.tsx │ ├── user.tsx │ └── workspaces.tsx ├── postcss.config.js ├── prisma │ ├── events_log.sql │ ├── metrics.sql │ ├── schema.prisma │ └── workspace-sync-runs.sql ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── external-link.svg │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── logo-classic-gray.svg │ ├── logo-classic.svg │ ├── logo-with-text.svg │ ├── logo.svg │ └── safari-pinned-tab.svg ├── scripts │ └── password-hash.ts ├── styles │ └── globals.css ├── tailwind.config.js ├── theme.config.js ├── tsconfig.json └── vercel.sh ├── ee-api ├── .eslintrc.json ├── .gitignore ├── README.md ├── components │ └── email-component.tsx ├── emails │ ├── billing-issues.tsx │ ├── churned.tsx │ ├── connection-status-failed.tsx │ ├── connection-status-success.tsx │ ├── quota-about-to-exceed.tsx │ ├── quota-exceeded.tsx │ ├── styles.ts │ ├── throttling-reminder.tsx │ ├── throttling-started.tsx │ └── welcome.tsx ├── lib │ ├── auth.ts │ ├── email.tsx │ ├── firebase-auth.ts │ ├── log.ts │ ├── route-helpers.ts │ ├── services.ts │ ├── sql │ │ └── workspace-info.sql │ ├── store.ts │ └── stripe.ts ├── next.config.js ├── package.json ├── pages │ ├── _app.tsx │ ├── api │ │ ├── billing │ │ │ ├── custom-plan.ts │ │ │ ├── export-subscriptions.ts │ │ │ ├── manage.ts │ │ │ ├── plans.ts │ │ │ ├── rotate-stripe-customer.ts │ │ │ ├── settings.ts │ │ │ ├── upgrade.ts │ │ │ └── workspaces.ts │ │ ├── custom-token.ts │ │ ├── email-history.tsx │ │ ├── email.tsx │ │ ├── healthcheck │ │ │ └── index.ts │ │ ├── is-active.ts │ │ ├── provision-db.ts │ │ ├── quotas │ │ │ └── sync.ts │ │ ├── report │ │ │ ├── overage.ts │ │ │ └── workspace-stat.ts │ │ ├── s3-connections.ts │ │ ├── s3-init.ts │ │ ├── set-throttle.ts │ │ ├── sync-cache.ts │ │ ├── unsubscribe.ts │ │ └── user-created.ts │ └── index.tsx ├── scripts │ ├── sql-exec.ts │ └── sql │ │ ├── 01-kv-schema.sql │ │ ├── 02-backup-connections.sql │ │ ├── 03-debug-views.sql │ │ ├── 04-events-export.sql │ │ └── 05-stat-cache.sql ├── tsconfig.json ├── vercel.json └── vercel.sh └── shared ├── package.json ├── src ├── email-template.tsx ├── email.tsx ├── index.ts ├── styles.ts ├── template-example.tsx └── types.ts └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Run `docker-compose -f devenv/docker-compose.yml up --force-recreate -d` 2 | # Open: 3 | # 1. http://localhost:3011/ for Redis UI 4 | # 2. http://localhost:3032/ for Kafka UI 5 | ###################################### 6 | ### LOCAL DEVELOPMENT ### 7 | ###################################### 8 | #GITHUB_CLIENT_ID= 9 | #GITHUB_CLIENT_SECRET= 10 | 11 | #AUTH_OIDC_PROVIDER='{"issuer":"http://localhost:8080/realms/dev_realm","clientId":"dev_client","clientSecret":"your_generated_secret"}' 12 | 13 | #DATABASE_URL=postgresql://postgres:postgres-mqf3nzx@localhost:5438/postgres 14 | #REDIS_URL=redis://default:redis-mqf3nzx@localhost:6380 15 | #KAFKA_BOOTSTRAP_SERVERS=localhost:19092 16 | 17 | ###################################### 18 | ### USEFUL VARS ### 19 | ###################################### 20 | #LOG_LEVEL=debug 21 | #REACT_EDITOR=idea 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue template 3 | about: Template for creating issues, both bugs and feature requests 4 | title: '' 5 | labels: '' 6 | assignees: vklimontovich 7 | 8 | --- 9 | ``` 10 | ┌─────────────────────────────────────────────────────────────────────────────────────────────────┐ 11 | │ IMPORTANT: GitHub is for discussing issues of self-hosting Jitsu. Please make sure that your │ 12 | │ issue can be reproduced for self-hosting environment. If you're experiencing problem with your │ 13 | │ Jitsu Cloud account, please contact support@jitsu.com │ 14 | └─────────────────────────────────────────────────────────────────────────────────────────────────┘ 15 | 16 | Please delete this block before submitting the issue 17 | ``` 18 | 19 | ## Summary 20 | 21 | 22 | 23 | ## System configuration and versions 24 | 25 | 28 | 29 | ## Artifacts (logs, etc) 30 | 31 | 35 | -------------------------------------------------------------------------------- /.github/workflows/close-stale-issues.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" #once a day at 1:30am 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v5 14 | with: 15 | days-before-issue-stale: 60 16 | days-before-issue-close: 60 17 | stale-issue-label: "🕰️Stale" 18 | exempt-issue-labels: "⏳Postpone" 19 | stale-issue-message: "This issue is stale because it has been open for 60 days with no activity." 20 | close-issue-message: "This issue was closed because it has been inactive for 60 days since being marked as stale." 21 | days-before-pr-stale: -1 22 | days-before-pr-close: -1 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | .idea 4 | .env.local 5 | .turbo 6 | .pnpm-debug.log 7 | 8 | .vercel 9 | 10 | /libs/jitsu-react/dist/ 11 | /examples/nextjs-app/.next/ 12 | /examples/hubspot-function/dist/ 13 | /cli/jitsu-cli/compiled/ 14 | /cli/jitsu-cli/dist/ 15 | /types/protocols/dist/ 16 | /libs/functions/dist/ 17 | /docker/data/** 18 | /cache/** 19 | /cli/build-scripts/compiled/ 20 | /cli/build-scripts/dist/ 21 | /libs/jsondiffpatch/coverage/ 22 | /libs/jsondiffpatch/dist/ 23 | /devenv-* 24 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm pre-commit 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.js 2 | # dependencies 3 | /node_modules 4 | .next 5 | .run 6 | # bundle stats 7 | *.mdx 8 | *.md 9 | 10 | jitsu 11 | # configs -- will keep multi-line json arrays 12 | .eslintrc.json 13 | package.json 14 | tsconfig.json 15 | tsconfig.paths.json 16 | core/jitsu-cli/lib 17 | core/node-bridge/bin 18 | core/jitsu-types/lib 19 | core/jlib/lib 20 | core/test-destination/dist 21 | pnpm-lock.yaml 22 | .pnpm-store 23 | /examples/react-app/build 24 | /libs/jitsu-react/dist 25 | /webapps/console/prisma/schema 26 | /libs/jitsu-js/__tests__/playwright/artifacts 27 | /libs/jitsu-js/compiled/ 28 | libs/jitsu-js/dist/ 29 | /cli/jitsu-cli/compiled/ 30 | /cli/jitsu-cli/dist/ 31 | /libs/functions/dist/ -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "singleQuote": false, 6 | "printWidth": 120, 7 | "semi": true, 8 | "arrowParens": "avoid" 9 | } 10 | -------------------------------------------------------------------------------- /.readme-assets/github-hero-dark-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/.readme-assets/github-hero-dark-mode.png -------------------------------------------------------------------------------- /.readme-assets/github-hero-light-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/.readme-assets/github-hero-light-mode.png -------------------------------------------------------------------------------- /.readme-assets/overview-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/.readme-assets/overview-screenshot.png -------------------------------------------------------------------------------- /.versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "beta": "2.3.*", 3 | "latest": "2.2.*" 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jitsu Labs, Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /builder.Dockerfile: -------------------------------------------------------------------------------- 1 | #Docker image for building the application 2 | #It installs all dependencies which speeds up CI builds. 3 | #Dependencies are: node, pnpm and playwright 4 | 5 | # Run `docker login` 6 | # Build & push it with 7 | # docker buildx build --platform linux/amd64 . -f builder.Dockerfile --push -t jitsucom/node22builder:latest 8 | 9 | FROM node:22-bookworm 10 | RUN apt-get update 11 | # Telnet is useful for debugging, and we need curl for Node 12 | RUN apt-get install git curl telnet python3 ca-certificates gnupg g++ make -y 13 | 14 | RUN npm -g install pnpm 15 | 16 | #Should be the same as playwrite version in ./libs/jitsu-js/package.json 17 | RUN npm install --global playwright@1.39.0 18 | RUN playwright install --with-deps 19 | -------------------------------------------------------------------------------- /cli/build-scripts/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@babel/preset-env", "@babel/preset-typescript"], 3 | plugins: [], 4 | }; 5 | -------------------------------------------------------------------------------- /cli/build-scripts/bin/jitsu-build-scripts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require("../dist/main.js"); -------------------------------------------------------------------------------- /cli/build-scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jitsu-build-scripts", 3 | "version": "0.0.0", 4 | "description": "", 5 | "author": "Jitsu Dev Team ", 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "bin": "./bin/jitsu-build-scripts", 10 | "license": "MIT", 11 | "private": false, 12 | "scripts": { 13 | "clean": "rm -rf ./dist", 14 | "compile": "tsc -p . ", 15 | "build": "pnpm compile && webpack", 16 | "exec": "ts-node src/index.ts" 17 | }, 18 | "dependencies": { 19 | "boxen": "^7.1.1", 20 | "colorette": "^2.0.20", 21 | "semver": "^7.5.4", 22 | "simple-git": "^3.22.0", 23 | "string-width": "^7.0.0", 24 | "tslib": "^2.6.3" 25 | }, 26 | "devDependencies": { 27 | "commander": "^11.0.0", 28 | "@types/node": "^18.15.3", 29 | "@types/semver": "^7.5.6", 30 | "@babel/preset-env": "^7.23.2", 31 | "@babel/preset-typescript": "^7.23.2", 32 | "babel-loader": "^9.1.3", 33 | "webpack": "^5.99.5", 34 | "webpack-cli": "^6.0.1", 35 | "juava": "workspace:*" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cli/build-scripts/src/colors.ts: -------------------------------------------------------------------------------- 1 | import { createColors } from "colorette"; 2 | 3 | export const color = createColors(); 4 | -------------------------------------------------------------------------------- /cli/build-scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./compiled", 4 | "rootDir": ".", 5 | "noImplicitAny": false, 6 | "allowSyntheticDefaultImports": true, 7 | "importHelpers": true, 8 | "removeComments": true, 9 | "target": "ES2021", 10 | "module": "commonjs", 11 | "lib": [ 12 | "dom","esnext" 13 | ], 14 | "allowJs": true, 15 | "strict": true, 16 | "esModuleInterop": true, 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "skipLibCheck": true 20 | }, 21 | "exclude": [ 22 | "node_modules" 23 | ], 24 | "include": [ 25 | "src/**/*" 26 | ] 27 | } 28 | 29 | -------------------------------------------------------------------------------- /cli/build-scripts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | 4 | const config = { 5 | entry: "./src/index.ts", 6 | target: "node", 7 | externals: { 8 | "../package.json": "require('../package.json')", 9 | }, 10 | node: { 11 | __dirname: false, 12 | }, 13 | devtool: "source-map", 14 | output: { 15 | path: path.resolve(__dirname, "dist"), 16 | }, 17 | plugins: [ 18 | new webpack.IgnorePlugin({ resourceRegExp: /^fsevents$/ }), // Ignore MacOS-only module 19 | ], 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.(ts|tsx)$/i, 24 | use: { 25 | loader: "babel-loader", 26 | }, 27 | }, 28 | { 29 | test: /\.node$/, 30 | loader: "node-loader", 31 | }, 32 | ], 33 | }, 34 | optimization: { 35 | minimize: false, 36 | }, 37 | resolve: { 38 | extensions: [".tsx", ".ts", ".jsx", ".js", ".node", "..."], 39 | }, 40 | mode: "production", 41 | }; 42 | 43 | module.exports = () => config; 44 | -------------------------------------------------------------------------------- /cli/jitsu-cli/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@babel/preset-env", "@babel/preset-typescript"], 3 | plugins: [], 4 | }; 5 | -------------------------------------------------------------------------------- /cli/jitsu-cli/bin/jitsu-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require("../dist/main.js"); -------------------------------------------------------------------------------- /cli/jitsu-cli/src/commands/shared.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import inquirer from "inquirer"; 3 | import { existsSync, readFileSync } from "fs"; 4 | import { b, red } from "../lib/chalk-code-highlight"; 5 | 6 | export async function loadPackageJson(projectDir: string): Promise<{ projectDir: string; packageJson: any }> { 7 | let packageJson = loadPackageJson0(projectDir); 8 | if (!packageJson) { 9 | projectDir = ( 10 | await inquirer.prompt([ 11 | { 12 | type: "input", 13 | name: "dir", 14 | message: `Enter path of project directory:`, 15 | }, 16 | ]) 17 | ).dir; 18 | packageJson = loadPackageJson0(projectDir); 19 | if (!packageJson) { 20 | process.exit(1); 21 | } 22 | } 23 | return { projectDir, packageJson }; 24 | } 25 | 26 | export function loadPackageJson0(projectDir: string): any { 27 | const packageJsonPath = path.resolve(projectDir, "package.json"); 28 | if (!existsSync(packageJsonPath)) { 29 | console.error(red(`Can't find node.js project in: ${b(projectDir)}`)); 30 | return undefined; 31 | } 32 | const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); 33 | if (!packageJson.devDependencies?.["jitsu-cli"]) { 34 | console.error(red(`directory ${b(projectDir)} doesn't contain jitsu-cli managed project`)); 35 | return undefined; 36 | } 37 | return packageJson; 38 | } 39 | -------------------------------------------------------------------------------- /cli/jitsu-cli/src/commands/test.ts: -------------------------------------------------------------------------------- 1 | import { run as jest } from "jest-cli"; 2 | import { loadPackageJson } from "./shared"; 3 | import chalk from "chalk"; 4 | import { b } from "../lib/chalk-code-highlight"; 5 | 6 | export async function test({ dir }: { dir?: string }) { 7 | const { packageJson, projectDir } = await loadPackageJson(dir || process.cwd()); 8 | 9 | console.log(`Running tests for ${b(packageJson.name)}`); 10 | 11 | const jestArgs = ["--passWithNoTests", "--projects", projectDir, "--preset", "ts-jest"]; 12 | 13 | await jest(jestArgs); 14 | } 15 | -------------------------------------------------------------------------------- /cli/jitsu-cli/src/lib/indent.ts: -------------------------------------------------------------------------------- 1 | function getIndentSize(line): number { 2 | let idx = 0; 3 | for (; line.charAt(idx) === " " && idx < line.length; idx++) {} 4 | return idx; 5 | } 6 | 7 | /** 8 | * Finds a common indentation in text and removes it 9 | */ 10 | export function removeIndentation(text: string, { trimLines = true } = {}): string { 11 | let lines = text.split("\n"); 12 | if (trimLines) { 13 | let start = 0, 14 | end = lines.length - 1; 15 | for (; lines[start].trim().length == 0 && start <= end; start++) {} 16 | for (; lines[end].trim().length == 0 && end >= start; end--) {} 17 | lines = lines.slice(start, end + 1); 18 | } 19 | let commonIndent = Math.min(...lines.filter(ln => ln.trim().length > 0).map(getIndentSize)); 20 | 21 | return lines.map(ln => ln.substring(commonIndent)).join("\n"); 22 | } 23 | 24 | export function align(text: string, { indent = 0, lnBefore = 0, lnAfter = 0 } = {}) { 25 | const cleanText = removeIndentation(text, { trimLines: true }); 26 | return [ 27 | ...new Array(lnBefore).fill(""), 28 | ...cleanText.split("\n").map(ln => " ".repeat(indent) + ln), 29 | ...new Array(lnAfter).fill(""), 30 | ].join("\n"); 31 | } 32 | 33 | export function jsonify(obj: any) { 34 | if (typeof obj === "string") { 35 | try { 36 | return JSON.parse(obj); 37 | } catch (e) { 38 | return obj; 39 | } 40 | } 41 | return obj; 42 | } 43 | -------------------------------------------------------------------------------- /cli/jitsu-cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./compiled", 4 | "rootDir": ".", 5 | "noImplicitAny": false, 6 | "allowSyntheticDefaultImports": true, 7 | "importHelpers": true, 8 | "removeComments": true, 9 | "target": "ES2021", 10 | "module": "commonjs", 11 | "lib": [ 12 | "dom","esnext" 13 | ], 14 | "allowJs": true, 15 | "strict": true, 16 | "esModuleInterop": true, 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "skipLibCheck": true 20 | }, 21 | "exclude": [ 22 | "node_modules" 23 | ], 24 | "include": [ 25 | "src/**/*" 26 | ] 27 | } 28 | 29 | -------------------------------------------------------------------------------- /cli/jitsu-cli/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | 4 | const config = { 5 | entry: "./src/index.ts", 6 | target: "node", 7 | externals: { 8 | figlet: "require('figlet')", 9 | "@swc/core": "require('@swc/core')", 10 | "@swc/wasm": "require('@swc/wasm')", 11 | typescript: "require('typescript')", 12 | //"isolated-vm": "require('isolated-vm')", 13 | "jest-cli": "require('jest-cli')", 14 | "../../package.json": "require('../package.json')", 15 | }, 16 | node: { 17 | __dirname: false, 18 | }, 19 | devtool: "source-map", 20 | output: { 21 | path: path.resolve(__dirname, "dist"), 22 | }, 23 | plugins: [ 24 | new webpack.IgnorePlugin({ resourceRegExp: /^fsevents$/ }), // Ignore MacOS-only module 25 | ], 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.(ts|tsx)$/i, 30 | use: { 31 | loader: "babel-loader", 32 | }, 33 | }, 34 | { 35 | test: /\.node$/, 36 | loader: "node-loader", 37 | }, 38 | ], 39 | }, 40 | optimization: { 41 | minimize: false, 42 | }, 43 | resolve: { 44 | extensions: [".tsx", ".ts", ".jsx", ".js", ".node", "..."], 45 | }, 46 | mode: "production", 47 | }; 48 | 49 | module.exports = () => config; 50 | -------------------------------------------------------------------------------- /console.cron: -------------------------------------------------------------------------------- 1 | */15 * * * * curl --silent --show-error http://localhost:3000/api/admin/events-log-trim 2 | -------------------------------------------------------------------------------- /consolebuild.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker buildx build --platform linux/amd64 . -f all.Dockerfile --target console --push -t jitsucom/console:latest -------------------------------------------------------------------------------- /devenv/.gitignore: -------------------------------------------------------------------------------- 1 | /data/ -------------------------------------------------------------------------------- /docker/.gitignore: -------------------------------------------------------------------------------- 1 | /.env 2 | /data/syncctl/* 3 | -------------------------------------------------------------------------------- /e2e/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("ts-jest").JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | testMatch: ["**/src/**/*.test.ts"], 6 | testTimeout: 60_000, //1 min 7 | 8 | //runner: "jest-runner", 9 | }; 10 | -------------------------------------------------------------------------------- /e2e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jitsu-internal/e2e", 3 | "version": "0.0.0", 4 | "description": "", 5 | "author": "Jitsu Dev Team ", 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "license": "MIT", 10 | "private": false, 11 | "scripts": { 12 | "test": "exit 0 || jest --verbose" 13 | }, 14 | "devDependencies": { 15 | "@jest/globals": "^29.3.1", 16 | "@jitsu-internal/console": "workspace:*", 17 | "@types/jest": "^29.1.1", 18 | "find-free-port": "^2.0.0", 19 | "ioredis": "^5.3.2", 20 | "jest": "^29.3.1", 21 | "json5": "^2.1.0", 22 | "juava": "workspace:*", 23 | "node-postgres": "^0.6.2", 24 | "testcontainers": "^9.0.0", 25 | "ts-jest": "29.0.5", 26 | "tslib": "^2.6.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /e2e/src/rotor.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, jest, test, beforeAll, afterAll } from "@jest/globals"; 2 | 3 | import { e2eTestEnabled, prepareTestEnvironment, TestEnv } from "./env"; 4 | 5 | let testEnv: TestEnv; 6 | let cleanup: () => Promise; 7 | 8 | beforeAll(async () => { 9 | if (!e2eTestEnabled()) { 10 | return; 11 | } 12 | [testEnv, cleanup] = await prepareTestEnvironment(); 13 | }); 14 | 15 | test("Test full sync", async () => { 16 | if (!e2eTestEnabled(true)) { 17 | return; 18 | } 19 | console.log("Test full sync"); 20 | }); 21 | 22 | afterAll(async () => { 23 | if (cleanup) { 24 | await cleanup(); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./compiled", 4 | "rootDir": "./src", 5 | "noImplicitAny": false, 6 | "allowSyntheticDefaultImports": true, 7 | "importHelpers": true, 8 | "removeComments": true, 9 | "target": "ES2021", 10 | "module": "commonjs", 11 | "lib": [ 12 | "esnext" 13 | ], 14 | "allowJs": true, 15 | "strict": true, 16 | "esModuleInterop": true, 17 | "jsx": "react-jsx", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true 20 | }, 21 | "exclude": [ 22 | "node_modules" 23 | ], 24 | "include": [ 25 | "src/**/*" 26 | ] 27 | } 28 | 29 | -------------------------------------------------------------------------------- /examples/react-app/.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jitsu/react-example", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@jitsu/jitsu-react": "workspace:*", 7 | "@types/node": "^18.15.3", 8 | "lodash": "^4.17.21", 9 | "react": "^18.3.1", 10 | "react-dom": "^18.3.1", 11 | "react-router": "^6.25.1", 12 | "react-router-dom": "^6.25.1", 13 | "web-vitals": "^2.1.4" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build" 18 | }, 19 | "browserslist": { 20 | "production": [ 21 | ">0.2%", 22 | "not dead", 23 | "not op_mini all" 24 | ], 25 | "development": [ 26 | "last 1 chrome version", 27 | "last 1 firefox version", 28 | "last 1 safari version" 29 | ] 30 | }, 31 | "devDependencies": { 32 | "react-scripts": "^5.0.1", 33 | "typescript": "^5.6.3", 34 | "@types/react-dom": "^18.3.0", 35 | "@types/react": "^18.3.3", 36 | "@types/lodash": "^4.14.185", 37 | "tailwindcss": "^3.4.14" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/examples/react-app/public/favicon.ico -------------------------------------------------------------------------------- /examples/react-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/examples/react-app/public/logo192.png -------------------------------------------------------------------------------- /examples/react-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/examples/react-app/public/logo512.png -------------------------------------------------------------------------------- /examples/react-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/react-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/react-app/src/Page.css: -------------------------------------------------------------------------------- 1 | /*@tailwind base;*/ 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* add css module styles here (optional) */ 6 | html, 7 | body { 8 | padding: 0; 9 | margin: 0; 10 | 11 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, 12 | Helvetica Neue, sans-serif; 13 | } 14 | 15 | * { 16 | box-sizing: border-box; 17 | } 18 | 19 | code { 20 | font-family: "SFMono-Regular", Menlo, Consolas, "PT Mono", "Liberation Mono", Courier, monospace; 21 | line-height: normal; 22 | background: rgba(135, 131, 120, 0.15); 23 | color: #eb5757; 24 | border-radius: 3px; 25 | font-size: 85%; 26 | padding: 0.2em 0.4em; 27 | } 28 | 29 | pre { 30 | background-color: #f6f8fa; 31 | font-family: "SFMono-Regular", Menlo, Consolas, "PT Mono", "Liberation Mono", Courier, monospace; 32 | font-size: 85%; 33 | border-radius: 3px; 34 | padding: 10px; 35 | margin: 10px 0; 36 | } 37 | 38 | .configTitle { 39 | width: 120px; 40 | min-width: 120px; 41 | } 42 | -------------------------------------------------------------------------------- /examples/react-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | 4 | import { BrowserRouter } from "react-router-dom"; 5 | 6 | import App from "./App"; 7 | 8 | const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); 9 | 10 | root.render( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /examples/react-app/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("tailwindcss").Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /examples/react-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /libs/core-functions/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /libs/core-functions/__tests__/hubspot-destination.test.ts: -------------------------------------------------------------------------------- 1 | import { testJitsuFunction, TestOptions } from "./lib/testing-lib"; 2 | import { eventsSequence } from "./lib/test-data"; 3 | import { HubspotCredentials } from "../src/meta"; 4 | import { HubspotDestination } from "../src/functions/hubspot-destination"; 5 | import { AnalyticsServerEvent } from "@jitsu/protocols/analytics"; 6 | import { undefined } from "zod"; 7 | 8 | test("hubspot-integration-test", async () => { 9 | if (!process.env.TEST_HUBSPOT_DESTINATIONS) { 10 | console.log("Skipping mixpanel destination integration test - TEST_HUBSPOT_DESTINATIONS is not set"); 11 | return; 12 | } 13 | const groupEvent: AnalyticsServerEvent = { 14 | context: undefined, 15 | messageId: "group1", 16 | type: "group", 17 | traits: { 18 | name: "Company 1", 19 | }, 20 | }; 21 | const events = [...eventsSequence(), groupEvent]; 22 | const opts: TestOptions = { 23 | func: HubspotDestination, 24 | configEnvVar: "TEST_HUBSPOT_DESTINATIONS", 25 | events: events, 26 | }; 27 | await testJitsuFunction(opts); 28 | }); 29 | 30 | // test("mixpanel-destination-unit", () => {љ 31 | // //implement later, when testing library is ready to mock fetch 32 | // }); 33 | -------------------------------------------------------------------------------- /libs/core-functions/__tests__/mixpanel.test.ts: -------------------------------------------------------------------------------- 1 | import { testJitsuFunction, TestOptions } from "./lib/testing-lib"; 2 | import MixpanelDestination from "../src/functions/mixpanel-destination"; 3 | import { eventsSequence } from "./lib/test-data"; 4 | import { MixpanelCredentials } from "../src/meta"; 5 | 6 | test("mixpanel-destination-integration", async () => { 7 | if (!process.env.TEST_MIXPANEL_DESTINATION) { 8 | console.log("Skipping mixpanel destination integration test - TEST_MIXPANEL_DESTINATION is not set"); 9 | return; 10 | } 11 | const events = eventsSequence(); 12 | const opts: TestOptions = { 13 | func: MixpanelDestination, 14 | configEnvVar: "TEST_MIXPANEL_DESTINATION", 15 | events: events, 16 | }; 17 | await testJitsuFunction(opts); 18 | }); 19 | 20 | test("mixpanel-destination-unit", () => { 21 | //implement later, when testing library is ready to mock fetch 22 | }); 23 | -------------------------------------------------------------------------------- /libs/core-functions/__tests__/posthog.test.ts: -------------------------------------------------------------------------------- 1 | import { eventsSequence } from "./lib/test-data"; 2 | import { testJitsuFunction, TestOptions } from "./lib/testing-lib"; 3 | import PosthogDestination from "../src/functions/posthog-destination"; 4 | 5 | //TEST_POSTHOG_DESTINATION={key: 'phc_tnUHCp3pRSnx9hR2mL1i1O9luW2ktkHvg4tyOOc15B1', enableAnonymousUserProfiles: true, sendIdentifyEvents: true} 6 | test("posthog-destination-integration", async () => { 7 | if (!process.env.TEST_POSTHOG_DESTINATION) { 8 | console.log("Skipping mixpanel destination integration test - TEST_MIXPANEL_DESTINATION is not set"); 9 | return; 10 | } 11 | const opts: TestOptions = { 12 | func: PosthogDestination, 13 | configEnvVar: "TEST_POSTHOG_DESTINATION", 14 | events: eventsSequence(), 15 | }; 16 | await testJitsuFunction(opts); 17 | }); 18 | 19 | test("posthog-destination-unit", () => { 20 | //implement later, when testing library is ready to mock fetch 21 | }); 22 | -------------------------------------------------------------------------------- /libs/core-functions/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("ts-jest").JestConfigWithTsJest} */ 2 | module.exports = { 3 | //preset: "ts-jest", 4 | preset: "ts-jest", 5 | testMatch: ["**/__tests__/**/*.test.ts"], 6 | testEnvironment: "node", 7 | runner: "jest-runner", 8 | "setupFiles": ["./jest.setup.js"] 9 | }; 10 | -------------------------------------------------------------------------------- /libs/core-functions/jest.setup.js: -------------------------------------------------------------------------------- 1 | 2 | global.console = { 3 | log: message => process.stdout.write(message + '\n'), 4 | error: console.error, 5 | warn: console.warn, 6 | info: console.info, 7 | debug: console.debug, 8 | }; 9 | -------------------------------------------------------------------------------- /libs/core-functions/src/functions/lib/crypto-code.ts: -------------------------------------------------------------------------------- 1 | export const cryptoCode = `const randomUUID = (options) => { 2 | return _jitsu_crypto.getSync("randomUUID", {accessors: true, reference: true}).applySync(undefined, [options], { 3 | arguments: { copy: true }, 4 | result: { copy: true } 5 | }); 6 | } 7 | 8 | const randomBytes = (size) => { 9 | return _jitsu_crypto.getSync("randomBytes", {accessors: true, reference: true}).applySync(undefined, [size], { 10 | arguments: { copy: true }, 11 | result: { copy: true } 12 | }); 13 | } 14 | 15 | const randomInt = (min, max) => { 16 | return _jitsu_crypto.getSync("randomInt", {accessors: true, reference: true}).applySync(undefined, [min, max], { 17 | arguments: { copy: true }, 18 | result: { copy: true } 19 | }); 20 | } 21 | 22 | const hash = (algorithm, input , encoding) => { 23 | return _jitsu_crypto.getSync("hash", {accessors: true, reference: true}).applySync(undefined, [algorithm, input, encoding], { 24 | arguments: { copy: true }, 25 | result: { copy: true } 26 | }); 27 | } 28 | 29 | export {hash, randomUUID, randomBytes, randomInt }; 30 | `; 31 | -------------------------------------------------------------------------------- /libs/core-functions/src/functions/lib/crypto-code.txtjs: -------------------------------------------------------------------------------- 1 | const randomUUID = (options) => { 2 | return _jitsu_crypto.getSync("randomUUID", {accessors: true, reference: true}).applySync(undefined, [options], { 3 | arguments: { copy: true }, 4 | result: { copy: true } 5 | }); 6 | } 7 | 8 | const randomBytes = (size) => { 9 | return _jitsu_crypto.getSync("randomBytes", {accessors: true, reference: true}).applySync(undefined, [size], { 10 | arguments: { copy: true }, 11 | result: { copy: true } 12 | }); 13 | } 14 | 15 | const randomInt = (min, max) => { 16 | return _jitsu_crypto.getSync("randomInt", {accessors: true, reference: true}).applySync(undefined, [min, max], { 17 | arguments: { copy: true }, 18 | result: { copy: true } 19 | }); 20 | } 21 | 22 | const hash = (algorithm, input , encoding) => { 23 | return _jitsu_crypto.getSync("hash", {accessors: true, reference: true}).applySync(undefined, [algorithm, input, encoding], { 24 | arguments: { copy: true }, 25 | result: { copy: true } 26 | }); 27 | } 28 | 29 | export {hash, randomUUID, randomBytes, randomInt }; 30 | -------------------------------------------------------------------------------- /libs/core-functions/src/functions/lib/http-agent.ts: -------------------------------------------------------------------------------- 1 | import { getSingleton } from "juava"; 2 | import Agent, { HttpsAgent } from "agentkeepalive"; 3 | 4 | export const httpAgent = getSingleton("http-agent", createHTTPAgent, { silent: true }); 5 | export const httpsAgent = getSingleton("https-agent", createHTTPSAgent, { silent: true }); 6 | 7 | async function createHTTPAgent(): Promise { 8 | const agent = new Agent({ timeout: 300000, freeSocketTimeout: 30000, maxSockets: 1024 }); 9 | return Promise.resolve(agent); 10 | } 11 | 12 | async function createHTTPSAgent(): Promise { 13 | const agent = new HttpsAgent({ timeout: 300000, freeSocketTimeout: 30000, maxSockets: 1024 }); 14 | return Promise.resolve(agent); 15 | } 16 | -------------------------------------------------------------------------------- /libs/core-functions/src/functions/lib/strings.ts: -------------------------------------------------------------------------------- 1 | export function idToSnakeCaseRegex(id: string) { 2 | return id.replace(/((?<=[a-zA-Z0-9])[A-Z])/g, "_$1").toLowerCase(); 3 | } 4 | -------------------------------------------------------------------------------- /libs/core-functions/src/functions/lib/ua.ts: -------------------------------------------------------------------------------- 1 | import { UserAgent } from "@jitsu/protocols/functions"; 2 | import omit from "lodash/omit"; 3 | import uaParser from "@amplitude/ua-parser-js"; 4 | import NodeCache from "node-cache"; 5 | 6 | const BotUAKeywords = ["bot", "spider", "headless", "crawler", "uptimia"]; 7 | const uaCacheTTL = 60 * 10; // 10 min; 8 | const uaCache = new NodeCache({ stdTTL: uaCacheTTL, checkperiod: 60, maxKeys: 1000, useClones: false }); 9 | 10 | export function parseUserAgent(userAgent?: string): UserAgent { 11 | if (!userAgent) { 12 | return {} as UserAgent; 13 | } 14 | const cached = uaCache.get(userAgent); 15 | if (cached) { 16 | uaCache.ttl(userAgent, uaCacheTTL); 17 | return cached as UserAgent; 18 | } 19 | const uas = userAgent || ""; 20 | const ua = omit(uaParser(uas), "ua") as UserAgent; 21 | const lower = uas.toLowerCase(); 22 | ua.bot = BotUAKeywords.some(keyword => lower.includes(keyword)); 23 | if (ua.device) { 24 | ua.device.type = ua.device.type || "desktop"; 25 | } 26 | try { 27 | uaCache.set(userAgent, ua); 28 | } catch (e) {} 29 | return ua; 30 | } 31 | -------------------------------------------------------------------------------- /libs/core-functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src", 4 | "outDir": "./dist", 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "moduleResolution": "node", 8 | "target": "ESNext", 9 | "module": "commonjs", 10 | "lib": [ 11 | "ESNext", 12 | "DOM" 13 | ], 14 | "noEmit": true 15 | }, 16 | "exclude": [ 17 | "__tests__", 18 | "node_modules", 19 | "dist", 20 | "test_projects", 21 | "test", 22 | "templates" 23 | ] 24 | } -------------------------------------------------------------------------------- /libs/functions/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("ts-jest").JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testMatch: ["**/__tests__/**/*.test.ts"], 5 | testEnvironment: "node", 6 | runner: "jest-runner", 7 | "setupFiles": ["./jest.setup.js"] 8 | }; 9 | -------------------------------------------------------------------------------- /libs/functions/jest.setup.js: -------------------------------------------------------------------------------- 1 | 2 | global.console = { 3 | log: message => process.stdout.write(message + '\n'), 4 | error: console.error, 5 | warn: console.warn, 6 | info: console.info, 7 | debug: console.debug, 8 | }; 9 | -------------------------------------------------------------------------------- /libs/functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jitsu/functions-lib", 3 | "version": "0.0.0", 4 | "main": "dist/index.cjs.js", 5 | "module": "dist/index.es.js", 6 | "types": "dist/index.d.ts", 7 | "description": "", 8 | "author": "Jitsu Dev Team ", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "license": "MIT", 13 | "private": false, 14 | "scripts": { 15 | "compile": "tsc -p .", 16 | "build": "pnpm compile && rollup -c", 17 | "test": "tsc -p . && jest --verbose" 18 | }, 19 | "devDependencies": { 20 | "@jitsu/protocols": "workspace:*", 21 | "rollup": "^4.24.3", 22 | "@rollup/plugin-typescript": "^11.1.5", 23 | "@rollup/plugin-node-resolve": "^16.0.0", 24 | "@rollup/plugin-commonjs": "^28.0.2", 25 | "@types/jest": "^29.1.1", 26 | "@types/node": "^18.15.3", 27 | "jest": "^29.1.2", 28 | "ts-jest": "29.0.5", 29 | "tslib": "^2.6.3", 30 | "@jitsu/sdk-js": "^3.1.5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /libs/functions/rollup.config.js: -------------------------------------------------------------------------------- 1 | const typescript = require("@rollup/plugin-typescript"); 2 | const resolve = require("@rollup/plugin-node-resolve"); 3 | const commonjs = require("@rollup/plugin-commonjs"); 4 | 5 | module.exports = [ 6 | { 7 | plugins: [typescript(), resolve({ preferBuiltins: false }), commonjs()], 8 | input: ["./src/index.ts"], 9 | output: [ 10 | { file: "dist/index.es.js", format: "es" }, 11 | { file: "dist/index.cjs.js", format: "cjs" }, 12 | ], 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /libs/functions/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./lib/functions"; 2 | export * from "./lib/objects"; 3 | export * from "./lib/strings"; 4 | -------------------------------------------------------------------------------- /libs/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "esnext"], 4 | "rootDir": "./src", 5 | "outDir": "./dist", 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "moduleResolution": "node", 9 | "noEmit": true, 10 | "target": "esnext", 11 | "skipLibCheck": true 12 | }, 13 | "exclude": [ 14 | "__tests__", 15 | "node_modules", 16 | "dist", 17 | "test_projects", 18 | "test", 19 | "templates" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /libs/jitsu-js/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /compiled/ 3 | /__tests__/playwright/artifacts/ 4 | -------------------------------------------------------------------------------- /libs/jitsu-js/README.md: -------------------------------------------------------------------------------- 1 | ## Install 2 | 3 | ```bash 4 | npm install --save @jitsu/jitsu-js 5 | ``` 6 | 7 | ## Usage 8 | 9 | **Please read a documentation on [Jitsu Docs](https://docs.jitsu.com/sending-data/npm)** 10 | -------------------------------------------------------------------------------- /libs/jitsu-js/__tests__/playwright/cases/anonymous-id-bug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tracking page 9 | 14 | 21 | 22 | 23 | 24 |

Test

25 | 26 | 27 | -------------------------------------------------------------------------------- /libs/jitsu-js/__tests__/playwright/cases/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tracking page 9 | 20 | 27 | 28 | 29 | 30 |

Test

31 | 32 | 33 | -------------------------------------------------------------------------------- /libs/jitsu-js/__tests__/playwright/cases/callbacks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tracking page 8 | 9 | 16 | 17 | 18 | 19 |

Test

20 | 21 | 22 | -------------------------------------------------------------------------------- /libs/jitsu-js/__tests__/playwright/cases/cookie-names.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tracking page 9 | 17 | 18 | 19 | 20 |

Test

21 | 22 | 23 | -------------------------------------------------------------------------------- /libs/jitsu-js/__tests__/playwright/cases/disable-user-ids.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tracking page 9 | 10 | 18 | 19 | 20 | 21 |

Test

22 | 23 | 24 | -------------------------------------------------------------------------------- /libs/jitsu-js/__tests__/playwright/cases/dont-send.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tracking page 9 | 10 | 17 | 18 | 19 | 20 |

Test

21 | 22 | 23 | -------------------------------------------------------------------------------- /libs/jitsu-js/__tests__/playwright/cases/ip-policy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tracking page 9 | 10 | 17 | 18 | 19 | 20 |

Test

21 | 22 | 23 | -------------------------------------------------------------------------------- /libs/jitsu-js/__tests__/playwright/cases/reset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tracking page 9 | 19 | 27 | 28 | 29 | 30 |

Test

31 | 32 | 33 | -------------------------------------------------------------------------------- /libs/jitsu-js/__tests__/playwright/cases/url-bug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tracking page 9 | 10 | 15 | 16 | 17 | 18 |

Test

19 | 20 | 21 | -------------------------------------------------------------------------------- /libs/jitsu-js/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("ts-jest").JestConfigWithTsJest} */ 2 | module.exports = { 3 | //preset: "ts-jest", 4 | preset: "ts-jest", 5 | testEnvironment: "node", 6 | runner: "jest-runner", 7 | rootDir: "./__tests__/node/", 8 | globals: { 9 | 'ts-jest': { 10 | tsConfig: 'tsconfig.test.json' 11 | } 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /libs/jitsu-js/src/destination-plugins/no-destination-plugins.ts: -------------------------------------------------------------------------------- 1 | import { AnalyticsClientEvent } from "@jitsu/protocols/analytics"; 2 | 3 | export type InternalPlugin = { 4 | id: string; 5 | handle(config: T & { debug?: boolean }, payload: AnalyticsClientEvent): Promise; 6 | }; 7 | 8 | export const internalDestinationPlugins: Record> = {}; 9 | -------------------------------------------------------------------------------- /libs/jitsu-js/src/version.ts: -------------------------------------------------------------------------------- 1 | import pkg from "../package.json"; 2 | 3 | const jitsuVersion = pkg.version !== "0.0.0" ? pkg.version : "2.0.0"; 4 | const jitsuLibraryName = "@jitsu/js"; 5 | 6 | export { jitsuVersion, jitsuLibraryName }; 7 | -------------------------------------------------------------------------------- /libs/jitsu-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "outDir": "./compiled", 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "moduleResolution": "Node", 8 | "resolveJsonModule": true, 9 | "target": "ES2015", 10 | "lib": ["es2020", "dom"], 11 | //this makes typescript igore @types/node during compilation 12 | "types": [] 13 | }, 14 | "include": ["./src", "./__tests__"], 15 | "exclude": [ 16 | "__tests__", 17 | "node_modules", 18 | "dist", 19 | "test_projects", 20 | "test", 21 | "templates" 22 | ] 23 | } -------------------------------------------------------------------------------- /libs/jitsu-js/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "outDir": "./compiled", 5 | "declaration": false, 6 | "esModuleInterop": true, 7 | "moduleResolution": "Node", 8 | "target": "ES2015", 9 | "lib": ["es2017", "dom"] 10 | //this makes typescript igore @types/node during compilation 11 | // "types": [] 12 | }, 13 | "include": ["./src", "./__tests__"], 14 | "exclude": ["__tests__", "node_modules", "dist", "test_projects", "test", "templates"] 15 | } 16 | -------------------------------------------------------------------------------- /libs/jitsu-react/README.md: -------------------------------------------------------------------------------- 1 | # jitsu-react 2 | 3 | 4 | [![NPM](https://img.shields.io/npm/v/@jitsu/jitsu-react.svg)](https://www.npmjs.com/package/@jitsu/jitsu-react) 5 | 6 | ## Install 7 | 8 | ```bash 9 | npm install --save @jitsu/jitsu-react 10 | ``` 11 | 12 | ## Usage 13 | 14 | **Please read a documentation on [Jitsu Docs](https://docs.jitsu.com/sending-data/react)** 15 | -------------------------------------------------------------------------------- /libs/jitsu-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jitsu/jitsu-react", 3 | "version": "0.0.0", 4 | "description": "", 5 | "license": "MIT", 6 | "main": "dist/index.js", 7 | "module": "dist/index.modern.js", 8 | "types": "dist/index.d.ts", 9 | "source": "src/index.ts", 10 | "engines": { 11 | "node": ">=10" 12 | }, 13 | "scripts": { 14 | "build": "pnpm compile && microbundle build --jsx React.createElement --no-compress --format es,cjs", 15 | "compile": "tsc -p .", 16 | "clean": "rm -rf dist" 17 | }, 18 | "dependencies": { 19 | "@jitsu/js": "workspace:*" 20 | }, 21 | "peerDependencies": { 22 | "react": "15.x || 16.x || 17.x || 18.x", 23 | "@types/react": "15.x || 16.x || 17.x || 18.x", 24 | "react-router-dom": "5.x || 6.x" 25 | }, 26 | "peerDependenciesMeta": { 27 | "react-router-dom": { 28 | "optional": true 29 | } 30 | }, 31 | "devDependencies": { 32 | "ts-toolbelt": "^9.6.0", 33 | "@testing-library/jest-dom": "^6.1.3", 34 | "@testing-library/react": "^13.4.0", 35 | "@testing-library/user-event": "^14.4.3", 36 | "@types/jest": "^29.2.3", 37 | "@types/node": "^18.15.3", 38 | "microbundle": "^0.15.1", 39 | "typescript": "^5.6.3", 40 | "@babel/plugin-transform-regenerator": "^7.22.10" 41 | }, 42 | "files": [ 43 | "dist" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /libs/jitsu-react/src/JitsuContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | import { AnalyticsInterface } from "@jitsu/js"; 3 | 4 | export type JitsuInstance = { analytics: AnalyticsInterface }; 5 | 6 | const JitsuContext = createContext(null); 7 | 8 | export default JitsuContext; 9 | -------------------------------------------------------------------------------- /libs/jitsu-react/src/JitsuProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { PropsWithChildren, useMemo } from "react"; 3 | import JitsuContext, { JitsuInstance } from "./JitsuContext"; 4 | import { emptyAnalytics, jitsuAnalytics } from "@jitsu/js"; 5 | import { ExtendedJitsuOptions } from "./useJitsu"; 6 | 7 | const JitsuProvider: React.FC> = props => { 8 | const instance: JitsuInstance = useMemo(() => { 9 | if (props.options.disabled) { 10 | return { analytics: emptyAnalytics }; 11 | } else if (!props.options.host) { 12 | const msg = ". Jitsu host is not defined. Jitsu will not be initialized"; 13 | console.error(`%c${msg}`, "color: red; font-weight: bold;"); 14 | return { analytics: emptyAnalytics }; 15 | } else { 16 | return { analytics: jitsuAnalytics(props.options) }; 17 | } 18 | }, [props.options.disabled, props.options.host]); 19 | return {props.children}; 20 | }; 21 | 22 | export default JitsuProvider; 23 | -------------------------------------------------------------------------------- /libs/jitsu-react/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as JitsuContext } from "./JitsuContext"; 2 | export { default as JitsuProvider } from "./JitsuProvider"; 3 | export { default as useJitsu } from "./useJitsu"; 4 | -------------------------------------------------------------------------------- /libs/jitsu-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "declaration": true, 10 | "esModuleInterop": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noImplicitAny": false, 14 | "strictNullChecks": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "allowSyntheticDefaultImports": true 18 | }, 19 | "include": ["src"], 20 | "exclude": ["node_modules", "dist"] 21 | } 22 | -------------------------------------------------------------------------------- /libs/jitsu-react/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*{.,-}min.js 2 | node_modules 3 | build 4 | .git 5 | coverage 6 | dist 7 | lib 8 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended-type-checked"], 4 | plugins: ["@typescript-eslint"], 5 | parser: "@typescript-eslint/parser", 6 | parserOptions: { 7 | project: true, 8 | tsconfigRootDir: __dirname, 9 | }, 10 | overrides: [ 11 | { 12 | files: ["test/**/*.ts"], 13 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended-type-checked"], 14 | plugins: ["@typescript-eslint"], 15 | parser: "@typescript-eslint/parser", 16 | parserOptions: { 17 | project: "./test/tsconfig.json", 18 | tsconfigRootDir: __dirname, 19 | }, 20 | }, 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/jest.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | extensionsToTreatAsEsm: [".ts"], 6 | moduleNameMapper: { 7 | "^(\\.{1,2}/.*)\\.js$": "$1", 8 | }, 9 | transform: { 10 | "^.+\\.ts$": [ 11 | "ts-jest", 12 | { 13 | tsconfig: "test/tsconfig.json", 14 | useESM: true, 15 | }, 16 | ], 17 | }, 18 | coverageThreshold: { 19 | global: { 20 | branches: 50, 21 | functions: 50, 22 | lines: 50, 23 | statements: 50, 24 | }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/src/contexts/context.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from "../types.js"; 2 | 3 | export default abstract class Context { 4 | abstract pipe: string; 5 | 6 | result?: TResult; 7 | hasResult?: boolean; 8 | exiting?: boolean; 9 | parent?: this; 10 | childName?: string | number; 11 | root?: this; 12 | options?: Options; 13 | children?: this[]; 14 | nextAfterChildren?: this | null; 15 | next?: this | null; 16 | 17 | setResult(result: TResult) { 18 | this.result = result; 19 | this.hasResult = true; 20 | return this; 21 | } 22 | 23 | exit() { 24 | this.exiting = true; 25 | return this; 26 | } 27 | 28 | push(child: this, name?: string | number) { 29 | child.parent = this; 30 | if (typeof name !== "undefined") { 31 | child.childName = name; 32 | } 33 | child.root = this.root || this; 34 | child.options = child.options || this.options; 35 | if (!this.children) { 36 | this.children = [child]; 37 | this.nextAfterChildren = this.next || null; 38 | this.next = child; 39 | } else { 40 | this.children[this.children.length - 1].next = child; 41 | this.children.push(child); 42 | } 43 | child.next = this; 44 | return this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/src/contexts/diff.ts: -------------------------------------------------------------------------------- 1 | import Context from "./context.js"; 2 | import type { Delta } from "../types.js"; 3 | 4 | class DiffContext extends Context { 5 | left: unknown; 6 | right: unknown; 7 | pipe: "diff"; 8 | 9 | leftType?: string; 10 | rightType?: string; 11 | leftIsArray?: boolean; 12 | rightIsArray?: boolean; 13 | 14 | constructor(left: unknown, right: unknown) { 15 | super(); 16 | this.left = left; 17 | this.right = right; 18 | this.pipe = "diff"; 19 | } 20 | 21 | setResult(result: Delta) { 22 | return super.setResult(result); 23 | } 24 | } 25 | 26 | export default DiffContext; 27 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/src/contexts/patch.ts: -------------------------------------------------------------------------------- 1 | import Context from "./context.js"; 2 | import type { Delta } from "../types.js"; 3 | 4 | class PatchContext extends Context { 5 | left: unknown; 6 | delta: Delta; 7 | pipe: "patch"; 8 | 9 | nested?: boolean; 10 | 11 | constructor(left: unknown, delta: Delta) { 12 | super(); 13 | this.left = left; 14 | this.delta = delta; 15 | this.pipe = "patch"; 16 | } 17 | } 18 | 19 | export default PatchContext; 20 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/src/diffpatcher.ts: -------------------------------------------------------------------------------- 1 | import Processor from "./processor.js"; 2 | import Pipe from "./pipe.js"; 3 | import DiffContext from "./contexts/diff.js"; 4 | import PatchContext from "./contexts/patch.js"; 5 | 6 | import * as trivial from "./filters/trivial.js"; 7 | import * as nested from "./filters/nested.js"; 8 | import * as dates from "./filters/dates.js"; 9 | import type { Delta, Options } from "./types.js"; 10 | 11 | class DiffPatcher { 12 | processor: Processor; 13 | 14 | constructor(options?: Options) { 15 | this.processor = new Processor(options); 16 | this.processor.pipe( 17 | new Pipe("diff") 18 | .append(nested.collectChildrenDiffFilter, trivial.diffFilter, dates.diffFilter, nested.objectsDiffFilter) 19 | .shouldHaveResult()! 20 | ); 21 | this.processor.pipe( 22 | new Pipe("patch") 23 | .append(nested.collectChildrenPatchFilter, trivial.patchFilter, nested.patchFilter) 24 | .shouldHaveResult()! 25 | ); 26 | } 27 | 28 | options(options: Options) { 29 | return this.processor.options(options); 30 | } 31 | 32 | diff(left: unknown, right: unknown) { 33 | return this.processor.process(new DiffContext(left, right)); 34 | } 35 | 36 | patch(left: unknown, delta: Delta) { 37 | return this.processor.process(new PatchContext(left, delta)); 38 | } 39 | } 40 | 41 | export default DiffPatcher; 42 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/src/filters/dates.ts: -------------------------------------------------------------------------------- 1 | import type { Filter } from "../types.js"; 2 | import type DiffContext from "../contexts/diff.js"; 3 | 4 | export const diffFilter: Filter = function datesDiffFilter(context) { 5 | if (context.left instanceof Date) { 6 | if (context.right instanceof Date) { 7 | if (context.left.getTime() !== context.right.getTime()) { 8 | context.setResult([context.left, context.right]); 9 | } else { 10 | context.setResult(undefined); 11 | } 12 | } else { 13 | context.setResult([context.left, context.right]); 14 | } 15 | context.exit(); 16 | } else if (context.right instanceof Date) { 17 | context.setResult([context.left, context.right]).exit(); 18 | } 19 | }; 20 | diffFilter.filterName = "dates"; 21 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/src/index.ts: -------------------------------------------------------------------------------- 1 | import DiffPatcher from "./diffpatcher.js"; 2 | import type { Delta, Options } from "./types.js"; 3 | import type Context from "./contexts/context.js"; 4 | import type DiffContext from "./contexts/diff.js"; 5 | import type PatchContext from "./contexts/patch.js"; 6 | 7 | export { DiffPatcher }; 8 | 9 | export type * from "./types.js"; 10 | export type { Context, DiffContext, PatchContext }; 11 | 12 | export function create(options?: Options) { 13 | return new DiffPatcher(options); 14 | } 15 | 16 | let defaultInstance: DiffPatcher; 17 | 18 | export function diff(left: unknown, right: unknown) { 19 | if (!defaultInstance) { 20 | defaultInstance = new DiffPatcher(); 21 | } 22 | return defaultInstance.diff(left, right); 23 | } 24 | 25 | export function patch(left: unknown, delta: Delta) { 26 | if (!defaultInstance) { 27 | defaultInstance = new DiffPatcher(); 28 | } 29 | return defaultInstance.patch(left, delta); 30 | } 31 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/src/types.ts: -------------------------------------------------------------------------------- 1 | import type Context from "./contexts/context.js"; 2 | import type DiffContext from "./contexts/diff.js"; 3 | 4 | export interface Options { 5 | objectHash?: (item: object, index?: number) => string | undefined; 6 | matchByPosition?: boolean; 7 | arrays?: { 8 | detectMove?: boolean; 9 | includeValueOnMove?: boolean; 10 | }; 11 | propertyFilter?: (name: string, context: DiffContext) => boolean; 12 | } 13 | 14 | export type AddedDelta = [unknown]; 15 | export type ModifiedDelta = [unknown, unknown]; 16 | export type DeletedDelta = [unknown, 0, 0]; 17 | 18 | export interface ObjectDelta { 19 | [property: string]: Delta; 20 | } 21 | 22 | export interface ArrayDelta { 23 | _t: "a"; 24 | [index: number | `${number}`]: Delta; 25 | [index: `_${number}`]: DeletedDelta | MovedDelta; 26 | } 27 | 28 | export type MovedDelta = [unknown, number, 3]; 29 | 30 | export type TextDiffDelta = [string, 0, 2]; 31 | 32 | export type Delta = 33 | | AddedDelta 34 | | ModifiedDelta 35 | | DeletedDelta 36 | | ObjectDelta 37 | | ArrayDelta 38 | | MovedDelta 39 | | TextDiffDelta 40 | | undefined; 41 | 42 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 43 | export interface Filter> { 44 | (context: TContext): void; 45 | filterName: string; 46 | } 47 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "node16", 5 | "types": ["jest"], 6 | "isolatedModules": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "skipLibCheck": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /libs/jsondiffpatch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "declaration": true, 5 | "outDir": "dist", 6 | "isolatedModules": true, 7 | "moduleResolution": "node", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "skipLibCheck": false 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/juava/.gitignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /libs/juava/__tests__/id.test.ts: -------------------------------------------------------------------------------- 1 | import { randomId } from "../src"; 2 | 3 | test("id test", () => { 4 | const id1 = randomId(); 5 | const id2 = randomId(10); 6 | const id3 = randomId({ digits: 10 }); 7 | const id4 = randomId({ digits: 10, prefix: "test" }); 8 | 9 | console.log([id1, id2, id3, id4].join("\n")); 10 | 11 | expect(id1.length).toBeGreaterThan(10); 12 | expect(id3.length).toBe(10); 13 | expect(id2.length).toBe(10); 14 | expect(id3.length).toBe(10); 15 | expect(id4.length).toBe("test_".length + 10); 16 | expect(id4.startsWith("test_")).toBe(true); 17 | }); 18 | -------------------------------------------------------------------------------- /libs/juava/__tests__/security.test.ts: -------------------------------------------------------------------------------- 1 | import { createHash, createAuthorized, checkHash, checkRawToken } from "../src/security"; 2 | 3 | test("security", () => { 4 | const password = "secretPassword"; 5 | const hashResult = createHash(password); 6 | console.log("Hash result = " + hashResult); 7 | expect(checkHash(hashResult, password)).toBe(true); 8 | expect(checkHash(hashResult.substring(2), password)).toBe(false); 9 | }); 10 | 11 | test("authorizer", () => { 12 | const hashedSecret = "215ef940-8f78-42bf-ab36-185090b9b62e"; 13 | const plaintextSecret = "af0e7958-5a10-4264-af4e-2516a630b602"; 14 | let auth = createAuthorized(createHash(hashedSecret), checkHash); 15 | expect(auth(plaintextSecret)).toBe(false); 16 | expect(auth(hashedSecret)).toBe(true); 17 | expect(auth("wrong")).toBe(false); 18 | 19 | auth = createAuthorized(plaintextSecret, checkRawToken); 20 | expect(auth(plaintextSecret)).toBe(true); 21 | expect(auth(hashedSecret)).toBe(false); 22 | expect(auth("wrong")).toBe(false); 23 | }); 24 | -------------------------------------------------------------------------------- /libs/juava/__tests__/sql-params.test.ts: -------------------------------------------------------------------------------- 1 | import { namedParameters, unrollParams } from "../src"; 2 | 3 | test("named-parameters and unroll", () => { 4 | const sql = 5 | "SELECT * FROM users WHERE id = :id AND secondId = :id AND name = :name AND age = :age AND thirdId=:id OR otherParam=:ne ORDER BY id"; 6 | const params = { 7 | ne: "ne", 8 | id: 1, 9 | name: "John", 10 | age: 30, 11 | }; 12 | 13 | const result = namedParameters(sql, params); 14 | console.log(result); 15 | expect(result.query).toBe( 16 | "SELECT * FROM users WHERE id = $1 AND secondId = $1 AND name = $2 AND age = $3 AND thirdId=$1 OR otherParam=$4 ORDER BY id" 17 | ); 18 | expect(result.values).toEqual([1, "John", 30, "ne"]); 19 | const unrolled = unrollParams(result.query, result.values); 20 | expect(unrolled).toBe( 21 | `SELECT * FROM users WHERE id = 1 AND secondId = 1 AND name = 'John' AND age = 30 AND thirdId=1 OR otherParam='ne' ORDER BY id` 22 | ); 23 | }); 24 | -------------------------------------------------------------------------------- /libs/juava/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("ts-jest").JestConfigWithTsJest} */ 2 | module.exports = { 3 | //preset: "ts-jest", 4 | preset: "ts-jest", 5 | testEnvironment: "node", 6 | runner: "jest-runner", 7 | }; 8 | -------------------------------------------------------------------------------- /libs/juava/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "juava", 3 | "version": "0.0.0", 4 | "main": "src/index.ts", 5 | "types": "src/index.ts", 6 | "description": "", 7 | "author": "Jitsu Dev Team ", 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "license": "MIT", 12 | "private": false, 13 | "scripts": { 14 | "compile": "tsc -p .", 15 | "build": "pnpm compile", 16 | "test": "tsc -p . && jest --verbose" 17 | }, 18 | "devDependencies": { 19 | "@types/jest": "^29.1.1", 20 | "@types/node": "^18.15.3", 21 | "jest": "^29.1.2", 22 | "ts-jest": "29.0.5" 23 | }, 24 | "dependencies": { 25 | "lodash": "^4.17.21", 26 | "tslib": "^2.6.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /libs/juava/src/asserts.ts: -------------------------------------------------------------------------------- 1 | export type ErrorLike = string | (() => Error) | Error; 2 | 3 | function toError(err: ErrorLike): Error { 4 | if (typeof err === "string") { 5 | return new Error(err); 6 | } else if (typeof err === "function") { 7 | return err(); 8 | } else { 9 | return err; 10 | } 11 | } 12 | 13 | export function assertTrue(obj: boolean | null | undefined, error?: ErrorLike): asserts obj is true { 14 | if (obj === undefined || obj === null) { 15 | throw toError(error || "Object is not defined"); 16 | } else if (obj === false) { 17 | throw toError(error || "Object is false"); 18 | } 19 | } 20 | 21 | export function assertFalse(obj: boolean, error?: ErrorLike): asserts obj is false { 22 | if (obj === true) { 23 | throw toError(error || "Object is true"); 24 | } 25 | } 26 | 27 | export function assertDefined(obj: T | undefined | null, error?: ErrorLike): asserts obj is T { 28 | if (obj === undefined || obj === null) { 29 | throw toError(error || "Object is not defined"); 30 | } 31 | } 32 | 33 | export function requireDefined(obj: T | undefined | null, error?: string): T { 34 | if (obj === undefined || obj === null) { 35 | throw toError(error || "Object is not defined"); 36 | } 37 | return obj as T; 38 | } 39 | -------------------------------------------------------------------------------- /libs/juava/src/boolean.ts: -------------------------------------------------------------------------------- 1 | export function isTruish(val: any) { 2 | return val === "true" || val === "1" || val === "yes" || val === true || val === 1; 3 | } 4 | -------------------------------------------------------------------------------- /libs/juava/src/cache.ts: -------------------------------------------------------------------------------- 1 | // A cache implementation that caches factory produced values 2 | // It makes sure that parameters of factory method work as a part of a cache key 3 | export function getCached(globalName: string, factory: (...args: Args) => T, ...args: Args): T { 4 | const key = `cached_${globalName}_${args.toString()}`; 5 | const value = global[key]; 6 | return typeof value !== "undefined" ? value : (global[key] = factory(...args)); 7 | } 8 | -------------------------------------------------------------------------------- /libs/juava/src/collections.ts: -------------------------------------------------------------------------------- 1 | export type Indexer = ((t: T) => K) | keyof T; 2 | 3 | export function index(array: T[], indexer: Indexer): Record { 4 | const indexerF = typeof indexer === "function" ? indexer : (t: T) => t[indexer] as any; 5 | return array.reduce((acc, t) => { 6 | acc[indexerF(t)] = t; 7 | return acc; 8 | }, {} as Record); 9 | } 10 | 11 | export function transformKeys( 12 | map: Record, 13 | transformer: (t: VSrc) => VDst 14 | ): Record { 15 | return Object.keys(map).reduce((acc, k) => { 16 | acc[k] = transformer(map[k]); 17 | return acc; 18 | }, {} as Record); 19 | } 20 | -------------------------------------------------------------------------------- /libs/juava/src/color.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/libs/juava/src/color.ts -------------------------------------------------------------------------------- /libs/juava/src/error.ts: -------------------------------------------------------------------------------- 1 | export function getErrorMessage(e: any): string { 2 | return e?.message || "unknown error"; 3 | } 4 | 5 | export function newError(message: string, cause?: any) { 6 | return new Error(cause?.message ? `${message}: ${cause?.message}` : message); 7 | } 8 | -------------------------------------------------------------------------------- /libs/juava/src/id.ts: -------------------------------------------------------------------------------- 1 | export type RandomOpts = { digits?: number; prefix?: string }; 2 | 3 | /** 4 | * Compatibility wrapper for old args 5 | */ 6 | export type RandomOptsCompat = RandomOpts | number; 7 | 8 | export function randomId(_opts: RandomOptsCompat = {}): string { 9 | const opts: RandomOpts = typeof _opts === "number" ? { digits: _opts } : _opts; 10 | const digits = opts.digits ?? 24; 11 | const prefix = opts.prefix ?? ""; 12 | let id = ""; 13 | for (let i = 0; i < digits; i++) { 14 | id += randomChar(i === 0); 15 | } 16 | return `${prefix ? prefix + "_" : ""}${id}`; 17 | } 18 | 19 | function randomChar(noDigits?: boolean) { 20 | const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 21 | while (true) { 22 | const index = Math.floor(Math.random() * chars.length); 23 | if (!noDigits || index > 9) { 24 | return chars[index]; 25 | } 26 | } 27 | } 28 | 29 | //sanitizes string for usage in file name 30 | export function sanitize(name: string, replacement = "-") { 31 | return name.replace(/[^a-zA-Z0-9]+/gi, replacement).toLowerCase(); 32 | } 33 | -------------------------------------------------------------------------------- /libs/juava/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./error"; 2 | export * from "./security"; 3 | export * from "./asserts"; 4 | export * from "./shorts"; 5 | export * from "./collections"; 6 | export * from "./log"; 7 | export * from "./stopwatch"; 8 | export * from "./id"; 9 | export * from "./rpc"; 10 | export * from "./singleton"; 11 | export * from "./cache"; 12 | export * from "./sql-params"; 13 | export * from "./boolean"; 14 | export * from "./number"; 15 | export * from "./throttle"; 16 | export * from "./objects"; 17 | export * from "./strings"; 18 | -------------------------------------------------------------------------------- /libs/juava/src/number.ts: -------------------------------------------------------------------------------- 1 | export function parseNumber(val: string | undefined, defaultValue?: T): T | number { 2 | if (val) { 3 | const n = parseFloat(val); 4 | if (!Number.isFinite(n)) { 5 | return defaultValue as T | number; 6 | } 7 | return n; 8 | } else { 9 | return defaultValue as T | number; 10 | } 11 | } 12 | 13 | export function parseRequiredNumber(val: string | undefined, error?: string): number { 14 | if (val) { 15 | const n = parseFloat(val); 16 | if (!Number.isFinite(n)) { 17 | throw new Error(error || "Object is not a finite number"); 18 | } 19 | return n; 20 | } else { 21 | throw new Error(error || "Object is not defined"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /libs/juava/src/objects.ts: -------------------------------------------------------------------------------- 1 | export function deepMerge(target: any, source: any) { 2 | if (typeof source !== "object" || source === null) { 3 | return source; 4 | } 5 | if (typeof target !== "object" || target === null) { 6 | return source; 7 | } 8 | return Object.entries(source).reduce((acc, [key, value]) => { 9 | acc[key] = deepMerge(target[key], value); 10 | return acc; 11 | }, target); 12 | } 13 | 14 | export function isEqual(x: any, y: any) { 15 | const ok = Object.keys, 16 | tx = typeof x, 17 | ty = typeof y; 18 | return x && y && tx === "object" && tx === ty 19 | ? ok(x).length === ok(y).length && ok(x).every(key => isEqual(x[key], y[key])) 20 | : x === y; 21 | } 22 | -------------------------------------------------------------------------------- /libs/juava/src/shorts.ts: -------------------------------------------------------------------------------- 1 | export function asArray(arrayLike: T | T[]): T[] { 2 | return Array.isArray(arrayLike) ? arrayLike : [arrayLike]; 3 | } 4 | 5 | export function asSingleton(arrayLike: T | T[]): T { 6 | if (Array.isArray(arrayLike)) { 7 | if (arrayLike.length !== 1) { 8 | throw new Error(`Expected singleton array. Got ${arrayLike.length} elements`); 9 | } 10 | return arrayLike[0]; 11 | } 12 | return arrayLike; 13 | } 14 | 15 | export type NotFunction = Exclude; 16 | 17 | export type FunctionLike = ((args: V) => NotFunction) | NotFunction; 18 | 19 | export function asFunction(f: FunctionLike): (arg: V) => T { 20 | if (typeof f === "function") { 21 | return f; 22 | } else { 23 | return () => f; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /libs/juava/src/stopwatch.ts: -------------------------------------------------------------------------------- 1 | export type Stopwatch = { 2 | startedAt: number; 3 | elapsedMs(): number; 4 | lapMs(): number; 5 | elapsedPretty(): string; 6 | }; 7 | 8 | function formatMs(ms: number) { 9 | if (Math.floor(ms / (60 * 1000)) >= 1) { 10 | return `${Math.floor(ms / (60 * 1000))}m ${formatMs(ms % (60 * 1000))}`; 11 | } 12 | if (Math.floor(ms / 1000) >= 1) { 13 | return `${Math.floor(ms / 1000)}s ${formatMs(ms % 1000)}`; 14 | } 15 | return `${ms}ms`; 16 | } 17 | 18 | export function stopwatch(): Stopwatch { 19 | let startedAt = Date.now(); 20 | let lastLap = startedAt; 21 | return { 22 | elapsedPretty(): string { 23 | return formatMs(this.elapsedMs()); 24 | }, 25 | elapsedMs(): number { 26 | return Date.now() - this.startedAt; 27 | }, 28 | lapMs(): number { 29 | const now = Date.now(); 30 | const lap = now - lastLap; 31 | lastLap = now; 32 | return lap; 33 | }, 34 | startedAt: startedAt, 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /libs/juava/src/strings.ts: -------------------------------------------------------------------------------- 1 | export function trimMiddle(str: string, maxLen: number, ellisis = "...") { 2 | if (str.length <= maxLen) { 3 | return str; 4 | } else { 5 | return str.substring(0, maxLen / 2 - (ellisis.length - 1)) + ellisis + str.substring(str.length - maxLen / 2 + 1); 6 | } 7 | } 8 | 9 | export function trimEnd(str: string, maxLen: number, ellisis = "...") { 10 | if (str.length <= maxLen) { 11 | return str; 12 | } else { 13 | return str.substring(0, maxLen - (ellisis.length - 1)) + ellisis; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /libs/juava/src/throttle.ts: -------------------------------------------------------------------------------- 1 | export interface Throttle { 2 | throttle(): number; 3 | fail: () => void; 4 | success: () => void; 5 | } 6 | 7 | export function noThrottle(): Throttle { 8 | return { 9 | throttle: () => 0, 10 | fail: () => {}, 11 | success: () => {}, 12 | }; 13 | } 14 | 15 | export function alwaysThrottle(): Throttle { 16 | return { 17 | throttle: () => 1, 18 | fail: () => {}, 19 | success: () => {}, 20 | }; 21 | } 22 | 23 | export function getThrottle(calculatePeriodMs: number): Throttle { 24 | let previousThrottleTime = Date.now(); 25 | let currentThrottle = 0; 26 | let fails = 0; 27 | let successes = 0; 28 | 29 | function recalculateThrottle() { 30 | const total = fails + successes; 31 | const now = Date.now(); 32 | if (total > 100 || (total >= 10 && now - previousThrottleTime > calculatePeriodMs)) { 33 | previousThrottleTime = now; 34 | 35 | const l = Math.min(100, total); 36 | currentThrottle = Math.min(fails / total, (l - 1) / l); 37 | fails = 0; 38 | successes = 0; 39 | } 40 | } 41 | 42 | return { 43 | fail: () => { 44 | fails++; 45 | recalculateThrottle(); 46 | }, 47 | success: () => { 48 | successes++; 49 | recalculateThrottle(); 50 | }, 51 | throttle: () => { 52 | return currentThrottle; 53 | }, 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /libs/juava/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2015", "dom", "es2017"], 4 | "rootDir": "./src", 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "noEmit": true 8 | }, 9 | "exclude": [ 10 | "__tests__", 11 | "node_modules", 12 | "dist", 13 | "test_projects", 14 | "test", 15 | "templates" 16 | ] 17 | } -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - e2e 3 | - examples/* 4 | - types/* 5 | - webapps/* 6 | - services/* 7 | - cli/* 8 | - libs/* 9 | onlyBuiltDependencies: 10 | - "@confluentinc/kafka-javascript" 11 | - "@firebase/util" 12 | - "@prisma/client" 13 | - "@prisma/engines" 14 | - core-js 15 | - core-js-pure 16 | - cpu-features 17 | - esbuild 18 | - isolated-vm 19 | - prisma 20 | - protobufjs 21 | - sharp 22 | - ssh2 23 | - turbo 24 | - zstd-napi 25 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pnpm run build-scripts docker ../../ -t console,rotor,profiles --platform linux/amd64,linux/arm64 --push $@ 4 | 5 | -------------------------------------------------------------------------------- /services/profiles/.gitignore: -------------------------------------------------------------------------------- 1 | /compiled/ 2 | /dist/ 3 | /__tests__/artifacts/ 4 | -------------------------------------------------------------------------------- /services/profiles/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | ["@babel/preset-react", { importSource: "react", runtime: "automatic" }], 6 | ], 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /services/profiles/dist_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jitsu-internal/profile-builder", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "isolated-vm": "5.0.1", 6 | "@confluentinc/kafka-javascript": "^1.3.0", 7 | "@mongodb-js/zstd": "^2.0.1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /services/profiles/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("ts-jest").JestConfigWithTsJest} */ 2 | module.exports = { 3 | //preset: "ts-jest", 4 | preset: "ts-jest", 5 | testMatch: ["**/__tests__/**/*.test.ts"], 6 | testEnvironment: "node", 7 | runner: "jest-runner", 8 | "setupFiles": ["./jest.setup.js"] 9 | }; 10 | -------------------------------------------------------------------------------- /services/profiles/jest.setup.js: -------------------------------------------------------------------------------- 1 | process.env.JUAVA_LOG_LEVEL = 'debug'; 2 | global.console = { 3 | log: message => process.stdout.write(message + '\n'), 4 | error: console.error, 5 | warn: console.warn, 6 | info: console.info, 7 | debug: console.debug, 8 | }; 9 | -------------------------------------------------------------------------------- /services/profiles/src/lib/repositories.ts: -------------------------------------------------------------------------------- 1 | import { EnrichedConnectionConfig, storeFunc, WorkspaceWithProfiles } from "@jitsu/core-functions"; 2 | 3 | export const profilesStore = storeFunc("workspaces-with-profiles"); 4 | export const connectionsStore = storeFunc("rotor-connections"); 5 | -------------------------------------------------------------------------------- /services/profiles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./compiled", 4 | "rootDir": ".", 5 | "noImplicitAny": false, 6 | "allowSyntheticDefaultImports": true, 7 | "importHelpers": true, 8 | "removeComments": true, 9 | "target": "ESNext", 10 | "module": "commonjs", 11 | "lib": [ 12 | "ESNext", "DOM" 13 | ], 14 | "allowJs": true, 15 | "strict": true, 16 | "esModuleInterop": true, 17 | "jsx": "react-jsx", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "skipLibCheck": true, 21 | }, 22 | "exclude": [ 23 | "node_modules" 24 | ], 25 | "include": [ 26 | "src/**/*" 27 | ] 28 | } 29 | 30 | -------------------------------------------------------------------------------- /services/profiles/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | 4 | const config = { 5 | entry: "./src/index.ts", 6 | target: "node", 7 | externals: { 8 | "isolated-vm": "require('isolated-vm')", 9 | "@confluentinc/kafka-javascript": "require('@confluentinc/kafka-javascript')", 10 | }, 11 | node: { 12 | __dirname: false, 13 | }, 14 | devtool: "source-map", 15 | output: { 16 | path: path.resolve(__dirname, "dist"), 17 | }, 18 | plugins: [ 19 | new webpack.IgnorePlugin({ resourceRegExp: /^pg-native$/ }), // Ignore native module 20 | // Add your plugins here 21 | // Learn more about plugins from https://webpack.js.org/configuration/plugins/ 22 | ], 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.(ts|tsx)$/i, 27 | //loader: "ts-loader", 28 | //exclude: ["/node_modules/"], 29 | use: { 30 | loader: "babel-loader", 31 | }, 32 | }, 33 | { 34 | test: /\.node$/, 35 | loader: "node-loader", 36 | }, 37 | ], 38 | }, 39 | optimization: { 40 | minimize: false, 41 | }, 42 | resolve: { 43 | extensions: [".tsx", ".ts", ".jsx", ".js", ".node", "..."], 44 | }, 45 | mode: "production", 46 | }; 47 | 48 | module.exports = () => config; 49 | -------------------------------------------------------------------------------- /services/rotor/.gitignore: -------------------------------------------------------------------------------- 1 | /compiled/ 2 | /dist/ 3 | /__tests__/artifacts/ 4 | -------------------------------------------------------------------------------- /services/rotor/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | ["@babel/preset-react", { importSource: "react", runtime: "automatic" }], 6 | ], 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /services/rotor/dist_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rotor", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "undici": "^6.21.2", 6 | "isolated-vm": "5.0.1", 7 | "@sensejs/kafkajs-zstd-support": "^0.11.0", 8 | "@mongodb-js/zstd": "^2.0.1" 9 | }, 10 | "overrides": { 11 | "@sensejs/kafkajs-zstd-support": { 12 | "zstd-napi": "^0.0.10" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /services/rotor/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("ts-jest").JestConfigWithTsJest} */ 2 | module.exports = { 3 | //preset: "ts-jest", 4 | preset: "ts-jest", 5 | testMatch: ["**/__tests__/**/*.test.ts"], 6 | testEnvironment: "node", 7 | runner: "jest-runner", 8 | "setupFiles": ["./jest.setup.js"] 9 | }; 10 | -------------------------------------------------------------------------------- /services/rotor/jest.setup.js: -------------------------------------------------------------------------------- 1 | process.env.JUAVA_LOG_LEVEL = 'debug'; 2 | global.console = { 3 | log: message => process.stdout.write(message + '\n'), 4 | error: console.error, 5 | warn: console.warn, 6 | info: console.info, 7 | debug: console.debug, 8 | }; 9 | -------------------------------------------------------------------------------- /services/rotor/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | //To fix a weird ts compiler error 3 | //type Blob = any; 4 | } 5 | 6 | export {}; 7 | -------------------------------------------------------------------------------- /services/rotor/src/http/profiles-udf.ts: -------------------------------------------------------------------------------- 1 | import { mongodb, createMongoStore, ProfileUDFTestRequest, ProfileUDFTestRun } from "@jitsu/core-functions"; 2 | import { getLog } from "juava"; 3 | import { connectionsStore } from "../lib/repositories"; 4 | 5 | const log = getLog("profile-udf-run"); 6 | 7 | export const ProfileUDFRunHandler = async (req, res) => { 8 | const body = req.body as ProfileUDFTestRequest; 9 | log.atInfo().log(`Running profile func: ${body?.id} workspace: ${body?.workspaceId}`, JSON.stringify(body)); 10 | body.store = createMongoStore(body?.workspaceId, mongodb, true, false); 11 | const result = await ProfileUDFTestRun(body, connectionsStore.getCurrent()); 12 | if (result.error) { 13 | log 14 | .atError() 15 | .log( 16 | `Error running profile function: ${body?.id} workspace: ${body?.workspaceId}\n${result.error.name}: ${result.error.message}` 17 | ); 18 | } 19 | res.json(result); 20 | }; 21 | -------------------------------------------------------------------------------- /services/rotor/src/http/udf.ts: -------------------------------------------------------------------------------- 1 | import { UDFTestRun, UDFTestRequest, mongodb, createMongoStore } from "@jitsu/core-functions"; 2 | import { getLog } from "juava"; 3 | import { connectionsStore } from "../lib/repositories"; 4 | 5 | const log = getLog("udf_run"); 6 | 7 | export const UDFRunHandler = async (req, res) => { 8 | const body = req.body as UDFTestRequest; 9 | log.atInfo().log(`Running function: ${body?.functionId} workspace: ${body?.workspaceId}`, JSON.stringify(body)); 10 | body.store = createMongoStore(body?.workspaceId, mongodb, true, false); 11 | const result = await UDFTestRun(body, connectionsStore.getCurrent()); 12 | if (result.error) { 13 | log 14 | .atError() 15 | .log( 16 | `Error running function: ${body?.functionId} workspace: ${body?.workspaceId}\n${result.error.name}: ${result.error.message}` 17 | ); 18 | } 19 | res.json(result); 20 | }; 21 | -------------------------------------------------------------------------------- /services/rotor/src/lib/repositories.ts: -------------------------------------------------------------------------------- 1 | import { EnrichedConnectionConfig, FunctionConfig, storeFunc, StreamWithDestinations } from "@jitsu/core-functions"; 2 | 3 | export const functionsStore = storeFunc("functions"); 4 | export const connectionsStore = storeFunc("rotor-connections"); 5 | export const streamsStore = storeFunc("streams-with-destinations"); 6 | -------------------------------------------------------------------------------- /services/rotor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./compiled", 4 | "rootDir": ".", 5 | "noImplicitAny": false, 6 | "allowSyntheticDefaultImports": true, 7 | "importHelpers": true, 8 | "removeComments": true, 9 | "target": "ESNext", 10 | "module": "commonjs", 11 | "lib": [ 12 | "ESNext", "DOM" 13 | ], 14 | "allowJs": true, 15 | "strict": true, 16 | "esModuleInterop": true, 17 | "jsx": "react-jsx", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "skipLibCheck": true, 21 | }, 22 | "exclude": [ 23 | "node_modules" 24 | ], 25 | "include": [ 26 | "src/**/*" 27 | ] 28 | } 29 | 30 | -------------------------------------------------------------------------------- /services/rotor/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | 4 | const config = { 5 | entry: "./src/index.ts", 6 | target: "node", 7 | externals: { 8 | "isolated-vm": "require('isolated-vm')", 9 | }, 10 | node: { 11 | __dirname: false, 12 | }, 13 | devtool: "source-map", 14 | output: { 15 | path: path.resolve(__dirname, "dist"), 16 | }, 17 | plugins: [ 18 | new webpack.IgnorePlugin({ resourceRegExp: /^pg-native$/ }), // Ignore native module 19 | // Add your plugins here 20 | // Learn more about plugins from https://webpack.js.org/configuration/plugins/ 21 | ], 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.(ts|tsx)$/i, 26 | //loader: "ts-loader", 27 | //exclude: ["/node_modules/"], 28 | use: { 29 | loader: "babel-loader", 30 | }, 31 | }, 32 | { 33 | test: /\.node$/, 34 | loader: "node-loader", 35 | }, 36 | ], 37 | }, 38 | optimization: { 39 | minimize: false, 40 | }, 41 | resolve: { 42 | extensions: [".tsx", ".ts", ".jsx", ".js", ".node", "..."], 43 | }, 44 | mode: "production", 45 | }; 46 | 47 | module.exports = () => config; 48 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "commonjs", 5 | "lib": ["ES6", "ES2015"], 6 | "outDir": "dist", 7 | "rootDir": "src", 8 | "strict": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "esModuleInterop": true, 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "importHelpers": true, 15 | "removeComments": true, 16 | "skipLibCheck": true 17 | }, 18 | "exclude": ["node_modules", "dist", "test_projects", "test", "templates"] 19 | } 20 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "pipeline": { 3 | "console:dev": { 4 | "dependsOn": ["^console:dev"], 5 | "cache": false 6 | }, 7 | "console:start": { 8 | "dependsOn": ["^console:start"], 9 | "cache": false 10 | }, 11 | "console:storybook": { 12 | "dependsOn": ["^console:storybook"], 13 | "cache": false 14 | }, 15 | "ee-api:dev": { 16 | "dependsOn": ["^ee-api:dev"], 17 | "cache": false 18 | }, 19 | "rotor:dev": { 20 | "dependsOn": ["^rotor:dev"], 21 | "cache": false 22 | }, 23 | "profiles:dev": { 24 | "dependsOn": ["^profiles:dev"], 25 | "cache": false 26 | }, 27 | "tool:hash": { 28 | "dependsOn": ["^tool:hash"], 29 | "cache": false 30 | }, 31 | "dev": { 32 | "dependsOn": ["console:dev", "rotor:dev"], 33 | "cache": false 34 | }, 35 | "test": { 36 | "dependsOn": ["^test"] 37 | }, 38 | "clean": { 39 | "dependsOn": ["^clean"] 40 | }, 41 | "lint": { 42 | "dependsOn": ["^lint"] 43 | }, 44 | "build": { 45 | "dependsOn": ["clean", "^build"], 46 | "outputs": ["dist/**", "compiled/**", "build/**", ".next/**", "!.next/cache/**"] 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /types/protocols/async-request.d.ts: -------------------------------------------------------------------------------- 1 | import { ISO8601Date } from "./iso8601"; 2 | import type { Geo } from "./analytics"; 3 | 4 | export type IngestType = "s2s" | "browser"; 5 | 6 | export type IngestMessage = { 7 | geo?: Geo; 8 | ingestType: IngestType; 9 | messageCreated: ISO8601Date; 10 | writeKey: string; 11 | messageId: string; 12 | //currently this not being filled 13 | connectionId: string; 14 | type: string; 15 | origin: { 16 | baseUrl: string; 17 | slug?: string; 18 | sourceId?: string; 19 | sourceName?: string; 20 | domain?: string; 21 | classic?: boolean; 22 | }; 23 | httpHeaders: Record; 24 | httpPayload: any; 25 | }; 26 | -------------------------------------------------------------------------------- /types/protocols/iso8601.d.ts: -------------------------------------------------------------------------------- 1 | //TODO: add an actual type for ISO 8601 date 2 | export type ISO8601Date = string; 3 | -------------------------------------------------------------------------------- /types/protocols/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jitsu/protocols", 3 | "version": "0.0.0", 4 | "description": "", 5 | "author": "Jitsu Dev Team ", 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "license": "MIT", 10 | "private": false, 11 | "scripts": {}, 12 | "devDependencies": {} 13 | } 14 | -------------------------------------------------------------------------------- /types/protocols/profile.d.ts: -------------------------------------------------------------------------------- 1 | import { FunctionContext } from "./functions"; 2 | import { AnalyticsServerEvent } from "./analytics"; 3 | 4 | export type ProfileResult = { 5 | profileId?: string; 6 | destinationId?: string; 7 | tableName?: string; 8 | traits: Record; 9 | }; 10 | 11 | export type ProfileUser = { 12 | profileId: string; 13 | userId?: string; 14 | anonymousId?: string; 15 | traits: Record; 16 | }; 17 | 18 | export type ProfileBuilderContext = { 19 | profileBuilder: { 20 | id: string; 21 | version: number; 22 | }; 23 | }; 24 | 25 | export type ProfileFunction = ( 26 | events: Iterable, 27 | user: ProfileUser, 28 | context: FunctionContext & ProfileBuilderContext 29 | ) => Promise; 30 | -------------------------------------------------------------------------------- /types/protocols/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | } -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /webapps/console/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "plugins": [ 4 | "unused-imports" 5 | ], 6 | "rules": { 7 | "no-unused-vars": "off", 8 | "unused-imports/no-unused-imports": "error", 9 | "react/jsx-curly-brace-presence": ["off", { "props": "never" }], 10 | // "unused-imports/no-unused-vars": [ 11 | // "warn", 12 | // { 13 | // "vars": "all", 14 | // "varsIgnorePattern": "^_", 15 | // "args": "after-used", 16 | // "argsIgnorePattern": "^_" 17 | // } 18 | // ], 19 | "react/no-unescaped-entities": 0, 20 | "@next/next/no-img-element": 0, 21 | "import/no-anonymous-default-export": 0 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /webapps/console/.gitignore: -------------------------------------------------------------------------------- 1 | /prisma/schema/ 2 | /.next/ 3 | 4 | *storybook.log 5 | -------------------------------------------------------------------------------- /webapps/console/.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from "@storybook/nextjs"; 2 | 3 | import { join, dirname } from "path"; 4 | 5 | /** 6 | * This function is used to resolve the absolute path of a package. 7 | * It is needed in projects that use Yarn PnP or are set up within a monorepo. 8 | */ 9 | function getAbsolutePath(value: string): any { 10 | return dirname(require.resolve(join(value, "package.json"))); 11 | } 12 | const config: StorybookConfig = { 13 | stories: ["../components/**/*.stories.@(js|jsx|mjs|ts|tsx)"], 14 | addons: [ 15 | getAbsolutePath("@storybook/addon-onboarding"), 16 | getAbsolutePath("@storybook/addon-links"), 17 | getAbsolutePath("@storybook/addon-essentials"), 18 | getAbsolutePath("@chromatic-com/storybook"), 19 | getAbsolutePath("@storybook/addon-interactions"), 20 | ], 21 | framework: { 22 | name: getAbsolutePath("@storybook/nextjs"), 23 | options: {}, 24 | }, 25 | staticDirs: ["../public"], 26 | }; 27 | export default config; 28 | -------------------------------------------------------------------------------- /webapps/console/.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import "../styles/globals.css"; 2 | 3 | import type { Preview } from "@storybook/react"; 4 | 5 | const preview: Preview = { 6 | parameters: { 7 | controls: { 8 | matchers: { 9 | color: /(background|color)$/i, 10 | date: /Date$/i, 11 | }, 12 | }, 13 | }, 14 | }; 15 | 16 | export default preview; 17 | -------------------------------------------------------------------------------- /webapps/console/components/AntdTheme/AntdTheme.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from "react"; 2 | import { ConfigProvider } from "antd"; 3 | import { StyleProvider } from "@ant-design/cssinjs"; 4 | 5 | const theme = require("../../theme.config.js"); 6 | 7 | export const AntdTheme: React.FC> = ({ children }) => { 8 | const antdColors = { 9 | colorPrimary: theme.primary, 10 | }; 11 | return ( 12 | 22 | {children} 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /webapps/console/components/AsyncButton/AsyncButton.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonProps } from "antd/lib/button/button"; 2 | import { useState } from "react"; 3 | import { Button } from "antd"; 4 | import { feedbackError, feedbackSuccess } from "../../lib/ui"; 5 | 6 | export type AsyncButtonProps = ButtonProps & { 7 | onClick: () => Promise; 8 | errorMessage?: string; 9 | successMessage?: string; 10 | onSuccess?: () => void; 11 | onError?: (e: any) => void; 12 | }; 13 | export const AsyncButton: React.FC = props => { 14 | const { onClick, ...rest } = props; 15 | const [loading, setLoading] = useState(false); 16 | return ( 17 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /webapps/console/components/BackButton/BackButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | import { ChevronLeft } from "lucide-react"; 3 | import { JitsuButton } from "../JitsuButton/JitsuButton"; 4 | import { useRouter } from "next/router"; 5 | import { usePreviousRoute } from "../../lib/previous-route"; 6 | 7 | type BackButtonProps = { 8 | href?: string; 9 | onClick?: () => void; 10 | useHistory?: boolean; 11 | }; 12 | 13 | export const BackButton: FC = ({ href, onClick, useHistory }) => { 14 | const router = useRouter(); 15 | const previousRoute = usePreviousRoute(); 16 | const canUseHistory = !!useHistory && !!previousRoute; 17 | 18 | if (!href && !onClick && !canUseHistory) { 19 | return <>; 20 | } 21 | return ( 22 | } 24 | type="link" 25 | size="small" 26 | onClick={() => { 27 | if (canUseHistory) { 28 | router.push(previousRoute!); 29 | return; 30 | } 31 | onClick ? onClick() : router.push(href!); 32 | }} 33 | > 34 | Back 35 | 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /webapps/console/components/Billing/BillingManager.module.css: -------------------------------------------------------------------------------- 1 | .comparisonSection { 2 | @apply mt-4; 3 | } 4 | 5 | .comparisonSection h5 { 6 | @apply flex items-center space-x-0; 7 | } 8 | 9 | .comparisonSection ul li { 10 | @apply flex items-center space-x-1 my-2; 11 | } 12 | 13 | .comparisonSection ul li > span { 14 | @apply text-textLight; 15 | } 16 | -------------------------------------------------------------------------------- /webapps/console/components/Billing/copy.tsx: -------------------------------------------------------------------------------- 1 | export const upgradeRequired = ( 2 | <> 3 | You have exceeded your monthly credit limit. Please upgrade your plan to continue using Jitsu. As a courtesy, we 4 | keep sending your data to destinations, but we can stop doing so at any moment 5 | 6 | ); 7 | -------------------------------------------------------------------------------- /webapps/console/components/ButtonGroup/ButtonGroup.module.css: -------------------------------------------------------------------------------- 1 | /*.buttonGroup > :global(.ant-btn) {*/ 2 | /* margin-inline-start: 0 !important;*/ 3 | /*}*/ 4 | -------------------------------------------------------------------------------- /webapps/console/components/Center/Center.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from "react"; 2 | 3 | export const Center: React.FC> = ({ 4 | vertical, 5 | horizontal, 6 | children, 7 | }) => { 8 | return ( 9 |
10 | {children} 11 |
12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /webapps/console/components/CodeBlock/CodeBlock.module.css: -------------------------------------------------------------------------------- 1 | .blockWrapper { 2 | } 3 | 4 | .blockWrapper pre { 5 | @apply text-background !important; 6 | font-family: var(--font-mono) !important; 7 | } 8 | -------------------------------------------------------------------------------- /webapps/console/components/CodeEditor/CodeEditor.module.css: -------------------------------------------------------------------------------- 1 | .editor { 2 | @apply rounded-lg; 3 | background-color: transparent; 4 | } 5 | 6 | .editor :global(.monaco-editor) { 7 | background-color: unset !important; 8 | } 9 | 10 | .editor :global(.monaco-editor-background) { 11 | background-color: unset !important; 12 | } 13 | -------------------------------------------------------------------------------- /webapps/console/components/ConfigObjectEditor/ConfigEditor.module.css: -------------------------------------------------------------------------------- 1 | .required { 2 | display: inline-block; 3 | margin-left: 0.5rem; 4 | margin-top: 0.1rem; 5 | font-size: 0.9rem; 6 | color: rgb(100, 100, 100); 7 | } 8 | 9 | .inner:first-child { 10 | padding-top: 4px; 11 | } 12 | 13 | .inner:last-child { 14 | margin-bottom: 0; 15 | } 16 | 17 | .invalidInput input { 18 | border-color: rgb(255, 0, 0); 19 | } 20 | 21 | .listTable :global(th.ant-table-cell) { 22 | @apply text-textLight font-bold; 23 | text-transform: uppercase; 24 | } 25 | 26 | .listTable :global(th.ant-table-cell::before) { 27 | content: none !important; 28 | } 29 | 30 | .listTable { 31 | @apply border border-backgroundDark rounded-lg; 32 | } 33 | 34 | .help a { 35 | text-decoration: underline; 36 | } 37 | 38 | .nestedObjectField :global(.ant-form-item-label) { 39 | max-width: 33.8% !important; 40 | text-transform: capitalize; 41 | } 42 | 43 | .nestedObjectField :global(.ant-form-item-control) { 44 | max-width: 66.2% !important; 45 | } 46 | -------------------------------------------------------------------------------- /webapps/console/components/ConfigObjectEditor/EditorBase.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useKeyboard } from "../../lib/ui"; 3 | 4 | export type EditorBaseProps = { 5 | onCancel: (isTouched: boolean) => void; 6 | isTouched: boolean; 7 | }; 8 | 9 | export const EditorBase: React.FC> = ({ onCancel, isTouched, children }) => { 10 | useKeyboard("Escape", () => { 11 | onCancel(isTouched); 12 | }); 13 | 14 | useEffect(() => { 15 | const handler = async event => { 16 | if (isTouched) { 17 | event.preventDefault(); 18 | return (event.returnValue = "Are you sure you want to exit? You have unsaved changes"); 19 | } 20 | }; 21 | window.addEventListener("beforeunload", handler); 22 | return () => window.removeEventListener("beforeunload", handler); 23 | }, [isTouched]); 24 | return ( 25 |
26 |
{children}
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /webapps/console/components/ConfigObjectEditor/EditorTitle.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import { JitsuButton } from "../JitsuButton/JitsuButton"; 3 | import { ChevronLeft } from "lucide-react"; 4 | 5 | export type EditorTitleProps = { 6 | title: ReactNode; 7 | subtitle?: ReactNode; 8 | onBack: () => void; 9 | }; 10 | 11 | export const EditorTitle: React.FC = ({ title, subtitle, onBack }) => { 12 | return ( 13 | <> 14 |
15 |

{title}

16 |
17 | } type="link" size="small" onClick={onBack}> 18 | Back 19 | 20 |
21 |
22 | {subtitle &&
{subtitle}
} 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /webapps/console/components/ConnectionEditorPage/ConnectionEditorPage.module.css: -------------------------------------------------------------------------------- 1 | .radioGroup :global(.ant-radio) { 2 | align-self: flex-start !important; 3 | margin-top: 4px !important; 4 | } 5 | -------------------------------------------------------------------------------- /webapps/console/components/CopyButton/CopyButton.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Button, Tooltip } from "antd"; 3 | import { copyTextToClipboard } from "../../lib/ui"; 4 | 5 | export function CopyButton({ 6 | text, 7 | className, 8 | children, 9 | }: { 10 | text: string; 11 | className?: string; 12 | children: React.ReactNode; 13 | }) { 14 | const [copyTitle, setCopyTitle] = useState("Copy"); 15 | return ( 16 | 17 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /webapps/console/components/DestinationsCatalog/DestinationsCatalog.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | width: 36px; 3 | height: 36px; 4 | } 5 | 6 | .icon svg { 7 | width: 36px; 8 | height: 36px; 9 | } 10 | -------------------------------------------------------------------------------- /webapps/console/components/Disable/Disable.tsx: -------------------------------------------------------------------------------- 1 | import { Children, cloneElement, PropsWithChildren, ReactNode } from "react"; 2 | import { Tooltip } from "antd"; 3 | 4 | export type DisableProps = 5 | | { 6 | disabled: false; 7 | } 8 | | { disabled: true; disabledReason: ReactNode }; 9 | 10 | export const Disable: React.FC> = props => { 11 | const arrayChildren = Children.toArray(props.children); 12 | const child = arrayChildren[0]; 13 | if (!props.disabled) { 14 | return <>{child}; 15 | } 16 | if (arrayChildren.length !== 1) { 17 | throw new Error(` must have exactly one child, found ${arrayChildren.length}`); 18 | } 19 | return {cloneElement(child as any, { disabled: true })}; 20 | }; 21 | -------------------------------------------------------------------------------- /webapps/console/components/DocumentedLabel/DocumentedLabel.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { Tooltip } from "antd"; 3 | import { FaInfoCircle } from "react-icons/fa"; 4 | 5 | export type DocumentedLabelProps = { 6 | name: ReactNode; 7 | doc?: ReactNode; 8 | }; 9 | 10 | export const DocumentedLabel: React.FC = ({ name, doc }) => { 11 | if (!doc) { 12 | return <>{name}; 13 | } 14 | return ( 15 |
16 |
{name}
17 | 18 | 19 | 20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /webapps/console/components/EditorToolbar/EditorToolbar.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import classNames from "classnames"; 3 | import Link from "next/link"; 4 | 5 | export type EditorToolbarProps = { 6 | items: { 7 | title: ReactNode; 8 | icon: ReactNode; 9 | href: string; 10 | onClick?: () => void; 11 | }[]; 12 | className?: string; 13 | }; 14 | 15 | export const EditorToolbar: React.FC = ({ items, className }) => { 16 | return ( 17 |
18 | {items.map(({ href, icon, title, onClick }, index) => ( 19 | 25 |
{icon}
26 | {title} 27 | 28 | ))} 29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /webapps/console/components/ExpandableButton/ExpandableButton.module.css: -------------------------------------------------------------------------------- 1 | .expandableButton { 2 | @apply bg-primaryLight text-white text-sm font-bold pl-3.5 py-2 rounded-full shadow-lg h-10 w-10; 3 | } 4 | 5 | .expandableButton:hover { 6 | @apply pr-3.5; 7 | width: 100%; 8 | 9 | transition: all 0.4s; 10 | } 11 | 12 | .expandableButton .icon { 13 | @apply w-4 h-4; 14 | min-height: 1rem; 15 | min-width: 1rem; 16 | } 17 | 18 | .expandableButton .icon > svg { 19 | @apply w-full h-full; 20 | } 21 | 22 | .expandableButton .title { 23 | display: none; 24 | } 25 | .expandableButton:hover > .title { 26 | @apply ml-2; 27 | display: block; 28 | overflow-x: clip; 29 | white-space: nowrap; 30 | } 31 | -------------------------------------------------------------------------------- /webapps/console/components/ExpandableButton/ExpandableButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren, ReactNode } from "react"; 2 | import styles from "./ExpandableButton.module.css"; 3 | 4 | export const ExpandableButton: React.FC< 5 | PropsWithChildren<{ icon: ReactNode; onClick: () => void | Promise }> 6 | > = props => { 7 | return ( 8 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /webapps/console/components/FieldListEditorLayout/FieldListEditorLayout.module.css: -------------------------------------------------------------------------------- 1 | .bordered { 2 | } 3 | 4 | .bordered .editorItemTable { 5 | @apply border border-textDisabled rounded-lg overflow-hidden; 6 | } 7 | 8 | .bordered .editorItemRow { 9 | @apply border-t border-textDisabled border-collapse; 10 | } 11 | 12 | .editorItemTable { 13 | @apply overflow-hidden; 14 | } 15 | 16 | .editorItemRow { 17 | @apply flex; 18 | } 19 | 20 | .editorItemRow .name { 21 | @apply py-4 pl-4 font-bold flex items-center; 22 | width: 18rem; 23 | align-items: flex-start; 24 | min-width: 18rem; 25 | } 26 | 27 | .editorItemRow .component { 28 | @apply py-4 flex-auto pr-2 py-4; 29 | } 30 | 31 | .editorItemRow:first-child { 32 | border-top: 0 !important; 33 | } 34 | -------------------------------------------------------------------------------- /webapps/console/components/FunctionsDebugger/CodeViewer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { CodeBlockLight } from "../CodeBlock/CodeBlockLight"; 3 | 4 | export const CodeViewer: React.FC<{ code: string }> = ({ code }) => { 5 | const [showCode, setShowCode] = useState(false); 6 | 7 | return ( 8 |
9 | 12 | {showCode && ( 13 | 17 | {code} 18 | 19 | )} 20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /webapps/console/components/FunctionsDebugger/FunctionsDebugger.module.css: -------------------------------------------------------------------------------- 1 | .editor { 2 | @apply border rounded-lg overflow-hidden; 3 | border-color: #d9d9d9; 4 | } 5 | 6 | .vars { 7 | @apply overflow-hidden; 8 | } 9 | 10 | .logs { 11 | @apply border rounded-lg overflow-auto; 12 | border-color: #d9d9d9; 13 | } 14 | 15 | .tabsHeightFix { 16 | height: 100%; 17 | } 18 | 19 | .tabsHeightFix :global(.ant-tabs-content) { 20 | height: 100% !important; 21 | } 22 | 23 | .settingsTable :global(.ant-tabs-content) { 24 | height: 100% !important; 25 | } 26 | 27 | .splitterFix :global(.ant-splitter-bar-dragger::before) { 28 | background-color: white !important; 29 | } 30 | -------------------------------------------------------------------------------- /webapps/console/components/FunctionsDebugger/code_templates.ts: -------------------------------------------------------------------------------- 1 | export const defaultFunctionTemplate = () => { 2 | return `export default async function(event, { log, fetch }) { 3 | log.info("Hello world") 4 | }`; 5 | }; 6 | -------------------------------------------------------------------------------- /webapps/console/components/Htmlizer/Htmlizer.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren } from "react"; 2 | 3 | export const Htmlizer: React.FC> = ({ children }) => { 4 | return typeof children === "string" ? : <>{children}; 5 | }; 6 | -------------------------------------------------------------------------------- /webapps/console/components/Icons/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | import Icon, { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon"; 2 | 3 | function Svg() { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | } 10 | 11 | export default function ExternalLink(props: Partial) { 12 | return ; 13 | } 14 | -------------------------------------------------------------------------------- /webapps/console/components/Icons/FileText.tsx: -------------------------------------------------------------------------------- 1 | import Icon, { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon"; 2 | 3 | function Svg() { 4 | return ( 5 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | 26 | export default function FileText(props: Partial) { 27 | return ; 28 | } 29 | -------------------------------------------------------------------------------- /webapps/console/components/JsonAsTable/JsonAsTable.module.css: -------------------------------------------------------------------------------- 1 | .jsonTable :global(td) { 2 | vertical-align: top; 3 | } 4 | -------------------------------------------------------------------------------- /webapps/console/components/LabelEllipsis/LabelEllipsis.tsx: -------------------------------------------------------------------------------- 1 | import { Tooltip } from "antd"; 2 | import { trimEnd, trimMiddle } from "juava"; 3 | 4 | export type LabelEllipsisProps = { 5 | maxLen: number; 6 | trim?: "middle" | "end"; 7 | children: any; 8 | className?: string; 9 | }; 10 | 11 | export function LabelEllipsis({ maxLen, children, trim = "middle", className }: LabelEllipsisProps) { 12 | if (children.length <= maxLen) { 13 | return {children}; 14 | } else { 15 | return ( 16 | 17 | 18 | {trim === "middle" ? trimMiddle(children, maxLen) : trimEnd(children, maxLen)} 19 | 20 | 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /webapps/console/components/ObjectTitle/ObjectTitle.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { FaExternalLinkAlt } from "react-icons/fa"; 4 | 5 | export const ObjectTitle: React.FC<{ 6 | size?: "small" | "default" | "large"; 7 | icon?: React.ReactNode; 8 | title: string | React.ReactNode; 9 | href?: string; 10 | }> = ({ icon, title, size = "default", href }) => { 11 | const iconClassName = (() => { 12 | switch (size) { 13 | case "small": 14 | return "h-4 w-4 flex-shrink-0"; 15 | case "large": 16 | return "h-10 w-10 flex-shrink-0"; 17 | default: 18 | return "h-6 w-6 flex-shrink-0"; 19 | } 20 | })(); 21 | return ( 22 |
23 | {icon &&
{icon}
} 24 |
{title}
25 | {href && ( 26 | 27 | 28 | 29 | )} 30 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /webapps/console/components/Overlay/Overlay.module.css: -------------------------------------------------------------------------------- 1 | .overlay { 2 | @apply fixed inset-0 w-full h-full flex justify-center; 3 | z-index: 1000; 4 | backdrop-filter: blur(8px); 5 | } 6 | 7 | .overlayInternal { 8 | @apply rounded-xl relative bg-backgroundLight shadow; 9 | margin-top: 5vh; 10 | margin-bottom: 5vh; 11 | height: 90vh; 12 | min-height: 500px; 13 | width: 90vw; 14 | } 15 | 16 | .overlayContent { 17 | @apply overflow-auto; 18 | height: 100%; 19 | } 20 | -------------------------------------------------------------------------------- /webapps/console/components/Overlay/Overlay.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from "react"; 2 | import styles from "./Overlay.module.css"; 3 | import { Button } from "antd"; 4 | import { useKeyboard } from "../../lib/ui"; 5 | import { CloseCircleFilled } from "@ant-design/icons"; 6 | 7 | export type OverlayProps = PropsWithChildren<{ className?: string; onClose?: () => void; closable?: boolean }>; 8 | export const Overlay: React.FC = ({ onClose = () => {}, children, className = "", closable = true }) => { 9 | useKeyboard("Escape", onClose); 10 | return ( 11 |
{ 14 | if (closable) { 15 | onClose(); 16 | e.stopPropagation(); 17 | } 18 | }} 19 | > 20 |
e.stopPropagation()}> 21 |
22 | {closable && ( 23 | 26 | )} 27 |
28 |
{children}
29 |
30 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /webapps/console/components/PageLayout/WorkspacePageLayout.module.css: -------------------------------------------------------------------------------- 1 | .widthControl { 2 | max-width: 1500px; 3 | width: 100%; 4 | min-width: 1024px; 5 | } 6 | 7 | .topMenu { 8 | background-color: rgb(250 250 250) !important; 9 | border-bottom: 0 !important; 10 | } 11 | 12 | .topMenu li :global(.ant-menu-submenu-title) { 13 | } 14 | 15 | .topMenu :global(.ant-menu-item) { 16 | top: 0 !important; 17 | margin-top: 0 !important; 18 | } 19 | 20 | .topMenu :global(.ant-menu-submenu) { 21 | top: 0 !important; 22 | margin-top: 0 !important; 23 | } 24 | 25 | .topMenu :global(li.ant-menu-submenu-selected) :global(.ant-menu-submenu-title) { 26 | /*background-color: white !important;*/ 27 | /*padding-left: 0.5rem;*/ 28 | /*padding-right: 0.5rem;*/ 29 | /*padding-bottom: 0 !important;*/ 30 | /*border-bottom: 0 !important;*/ 31 | } 32 | 33 | .topMenu :global(li.ant-menu-submenu-selected) :global(.ant-menu-submenu::after) { 34 | all: unset; 35 | } 36 | 37 | /*.topMenu :global .ant-menu {*/ 38 | /* background-color: rgb(250 250 250) !important;*/ 39 | /* border-bottom-color: #eb5757 !important;*/ 40 | /*}*/ 41 | -------------------------------------------------------------------------------- /webapps/console/components/ProfileBuilderPage/ProfileBuilderPage.module.css: -------------------------------------------------------------------------------- 1 | .tabsHeightFix { 2 | height: 100%; 3 | } 4 | 5 | .tabsHeightFix :global(.ant-tabs-content) { 6 | height: 100% !important; 7 | } 8 | 9 | .settingsTable :global(.ant-tabs-content) { 10 | height: 100% !important; 11 | } 12 | 13 | .splitterFix :global(.ant-splitter-bar-dragger::before) { 14 | background-color: white !important; 15 | } 16 | -------------------------------------------------------------------------------- /webapps/console/components/QueryResponse/QueryResponse.tsx: -------------------------------------------------------------------------------- 1 | import { UseQueryResult } from "@tanstack/react-query/src/types"; 2 | import { ReactNode, useEffect } from "react"; 3 | import { LoadingAnimation } from "../GlobalLoader/GlobalLoader"; 4 | import { GlobalError } from "../GlobalError/GlobalError"; 5 | 6 | type QueryResultDisplay = { 7 | render: (data: T) => ReactNode; 8 | errorTitle?: string; 9 | errorRender?: (error: any) => ReactNode; 10 | prepare?: (data: T) => void; 11 | }; 12 | 13 | type QueryResponseProps = { 14 | result: UseQueryResult; 15 | render: (data: T) => ReactNode; 16 | } & QueryResultDisplay; 17 | 18 | export function QueryResponse(props: QueryResponseProps) { 19 | const { result, render } = props; 20 | useEffect(() => { 21 | if (!result.isLoading && result.data && props.prepare) { 22 | props.prepare(result.data); 23 | } 24 | }, [result.data, props.prepare, result.isLoading, props]); 25 | 26 | if (result.isLoading) { 27 | return ; 28 | } 29 | if (result.error) { 30 | if (props.errorRender) { 31 | return <>{props.errorRender(result.error)}; 32 | } 33 | return ; 34 | } 35 | return <>{render(result.data)}; 36 | } 37 | -------------------------------------------------------------------------------- /webapps/console/components/Redirect/Redirect.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useRouter } from "next/router"; 3 | import { LoadingAnimation } from "../GlobalLoader/GlobalLoader"; 4 | 5 | export const Redirect: React.FC<{ href: string; title?: string }> = ({ title, href }) => { 6 | const router = useRouter(); 7 | useEffect(() => { 8 | router.push(href); 9 | }, []); 10 | return ; 11 | }; 12 | -------------------------------------------------------------------------------- /webapps/console/components/SQLViewer/SQLViewer.module.css: -------------------------------------------------------------------------------- 1 | .editor { 2 | @apply border rounded-lg overflow-hidden; 3 | border-color: #d9d9d9; 4 | flex-shrink: 0; 5 | } 6 | .container { 7 | background-color: transparent !important; 8 | } 9 | 10 | .container :global(.ant-layout-sider-light) { 11 | background-color: transparent !important; 12 | } 13 | 14 | .container :global(.ant-tree) { 15 | background-color: transparent !important; 16 | } 17 | 18 | .container :global(.ant-layout) { 19 | background-color: transparent !important; 20 | } 21 | 22 | .tableTree :global(.ant-tree-indent) { 23 | display: none; 24 | } 25 | 26 | /*.tableTree :global(.ant-tree-switcher_open) {*/ 27 | /* display: none !important;*/ 28 | /*}*/ 29 | 30 | /*.tableTree :global(.ant-tree-switcher_close) {*/ 31 | /* display: none !important;*/ 32 | /*}*/ 33 | 34 | /*.tableTree :global(.ant-tree-switcher-noop) {*/ 35 | /* width: 22px !important;*/ 36 | /*}*/ 37 | -------------------------------------------------------------------------------- /webapps/console/components/Selectors/SourceSelector.tsx: -------------------------------------------------------------------------------- 1 | import { StreamConfig } from "../../lib/schema"; 2 | import { Disable } from "../Disable/Disable"; 3 | import { Select } from "antd"; 4 | import { StreamTitle } from "../../pages/[workspaceId]/streams"; 5 | import { WLink } from "../Workspace/WLink"; 6 | import { FaExternalLinkAlt } from "react-icons/fa"; 7 | import React from "react"; 8 | import { SelectorProps } from "./DestinationSelector"; 9 | 10 | export function SourceSelector(props: SelectorProps) { 11 | return ( 12 |
13 | 14 | 21 | 22 | {!props.enabled && props.showLink && ( 23 |
24 | 25 | 26 | 27 |
28 | )} 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /webapps/console/components/ServicesCatalog/ServicesCatalog.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | width: 48px; 3 | height: 48px; 4 | flex-shrink: 0; 5 | } 6 | 7 | .icon svg { 8 | width: 48px; 9 | height: 48px; 10 | flex-shrink: 0; 11 | } 12 | -------------------------------------------------------------------------------- /webapps/console/components/SignInOrUp/GoogleLogo.tsx: -------------------------------------------------------------------------------- 1 | export const GoogleLogo: React.FC<{}> = () => { 2 | return ( 3 | 12 | 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /webapps/console/components/SignInOrUp/use-query-string-copy.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | 3 | export function useQueryStringCopy(): "" | `?${string}` { 4 | const router = useRouter(); 5 | if (Object.entries(router.query).length === 0) { 6 | return ""; 7 | } 8 | //arrays are not supported yet 9 | return `?${Object.entries(router.query) 10 | .map(([key, value]) => `${key}=${encodeURIComponent(value as string)}`) 11 | .join("&")}`; 12 | } 13 | -------------------------------------------------------------------------------- /webapps/console/components/TrackingIntegrationDocumentation/Other.tsx: -------------------------------------------------------------------------------- 1 | export const Other: React.FC<{ domain: string }> = ({ domain }) => { 2 | return ( 3 |
4 |
5 | 27 |
28 |
29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /webapps/console/components/TrackingIntegrationDocumentation/TrackingIntegrationDocumentation.module.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/webapps/console/components/TrackingIntegrationDocumentation/TrackingIntegrationDocumentation.module.css -------------------------------------------------------------------------------- /webapps/console/components/Workspace/WLink.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { LinkProps } from "next/dist/client/link"; 3 | import { PropsWithChildren } from "react"; 4 | import { useWorkspace } from "../../lib/context"; 5 | 6 | export type WLinkProps = LinkProps & { 7 | target?: string; 8 | rel?: string; 9 | className?: string; 10 | }; 11 | 12 | export const WLink: React.FC> = ({ href, target, rel, children, ...props }) => { 13 | const workspace = useWorkspace(); 14 | return ( 15 | 16 | {children} 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /webapps/console/emails/styles.ts: -------------------------------------------------------------------------------- 1 | export const main = { 2 | fontFamily: 3 | '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif', 4 | backgroundColor: "transparent", 5 | }; 6 | -------------------------------------------------------------------------------- /webapps/console/lib/auth.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /webapps/console/lib/openapi.ts: -------------------------------------------------------------------------------- 1 | import { Api } from "./api"; 2 | import * as workspace from "../pages/api/workspace/[workspaceIdOrSlug]"; 3 | 4 | export type OpenapiExport = { 5 | apis: Api[]; 6 | }; 7 | 8 | const openapiExport: OpenapiExport = { 9 | apis: [workspace.api], 10 | }; 11 | 12 | export function generateOpenApiSpec() { 13 | return {}; 14 | } 15 | -------------------------------------------------------------------------------- /webapps/console/lib/previous-route.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { useEffect, useRef, useState, createContext, FC, useContext } from "react"; 3 | 4 | const PreviousRouteContext = createContext(null); 5 | 6 | export const PreviousRouteContextProvider: FC<{ children: React.ReactNode }> = ({ children }) => { 7 | const router = useRouter(); 8 | const [previousRoute, setPreviousRoute] = useState(null); 9 | const routeRef = useRef(router.asPath); 10 | 11 | useEffect(() => { 12 | const handleRouteChange = (url: string) => { 13 | setPreviousRoute(routeRef.current); 14 | routeRef.current = url; 15 | }; 16 | 17 | router.events.on("routeChangeStart", handleRouteChange); 18 | return () => { 19 | router.events.off("routeChangeStart", handleRouteChange); 20 | }; 21 | }, [router]); 22 | 23 | return {children}; 24 | }; 25 | 26 | export function usePreviousRoute() { 27 | return useContext(PreviousRouteContext); 28 | } 29 | -------------------------------------------------------------------------------- /webapps/console/lib/recovery-log.ts: -------------------------------------------------------------------------------- 1 | import { ISO8601Date } from "@jitsu/protocols/iso8601"; 2 | 3 | export type RecoveryLogMessage = { 4 | timestamp: ISO8601Date; 5 | kind: string; 6 | json: any; 7 | }; 8 | 9 | export type RecoveryLog = {}; 10 | -------------------------------------------------------------------------------- /webapps/console/lib/schema/clickhouse-connection-credentials.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const ClickhouseConnectionCredentials = z.object({ 4 | host: z.string(), 5 | username: z.string(), 6 | database: z.string(), 7 | password: z.string(), 8 | httpPort: z.number(), 9 | pgPort: z.number(), 10 | tcpPort: z.number(), 11 | }); 12 | export type ClickhouseConnectionCredentials = z.infer; 13 | -------------------------------------------------------------------------------- /webapps/console/lib/schema/icons/clickhouse.tsx: -------------------------------------------------------------------------------- 1 | const ClickhouseIcon: React.FC<{}> = () => { 2 | return ( 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default ClickhouseIcon; 16 | export { ClickhouseIcon }; 17 | -------------------------------------------------------------------------------- /webapps/console/lib/schema/icons/devnull.tsx: -------------------------------------------------------------------------------- 1 | export default undefined; 2 | -------------------------------------------------------------------------------- /webapps/console/lib/schema/icons/ga4.tsx: -------------------------------------------------------------------------------- 1 | export default ( 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /webapps/console/lib/schema/icons/gtm.tsx: -------------------------------------------------------------------------------- 1 | export default ( 2 | 12 | 13 | 14 | 15 | 19 | 24 | 25 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /webapps/console/lib/schema/icons/intercom.tsx: -------------------------------------------------------------------------------- 1 | export default ( 2 | 9 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /webapps/console/lib/schema/icons/mixpanel.tsx: -------------------------------------------------------------------------------- 1 | export default ( 2 | 3 | 4 | 5 | 6 | 7 | 8 | ); 9 | -------------------------------------------------------------------------------- /webapps/console/lib/schema/icons/mongodb.tsx: -------------------------------------------------------------------------------- 1 | export default ( 2 | 3 | 4 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /webapps/console/lib/schema/icons/redshift.tsx: -------------------------------------------------------------------------------- 1 | export default ( 2 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /webapps/console/lib/schema/icons/segment.tsx: -------------------------------------------------------------------------------- 1 | export default ( 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /webapps/console/lib/schema/icons/tag.tsx: -------------------------------------------------------------------------------- 1 | export default ( 2 | 3 | 4 | 5 | ); 6 | -------------------------------------------------------------------------------- /webapps/console/lib/server/audit-log.ts: -------------------------------------------------------------------------------- 1 | import { isTruish } from "../shared/chores"; 2 | 3 | export const enableAuditLog = isTruish(process.env.CONSOLE_ENABLE_AUDIT_LOG); 4 | -------------------------------------------------------------------------------- /webapps/console/lib/server/clickhouse.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "@clickhouse/client"; 2 | import { isTruish, requireDefined } from "juava"; 3 | 4 | function clickhouseHost() { 5 | if (process.env.CLICKHOUSE_URL) { 6 | return process.env.CLICKHOUSE_URL; 7 | } 8 | return `${isTruish(process.env.CLICKHOUSE_SSL) ? "https://" : "http://"}${requireDefined( 9 | process.env.CLICKHOUSE_HOST, 10 | "env CLICKHOUSE_HOST is not defined" 11 | )}`; 12 | } 13 | 14 | export const clickhouse = createClient({ 15 | url: clickhouseHost(), 16 | username: process.env.CLICKHOUSE_USERNAME || "default", 17 | password: requireDefined(process.env.CLICKHOUSE_PASSWORD, `env CLICKHOUSE_PASSWORD is not defined`), 18 | compression: { 19 | response: true, 20 | }, 21 | }); 22 | 23 | export function dateToClickhouse(date: Date): string { 24 | return date.toISOString().replace("T", " ").replace("Z", "").split(".")[0]; 25 | } 26 | -------------------------------------------------------------------------------- /webapps/console/lib/server/data-domains.ts: -------------------------------------------------------------------------------- 1 | export const dataDomains: Set | undefined = process.env.DATA_DOMAIN 2 | ? new Set(process.env.DATA_DOMAIN.split(",")) 3 | : undefined; 4 | 5 | export const mainDataDomain: string | undefined = process.env.DATA_DOMAIN 6 | ? process.env.DATA_DOMAIN.split(",")[0] 7 | : undefined; 8 | -------------------------------------------------------------------------------- /webapps/console/lib/server/events-log.ts: -------------------------------------------------------------------------------- 1 | export type EventsLogFilter = { 2 | start?: Date; 3 | end?: Date; 4 | filter?: (any) => boolean; 5 | }; 6 | 7 | export type EventsLogRecord = { 8 | id: string; 9 | date: Date; 10 | content: any; 11 | }; 12 | -------------------------------------------------------------------------------- /webapps/console/lib/server/http-agent.ts: -------------------------------------------------------------------------------- 1 | import { getSingleton } from "juava"; 2 | import Agent, { HttpsAgent } from "agentkeepalive"; 3 | 4 | export const httpAgent = getSingleton("http-agent", createHTTPAgent, { silent: true }); 5 | export const httpsAgent = getSingleton("https-agent", createHTTPSAgent, { silent: true }); 6 | 7 | async function createHTTPAgent(): Promise { 8 | const agent = new Agent({ timeout: 300000, freeSocketTimeout: 30000, maxSockets: 1024 }); 9 | return Promise.resolve(agent); 10 | } 11 | 12 | async function createHTTPSAgent(): Promise { 13 | const agent = new HttpsAgent({ timeout: 300000, freeSocketTimeout: 30000, maxSockets: 1024 }); 14 | return Promise.resolve(agent); 15 | } 16 | -------------------------------------------------------------------------------- /webapps/console/lib/server/log.ts: -------------------------------------------------------------------------------- 1 | import { getLog, LoggerOpts, LogLevel, setGlobalLogLevel, setServerJsonFormat, setServerLogColoring } from "juava"; 2 | 3 | setGlobalLogLevel((process.env.LOG_LEVEL || "info") as LogLevel); 4 | setServerLogColoring( 5 | process.env.DISABLE_SERVER_LOGS_ANSI_COLORING !== "true" && process.env.DISABLE_SERVER_LOGS_ANSI_COLORING !== "1" 6 | ); 7 | setServerJsonFormat(process.env.LOG_FORMAT === "json"); 8 | 9 | export function getServerLog(_opts?: LoggerOpts | string) { 10 | return getLog(_opts); 11 | } 12 | -------------------------------------------------------------------------------- /webapps/console/lib/server/oauth/nango-config.ts: -------------------------------------------------------------------------------- 1 | import { Simplify } from "type-fest"; 2 | import { requireDefined } from "juava"; 3 | 4 | export type NangoParams = { 5 | callback: string; 6 | secretKey: string; 7 | publicKey: string; 8 | nangoAppHost: string; 9 | nangoApiHost: string; 10 | }; 11 | 12 | export type NangoConfig = Simplify< 13 | ({ enabled: false } & { [k in keyof NangoParams]?: never }) | ({ enabled: true } & NangoParams) 14 | >; 15 | 16 | function getNangoConfig(): NangoConfig { 17 | if (!process.env.NANGO_APP_HOST) { 18 | return { enabled: false }; 19 | } 20 | return { 21 | enabled: true, 22 | nangoAppHost: process.env.NANGO_APP_HOST, 23 | nangoApiHost: requireDefined(process.env.NANGO_API_HOST, `env NANGO_API_HOST is required`), 24 | secretKey: requireDefined(process.env.NANGO_SECRET_KEY, `env NANGO_SECRET_KEY is required`), 25 | publicKey: requireDefined(process.env.NANGO_PUBLIC_KEY, `env NANGO_SECRET_KEY is required`), 26 | callback: process.env.NANGO_CALLBACK || `${process.env.NANGO_HOST}/oauth/callback`, 27 | }; 28 | } 29 | 30 | export const nangoConfig: NangoConfig = getNangoConfig(); 31 | -------------------------------------------------------------------------------- /webapps/console/lib/server/origin.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest } from "next"; 2 | 3 | export function getRequestHost(req: NextApiRequest) { 4 | return (req.headers["x-forwarded-host"] || req.headers.host) as string; 5 | } 6 | 7 | export function getTopLevelDomain(requestDomain: string): string { 8 | const parts = requestDomain.split("."); 9 | if (parts.length < 2) { 10 | return parts[0]; 11 | } 12 | return parts.slice(-2).join("."); 13 | } 14 | -------------------------------------------------------------------------------- /webapps/console/lib/server/read-only-mode.ts: -------------------------------------------------------------------------------- 1 | import { getLog } from "juava"; 2 | 3 | export const isReadOnly = !!process.env.JITSU_CONSOLE_READ_ONLY_UNTIL; 4 | 5 | export const readOnlyUntil = getReadOnlyUntil(); 6 | 7 | function getReadOnlyUntil(): Date | undefined { 8 | if (!isReadOnly) { 9 | return undefined; 10 | } 11 | let readOnlyUntil; 12 | try { 13 | readOnlyUntil = new Date(process.env.JITSU_CONSOLE_READ_ONLY_UNTIL!); 14 | } catch (e) { 15 | getLog() 16 | .atWarn() 17 | .log( 18 | `Read only until is not a valid date: ${process.env.JITSU_CONSOLE_READ_ONLY_UNTIL}. Setting a date 2 hours from now` 19 | ); 20 | readOnlyUntil = new Date(Date.now() + 2 * 60 * 60 * 1000); 21 | } 22 | getLog().atInfo().log(`Read only mode enabled until: ${readOnlyUntil}`); 23 | return readOnlyUntil; 24 | } 25 | -------------------------------------------------------------------------------- /webapps/console/lib/server/whoami.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest } from "next"; 2 | 3 | export function whoamiUrl(req: NextApiRequest) { 4 | const protocol = req.headers["x-forwarded-proto"] || "http"; 5 | const host = req.headers["x-forwarded-host"] || req.headers.host; 6 | return `${protocol}://${host}`; 7 | } 8 | -------------------------------------------------------------------------------- /webapps/console/lib/shared/arrays.ts: -------------------------------------------------------------------------------- 1 | export function arrayToMap>( 2 | arr: T[] 3 | ): Record { 4 | if (!arr || arr.length === 0) { 5 | return {}; 6 | } 7 | return arr.reduce((acc, entity) => { 8 | acc[entity.id] = entity; 9 | return acc; 10 | }, {}); 11 | } 12 | -------------------------------------------------------------------------------- /webapps/console/lib/shared/chores.ts: -------------------------------------------------------------------------------- 1 | export function isTruish(val: any) { 2 | return val === "true" || val === "1" || val === "yes" || val === true || val === 1; 3 | } 4 | -------------------------------------------------------------------------------- /webapps/console/lib/shared/data-retention.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { Simplify } from "type-fest"; 3 | 4 | export const DataRetentionSettings = z.object({ 5 | kafkaRetentionHours: z.coerce.number(), 6 | identityStitchingRetentionDays: z.coerce.number(), 7 | logsRetentionDays: z.object({ 8 | maxRecords: z.coerce.number(), 9 | maxHours: z.coerce.number(), 10 | }), 11 | customMongoDb: z.string().optional(), 12 | disableS3Archive: z.coerce.boolean().default(false).optional(), 13 | pendingUpdate: z.coerce.boolean().default(false).optional(), 14 | }); 15 | 16 | // Example of type derived from Zod schema 17 | export type DataRetentionSettings = Simplify>; 18 | 19 | export const defaultDataRetentionSettings: DataRetentionSettings = { 20 | kafkaRetentionHours: 7 * 24, 21 | identityStitchingRetentionDays: 30, 22 | logsRetentionDays: { 23 | maxRecords: 1000, 24 | maxHours: 7 * 24, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /webapps/console/lib/shared/domain-check-response.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { Simplify } from "type-fest"; 3 | 4 | export const DomainCheckResponse = z.union([ 5 | z.object({ 6 | ok: z.literal(true), 7 | reason: z.never().optional(), 8 | }), 9 | z.object({ 10 | ok: z.literal(false), 11 | reason: z.union([ 12 | z.literal("used_by_other_workspace"), 13 | z.literal("invalid_domain_name"), 14 | z.literal("pending_ssl"), 15 | z.literal("internal_error"), 16 | ]), 17 | cnames: z.never().optional(), 18 | }), 19 | z.object({ 20 | ok: z.literal(false), 21 | reason: z.literal("requires_cname_configuration"), 22 | cnames: z.array(z.object({ name: z.string(), value: z.string(), ok: z.boolean() })), 23 | }), 24 | ]); 25 | 26 | export type DomainCheckResponse = Simplify>; 27 | -------------------------------------------------------------------------------- /webapps/console/lib/shared/email-domains.ts: -------------------------------------------------------------------------------- 1 | export const publicEmailDomains = [ 2 | "gmail.com", 3 | "outlook.com", 4 | "hotmail.com", 5 | "yahoo.com", 6 | "yandex.com", 7 | "googlemail.com", 8 | "mail.ru", 9 | "yandex.ru", 10 | "protonmail.com", 11 | "protonmail.me", 12 | "icloud.com", 13 | "hey.com", 14 | ]; 15 | -------------------------------------------------------------------------------- /webapps/console/lib/shared/errors.ts: -------------------------------------------------------------------------------- 1 | export class ApiError extends Error { 2 | status?: number; 3 | responseObject: object; 4 | 5 | constructor(message: string, responseObject: object = {}, { status = 500 }: { status?: number } = {}) { 6 | super(message); 7 | this.status = status; 8 | this.responseObject = responseObject; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /webapps/console/lib/shared/json.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sometime JSON.stringify() throws an exception, if the object contains circular references. 3 | * 4 | * This method fixes that by catching the exception and returning stringifies option instead. It's intended to be used 5 | * for debugging logging 6 | */ 7 | export function safeJsonStringify(obj: any, space?: number): string { 8 | try { 9 | if (!space) { 10 | return JSON.stringify(obj); 11 | } else { 12 | return JSON.stringify(obj, null, space); 13 | } 14 | } catch (e) { 15 | return obj + ""; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /webapps/console/lib/shared/url.ts: -------------------------------------------------------------------------------- 1 | export function toURL(url: string, params: Record = {}): string { 2 | if (Object.keys(params).length) { 3 | const urlParams = new URLSearchParams(params); 4 | return `${url}?${urlParams.toString()}`; 5 | } else { 6 | return url; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /webapps/console/lib/shared/zod.ts: -------------------------------------------------------------------------------- 1 | import { ZodError, ZodType } from "zod"; 2 | 3 | export type ZodErrorInfo = { 4 | object: any; 5 | zodError: ZodError; 6 | zodType: any; 7 | }; 8 | 9 | export class ExtendedZodError extends Error { 10 | public info: ZodErrorInfo; 11 | 12 | constructor(info: ZodErrorInfo) { 13 | super(JSON.stringify(info)); 14 | this.info = info; 15 | } 16 | } 17 | 18 | export function zParse(z: ZodType, obj: any): T { 19 | const res = z.safeParse(obj); 20 | if (res.success) { 21 | return res.data; 22 | } else { 23 | throw new ExtendedZodError({ 24 | object: obj, 25 | zodError: res.error, 26 | zodType: z.description, 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /webapps/console/lib/version.ts: -------------------------------------------------------------------------------- 1 | export type ApplicationVersion = { 2 | /** 3 | * Version as 3.2.1 or "dev" 4 | */ 5 | version: string; 6 | /** 7 | * "latest" or "beta" or "dev" 8 | */ 9 | stream: string; 10 | 11 | git?: { 12 | //Id of latest commit 13 | commitId: string; 14 | }; 15 | }; 16 | 17 | export type EnvVars = { 18 | JITSU_VERSION_COMMIT_SHA?: string; 19 | JITSU_VERSION_DOCKER_TAG?: string; 20 | JITSU_VERSION_STRING?: string; 21 | VERCEL_GIT_COMMIT_SHA?: string; 22 | }; 23 | 24 | function getGit(env: EnvVars): ApplicationVersion["git"] { 25 | if (env.JITSU_VERSION_COMMIT_SHA) { 26 | return { 27 | commitId: env.JITSU_VERSION_COMMIT_SHA, 28 | }; 29 | } else if (env.JITSU_VERSION_COMMIT_SHA) { 30 | return { 31 | commitId: env.JITSU_VERSION_COMMIT_SHA, 32 | }; 33 | } 34 | } 35 | 36 | export function getApplicationVersion(): ApplicationVersion { 37 | const env = process.env as EnvVars; 38 | return { 39 | version: env.JITSU_VERSION_STRING || "dev", 40 | stream: env.JITSU_VERSION_DOCKER_TAG || "dev", 41 | git: getGit(env), 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /webapps/console/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /webapps/console/pages/403.tsx: -------------------------------------------------------------------------------- 1 | import { GlobalError } from "../components/GlobalError/GlobalError"; 2 | 3 | export default function Custom403() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /webapps/console/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { Button } from "antd"; 3 | 4 | export default function Custom404() { 5 | const router = useRouter(); 6 | return ( 7 |
8 |
9 |

Error 404

10 |

The page does not exist

11 |

12 | URL: {window.location.pathname} 13 |

14 |
15 | 18 |
19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /webapps/console/pages/405.tsx: -------------------------------------------------------------------------------- 1 | import { GlobalError } from "../components/GlobalError/GlobalError"; 2 | 3 | export default function Custom405() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /webapps/console/pages/500.tsx: -------------------------------------------------------------------------------- 1 | import { GlobalError } from "../components/GlobalError/GlobalError"; 2 | 3 | export default function Custom405() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /webapps/console/pages/[workspaceId]/data.tsx: -------------------------------------------------------------------------------- 1 | import { WorkspacePageLayout } from "../../components/PageLayout/WorkspacePageLayout"; 2 | import { useTitle } from "../../lib/ui"; 3 | import { branding } from "../../lib/branding"; 4 | import React from "react"; 5 | import { DataView } from "../../components/DataView/DataView"; 6 | 7 | const DataViewPage: React.FC = () => { 8 | useTitle(`${branding.productName} » Live Events`); 9 | 10 | return ( 11 | 12 |
13 |
14 |

Live Events

15 |
16 |
17 | 18 |
19 |
20 |
21 | ); 22 | }; 23 | 24 | export default DataViewPage; 25 | -------------------------------------------------------------------------------- /webapps/console/pages/[workspaceId]/event-stat.tsx: -------------------------------------------------------------------------------- 1 | import { EventStatPage } from "../../components/EventStat/EventStatPage"; 2 | import { WorkspacePageLayout } from "../../components/PageLayout/WorkspacePageLayout"; 3 | 4 | export default function EventStatPage0() { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /webapps/console/pages/[workspaceId]/profile-builder.tsx: -------------------------------------------------------------------------------- 1 | import { ProfileBuilderPage } from "../../components/ProfileBuilderPage/ProfileBuilderPage"; 2 | 3 | export default function ProfileBuilder() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /webapps/console/pages/[workspaceId]/settings/billing/details.tsx: -------------------------------------------------------------------------------- 1 | import { WorkspacePageLayout } from "../../../../components/PageLayout/WorkspacePageLayout"; 2 | import { BillingDetails } from "../../../../components/BillingDetails/BillingDetails"; 3 | 4 | const BillingDetailsPage: React.FC = () => { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default BillingDetailsPage; 13 | -------------------------------------------------------------------------------- /webapps/console/pages/[workspaceId]/settings/data-retention.tsx: -------------------------------------------------------------------------------- 1 | import { DataRetentionEditorLoader } from "../../../components/DataRentionEditor/DataRentionEditor"; 2 | import { WorkspacePageLayout } from "../../../components/PageLayout/WorkspacePageLayout"; 3 | 4 | const DataRetentionEditorPage = () => { 5 | return ( 6 | 7 |
8 |

Data Retention

9 | 10 |
11 |
12 | ); 13 | }; 14 | 15 | export default DataRetentionEditorPage; 16 | -------------------------------------------------------------------------------- /webapps/console/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Head, Html, Main, NextScript } from "next/document"; 2 | import { getLog } from "juava"; 3 | 4 | export default function Document() { 5 | return ( 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | const log = getLog("app"); 17 | -------------------------------------------------------------------------------- /webapps/console/pages/_error.tsx: -------------------------------------------------------------------------------- 1 | const Page500 = props => { 2 | return
{JSON.stringify(props, null, 2)}
; 3 | }; 4 | 5 | Page500.getInitialProps = ({ res, err }) => { 6 | const statusCode = res ? res.statusCode : err ? err.statusCode : 404; 7 | return { statusCode }; 8 | }; 9 | 10 | export default Page500; 11 | -------------------------------------------------------------------------------- /webapps/console/pages/admin/events-debug.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, useState } from "react"; 2 | import { Button } from "antd"; 3 | import { feedbackError } from "../../lib/ui"; 4 | import { useJitsu } from "@jitsu/jitsu-react"; 5 | 6 | const SendEventButton: React.FC<{ children: ReactNode; onClick: () => Promise }> = ({ children, onClick }) => { 7 | const [loading, setLoading] = useState(false); 8 | return ( 9 | 25 | ); 26 | }; 27 | 28 | export default function TrackingEventsDebugger() { 29 | const { analytics } = useJitsu(); 30 | return ( 31 |
32 | { 34 | await analytics.track("sign_up"); 35 | }} 36 | > 37 | Sign Up 38 | 39 | { 41 | await analytics.track("login"); 42 | }} 43 | > 44 | Login 45 | 46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /webapps/console/pages/api/[...not-found-404].ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | 3 | export default function handler(req: NextApiRequest, res: NextApiResponse) { 4 | res 5 | .status(404) 6 | .setHeader("Content-type", "application/json") 7 | .send({ error: "API route not found: " + req.url }); 8 | } 9 | -------------------------------------------------------------------------------- /webapps/console/pages/api/admin/become.ts: -------------------------------------------------------------------------------- 1 | import { createRoute } from "../../../lib/api"; 2 | import { z } from "zod"; 3 | import { assertDefined, assertTrue, requireDefined } from "juava"; 4 | import { firebase, isFirebaseEnabled } from "../../../lib/server/firebase-server"; 5 | import { db } from "../../../lib/server/db"; 6 | import { SessionUser } from "../../../lib/schema"; 7 | 8 | async function check(user: SessionUser) { 9 | assertDefined(isFirebaseEnabled(), `Admin users-tool works with firebase only`); 10 | const userModel = requireDefined( 11 | await db.prisma().userProfile.findUnique({ where: { id: user.internalId } }), 12 | `User ${user.internalId} does not exist` 13 | ); 14 | assertTrue(userModel.admin, `User ${user.internalId} is not admin`); 15 | } 16 | 17 | export default createRoute() 18 | .POST({ 19 | auth: true, 20 | body: z.object({ 21 | externalId: z.string(), 22 | }), 23 | result: z.object({ 24 | token: z.string(), 25 | }), 26 | }) 27 | .handler(async ({ req, user, body }) => { 28 | await check(user); 29 | 30 | const firebaseUser = await firebase().auth().getUser(body.externalId); 31 | const token = await firebase() 32 | .auth() 33 | .createCustomToken(firebaseUser.uid, { internalId: firebaseUser.customClaims?.internalId }); 34 | return { token }; 35 | }) 36 | .toNextApiHandler(); 37 | -------------------------------------------------------------------------------- /webapps/console/pages/api/admin/email-templates/[template].tsx: -------------------------------------------------------------------------------- 1 | import { Api, nextJsApiHandler } from "../../../../lib/api"; 2 | import * as emailTemplates from "../../../../lib/server/templates"; 3 | import { assertDefined, assertTrue } from "juava"; 4 | import { db } from "../../../../lib/server/db"; 5 | import mjml2html from "mjml"; 6 | import { z } from "zod"; 7 | import { renderToString } from "react-dom/server"; 8 | 9 | export const api: Api = { 10 | POST: { 11 | types: { 12 | body: z.object({ 13 | props: z.any(), 14 | }), 15 | query: z.object({ 16 | template: z.any(), 17 | }), 18 | }, 19 | auth: true, 20 | handle: async ({ user, body, query }) => { 21 | const userProfile = await db.prisma().userProfile.findFirst({ where: { id: user.internalId } }); 22 | assertDefined(userProfile, "User profile not found"); 23 | assertTrue(userProfile.admin, "Not enough permissions"); 24 | const EmailComponent = emailTemplates[query.template]; 25 | const str = renderToString(); 26 | const renderedEmail = await mjml2html(str, { 27 | validationLevel: "soft", 28 | }); 29 | return { 30 | html: renderedEmail.html, 31 | allProps: renderedEmail, 32 | }; 33 | }, 34 | }, 35 | }; 36 | 37 | export default nextJsApiHandler(api); 38 | -------------------------------------------------------------------------------- /webapps/console/pages/api/admin/email-templates/index.ts: -------------------------------------------------------------------------------- 1 | import { Api, nextJsApiHandler } from "../../../../lib/api"; 2 | import * as emailTemplates from "../../../../lib/server/templates"; 3 | import { assertDefined, assertTrue } from "juava"; 4 | import { db } from "../../../../lib/server/db"; 5 | 6 | export const api: Api = { 7 | GET: { 8 | auth: true, 9 | handle: async ({ user }) => { 10 | const userProfile = await db.prisma().userProfile.findFirst({ where: { id: user.internalId } }); 11 | assertDefined(userProfile, "User profile not found"); 12 | assertTrue(userProfile.admin, "Not enough permissions"); 13 | return { 14 | templates: Object.keys(emailTemplates), 15 | }; 16 | }, 17 | }, 18 | }; 19 | 20 | export default nextJsApiHandler(api); 21 | -------------------------------------------------------------------------------- /webapps/console/pages/api/admin/env.ts: -------------------------------------------------------------------------------- 1 | import { Api, nextJsApiHandler } from "../../../lib/api"; 2 | import { db } from "../../../lib/server/db"; 3 | import { assertDefined, assertTrue } from "juava"; 4 | 5 | function sortByKey(dict: Record): Record { 6 | return Object.fromEntries(Object.entries(dict).sort(([a], [b]) => a.localeCompare(b))); 7 | } 8 | export const api: Api = { 9 | GET: { 10 | auth: true, 11 | handle: async ({ user, req }) => { 12 | const userProfile = await db.prisma().userProfile.findFirst({ where: { id: user.internalId } }); 13 | assertDefined(userProfile, "User profile not found"); 14 | assertTrue(userProfile.admin, "Not enough permissions"); 15 | return { 16 | env: sortByKey(process.env), 17 | headers: sortByKey(req.headers), 18 | cookies: sortByKey(req.cookies), 19 | nodeVersion: process.versions.node, 20 | remoteAddress: req.socket.remoteAddress, 21 | }; 22 | }, 23 | }, 24 | }; 25 | 26 | export default nextJsApiHandler(api); 27 | -------------------------------------------------------------------------------- /webapps/console/pages/api/auth/[...nextauth].ts: -------------------------------------------------------------------------------- 1 | import { nextAuthConfig } from "../../../lib/nextauth.config"; 2 | import NextAuth from "next-auth"; 3 | 4 | export default NextAuth(nextAuthConfig); 5 | -------------------------------------------------------------------------------- /webapps/console/pages/api/ee/jwt.ts: -------------------------------------------------------------------------------- 1 | import { Api, inferUrl, nextJsApiHandler, verifyAccess } from "../../../lib/api"; 2 | import { z } from "zod"; 3 | import { createJwt } from "../../../lib/server/ee"; 4 | 5 | export function isEEAvailable(): boolean { 6 | return !!process.env.EE_CONNECTION; 7 | } 8 | 9 | export const api: Api = { 10 | url: inferUrl(__filename), 11 | GET: { 12 | auth: true, 13 | types: { 14 | query: z.object({ 15 | workspaceId: z.string(), 16 | }), 17 | result: z.object({ 18 | jwt: z.string(), 19 | expiresAt: z.string(), 20 | }), 21 | }, 22 | handle: async ({ user, query }) => { 23 | await verifyAccess(user, query.workspaceId); 24 | //issue short-lived (10m) token 25 | return createJwt(user.internalId, user.email, query.workspaceId, 60 * 10); 26 | }, 27 | }, 28 | }; 29 | 30 | export default nextJsApiHandler(api); 31 | -------------------------------------------------------------------------------- /webapps/console/pages/api/fb-auth/create-user.ts: -------------------------------------------------------------------------------- 1 | import { Api, inferUrl, nextJsApiHandler } from "../../../lib/api"; 2 | import { requireDefined } from "juava"; 3 | import { getFirebaseUser, linkFirebaseUser } from "../../../lib/server/firebase-server"; 4 | import { getOrCreateUser } from "../../../lib/nextauth.config"; 5 | 6 | export const api: Api = { 7 | url: inferUrl(__filename), 8 | POST: { 9 | auth: false, 10 | handle: async ({ req, res }) => { 11 | const user = requireDefined(await getFirebaseUser(req), `Not authorized`); 12 | if (!user.internalId) { 13 | const dbUser = await getOrCreateUser({ 14 | externalId: user.externalId, 15 | loginProvider: "firebase", 16 | email: user.email, 17 | name: user.name || user.email, 18 | req, 19 | }); 20 | await linkFirebaseUser(user.externalId, dbUser.id); 21 | } else { 22 | throw new Error( 23 | `Firebase user already has internalId (${user.internalId}), this endpoint should not be called` 24 | ); 25 | } 26 | }, 27 | }, 28 | }; 29 | 30 | export default nextJsApiHandler(api); 31 | -------------------------------------------------------------------------------- /webapps/console/pages/api/fb-auth/revoke-session.ts: -------------------------------------------------------------------------------- 1 | import { createRoute } from "../../../lib/api"; 2 | import { firebaseAuthCookieName, signOut } from "../../../lib/server/firebase-server"; 3 | import { serialize } from "cookie"; 4 | import { getAppEndpoint } from "../../../lib/domains"; 5 | import { getServerLog } from "../../../lib/server/log"; 6 | 7 | const log = getServerLog("firebase"); 8 | 9 | export default createRoute() 10 | .GET({ auth: true }) 11 | .handler(async ({ req, body, res, user }) => { 12 | await signOut(user.externalId); 13 | const secure = getAppEndpoint(req).protocol === "https"; 14 | res.setHeader( 15 | "Set-Cookie", 16 | serialize(firebaseAuthCookieName, "", { 17 | maxAge: 0, 18 | httpOnly: true, 19 | secure, 20 | }) 21 | ); 22 | }) 23 | .toNextApiHandler(); 24 | -------------------------------------------------------------------------------- /webapps/console/pages/api/healthcheck/db.ts: -------------------------------------------------------------------------------- 1 | import { createRoute } from "../../../lib/api"; 2 | import { db, isUsingPgBouncer } from "../../../lib/server/db"; 3 | import { assertTrue } from "juava"; 4 | 5 | export default createRoute() 6 | .GET({ auth: true }) 7 | .handler(async ({ user }) => { 8 | const userProfile = await db.prisma().userProfile.findFirst({ where: { id: user.internalId } }); 9 | assertTrue(userProfile?.admin, "Not enough permissions"); 10 | return { 11 | pgBouncer: isUsingPgBouncer(), 12 | isUsingSeparateAppDb: !!process.env.APP_DATABASE_URL, 13 | }; 14 | }) 15 | .toNextApiHandler(); 16 | -------------------------------------------------------------------------------- /webapps/console/pages/api/healthcheck/index.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | import { db } from "../../../lib/server/db"; 3 | import { getServerLog } from "../../../lib/server/log"; 4 | 5 | const healthChecks: Record Promise> = { 6 | prisma: async () => { 7 | await db.prisma.waitInit(); 8 | await db.prisma().configurationObject.count(); 9 | await db.prisma().configurationObjectLink.count(); 10 | await db.prisma().userProfile.count(); 11 | }, 12 | postgres: async () => { 13 | await db.pgPool.waitInit(); 14 | await db.pgPool().query(`SELECT 1 as pgpool_healthcheck`); 15 | }, 16 | }; 17 | 18 | const log = getServerLog("healthcheck"); 19 | 20 | export default async function handler(req: NextApiRequest, res: NextApiResponse) { 21 | const result: Record = {}; 22 | let hasErrors: boolean = false; 23 | for (const [service, check] of Object.entries(healthChecks)) { 24 | try { 25 | const start = Date.now(); 26 | await check(); 27 | const ms = Date.now() - start; 28 | result[service] = { status: "ok", ms }; 29 | } catch (e) { 30 | log.atError().withCause(e).log(`Service ${service} failed to initialize`, e); 31 | result[service] = { status: "error" }; 32 | hasErrors = true; 33 | } 34 | } 35 | res.status(hasErrors ? 503 : 200).send({ status: hasErrors ? "error" : "ok", ...result }); 36 | } 37 | -------------------------------------------------------------------------------- /webapps/console/pages/api/make-error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This api route is used to test error handling 3 | */ 4 | 5 | import { NextApiHandler } from "next/types"; 6 | 7 | const handler: NextApiHandler = async (req, res) => { 8 | throw new Error("This is an error"); 9 | }; 10 | 11 | export default handler; 12 | -------------------------------------------------------------------------------- /webapps/console/pages/api/oauth/catalog.ts: -------------------------------------------------------------------------------- 1 | import { createRoute } from "../../../lib/api"; 2 | import { oauthDecorators } from "../../../lib/server/oauth/services"; 3 | import omit from "lodash/omit"; 4 | 5 | export default createRoute() 6 | .GET({ auth: true }) 7 | .handler(async ({ res }) => { 8 | return { decorators: oauthDecorators.map(s => omit(s, "merge")) }; 9 | }) 10 | .toNextApiHandler(); 11 | -------------------------------------------------------------------------------- /webapps/console/pages/api/oauth/init.ts: -------------------------------------------------------------------------------- 1 | import { createRoute } from "../../../lib/api"; 2 | import { nangoConfig } from "../../../lib/server/oauth/nango-config"; 3 | import { assertTrue } from "juava"; 4 | 5 | export default createRoute() 6 | .GET({ auth: true }) 7 | .handler(async ({ res }) => { 8 | assertTrue(nangoConfig.enabled, "Oauth is disabled"); 9 | res.redirect(nangoConfig.callback); 10 | }) 11 | .toNextApiHandler(); 12 | -------------------------------------------------------------------------------- /webapps/console/pages/api/ping.ts: -------------------------------------------------------------------------------- 1 | import { createRoute } from "../../lib/api"; 2 | import { initTelemetry, trackTelemetryEvent } from "../../lib/server/telemetry"; 3 | 4 | export default createRoute() 5 | .GET({ auth: false }) 6 | .handler(async ({ user }) => { 7 | const telemetry = await initTelemetry(); 8 | await trackTelemetryEvent("ping"); 9 | return { 10 | health: "ok", 11 | telemetryEnabled: !!telemetry, 12 | deploymentId: telemetry?.deploymentId, 13 | }; 14 | }) 15 | .toNextApiHandler(); 16 | -------------------------------------------------------------------------------- /webapps/console/pages/api/s/javascript-library.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | const scriptSrc = require("@jitsu/js/dist/web/p.js.txt").default; 3 | 4 | export default function handler(req: NextApiRequest, res: NextApiResponse) { 5 | res.status(200).setHeader("Content-type", "application/javascript").send(scriptSrc); 6 | } 7 | -------------------------------------------------------------------------------- /webapps/console/pages/api/user/change-password.ts: -------------------------------------------------------------------------------- 1 | import { createRoute } from "../../../lib/api"; 2 | import { z } from "zod"; 3 | import { db } from "../../../lib/server/db"; 4 | import { assertTrue, checkHash, createHash } from "juava"; 5 | 6 | export default createRoute() 7 | .POST({ 8 | auth: true, 9 | body: z.object({ 10 | currentPassword: z.string(), 11 | newPassword: z.string(), 12 | }), 13 | }) 14 | .handler(async ({ user, body }) => { 15 | assertTrue(user.loginProvider === "credentials", "Only credentials login is supported"); 16 | const password = await db.prisma().userPassword.findFirst({ where: { userId: user.internalId } }); 17 | if (!password) { 18 | throw new Error("Password is not set"); 19 | } 20 | if (!checkHash(password.hash, body.currentPassword)) { 21 | throw new Error("Current password is invalid"); 22 | } 23 | await db 24 | .prisma() 25 | .userPassword.update({ where: { userId: user.internalId }, data: { hash: createHash(body.newPassword) } }); 26 | return { 27 | status: "ok", 28 | }; 29 | }) 30 | .toNextApiHandler(); 31 | -------------------------------------------------------------------------------- /webapps/console/pages/api/user/cli-key.ts: -------------------------------------------------------------------------------- 1 | import { Api, inferUrl, nextJsApiHandler } from "../../../lib/api"; 2 | import { ApiKey } from "../../../lib/schema"; 3 | import { db } from "../../../lib/server/db"; 4 | import { hint, randomId } from "juava"; 5 | import { createHash } from "juava"; 6 | 7 | const api: Api = { 8 | url: inferUrl(__filename), 9 | GET: { 10 | auth: true, 11 | types: { 12 | result: ApiKey, 13 | }, 14 | handle: async ({ user, req, res }) => { 15 | const newKey = randomId(32); 16 | const key = { id: `jitsu-cli-${randomId(22)}`, plaintext: newKey }; 17 | const toCreate = { 18 | id: key.id, 19 | userId: user.internalId, 20 | hint: hint(newKey), 21 | hash: createHash(newKey), 22 | }; 23 | await db.prisma().userApiToken.create({ data: toCreate }); 24 | 25 | return key; 26 | }, 27 | }, 28 | }; 29 | 30 | export default nextJsApiHandler(api); 31 | -------------------------------------------------------------------------------- /webapps/console/pages/api/user/properties.ts: -------------------------------------------------------------------------------- 1 | import { createRoute } from "../../../lib/api"; 2 | import { z } from "zod"; 3 | import { requireDefined } from "juava"; 4 | import { db } from "../../../lib/server/db"; 5 | 6 | export default createRoute() 7 | .GET({ auth: true, result: z.object({ admin: z.boolean() }) }) 8 | .handler(async ({ user }) => { 9 | const userModel = requireDefined( 10 | await db.prisma().userProfile.findUnique({ where: { id: user.internalId } }), 11 | `User ${user.internalId} does not exist` 12 | ); 13 | return { 14 | admin: !!userModel.admin, 15 | }; 16 | }) 17 | .toNextApiHandler(); 18 | -------------------------------------------------------------------------------- /webapps/console/pages/api/version.ts: -------------------------------------------------------------------------------- 1 | import { createRoute } from "../../lib/api"; 2 | import { getApplicationVersion } from "../../lib/version"; 3 | import { isTruish } from "juava"; 4 | 5 | function sortByKey(dict: Record): Record { 6 | return Object.fromEntries(Object.entries(dict).sort(([a], [b]) => a.localeCompare(b))); 7 | } 8 | 9 | function getDiagnostics() { 10 | if (isTruish(process.env.__DANGEROUS_ENABLE_FULL_DIAGNOSTICS)) { 11 | return { 12 | env: sortByKey(process.env), 13 | proc: { 14 | config: sortByKey(process.config), 15 | versions: sortByKey(process.versions), 16 | execPath: process.execPath, 17 | argv: process.argv, 18 | }, 19 | }; 20 | } 21 | } 22 | 23 | export default createRoute() 24 | .GET({ 25 | auth: false, 26 | }) 27 | .handler(async () => { 28 | return { 29 | ...getApplicationVersion(), 30 | node: { 31 | version: process.version, 32 | platform: process.platform, 33 | arch: process.arch, 34 | env: process.env.NODE_ENV, 35 | }, 36 | diagnostics: getDiagnostics(), 37 | }; 38 | }) 39 | .toNextApiHandler(); 40 | -------------------------------------------------------------------------------- /webapps/console/pages/signup.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useRouter } from "next/router"; 3 | import { useAppConfig } from "../lib/context"; 4 | import { ErrorCard } from "../components/GlobalError/GlobalError"; 5 | import { Button } from "antd"; 6 | import { SignUp } from "../components/SignInOrUp/SignUp"; 7 | 8 | const Signup = () => { 9 | const appConfig = useAppConfig(); 10 | const router = useRouter(); 11 | const [email, setEmail] = useState(router?.query.email as any); 12 | const [loading, setLoading] = useState(false); 13 | const [error, setError] = useState(); 14 | const [emailSent, setEmailSent] = useState(false); 15 | 16 | if (!appConfig?.auth?.firebasePublic) { 17 | return ( 18 |
19 | 20 |
21 | 24 |
25 |
26 | ); 27 | } 28 | 29 | return ; 30 | }; 31 | 32 | Signup.getInitialProps = () => { 33 | return { publicPage: true }; 34 | }; 35 | 36 | export default Signup; 37 | -------------------------------------------------------------------------------- /webapps/console/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /webapps/console/prisma/events_log.sql: -------------------------------------------------------------------------------- 1 | -- not managed by prisma 2 | create table IF NOT EXISTS newjitsu_metrics.events_log 3 | --ON CLUSTER jitsu_cluster 4 | ( 5 | timestamp DateTime64(3), 6 | actorId LowCardinality(String), 7 | type LowCardinality(String), 8 | level LowCardinality(String), 9 | message String 10 | ) 11 | engine = MergeTree() 12 | --engine = ReplicatedMergeTree('/clickhouse/tables/{shard}/newjitsu_metrics/events_log3', '{replica}') 13 | PARTITION BY toYYYYMM(timestamp) 14 | ORDER BY (actorId, type, timestamp) 15 | SETTINGS index_granularity = 8192; 16 | 17 | -------------------------------------------------------------------------------- /webapps/console/prisma/workspace-sync-runs.sql: -------------------------------------------------------------------------------- 1 | WITH ranked_tasks AS ( 2 | SELECT 3 | link."workspaceId", 4 | workspace.slug AS "workspaceSlug", 5 | task.sync_id, 6 | task.started_at, 7 | ROW_NUMBER() OVER (PARTITION BY link."workspaceId" ORDER BY task.started_at DESC) AS row_number 8 | FROM newjitsu.source_task task 9 | LEFT JOIN newjitsu."ConfigurationObjectLink" link ON task.sync_id = link.id 10 | LEFT JOIN newjitsu."Workspace" workspace ON link."workspaceId" = workspace.id 11 | WHERE link."workspaceId" IS NOT NULL 12 | ) 13 | SELECT 14 | 'all' AS period, 15 | ranked_tasks."workspaceId", 16 | ranked_tasks."workspaceSlug", 17 | COUNT(*) AS runs, 18 | MAX(ranked_tasks.started_at) AS last_sync, 19 | COUNT(DISTINCT ranked_tasks.sync_id) AS unique_syncs, 20 | MAX(CASE WHEN ranked_tasks.row_number = 1 THEN ranked_tasks.sync_id END) AS latest_sync_id 21 | FROM ranked_tasks 22 | GROUP BY period, ranked_tasks."workspaceId", ranked_tasks."workspaceSlug" 23 | ORDER BY period DESC, runs DESC, ranked_tasks."workspaceSlug" -------------------------------------------------------------------------------- /webapps/console/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/webapps/console/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /webapps/console/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/webapps/console/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /webapps/console/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/webapps/console/public/apple-touch-icon.png -------------------------------------------------------------------------------- /webapps/console/public/external-link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webapps/console/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/webapps/console/public/favicon-16x16.png -------------------------------------------------------------------------------- /webapps/console/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/webapps/console/public/favicon-32x32.png -------------------------------------------------------------------------------- /webapps/console/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/webapps/console/public/favicon.ico -------------------------------------------------------------------------------- /webapps/console/public/logo-classic-gray.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /webapps/console/public/logo-classic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /webapps/console/public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webapps/console/scripts/password-hash.ts: -------------------------------------------------------------------------------- 1 | import minimist from "minimist"; 2 | import { createHash, randomId } from "juava"; 3 | import { getServerLog } from "../lib/server/log"; 4 | 5 | const log = getServerLog("password-hash"); 6 | 7 | async function main(): Promise { 8 | const args = minimist(process.argv.slice(2)); 9 | if (!args._ || args._.length === 0) { 10 | log.atInfo().log("No secret provided as a first arg, generating a random one"); 11 | } 12 | const secret = !args._ || args._.length === 0 ? randomId(32) : args._[0]; 13 | log 14 | .atInfo() 15 | .log( 16 | `Calculating password hash. Using ${ 17 | process.env.GLOBAL_HASH_SECRET || process.env.CONSOLE_TOKEN_SECRET 18 | ? "custom token secret" 19 | : "default hash secret" 20 | }` 21 | ); 22 | log.atInfo().log(`Hashing ${secret} → ${createHash(secret)}`); 23 | } 24 | 25 | main(); 26 | -------------------------------------------------------------------------------- /webapps/console/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const theme = require("./theme.config.js"); 2 | 3 | const disabledCss = { 4 | "code::before": false, 5 | "code::after": false, 6 | "blockquote p:first-of-type::before": false, 7 | "blockquote p:last-of-type::after": false, 8 | pre: false, 9 | code: false, 10 | "h2 code": false, 11 | "pre code": false, 12 | }; 13 | 14 | module.exports = { 15 | content: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"], 16 | theme: { 17 | extend: { 18 | transitionProperty: { 19 | 'width': 'width' 20 | }, 21 | typography: { 22 | DEFAULT: { css: disabledCss }, 23 | sm: { css: disabledCss }, 24 | lg: { css: disabledCss }, 25 | xl: { css: disabledCss }, 26 | "2xl": { css: disabledCss }, 27 | }, 28 | colors: { 29 | transparent: "ttailansparent", 30 | current: "currentColor", 31 | ...Object.entries(theme) 32 | .map(([name, color]) => ({ [name]: { DEFAULT: color } })) 33 | .reduce((acc, cur) => ({ ...acc, ...cur }), {}), 34 | }, 35 | fontSize: { 36 | "3xs": "0.5rem", 37 | xxs: "0.7rem", 38 | }, 39 | }, 40 | }, 41 | plugins: [require("@tailwindcss/typography")], 42 | }; 43 | -------------------------------------------------------------------------------- /webapps/console/theme.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | backgroundLight: "#ffffff", 4 | background: "#fafafa", 5 | backgroundDark: "rgb(234,234,234)", 6 | 7 | textDark: "#171717", //neutral-900 8 | text: "#525252", //neutral-600 9 | textLight: "#737373", //neutral-500 10 | textDisabled: "#d4d4d4", //neutral-300 11 | 12 | textInverted: "#F9FAFA", 13 | 14 | primaryDark: "#4f46e5", //indigo-800 15 | primary: "#4f46e5", //indigo-600 16 | primaryLight: "#6366f1", //indigo-500 17 | primaryLighter: "#a5b4fc", //indigo-300 18 | 19 | success: "#009140", 20 | successBorder: "#75b691", 21 | successBackground: "#e2ffe3", 22 | 23 | warning: "#ffc021", 24 | 25 | error: "rgb(224, 49, 48)", 26 | errorBorder: "rgb(213,149,140)", 27 | errorBackground: "#ffe3e2", 28 | }; 29 | -------------------------------------------------------------------------------- /webapps/console/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "module": "commonjs", 6 | "importHelpers": false, 7 | "lib": ["dom", "dom.iterable", "esnext", "ES6", "ES2015"], 8 | "rootDir": ".", 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "noEmit": true, 15 | "esModuleInterop": true, 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true, 21 | "sourceMap": true 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /webapps/console/vercel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script defines if vercel should deploy the commit, 4 | # see https://vercel.com/support/articles/how-do-i-use-the-ignored-build-step-field-on-vercel 5 | 6 | if [[ "$VERCEL_GIT_COMMIT_REF" == "functions-cli" ]]; then 7 | npx turbo-ignore 8 | else 9 | if [[ "$VERCEL_GIT_COMMIT_REF" != *"newjitsu"* ]]; then 10 | echo "❌ Not a newjitsu branch, skipping deploy" 11 | exit 0 12 | fi 13 | 14 | npx turbo-ignore 15 | fi 16 | 17 | 18 | -------------------------------------------------------------------------------- /webapps/ee-api/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /webapps/ee-api/.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 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /webapps/ee-api/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsucom/jitsu/1bf069be9458b9e20eb56d1fb2205646aa6c2903/webapps/ee-api/README.md -------------------------------------------------------------------------------- /webapps/ee-api/emails/styles.ts: -------------------------------------------------------------------------------- 1 | export const main = { 2 | fontFamily: 3 | '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif', 4 | backgroundColor: "transparent", 5 | }; 6 | -------------------------------------------------------------------------------- /webapps/ee-api/lib/log.ts: -------------------------------------------------------------------------------- 1 | import { getLog, LoggerOpts, LogLevel, setGlobalLogLevel, setServerJsonFormat, setServerLogColoring } from "juava"; 2 | 3 | setGlobalLogLevel((process.env.LOG_LEVEL || "info") as LogLevel); 4 | setServerLogColoring( 5 | process.env.DISABLE_SERVER_LOGS_ANSI_COLORING !== "true" && process.env.DISABLE_SERVER_LOGS_ANSI_COLORING !== "1" 6 | ); 7 | setServerJsonFormat(process.env.LOG_FORMAT === "json"); 8 | 9 | export function getServerLog(_opts?: LoggerOpts | string) { 10 | return getLog(_opts); 11 | } 12 | -------------------------------------------------------------------------------- /webapps/ee-api/lib/services.ts: -------------------------------------------------------------------------------- 1 | import { requireDefined } from "juava"; 2 | import { createPg, getPostgresStore } from "./store"; 3 | import { createClient } from "@clickhouse/client"; 4 | import { S3Client } from "@aws-sdk/client-s3"; 5 | 6 | const dbUrl = requireDefined(process.env.DATABASE_URL, "DATABASE_URL"); 7 | 8 | export const pg = createPg(dbUrl, { connectionName: "kvstore" }); 9 | 10 | export const store = getPostgresStore(pg, { tableName: "newjitsuee.kvstore" }); 11 | 12 | export const telemetryDb = createPg(process.env.TELEMETRY_DATABASE_URL || dbUrl, { connectionName: "telemetry" }); 13 | 14 | export const clickhouse = createClient({ 15 | url: requireDefined(process.env.CLICKHOUSE_URL, `env CLICKHOUSE_URL is not defined`), 16 | username: process.env.CLICKHOUSE_USERNAME || "default", 17 | password: requireDefined(process.env.CLICKHOUSE_PASSWORD, `env CLICKHOUSE_PASSWORD is not defined`), 18 | request_timeout: 600_000, 19 | }); 20 | 21 | export const s3client = new S3Client({ 22 | region: requireDefined(process.env.S3_REGION, `env S3_REGION is not defined`), 23 | credentials: { 24 | accessKeyId: requireDefined(process.env.S3_ACCESS_KEY_ID, `env S3_ACCESS_KEY_ID is not defined`), 25 | secretAccessKey: requireDefined(process.env.S3_SECRET_ACCESS_KEY, `env S3_SECRET_ACCESS_KEY is not defined`), 26 | }, 27 | endpoint: process.env.S3_ENDPOINT || undefined, 28 | }); 29 | -------------------------------------------------------------------------------- /webapps/ee-api/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | transpilePackages: ["juava"], 4 | experimental: { 5 | turbo: { 6 | rules: { 7 | '*.sql$': { 8 | loaders: ['raw-loader'], 9 | as: '*.js', 10 | }, 11 | }, 12 | }, 13 | }, 14 | reactStrictMode: true, 15 | swcMinify: true, 16 | webpack: (config, opts) => { 17 | // if (prevWebpack) { 18 | // prevWebpack(config, opts); 19 | // } 20 | config.module.rules.push({ 21 | test: /\.sql$/, 22 | use: "raw-loader", 23 | }); 24 | return config; 25 | }, 26 | async headers() { 27 | return [ 28 | { 29 | source: "/:path*{/}?", 30 | headers: [ 31 | { 32 | key: "X-Frame-Options", 33 | value: "DENY", 34 | }, 35 | { 36 | key: "X-Content-Type-Options", 37 | value: "nosniff", 38 | }, 39 | ], 40 | }, 41 | ]; 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /webapps/ee-api/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from "next/app"; 2 | 3 | export default function App({ Component, pageProps }: AppProps) { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /webapps/ee-api/pages/api/billing/export-subscriptions.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | import { exportSubscriptions, getAvailableProducts, stripe } from "../../../lib/stripe"; 3 | import { withErrorHandler } from "../../../lib/route-helpers"; 4 | import { assertTrue, requireDefined } from "juava"; 5 | import { auth } from "../../../lib/auth"; 6 | import { store } from "../../../lib/services"; 7 | 8 | const extendedStripeData = 9 | (process.env.STRIPE_SECRET_KEY as string)?.indexOf("_live_") >= 0 10 | ? "stripe-customer-info" 11 | : "stripe-customer-info-test-mode"; 12 | 13 | const handler = async function handler(req: NextApiRequest, res: NextApiResponse) { 14 | const claims = await auth(req, res); 15 | if (!claims) { 16 | throw new Error("Unauthorized"); 17 | } 18 | assertTrue(claims.type === "admin", "Only admins can export subscriptions"); 19 | const subscriptions = await exportSubscriptions(); 20 | await store.getTable(extendedStripeData).clear(); 21 | for (const [sub, data] of Object.entries(subscriptions)) { 22 | await store.getTable(extendedStripeData).put(data.customer.id, data); 23 | } 24 | return res.json(subscriptions); 25 | }; 26 | 27 | export default withErrorHandler(handler); 28 | -------------------------------------------------------------------------------- /webapps/ee-api/pages/api/billing/rotate-stripe-customer.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | import { ErrorResponse } from "./plans"; 3 | import assert from "assert"; 4 | import { auth } from "../../../lib/auth"; 5 | import { rotateStripeCustomer } from "../../../lib/stripe"; 6 | import { withErrorHandler } from "../../../lib/route-helpers"; 7 | 8 | const handler = async function handler(req: NextApiRequest, res: NextApiResponse) { 9 | const workspaceId = req.query.workspaceId as string | undefined; 10 | const dryRun = req.query.dryRun === "true" || req.query.dryRun === "1"; 11 | assert(workspaceId, "workspaceId is required"); 12 | const claims = await auth(req, res); 13 | if (!claims || claims.type !== "admin") { 14 | return; 15 | } 16 | await rotateStripeCustomer(workspaceId, dryRun); 17 | res.status(200).json({ ok: true }); 18 | }; 19 | 20 | export default withErrorHandler(handler); 21 | -------------------------------------------------------------------------------- /webapps/ee-api/pages/api/custom-token.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | import { withErrorHandler } from "../../lib/route-helpers"; 3 | import { createCustomToken } from "../../lib/firebase-auth"; 4 | 5 | const handler = async function handler(req: NextApiRequest, res: NextApiResponse) { 6 | res.setHeader("Access-Control-Allow-Origin", req.headers.origin || "*"); 7 | res.setHeader("Access-Control-Allow-Methods", "*"); 8 | res.setHeader("Access-Control-Allow-Headers", "authorization, content-type, baggage, sentry-trace"); 9 | res.setHeader("Access-Control-Allow-Credentials", "true"); 10 | if (req.method === "OPTIONS") { 11 | res.status(200).end(); 12 | return; 13 | } 14 | const customToken = await createCustomToken(req); 15 | if (customToken) { 16 | return { customToken: customToken }; 17 | } else { 18 | return {}; 19 | } 20 | }; 21 | 22 | export default withErrorHandler(handler); 23 | -------------------------------------------------------------------------------- /webapps/ee-api/pages/api/email-history.tsx: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | import { withErrorHandler } from "../../lib/route-helpers"; 3 | import { store } from "../../lib/services"; 4 | import { requireDefined } from "juava"; 5 | import { sortBy } from "lodash"; 6 | import { auth } from "../../lib/auth"; 7 | 8 | const handler = async function handler(req: NextApiRequest, res: NextApiResponse) { 9 | const workspaceId = requireDefined(req.query.workspaceId, "workspaceId is required") as string; 10 | const claims = await auth(req, res); 11 | if (claims?.type !== "admin") { 12 | throw new Error("Unauthorized"); 13 | } 14 | const logsEntry = await store.getTable("email-logs").get(workspaceId); 15 | if (!logsEntry) { 16 | res.json([]); 17 | return; 18 | } else { 19 | const appUrl = process.env.JITSU_APPLICATION_URL || "https://use.jitsu.com"; 20 | res.json( 21 | sortBy(logsEntry.logs, "timestamp") 22 | .reverse() 23 | .map(({ subject, ...rest }) => { 24 | const subjectDeduped = [...new Set(subject)]; 25 | return { 26 | ...rest, 27 | subject: subjectDeduped.length === 1 ? subjectDeduped[0] : subjectDeduped, 28 | workspaceId, 29 | url: `${appUrl}/${workspaceId}`, 30 | }; 31 | }) 32 | ); 33 | } 34 | }; 35 | 36 | export default withErrorHandler(handler); 37 | -------------------------------------------------------------------------------- /webapps/ee-api/pages/api/unsubscribe.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | import { withErrorHandler } from "../../lib/route-helpers"; 3 | import { getUnsubscribeCode, isUnsubscribed, unsubscribe } from "../../lib/email"; 4 | 5 | const handler = async function handler(req: NextApiRequest, res: NextApiResponse) { 6 | const email = req.query.email as string; 7 | const code = req.query.code as string; 8 | const codeValidation = await getUnsubscribeCode(email.toLowerCase().trim(), { rotateExpired: false }); 9 | if (codeValidation !== code) { 10 | res.status(400); 11 | res.write("ERROR: validation code is invalid or expired. Please try again."); 12 | res.end(); 13 | return; 14 | } else if (await isUnsubscribed(email.toLowerCase().trim())) { 15 | res.status(200); 16 | res.write(`${email} is already unsubscribed. You can close this page.`); 17 | res.end(); 18 | } else { 19 | await unsubscribe(email.toLowerCase().trim()); 20 | res.status(200); 21 | res.write(`${email} has been unsubscribed. You can close this page.`); 22 | res.end(); 23 | } 24 | //res.setHeader("Content-Type", "html/text"); 25 | res.status(200); 26 | res.write("Hey!"); 27 | res.end(); 28 | }; 29 | 30 | export default withErrorHandler(handler); 31 | -------------------------------------------------------------------------------- /webapps/ee-api/pages/api/user-created.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | import { withErrorHandler } from "../../lib/route-helpers"; 3 | import { assertDefined, assertTrue, requireDefined } from "juava"; 4 | import { auth } from "../../lib/auth"; 5 | import { sendEmail } from "../../lib/email"; 6 | import { makeAddress } from "./email"; 7 | 8 | const handler = async function handler(req: NextApiRequest, res: NextApiResponse) { 9 | if (req.method === "POST") { 10 | const claims = requireDefined(await auth(req, res), `Auth is required`); 11 | assertTrue(claims.type === "admin", "Should be admin"); 12 | const { email, name } = requireDefined(req.body, "Body is required"); 13 | assertDefined(email, "email is required"); 14 | await sendEmail({ 15 | to: makeAddress({ name, email }), 16 | template: "welcome", 17 | //new user means new email, we don't allow to create multiple accounts with the same email 18 | //no need to respect unsubscribe for welcome email 19 | respectUnsubscribe: false, 20 | allowUnsubscribe: true, 21 | }); 22 | } else { 23 | res.status(405).json({ error: "use POST" }); 24 | res.end(); 25 | } 26 | }; 27 | 28 | export default withErrorHandler(handler); 29 | -------------------------------------------------------------------------------- /webapps/ee-api/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import Image from "next/image"; 3 | import styles from "../styles/Home.module.css"; 4 | 5 | export default function Home() { 6 | return
Nothing here.
; 7 | } 8 | -------------------------------------------------------------------------------- /webapps/ee-api/scripts/sql/01-kv-schema.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA IF NOT EXISTS newjitsuee; 2 | 3 | CREATE TABLE IF NOT EXISTS newjitsuee.kvstore 4 | ( 5 | id TEXT NOT NULL, 6 | namespace TEXT NOT NULL, 7 | obj JSONB NOT NULL default '{}'::jsonb, 8 | expire TIMESTAMP WITH TIME ZONE, 9 | primary key (id, namespace) 10 | ); 11 | ALTER TABLE newjitsuee.kvstore 12 | ADD COLUMN IF NOT EXISTS id TEXT NOT NULL; 13 | ALTER TABLE newjitsuee.kvstore 14 | ADD COLUMN IF NOT EXISTS namespace TEXT NOT NULL; 15 | ALTER TABLE newjitsuee.kvstore 16 | ADD COLUMN IF NOT EXISTS obj JSONB NOT NULL default '{}'::jsonb; 17 | ALTER TABLE newjitsuee.kvstore 18 | ADD COLUMN IF NOT EXISTS expire TIMESTAMP WITH TIME ZONE; 19 | -------------------------------------------------------------------------------- /webapps/ee-api/scripts/sql/03-debug-views.sql: -------------------------------------------------------------------------------- 1 | -- Those views isn't used anywhere in code. It's just for convenience 2 | 3 | create or replace view newjitsu."ConnectorsUsage"(slug, package, started_at, status, started_by) as 4 | SELECT workspace.slug, 5 | task.package, 6 | task.started_at, 7 | task.status, 8 | task.started_by 9 | FROM newjitsu.source_task task 10 | JOIN newjitsu."ConfigurationObjectLink" sync ON sync.id = task.sync_id 11 | JOIN newjitsu."Workspace" workspace ON sync."workspaceId" = workspace.id 12 | WHERE workspace.slug <> 'jitsu'::text 13 | ORDER BY task.started_at DESC; 14 | 15 | 16 | create or replace view newjitsu."AuditLogView"(timestamp, type, email, slug, name, "objectType") as 17 | SELECT l."timestamp", 18 | l.type, 19 | u.email, 20 | ws.slug, 21 | ws.name, 22 | COALESCE(l.changes ->> 'objectType'::text, l.changes ->> 'type'::text) AS "objectType" 23 | FROM newjitsu."AuditLog" l 24 | JOIN newjitsu."Workspace" ws ON l."workspaceId" = ws.id 25 | JOIN newjitsu."UserProfile" u ON l."userId" = u.id 26 | ORDER BY l."timestamp" DESC; 27 | 28 | 29 | -------------------------------------------------------------------------------- /webapps/ee-api/scripts/sql/05-stat-cache.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA IF NOT EXISTS newjitsuee; 2 | 3 | CREATE TABLE IF NOT EXISTS newjitsuee.stat_cache 4 | ( 5 | "workspaceId" TEXT NOT NULL, 6 | "period" TIMESTAMP WITHOUT TIME ZONE, 7 | "events" NUMERIC NOT NULL 8 | ); 9 | 10 | --ALTER TABLE newjitsuee.stat_cache add CONSTRAINT stat_cache_pk PRIMARY KEY ("workspaceId", "period"); 11 | ALTER TABLE newjitsuee.stat_cache ADD COlUMN IF NOT EXISTS "syncs" NUMERIC NOT NULL DEFAULT 0; 12 | 13 | -------------------------------------------------------------------------------- /webapps/ee-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "module": "commonjs", 6 | "lib": ["dom", "dom.iterable", "esnext", "ES6", "ES2015"], 7 | "rootDir": ".", 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "allowSyntheticDefaultImports": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "preserve", 19 | "incremental": true 20 | }, 21 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /webapps/ee-api/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "crons": [ 3 | { 4 | "path": "/api/billing/export-subscriptions", 5 | "schedule": "*/5 * * * *" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /webapps/ee-api/vercel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script defines if vercel should deploy the commit, 4 | # see https://vercel.com/support/articles/how-do-i-use-the-ignored-build-step-field-on-vercel 5 | 6 | if [[ "$VERCEL_GIT_COMMIT_REF" != *"newjitsu"* ]]; then 7 | echo "❌ Not a newjitsu branch, skipping deploy" 8 | exit 0 9 | fi 10 | 11 | npx turbo-ignore 12 | -------------------------------------------------------------------------------- /webapps/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jitsu-internal/webapps-shared", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "src/index.ts", 6 | "types": "src/index.ts", 7 | "scripts": { 8 | }, 9 | "dependencies": { 10 | "dayjs": "^1.11.10", 11 | "nodemailer": "^6.10.1", 12 | "@react-email/components": "^0.0.34", 13 | "@react-email/render": "^1.0.5", 14 | "@types/node": "^18.15.3", 15 | "@types/react": "18.3.3", 16 | "@types/react-dom": "18.3.0", 17 | "juava": "workspace:*", 18 | "lodash": "^4.17.21", 19 | "react": "^18.3.1", 20 | "react-dom": "18.3.1", 21 | "tslib": "^2.6.3", 22 | "zod": "^3.23.8" 23 | }, 24 | "devDependencies": { 25 | "eslint": "^8.57.0", 26 | "@types/lodash": "^4.14.185", 27 | "ts-node": "^10.9.2", 28 | "type-fest": "^3.5.7", 29 | "typescript": "^5.6.3", 30 | "@types/nodemailer": "^6.4.17" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /webapps/shared/src/email-template.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Hr, Text } from "@react-email/components"; 3 | 4 | export const UnsubscribeLink: React.FC<{ unsubscribeLink?: string }> = ({ unsubscribeLink }) => { 5 | if (!unsubscribeLink) { 6 | return <>; 7 | } 8 | return ( 9 | <> 10 |
11 | 12 | Jitsu Labs Inc. 2261 Market Street #4109, San Francisco, CA 94114 13 |
14 | 15 | Unsubscribe 16 | 17 |
18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /webapps/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | export * from "./email-template"; 3 | export * from "./email"; 4 | -------------------------------------------------------------------------------- /webapps/shared/src/styles.ts: -------------------------------------------------------------------------------- 1 | export const main = { 2 | fontFamily: 3 | '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif', 4 | backgroundColor: "transparent", 5 | }; 6 | -------------------------------------------------------------------------------- /webapps/shared/src/types.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export type UnsubscribeLinkProps = { unsubscribeLink?: string }; 4 | 5 | export type WorkspaceEmailProps = { 6 | // User's name 7 | name?: string; 8 | workspaceName: string; 9 | workspaceSlug: string; 10 | }; 11 | 12 | export type EmailTemplateConfig

= { 13 | from?: string; 14 | bcc?: string; 15 | replyTo?: string; 16 | isMarketingEmail?: boolean; 17 | scheduleAt?: (now: Date) => Date | undefined; 18 | }; 19 | 20 | export interface EmailTemplate

extends EmailTemplateConfig

{ 21 | (props: P): React.ReactNode; 22 | // previewValues is used just to fill template with something to properly preview it in UI 23 | PreviewProps?: Required

; 24 | subject(props: P): string; 25 | plaintext?(props: P): string; 26 | } 27 | -------------------------------------------------------------------------------- /webapps/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2015", "dom", "es2017"], 4 | "rootDir": "./src", 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "noEmit": true, 8 | "jsx": "preserve" 9 | }, 10 | "exclude": [ 11 | "__tests__", 12 | "node_modules", 13 | "dist", 14 | "test_projects", 15 | "test", 16 | "templates" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------