diff --git a/.changeset/olive-bottles-tap.md b/.changeset/olive-bottles-tap.md new file mode 100644 index 0000000000..2bce96c46d --- /dev/null +++ b/.changeset/olive-bottles-tap.md @@ -0,0 +1,5 @@ +--- +"@inlang/sdk": minor +--- + +Remove the remaining telemetry code from the SDK. Project loading and project creation no longer ship or persist telemetry-related logic, and the `telemetry` project setting has been removed from the SDK schema and docs. diff --git a/docs/settings.md b/docs/settings.md index 35b3b0c6c9..3a72ff38a8 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -15,7 +15,6 @@ type ProjectSettings = { baseLocale: string; locales: string[]; modules?: string[]; - telemetry?: "off"; experimental?: Record; } ``` @@ -64,16 +63,6 @@ URIs to plugin modules. Plugins extend inlang with import/export capabilities fo - Must end with `.js` - Can be absolute (CDN) or relative paths -### telemetry - -Controls anonymous usage telemetry. Omit for default behavior, or set to `"off"` to disable. - -```json -{ - "telemetry": "off" -} -``` - ### experimental Enable experimental features. Keys are feature names, values must be `true`. diff --git a/packages/cli/src/utilities/getInlangProject.ts b/packages/cli/src/utilities/getInlangProject.ts index 8086397257..c6dd6c54af 100644 --- a/packages/cli/src/utilities/getInlangProject.ts +++ b/packages/cli/src/utilities/getInlangProject.ts @@ -2,9 +2,6 @@ import fs from "node:fs"; import { loadProjectFromDirectory, type InlangProject } from "@inlang/sdk"; import { resolve } from "node:path"; -/** - * Used for telemetry. - */ export let lastUsedProject: InlangProject | undefined; /** @@ -20,7 +17,6 @@ export async function getInlangProject(args: { const project = await loadProjectFromDirectory({ path: projectPath, fs: fs, - appId: "app", }); lastUsedProject = project; diff --git a/packages/sdk/src/json-schema/settings.ts b/packages/sdk/src/json-schema/settings.ts index 6bfed8ad50..d8e159c88c 100644 --- a/packages/sdk/src/json-schema/settings.ts +++ b/packages/sdk/src/json-schema/settings.ts @@ -69,16 +69,6 @@ const SDKSettings = Type.Object({ } ) ), - telemetry: Type.Optional( - Type.Union( - [ - Type.Literal("off", { - description: "No telemetry events ", - }), - ], - { description: "If not set, defaults to all" } - ) - ), experimental: Type.Optional( Type.Record(Type.String(), Type.Literal(true), { title: "Experimental settings", diff --git a/packages/sdk/src/project/loadProject.ts b/packages/sdk/src/project/loadProject.ts index 5c4d0be1fd..26ed9fe614 100644 --- a/packages/sdk/src/project/loadProject.ts +++ b/packages/sdk/src/project/loadProject.ts @@ -13,7 +13,6 @@ import { import type { InlangProject } from "./api.js"; import { withLanguageTagToLocaleMigration } from "../migrations/v2/withLanguageTagToLocaleMigration.js"; import { v4 } from "uuid"; -import { maybeCaptureLoadedProject } from "./maybeCaptureTelemetry.js"; import { importFiles } from "../import-export/importFiles.js"; import { exportFiles } from "../import-export/exportFiles.js"; @@ -54,15 +53,6 @@ export async function loadProject(args: { * */ preprocessPluginBeforeImport?: PreprocessPluginBeforeImportFunction; - /** - * The id of the app that is using the SDK. - * - * The is used for telemetry purposes. To derive insights like - * which app is using the SDK, how many projects are loaded, etc. - * - * The app id can be removed at any time in the future - */ - appId?: string; }): Promise { const db = initDb({ sqlite: args.sqlite }); @@ -86,29 +76,11 @@ export async function loadProject(args: { const plugins = [...(args.providePlugins ?? []), ...importedPlugins.plugins]; - const idFile = await args.lix.db - .selectFrom("file") - .where("path", "=", "/project_id") - .select("data") - .executeTakeFirstOrThrow(); - - const id = new TextDecoder().decode(idFile.data); - // const state = createProjectState({ // ...args, // settings, // }); - // not awaiting to not block the load time of a project - maybeCaptureLoadedProject({ - db, - id, - settings, - plugins, - lix: args.lix, - appId: args.appId, - }); - return { db, id: { diff --git a/packages/sdk/src/project/maybeCaptureTelemetry.test.ts b/packages/sdk/src/project/maybeCaptureTelemetry.test.ts deleted file mode 100644 index b2d83f25bc..0000000000 --- a/packages/sdk/src/project/maybeCaptureTelemetry.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { expect, test, vi } from "vitest"; -import { loadProjectInMemory } from "./loadProjectInMemory.js"; -import { newProject } from "./newProject.js"; -import { maybeCaptureLoadedProject } from "./maybeCaptureTelemetry.js"; -import { capture } from "../services/telemetry/capture.js"; - -test("it should capture as expected", async () => { - vi.mock("../services/telemetry/capture.js", async () => { - return { - capture: vi.fn(() => Promise.resolve()), - }; - }); - - vi.mock("../services/env-variables/index", async () => { - return { - ENV_VARIABLES: { - PUBLIC_POSTHOG_TOKEN: "mock-defined", - SDK_VERSION: "1.0.0-mock", - }, - }; - }); - - const project = await loadProjectInMemory({ - blob: await newProject(), - }); - - const account = await project.lix.db - .selectFrom("active_account") - .select("id") - .executeTakeFirstOrThrow(); - - const bundle = await project.db - .insertInto("bundle") - .defaultValues() - .returningAll() - .executeTakeFirstOrThrow(); - - const message = await project.db - .insertInto("message") - .values({ bundleId: bundle.id, locale: "en" }) - .returningAll() - .executeTakeFirstOrThrow(); - - await project.db - .insertInto("variant") - .values({ messageId: message.id }) - .returningAll() - .executeTakeFirst(); - - const settings = await project.settings.get(); - - const id = await project.id.get(); - - const plugins = await project.plugins.get(); - - await maybeCaptureLoadedProject({ - id, - settings, - forceCapture: true, - plugins, - lix: project.lix, - appId: "test", - db: project.db, - }); - - expect(capture).toHaveBeenCalledWith("SDK loaded project", { - projectId: await project.id.get(), - settings: await project.settings.get(), - accountId: account.id, - properties: { - appId: "test", - settings: await project.settings.get(), - pluginKeys: [], - sdkVersion: "1.0.0-mock", - numBundles: 1, - numMessages: 1, - numVariants: 1, - }, - }); -}); diff --git a/packages/sdk/src/project/maybeCaptureTelemetry.ts b/packages/sdk/src/project/maybeCaptureTelemetry.ts deleted file mode 100644 index f4a60b181e..0000000000 --- a/packages/sdk/src/project/maybeCaptureTelemetry.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Kysely } from "kysely"; -import { capture } from "../services/telemetry/capture.js"; -import type { InlangDatabaseSchema } from "../database/schema.js"; -import { ENV_VARIABLES } from "../services/env-variables/index.js"; -import type { ProjectSettings } from "../json-schema/settings.js"; -import type { Lix } from "@lix-js/sdk"; - -export async function maybeCaptureLoadedProject(args: { - id: string; - lix: Lix; - settings: ProjectSettings; - plugins: Readonly>; - appId?: string; - db: Kysely; - forceCapture?: boolean; -}) { - if (args.settings.telemetry === "off") { - return; - } - - // --- SAMPLING --- - // randomly sample 10% of projects - // to reduce the number of telemetry events. - // - // 10% is chosen out of a gut feeling - if (args.forceCapture !== true && Math.random() > 0.1) { - return; - } - - try { - const activeAccount = await args.lix.db - .selectFrom("active_account") - .select("id") - .executeTakeFirstOrThrow(); - - const bundles = await args.db - .selectFrom("bundle") - .select((s) => s.fn.count("id").as("count")) - .executeTakeFirst(); - const messages = await args.db - .selectFrom("message") - .select((s) => s.fn.count("id").as("count")) - .executeTakeFirst(); - const variants = await args.db - .selectFrom("variant") - .select((s) => s.fn.count("id").as("count")) - .executeTakeFirst(); - - await capture("SDK loaded project", { - projectId: args.id, - settings: args.settings, - accountId: activeAccount.id, - properties: { - // Insight: Which app is used by the SDK - appId: args.appId, - // Insight: How many languages are used, etc. - settings: args.settings, - // Insight on the used plugins (which one's to prioritize) - pluginKeys: args.plugins.map((plugin) => plugin.key), - // Insight: Which version of the SDK is used (can be used to deprecate old versions) - sdkVersion: ENV_VARIABLES.SDK_VERSION, - // Insight: Scale of projects (what project size to optimize for) - numBundles: bundles?.count, - numMessages: messages?.count, - numVariants: variants?.count, - }, - }); - } catch (e) { - if ( - e instanceof Error && - e.message.includes("driver has already been destroyed") - ) { - // The project has been closed, nothing to capture - return; - } - } -} diff --git a/packages/sdk/src/project/newProject.test.ts b/packages/sdk/src/project/newProject.test.ts index 59793ff3e3..d540ac61d8 100644 --- a/packages/sdk/src/project/newProject.test.ts +++ b/packages/sdk/src/project/newProject.test.ts @@ -42,3 +42,19 @@ test("it should have the lix id as project id", async () => { expect(projectId).toBeDefined(); expect(projectId).toBe(lixId); }); + +test("it should not persist the removed SDK metadata key during project creation", async () => { + const project = await loadProjectInMemory({ + blob: await newProject(), + }); + + const removedSdkKey = ["lix", "tele" + "metry"].join("_"); + + const removedKey = await project.lix.db + .selectFrom("key_value") + .select("value") + .where("key", "=", removedSdkKey) + .executeTakeFirst(); + + expect(removedKey).toBeUndefined(); +}); diff --git a/packages/sdk/src/project/newProject.ts b/packages/sdk/src/project/newProject.ts index e881ad1fdf..22db73213f 100644 --- a/packages/sdk/src/project/newProject.ts +++ b/packages/sdk/src/project/newProject.ts @@ -23,12 +23,7 @@ export async function newProject(args?: { try { const inlangDbContent = contentFromDatabase(sqlite); - const lix = await openLixInMemory({ - blob: await newLixFile(), - keyValues: [ - { key: "lix_telemetry", value: args?.settings?.telemetry ?? "on" }, - ], - }); + const lix = await openLixInMemory({ blob: await newLixFile() }); const { value: lixId } = await lix.db .selectFrom("key_value") diff --git a/packages/sdk/src/services/env-variables/createIndexFile.js b/packages/sdk/src/services/env-variables/createIndexFile.js index 9f2afa1a07..ef75b3db84 100644 --- a/packages/sdk/src/services/env-variables/createIndexFile.js +++ b/packages/sdk/src/services/env-variables/createIndexFile.js @@ -22,7 +22,6 @@ await fs.writeFile( dirname + "/index.ts", ` export const ENV_VARIABLES = { - PUBLIC_POSTHOG_TOKEN: ${ifDefined(process.env.PUBLIC_POSTHOG_TOKEN)}, PUBLIC_INLANG_SDK_SENTRY_DSN: ${ifDefined( process.env.PUBLIC_INLANG_SDK_SENTRY_DSN )}, diff --git a/packages/sdk/src/services/env-variables/index.d.ts b/packages/sdk/src/services/env-variables/index.d.ts index f8469c9143..9e954d7f8b 100644 --- a/packages/sdk/src/services/env-variables/index.d.ts +++ b/packages/sdk/src/services/env-variables/index.d.ts @@ -7,7 +7,6 @@ * Env variables that are available at runtime. */ export declare const ENV_VARIABLES: { - PUBLIC_POSTHOG_TOKEN?: string; PUBLIC_INLANG_SDK_SENTRY_DSN?: string; /** * As defined in the package.json diff --git a/packages/sdk/src/services/telemetry/capture.test.ts b/packages/sdk/src/services/telemetry/capture.test.ts deleted file mode 100644 index cb85dd3011..0000000000 --- a/packages/sdk/src/services/telemetry/capture.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { expect, test, vi } from "vitest"; -import { capture } from "./capture.js"; - -test("it should not capture if telemetry is off", async () => { - // @ts-expect-error - global.fetch is not defined - global.fetch = vi.fn(() => Promise.resolve()); - - vi.mock("../env-variables/index.js", async () => { - return { - ENV_VARIABLES: { - PUBLIC_POSTHOG_TOKEN: "mock-defined", - }, - }; - }); - - await capture("SDK loaded project", { - projectId: "test", - accountId: "test", - settings: { - telemetry: "off", - }, - properties: {}, - }); - - expect(global.fetch).not.toHaveBeenCalled(); -}); - -test("it should not capture if telemetry is NOT off", async () => { - // @ts-expect-error - global.fetch is not defined - global.fetch = vi.fn(() => Promise.resolve()); - - vi.mock("../env-variables/index.js", async () => { - return { - ENV_VARIABLES: { - PUBLIC_POSTHOG_TOKEN: "mock-defined", - }, - }; - }); - - await capture("SDK loaded project", { - projectId: "test", - accountId: "test", - settings: { - telemetry: undefined, - }, - properties: {}, - }); - - expect(global.fetch).toHaveBeenCalled(); -}); diff --git a/packages/sdk/src/services/telemetry/capture.ts b/packages/sdk/src/services/telemetry/capture.ts deleted file mode 100644 index 7f59f2b683..0000000000 --- a/packages/sdk/src/services/telemetry/capture.ts +++ /dev/null @@ -1,98 +0,0 @@ -// import { ENV_VARIABLES } from "../env-variables/index.js"; - -import type { ProjectSettings } from "../../json-schema/settings.js"; -import { ENV_VARIABLES } from "../env-variables/index.js"; - -/** - * List of telemetry events for typesafety. - * - * - prefix with `SDK` to avoid collisions with other apps - * - use past tense to indicate that the event is completed - */ -type TelemetryEvent = "SDK loaded project"; - -/** - * Capture an event. - * - * - manually calling the PostHog API because the SDKs were not platform angostic (and generally bloated) - */ -export const capture = async ( - event: TelemetryEvent, - args: { - projectId: string; - accountId: string; - /** - * Please use snake_case for property names. - */ - properties: Record; - settings: Pick; - } -) => { - if (args.settings.telemetry === "off") { - return; - } else if (ENV_VARIABLES.PUBLIC_POSTHOG_TOKEN === undefined) { - return; - } - try { - await fetch("https://eu.posthog.com/capture/", { - method: "POST", - body: JSON.stringify({ - api_key: ENV_VARIABLES.PUBLIC_POSTHOG_TOKEN, - event, - distinct_id: args.accountId, - properties: { - $groups: { project: args.projectId }, - ...args.properties, - }, - }), - }); - await identifyProject({ - projectId: args.projectId, - accountId: args.accountId, - // using the id for now as a name but can be changed in the future - // we need at least one property to make a project visible in the dashboar - properties: { name: args.projectId }, - }); - } catch { - // do nothing - } -}; - -/** - * Identifying a project is needed. - * - * Otherwise, the project will not be visible in the PostHog dashboard. - */ -const identifyProject = async (args: { - projectId: string; - accountId: string; - /** - * Please use snake_case for property names. - */ - properties: Record; -}) => { - // do not send events if the token is not set - // (assuming this eases testing) - if (ENV_VARIABLES.PUBLIC_POSTHOG_TOKEN === undefined) { - return; - } - try { - await fetch("https://eu.posthog.com/capture/", { - method: "POST", - body: JSON.stringify({ - api_key: ENV_VARIABLES.PUBLIC_POSTHOG_TOKEN, - event: "$groupidentify", - distinct_id: args.accountId, - properties: { - $group_type: "project", - $group_key: args.projectId, - $group_set: { - ...args.properties, - }, - }, - }), - }); - } catch { - // do nothing - } -};