diff --git a/package-lock.json b/package-lock.json
index c3584a036a..f3e2c1a152 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"workspaces": [
"packages/insomnia-testing",
"packages/insomnia",
+ "packages/insomnia-analytics",
"packages/insomnia-api",
"packages/insomnia-inso",
"packages/insomnia-smoke-test",
@@ -11325,9 +11326,9 @@
"license": "MIT"
},
"node_modules/@xmldom/xmldom": {
- "version": "0.8.12",
- "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz",
- "integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==",
+ "version": "0.8.13",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
+ "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -18409,6 +18410,10 @@
"resolved": "packages/insomnia",
"link": true
},
+ "node_modules/insomnia-analytics": {
+ "resolved": "packages/insomnia-analytics",
+ "link": true
+ },
"node_modules/insomnia-api": {
"resolved": "packages/insomnia-api",
"link": true
@@ -29183,7 +29188,6 @@
"@rjsf/utils": "6.0.0-beta.15",
"@rjsf/validator-ajv8": "6.0.0-beta.15",
"@seald-io/nedb": "^4.1.1",
- "@segment/analytics-node": "2.2.1",
"@sentry/electron": "^6.5.0",
"@stoplight/spectral-core": "^1.22.0",
"@stoplight/spectral-formats": "^1.8.2",
@@ -29336,6 +29340,13 @@
"@kong/insomnia-plugin-external-vault": "0.1.4-dev.20251224090833"
}
},
+ "packages/insomnia-analytics": {
+ "version": "12.5.1-alpha.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@segment/analytics-node": "2.2.1"
+ }
+ },
"packages/insomnia-api": {
"version": "12.5.1-alpha.0",
"license": "Apache-2.0",
@@ -29348,7 +29359,6 @@
"license": "Apache-2.0",
"dependencies": {
"@seald-io/nedb": "^4.1.1",
- "@segment/analytics-node": "^2.2.1",
"@stoplight/spectral-core": "^1.22.0",
"@stoplight/spectral-formats": "^1.8.2",
"@stoplight/spectral-ruleset-bundler": "1.7.0",
@@ -29938,9 +29948,9 @@
}
},
"packages/insomnia/node_modules/@xmldom/xmldom": {
- "version": "0.9.9",
- "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.9.tgz",
- "integrity": "sha512-qycIHAucxy/LXAYIjmLmtQ8q9GPnMbnjG1KXhWm9o5sCr6pOYDATkMPiTNa6/v8eELyqOQ2FsEqeoFYmgv/gJg==",
+ "version": "0.9.10",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz",
+ "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==",
"license": "MIT",
"engines": {
"node": ">=14.6"
diff --git a/package.json b/package.json
index 06d77047f0..24861f27ee 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"workspaces": [
"packages/insomnia-testing",
"packages/insomnia",
+ "packages/insomnia-analytics",
"packages/insomnia-api",
"packages/insomnia-inso",
"packages/insomnia-smoke-test",
diff --git a/packages/insomnia-analytics/package.json b/packages/insomnia-analytics/package.json
new file mode 100644
index 0000000000..a4c328f7be
--- /dev/null
+++ b/packages/insomnia-analytics/package.json
@@ -0,0 +1,36 @@
+{
+ "private": true,
+ "name": "insomnia-analytics",
+ "license": "Apache-2.0",
+ "version": "12.5.1-alpha.0",
+ "author": "Kong ",
+ "description": "Shared analytics client for the Insomnia desktop app and CLI",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/Kong/insomnia.git",
+ "directory": "packages/insomnia-analytics"
+ },
+ "bugs": {
+ "url": "https://github.com/kong/insomnia/issues"
+ },
+ "homepage": "https://github.com/Kong/insomnia#readme",
+ "sideEffects": false,
+ "exports": {
+ ".": {
+ "import": "./src/index.ts",
+ "types": "./src/index.ts"
+ },
+ "./events": {
+ "import": "./src/events.ts",
+ "types": "./src/events.ts"
+ }
+ },
+ "scripts": {
+ "lint": "eslint . --ext .ts --cache",
+ "type-check": "tsc --noEmit --project tsconfig.json",
+ "test": "vitest run"
+ },
+ "dependencies": {
+ "@segment/analytics-node": "2.2.1"
+ }
+}
diff --git a/packages/insomnia-analytics/src/analytics.test.ts b/packages/insomnia-analytics/src/analytics.test.ts
new file mode 100644
index 0000000000..0bf2e2dd3f
--- /dev/null
+++ b/packages/insomnia-analytics/src/analytics.test.ts
@@ -0,0 +1,73 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+
+const mockTrack = vi.fn();
+const mockPage = vi.fn();
+const mockCloseAndFlush = vi.fn().mockResolvedValue(void 0);
+
+vi.mock('@segment/analytics-node', () => ({
+ Analytics: vi.fn(() => ({
+ track: mockTrack,
+ page: mockPage,
+ closeAndFlush: mockCloseAndFlush,
+ })),
+}));
+
+describe('InsomniaAnalytics', () => {
+ beforeEach(() => {
+ vi.resetModules();
+ mockTrack.mockReset();
+ mockPage.mockReset();
+ mockCloseAndFlush.mockReset().mockResolvedValue(void 0);
+ });
+
+ it('forwards track calls with platform-tagged properties and an app+os context', async () => {
+ const { InsomniaAnalytics } = await import('./analytics');
+ const analytics = new InsomniaAnalytics({
+ writeKey: 'test',
+ app: { appName: 'inso', appVersion: '1.2.3', osVersion: '14.0', platform: 'cli' },
+ });
+
+ analytics.track({ event: 'inso_run_test', properties: { suite: 'echo' }, anonymousId: 'anon-1' });
+
+ expect(mockTrack).toHaveBeenCalledTimes(1);
+ const [payload] = mockTrack.mock.calls[0];
+ expect(payload).toMatchObject({
+ event: 'inso_run_test',
+ anonymousId: 'anon-1',
+ userId: '',
+ properties: { suite: 'echo', platform: 'cli' },
+ context: { app: { name: 'inso', version: '1.2.3' }, os: { version: '14.0' } },
+ });
+ });
+
+ it('resolves osVersion lazily when provided as a function', async () => {
+ const { InsomniaAnalytics } = await import('./analytics');
+ let v = '1.0';
+ const analytics = new InsomniaAnalytics({
+ writeKey: 'test',
+ app: { appName: 'app', appVersion: '0.1', osVersion: () => v, platform: 'app' },
+ });
+
+ v = '2.0';
+ analytics.track({ event: 'App Started', anonymousId: 'a', userId: 'u' });
+
+ const [payload] = mockTrack.mock.calls[0];
+ expect(payload.context.os.version).toBe('2.0');
+ });
+
+ it('routes analytics errors through the onError handler', async () => {
+ const onError = vi.fn();
+ const { InsomniaAnalytics } = await import('./analytics');
+ const analytics = new InsomniaAnalytics({
+ writeKey: 'test',
+ app: { appName: 'app', appVersion: '0.1', osVersion: '1.0', platform: 'app' },
+ onError,
+ });
+
+ analytics.track({ event: 'App Started', anonymousId: 'a' });
+ const [, callback] = mockTrack.mock.calls[0];
+ callback(new Error('segment failure'));
+
+ expect(onError).toHaveBeenCalledWith(expect.any(Error));
+ });
+});
diff --git a/packages/insomnia-analytics/src/analytics.ts b/packages/insomnia-analytics/src/analytics.ts
new file mode 100644
index 0000000000..774ce1adee
--- /dev/null
+++ b/packages/insomnia-analytics/src/analytics.ts
@@ -0,0 +1,103 @@
+import { Analytics, type AnalyticsSettings } from '@segment/analytics-node';
+
+export interface AppContext {
+ appName: string;
+ appVersion: string;
+ osVersion: string | (() => string);
+ platform: 'app' | 'cli';
+}
+
+export interface TrackOptions {
+ event: string;
+ properties?: Record;
+ anonymousId: string;
+ userId?: string;
+}
+
+export interface PageOptions {
+ name: string;
+ anonymousId: string;
+ userId?: string;
+}
+
+export interface InsomniaAnalyticsOptions {
+ writeKey: string;
+ app: AppContext;
+ settings?: Omit;
+ onError?: (error: unknown) => void;
+}
+
+export function getNormalizedOsName(platform: NodeJS.Platform | string): string {
+ switch (platform) {
+ case 'darwin': {
+ return 'mac';
+ }
+ case 'win32': {
+ return 'windows';
+ }
+ default: {
+ return platform;
+ }
+ }
+}
+
+export class InsomniaAnalytics {
+ private readonly client: Analytics;
+ private readonly app: AppContext;
+ private readonly onError: (error: unknown) => void;
+
+ constructor({ writeKey, app, settings, onError }: InsomniaAnalyticsOptions) {
+ this.client = new Analytics({ writeKey, ...settings });
+ this.app = app;
+ this.onError = onError ?? (() => {});
+ }
+
+ track({ event, properties, anonymousId, userId }: TrackOptions): void {
+ this.client.track(
+ {
+ event,
+ anonymousId,
+ userId: userId ?? '',
+ properties: { ...properties, platform: this.app.platform },
+ context: this.buildContext(),
+ },
+ error => {
+ if (error) {
+ this.onError(error);
+ }
+ },
+ );
+ }
+
+ page({ name, anonymousId, userId }: PageOptions): void {
+ this.client.page(
+ {
+ name,
+ anonymousId,
+ userId: userId ?? '',
+ context: this.buildContext(),
+ },
+ error => {
+ if (error) {
+ this.onError(error);
+ }
+ },
+ );
+ }
+
+ async closeAndFlush(timeoutMs = 5000): Promise {
+ try {
+ await this.client.closeAndFlush({ timeout: timeoutMs });
+ } catch (error) {
+ this.onError(error);
+ }
+ }
+
+ private buildContext() {
+ const osVersion = typeof this.app.osVersion === 'function' ? this.app.osVersion() : this.app.osVersion;
+ return {
+ app: { name: this.app.appName, version: this.app.appVersion },
+ os: { name: getNormalizedOsName(process.platform), version: osVersion },
+ };
+ }
+}
diff --git a/packages/insomnia-analytics/src/events.test.ts b/packages/insomnia-analytics/src/events.test.ts
new file mode 100644
index 0000000000..2072bdf6e1
--- /dev/null
+++ b/packages/insomnia-analytics/src/events.test.ts
@@ -0,0 +1,16 @@
+import { describe, expect, it } from 'vitest';
+
+import { AnalyticsEvent, InsoEvent } from './events';
+
+describe('events', () => {
+ it('AnalyticsEvent has expected entries', () => {
+ expect(AnalyticsEvent.appStarted).toBe('App Started');
+ expect(AnalyticsEvent.unitTestRun).toBe('Ran Individual Unit Test');
+ expect(AnalyticsEvent.installPlugin).toBe('Plugin Installed');
+ });
+
+ it('InsoEvent prefixes with inso_', () => {
+ expect(InsoEvent.runTest).toBe('inso_run_test');
+ expect(InsoEvent.script).toBe('inso_script');
+ });
+});
diff --git a/packages/insomnia-analytics/src/events.ts b/packages/insomnia-analytics/src/events.ts
new file mode 100644
index 0000000000..50dd647742
--- /dev/null
+++ b/packages/insomnia-analytics/src/events.ts
@@ -0,0 +1,142 @@
+export enum AnalyticsEvent {
+ appStarted = 'App Started',
+ analyticsDisabled = 'Analytics Disabled',
+ collectionCreate = 'Collection Created',
+ dataExport = 'Data Exported',
+ exportCompleted = 'Export Completed',
+ dataImport = 'Data Imported',
+ importStarted = 'Import Started',
+ importScanned = 'Import Scanned',
+ importCompleted = 'Import Completed',
+ importLoginRequired = 'Import Login Required',
+ importResumedAfterLogin = 'Import Resumed After Login',
+ importedRequestFirstSend = 'Imported Request First Send',
+ documentCreate = 'Document Created',
+ mockCreateModalOpened = 'Mock Server Create Modal Opened',
+ mockCreate = 'Mock Created',
+ mockEdit = 'Mock Server Edited',
+ mockDelete = 'Mock Server Deleted',
+ mockRouteCreate = 'Mock Route Created',
+ mockRouteEdit = 'Mock Route Edited',
+ mockRouteDelete = 'Mock Route Deleted',
+ generateCollection = 'Generated Collection',
+ generateCollectionFromMock = 'Generate Collection From Mock',
+ environmentCreate = 'Environment Created',
+ loginSuccess = 'Login Success',
+ inviteTrigger = 'Invite Triggered From App',
+ exportAllCollections = 'Exported All Collections',
+ kongConnected = 'Kong Connected',
+ kongSync = 'Kong Synced',
+ requestBodyTypeSelect = 'Request Body Type Selected',
+ requestCreated = 'Request Created',
+ requestExecuted = 'Request Executed',
+ requestEdit = 'Request Edited',
+ requestDeleted = 'Request Deleted',
+ requestRenamed = 'Request Renamed',
+ requestUrlCopied = 'Request URL Copied',
+ collectionRunExecute = 'Collection Run Executed',
+ projectLocalCreate = 'Local Project Created',
+ projectLocalDelete = 'Local Project Deleted',
+ selectScratchpad = 'Scratchpad Selected at Login',
+ syncConflictResolutionStart = 'Sync Conflict Resolution Started',
+ syncConflictResolutionCompleteMine = 'Sync Conflict Resolution Completed Mine',
+ syncConflictResolutionCompleteTheirs = 'Sync Conflict Resolution Completed Theirs',
+ testSuiteCreate = 'Test Suite Created',
+ testSuiteDelete = 'Test Suite Deleted',
+ unitTestCreate = 'Unit Test Created',
+ unitTestDelete = 'Unit Test Deleted',
+ unitTestRun = 'Ran Individual Unit Test',
+ unitTestRunAll = 'Ran All Unit Tests',
+ vcsSyncStart = 'VCS Sync Started',
+ vcsSyncComplete = 'VCS Sync Completed',
+ vcsAction = 'VCS Action Executed',
+ gitAuthenticationCompleted = 'Git Authentication Completed',
+ gitAuthenticationUpdated = 'Git Authentication Updated',
+ buttonClick = 'Button Clicked',
+ inviteMember = 'Invite Sent',
+ inviteResent = 'Invite Resent',
+ inviteRevoked = 'Invite Revoked',
+ projectCreated = 'Project Created',
+ projectUpdated = 'Project Updated',
+ exportStarted = 'Export Started',
+ exportRequestsChosen = 'Export Requests Chosen',
+ recommendCommitsGenerated = 'Recommend Commits Generated',
+ recommendCommitsSaved = 'Recommend Commits Saved',
+ recommendCommitsCancelled = 'Recommend Commits Cancelled',
+ recommendCommitsClicked = 'Recommend Commits Clicked',
+ mcpClientWorkspaceCreate = 'MCP Client Workspace Created',
+ mcpClientAdded = 'MCP Client Added',
+ mcpClientConnected = 'MCP Client Connected',
+ mcpClientDisconnected = 'MCP Client Disconnected',
+ mcpToolCalled = 'MCP Tool Called',
+ mcpResourceRead = 'MCP Resource Read',
+ mcpPromptCalled = 'MCP Prompt Called',
+ inviteNotPermitted = 'Invite Not Permitted',
+ responseToMockClicked = 'Response To Mock Clicked',
+ gitSyncButtonClicked = 'Git Sync Button Clicked',
+ preferencesViewed = 'Preferences Viewed',
+ copyAsCurl = 'Copied As cURL',
+ themeChanged = 'Theme Changed',
+ generateCodeClicked = 'Generate Code Clicked',
+ generateCodeLanguageChanged = 'Generate Code Language Changed',
+ filterCreatedHomePage = 'Filter Created From Home Page',
+ filterCreatedProjects = 'Filter Created Projects',
+ filterCreatedRequests = 'Filter Created Requests',
+ filterCreatedResponseBody = 'Filter Created Response Body',
+ aiFeatureEnabled = 'AI Feature Enabled',
+ aiFeatureDisabled = 'AI Feature Disabled',
+ installPlugin = 'Plugin Installed',
+
+ homepageFiltered = 'homepage-filtered',
+ quickSearchOpenedByKeyboard = 'quick-search-opened-by-keyboard',
+ quickSearchOpenedByMouse = 'quick-search-opened-by-mouse',
+ statusbarLeftbarToggled = 'statusbar-leftbar-toggled',
+ statusbarTopbarToggled = 'statusbar-topbar-toggled',
+ statusbarOrphanedProjectsClicked = 'statusbar-orphaned-projects-clicked',
+ designerGenerateMockClicked = 'designer-generate-mock-clicked',
+ designerPreviewToggled = 'designer-preview-toggled',
+ requestEnvironmentClicked = 'request-environment-clicked',
+ requestAddCookiesClicked = 'request-add-cookies-clicked',
+ requestAddCertificatesClicked = 'request-add-certificates-clicked',
+ requestListSortClicked = 'request-list-sort-clicked',
+ requestListExpandCollapseClicked = 'request-list-expand-collapse-clicked',
+ requestParamsDescriptionToggled = 'request-params-description-toggled',
+ requestParamsImportFromURLClicked = 'request-params-import-from-URL-clicked',
+ requestParamsBulkEditToggled = 'request-params-bulk-edit-toggled',
+ responsePreviewJSONPathEntered = 'response-preview-jsonpath-entered',
+ requestBodyBeautifyClicked = 'request-body-beautify-clicked',
+ requestHeadersDescriptionToggled = 'request-headers-description-toggled',
+ requestHeadersBulkEditToggled = 'request-headers-bulk-edit-toggled',
+ requestScriptsPreScriptSnippetAdded = 'request-scripts-prescript-snippet-added',
+ requestScriptsPostScriptSnippetAdded = 'request-scripts-postscript-snippet-added',
+ responseHeadersCopyAllClicked = 'response-headers-copy-all-clicked',
+ responseCookiesManageCookiesClicked = 'response-cookies-manage-cookies-clicked',
+ requestOpenInNewTabClicked = 'request-open-in-new-tab-clicked',
+ requestListMenuPinClicked = 'request-list-menu-pin-clicked',
+ requestListMenuDuplicateClicked = 'request-list-menu-duplicate-clicked',
+ requestListMenuRenameClicked = 'request-list-menu-rename-clicked',
+ requestListMenuSettingsClicked = 'request-list-menu-settings-clicked',
+ requestSendMenuGenerateCodeClicked = 'request-send-menu-generate-code-clicked',
+ requestSendMenuSendAfterDelayClicked = 'request-send-menu-send-after-delay-clicked',
+ requestSendMenuRepeatAfterIntervalClicked = 'request-send-menu-repeat-after-interval-clicked',
+ requestSendMenuDownloadAfterSendClicked = 'request-send-menu-download-after-send-clicked',
+ requestSendMenuSendAndDownloadClicked = 'request-send-menu-send-and-download-clicked',
+ mcpListExpandCollapseClicked = 'mcp-list-expand-collapse-clicked',
+ mcpListFiltered = 'mcp-list-filtered',
+ mcpRequestParamsBeautifyClicked = 'mcp-request-params-beautify-clicked',
+ mcpRequestHeadersDescriptionToggled = 'mcp-request-headers-description-toggled',
+ mcpRequestRootsNotifyClicked = 'mcp-request-roots-notify-clicked',
+ mcpResponseHeadersCopyAllClicked = 'mcp-response-headers-copy-all-clicked',
+ kongKonnectPatValidated = 'kong-konnect-pat-validated',
+ kongKonnectSyncCompleted = 'kong-konnect-sync-completed',
+ emptyStateSendRequestClicked = 'empty-state-send-request-clicked',
+ emptyStateCreateDocumentClicked = 'empty-state-create-document-clicked',
+}
+
+export enum InsoEvent {
+ runTest = 'inso_run_test',
+ runCollection = 'inso_run_collection',
+ lintSpec = 'inso_lint_spec',
+ exportSpec = 'inso_export_spec',
+ script = 'inso_script',
+}
diff --git a/packages/insomnia-analytics/src/index.ts b/packages/insomnia-analytics/src/index.ts
new file mode 100644
index 0000000000..c369a9583b
--- /dev/null
+++ b/packages/insomnia-analytics/src/index.ts
@@ -0,0 +1,2 @@
+export * from './events';
+export * from './analytics';
diff --git a/packages/insomnia-analytics/tsconfig.json b/packages/insomnia-analytics/tsconfig.json
new file mode 100644
index 0000000000..1cf9c3d3a3
--- /dev/null
+++ b/packages/insomnia-analytics/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "target": "es2020",
+ "allowJs": false,
+ "resolveJsonModule": true,
+ "moduleResolution": "bundler",
+ "isolatedModules": true,
+ "module": "ESNext",
+ "sourceMap": true,
+ "baseUrl": ".",
+ "rootDirs": ["."],
+ "lib": ["ES2023", "DOM"],
+ "types": [],
+ "strict": true,
+ "noImplicitReturns": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "useUnknownInCatchVariables": false,
+ "verbatimModuleSyntax": true,
+ "forceConsistentCasingInFileNames": true
+ }
+}
diff --git a/packages/insomnia-analytics/vitest.config.ts b/packages/insomnia-analytics/vitest.config.ts
new file mode 100644
index 0000000000..4ac6027d57
--- /dev/null
+++ b/packages/insomnia-analytics/vitest.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ environment: 'node',
+ },
+});
diff --git a/packages/insomnia-component-docs/package-lock.json b/packages/insomnia-component-docs/package-lock.json
index da16019540..436290e9cc 100644
--- a/packages/insomnia-component-docs/package-lock.json
+++ b/packages/insomnia-component-docs/package-lock.json
@@ -26,7 +26,7 @@
"typescript": "~5.6.2"
},
"engines": {
- "node": ">=22.0"
+ "node": ">=24.0"
}
},
"node_modules/@ai-sdk/gateway": {
@@ -9144,9 +9144,10 @@
"license": "ISC"
},
"node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
diff --git a/packages/insomnia-inso/package.json b/packages/insomnia-inso/package.json
index 7d75429a7e..ba587680b0 100644
--- a/packages/insomnia-inso/package.json
+++ b/packages/insomnia-inso/package.json
@@ -51,7 +51,6 @@
"shellwords": "^1.0.1"
},
"dependencies": {
- "@segment/analytics-node": "^2.2.1",
"@seald-io/nedb": "^4.1.1",
"@stoplight/spectral-core": "^1.22.0",
"@stoplight/spectral-formats": "^1.8.2",
diff --git a/packages/insomnia-inso/src/analytics.ts b/packages/insomnia-inso/src/analytics.ts
index ba4c0bf657..2f116f99cd 100644
--- a/packages/insomnia-inso/src/analytics.ts
+++ b/packages/insomnia-inso/src/analytics.ts
@@ -1,7 +1,7 @@
import os from 'node:os';
-import { Analytics } from '@segment/analytics-node';
import { getSegmentWriteKey } from 'insomnia/src/common/constants';
+import { InsoEvent, InsomniaAnalytics } from 'insomnia-analytics';
import { v4 as uuidv4 } from 'uuid';
import type { Settings } from '~/insomnia-data';
@@ -10,15 +10,18 @@ import packageJson from '../package.json';
import neDbAdapter from './db/adapters/ne-db-adapter';
import { getAppDataDir, getDefaultProductName } from './util';
-export enum InsoEvent {
- runTest = 'inso_run_test',
- runCollection = 'inso_run_collection',
- lintSpec = 'inso_lint_spec',
- exportSpec = 'inso_export_spec',
- script = 'inso_script',
-}
+export { InsoEvent };
+
+const analytics = new InsomniaAnalytics({
+ writeKey: getSegmentWriteKey(),
+ app: {
+ appName: 'inso',
+ appVersion: process.env.VERSION || packageJson.version,
+ osVersion: () => os.release(),
+ platform: 'cli',
+ },
+});
-const analyticsClient = new Analytics({ writeKey: getSegmentWriteKey() });
let deviceId: string | null = null;
let localSettings: Settings | null = null;
@@ -54,25 +57,16 @@ const getDeviceId = async (): Promise => {
return deviceId;
};
-const getOsName = (): string => {
- switch (process.platform) {
- case 'darwin': {
- return 'mac';
- }
- case 'win32': {
- return 'windows';
- }
- default: {
- return process.platform;
- }
- }
-};
-
export const trackInsoEvent = async (event: InsoEvent, properties?: Record): Promise => {
if (process.env.NODE_ENV === 'test') {
return;
}
+ // new for v13 - provide a way to disable analytics
+ if (process.env.INSO_TELEMETRY_DISABLED) {
+ return;
+ }
+
const settings = await getLocalSettings();
if (settings && !settings.enableAnalytics) {
return;
@@ -80,40 +74,10 @@ export const trackInsoEvent = async (event: InsoEvent, properties?: Record {
- // Silently fail
- },
- );
- } catch {
- // Silently fail
- }
+ analytics.track({ event, anonymousId, properties });
+ } catch {}
};
export const flushAnalytics = async (): Promise => {
- try {
- await analyticsClient.closeAndFlush({ timeout: 5000 });
- } catch {
- // Silently fail
- }
+ await analytics.closeAndFlush(5000);
};
diff --git a/packages/insomnia-smoke-test/fixtures/files/dummy.pdf b/packages/insomnia-smoke-test/fixtures/files/dummy.pdf
index 774c2ea70c..8cd2c288db 100644
Binary files a/packages/insomnia-smoke-test/fixtures/files/dummy.pdf and b/packages/insomnia-smoke-test/fixtures/files/dummy.pdf differ
diff --git a/packages/insomnia-smoke-test/tests/smoke/app.test.ts b/packages/insomnia-smoke-test/tests/smoke/app.test.ts
index 18902b10e3..87f92cb2d2 100644
--- a/packages/insomnia-smoke-test/tests/smoke/app.test.ts
+++ b/packages/insomnia-smoke-test/tests/smoke/app.test.ts
@@ -5,8 +5,9 @@ import { test } from '../../playwright/test';
test('can send requests', async ({ page, insomnia }) => {
test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms');
- const statusTag = page.locator('[data-testid="response-status-tag"]:visible');
+ const statusTag = page.getByTestId('response-pane').getByTestId('response-status-tag');
const responseBody = page.getByTestId('response-pane');
+ const responsePreviewBody = page.locator('[data-testid="response-pane"] >> [data-testid="CodeEditor"]:visible');
await insomnia.projectPage.importFixture('smoke-test-collection.yaml');
@@ -36,10 +37,10 @@ test('can send requests', async ({ page, insomnia }) => {
.toBeVisible();
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect.soft(statusTag).toContainText('200 OK');
- await expect.soft(responseBody).toContainText('"id": "1"');
+ await expect.soft(responsePreviewBody).toContainText('"id": "1"');
await page.getByRole('button', { name: 'Preview' }).click();
await page.getByRole('menuitem', { name: 'Raw Data' }).click();
- await expect.soft(responseBody).toContainText('{"id":"1"}');
+ await expect.soft(responsePreviewBody).toContainText('{"id":"1"}');
await insomnia.navigationSidebar.clickRequestOrFolder('connects to event stream and shows ping response');
await expect
@@ -47,7 +48,7 @@ test('can send requests', async ({ page, insomnia }) => {
.toBeVisible();
await page.getByTestId('request-pane').getByRole('button', { name: 'Connect' }).click();
await expect.soft(statusTag).toContainText('200 OK');
- await page.getByRole('tab', { name: 'Console' }).click();
+ await page.getByTestId('response-pane').getByRole('tab', { name: 'Console' }).click();
await expect.soft(responseBody).toContainText('Connected to 127.0.0.1');
await page.getByTestId('request-pane').getByRole('button', { name: 'Disconnect' }).click();
@@ -61,7 +62,7 @@ test('can send requests', async ({ page, insomnia }) => {
await expect.soft(statusTag).toContainText('200 OK');
await page.getByRole('button', { name: 'Preview' }).click();
await page.getByRole('menuitem', { name: 'Raw Data' }).click();
- await expect.soft(responseBody).toContainText('a,b,c');
+ await expect.soft(responsePreviewBody).toContainText('a,b,c');
await insomnia.navigationSidebar.clickRequestOrFolder('sends dummy.xml request and shows raw response');
await expect
@@ -71,8 +72,8 @@ test('can send requests', async ({ page, insomnia }) => {
.toBeVisible();
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect.soft(statusTag).toContainText('200 OK');
- await expect.soft(responseBody).toContainText('xml version="1.0"');
- await expect.soft(responseBody).toContainText('');
+ await expect.soft(responsePreviewBody).toContainText('xml version="1.0"');
+ await expect.soft(responsePreviewBody).toContainText('');
await insomnia.navigationSidebar.clickRequestOrFolder('sends dummy.pdf request and shows rich response');
await expect
@@ -82,13 +83,34 @@ test('can send requests', async ({ page, insomnia }) => {
.toBeVisible();
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect.soft(statusTag).toContainText('200 OK');
- await page.getByRole('tab', { name: 'Console' }).click();
+
+ const pdfIframe = page.getByTestId('ResponsePDFView');
+ await expect.soft(pdfIframe).toBeVisible();
+ await expect.soft(pdfIframe).toHaveAttribute('src', /^blob:/);
+
+ // find Electron/Chromium's built-in PDF viewer extension
+ await expect
+ .poll(() => page.frames().some(f => f.url().startsWith('chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai')), {
+ timeout: 5000,
+ message: 'Expected Chromium built-in PDF viewer extension frame to mount inside the PDF preview iframe',
+ })
+ .toBe(true);
+
+ await expect.soft(pdfIframe).toHaveScreenshot('dummy-pdf-preview.png', {
+ animations: 'disabled',
+ maxDiffPixelRatio: 0.15, // 15% discrepancy allowed for CI/environment differences
+ timeout: 5000,
+ });
+
+ await page.getByTestId('response-pane').getByRole('tab', { name: 'Console' }).click();
await page.locator('pre').filter({ hasText: '< Content-Type: application/pdf' }).click();
+ await page.getByTestId('response-pane').getByRole('tab', { name: 'Preview' }).click();
await insomnia.navigationSidebar.clickRequestOrFolder('sends request with basic authentication');
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect.soft(statusTag).toContainText('200 OK');
- await expect.soft(responseBody).toContainText('basic auth received');
+ await page.getByTestId('response-pane').getByRole('tab', { name: 'Preview' }).click();
+ await expect.soft(responsePreviewBody).toContainText('basic auth received');
await insomnia.navigationSidebar.clickRequestOrFolder('sends request with cookie and get cookie in response');
await expect
@@ -96,7 +118,7 @@ test('can send requests', async ({ page, insomnia }) => {
.toBeVisible();
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect.soft(statusTag).toContainText('200 OK');
- await page.getByRole('tab', { name: 'Console' }).click();
+ await page.getByTestId('response-pane').getByRole('tab', { name: 'Console' }).click();
await expect.soft(responseBody).toContainText('Set-Cookie: insomnia-test-cookie=value123');
await insomnia.navigationSidebar.clickRequestOrFolder('delayed request');
diff --git a/packages/insomnia-smoke-test/tests/smoke/app.test.ts-snapshots/dummy-pdf-preview-Smoke-darwin.png b/packages/insomnia-smoke-test/tests/smoke/app.test.ts-snapshots/dummy-pdf-preview-Smoke-darwin.png
new file mode 100644
index 0000000000..bc1f311ea0
Binary files /dev/null and b/packages/insomnia-smoke-test/tests/smoke/app.test.ts-snapshots/dummy-pdf-preview-Smoke-darwin.png differ
diff --git a/packages/insomnia-smoke-test/tests/smoke/app.test.ts-snapshots/dummy-pdf-preview-Smoke-linux.png b/packages/insomnia-smoke-test/tests/smoke/app.test.ts-snapshots/dummy-pdf-preview-Smoke-linux.png
new file mode 100644
index 0000000000..e0687de949
Binary files /dev/null and b/packages/insomnia-smoke-test/tests/smoke/app.test.ts-snapshots/dummy-pdf-preview-Smoke-linux.png differ
diff --git a/packages/insomnia-smoke-test/tests/smoke/app.test.ts-snapshots/dummy-pdf-preview-Smoke-win32.png b/packages/insomnia-smoke-test/tests/smoke/app.test.ts-snapshots/dummy-pdf-preview-Smoke-win32.png
new file mode 100644
index 0000000000..5183119cee
Binary files /dev/null and b/packages/insomnia-smoke-test/tests/smoke/app.test.ts-snapshots/dummy-pdf-preview-Smoke-win32.png differ
diff --git a/packages/insomnia/package.json b/packages/insomnia/package.json
index 8b4f30ee99..9995d1830e 100644
--- a/packages/insomnia/package.json
+++ b/packages/insomnia/package.json
@@ -64,7 +64,6 @@
"@rjsf/utils": "6.0.0-beta.15",
"@rjsf/validator-ajv8": "6.0.0-beta.15",
"@seald-io/nedb": "^4.1.1",
- "@segment/analytics-node": "2.2.1",
"@sentry/electron": "^6.5.0",
"@stoplight/spectral-core": "^1.22.0",
"@stoplight/spectral-formats": "^1.8.2",
diff --git a/packages/insomnia/src/__tests__/install-plugin.test.ts b/packages/insomnia/src/__tests__/install-plugin.test.ts
index 91e8a62391..f90edea2d6 100644
--- a/packages/insomnia/src/__tests__/install-plugin.test.ts
+++ b/packages/insomnia/src/__tests__/install-plugin.test.ts
@@ -66,8 +66,8 @@ vi.mock('../main/install-plugin', async () => {
});
vi.mock('../main/analytics', () => ({
- trackSegmentEvent: vi.fn(),
- SegmentEvent: {
+ trackAnalyticsEvent: vi.fn(),
+ AnalyticsEvent: {
installPlugin: 'Plugin Installed',
},
}));
diff --git a/packages/insomnia/src/account/session.ts b/packages/insomnia/src/account/session.ts
index 3b9a7f9c58..67d7d09d14 100644
--- a/packages/insomnia/src/account/session.ts
+++ b/packages/insomnia/src/account/session.ts
@@ -26,11 +26,7 @@ export async function absorbKey(sessionId: string, key: string) {
getUserProfile({ sessionId: sessionIdResolved }),
getEncryptionKeys({ sessionId: sessionIdResolved }),
]);
- const {
- public_key: publicKey,
- enc_private_key: encPrivateKey,
- enc_symmetric_key: encSymmetricKey,
- } = keys;
+ const { public_key: publicKey, enc_private_key: encPrivateKey, enc_symmetric_key: encSymmetricKey } = keys;
const { email, id: accountId, first_name: firstName, last_name: lastName } = profile;
const symmetricKeyStr = crypt.decryptAES(key, JSON.parse(encSymmetricKey));
@@ -67,7 +63,7 @@ export async function getPrivateKey() {
}
export async function getCurrentSessionId() {
- const { id } = await services.userSession.getOrCreate();
+ const { id } = await services.userSession.get();
return id;
}
@@ -122,16 +118,14 @@ export async function setSessionData(
lastName,
};
- const userData = await services.userSession.getOrCreate();
- await services.userSession.update(userData, sessionData);
+ await services.userSession.update(sessionData);
return sessionData;
}
/** Update the session data with vault salt and vault key */
export async function setVaultSessionData(vaultSalt: string, vaultKey: string) {
- const userData = await services.userSession.getOrCreate();
- await services.userSession.update(userData, { vaultSalt, vaultKey });
+ await services.userSession.update({ vaultSalt, vaultKey });
}
// ~~~~~~~~~~~~~~~~ //
@@ -139,25 +133,13 @@ export async function setVaultSessionData(vaultSalt: string, vaultKey: string) {
// ~~~~~~~~~~~~~~~~ //
export async function getUserSession(): Promise {
- const userData = await services.userSession.getOrCreate();
+ const userData = await services.userSession.get();
return userData;
}
async function _unsetSessionData() {
- await services.userSession.getOrCreate();
- await services.userSession.update(await services.userSession.getOrCreate(), {
- id: '',
- accountId: '',
- email: '',
- firstName: '',
- lastName: '',
- symmetricKey: {} as JsonWebKey,
- publicKey: {} as JsonWebKey,
- encPrivateKey: {} as crypt.AESMessage,
- vaultSalt: '',
- vaultKey: '',
- });
+ await services.userSession.remove();
}
/**
@@ -272,12 +254,12 @@ export async function migrateFromLocalStorage() {
try {
const sessionData = JSON.parse(session) as SessionData;
- const currentUserSession = await services.userSession.getOrCreate();
+ const currentUserSession = await services.userSession.get();
if (currentUserSession.id) {
console.warn('Session already exists, skipping migration');
} else {
- await services.userSession.update(currentUserSession, sessionData);
+ await services.userSession.update(sessionData);
}
} catch (e) {
console.error('Failed to parse session data', e);
diff --git a/packages/insomnia/src/common/__tests__/har.test.ts b/packages/insomnia/src/common/__tests__/har.test.ts
index ff635fe996..d3a6607e2f 100644
--- a/packages/insomnia/src/common/__tests__/har.test.ts
+++ b/packages/insomnia/src/common/__tests__/har.test.ts
@@ -12,7 +12,7 @@ import { getRenderedRequestAndContext } from '../render';
describe('export', () => {
beforeEach(async () => {
await db.init({ inMemoryOnly: true }, true);
- await services.project.all();
+ await services.project.list();
await services.settings.getOrCreate();
});
diff --git a/packages/insomnia/src/common/__tests__/render.test.ts b/packages/insomnia/src/common/__tests__/render.test.ts
index 2497d2154f..8a67d05a40 100644
--- a/packages/insomnia/src/common/__tests__/render.test.ts
+++ b/packages/insomnia/src/common/__tests__/render.test.ts
@@ -12,7 +12,6 @@ const reqGroupBuilder = createBuilder(requestGroupModelSchema);
describe('render tests', () => {
beforeEach(async () => {
- await services.project.all();
await services.settings.getOrCreate();
envBuilder.reset();
reqGroupBuilder.reset();
diff --git a/packages/insomnia/src/common/import.ts b/packages/insomnia/src/common/import.ts
index 548f0d67d0..faa4db3591 100644
--- a/packages/insomnia/src/common/import.ts
+++ b/packages/insomnia/src/common/import.ts
@@ -592,7 +592,7 @@ export const importResourcesToNewWorkspace = async ({
}): Promise => {
invariant(resourceCacheItem, 'No resources to import');
- const project = await services.project.getById(projectId);
+ const project = await services.project.get(projectId);
invariant(project, 'Project not found');
const resources = resourceCacheItem.resources;
@@ -766,8 +766,7 @@ export async function findExistingImportedSpec(
}
| undefined
> {
- const allProjects = await services.project.all();
- const filteredProjects = organizationId ? allProjects.filter(p => p.parentId === organizationId) : allProjects;
+ const filteredProjects = await services.project.list({ organizationId });
// match active project first, then look in rest
const projectIds = new Set();
diff --git a/packages/insomnia/src/common/organization-storage-rules.ts b/packages/insomnia/src/common/organization-storage-rules.ts
index e6076490c6..b907734845 100644
--- a/packages/insomnia/src/common/organization-storage-rules.ts
+++ b/packages/insomnia/src/common/organization-storage-rules.ts
@@ -34,7 +34,7 @@ export async function fetchAndCacheOrganizationStorageRule(
}
}
- const { id: sessionId } = await services.userSession.getOrCreate();
+ const { id: sessionId } = await services.userSession.get();
return await getOrganizationStorageRule({
organizationId,
diff --git a/packages/insomnia/src/common/render.ts b/packages/insomnia/src/common/render.ts
index cfcbcccfac..d401b3f349 100644
--- a/packages/insomnia/src/common/render.ts
+++ b/packages/insomnia/src/common/render.ts
@@ -129,6 +129,9 @@ export async function buildRenderContext({
const keys = _getOrderedEnvironmentKeys(subObject);
for (const key of keys) {
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
+ continue;
+ }
/*
* If we're overwriting a string, try to render it first using the same key from the base
* environment to support same-variable recursion. This allows for the following scenario:
diff --git a/packages/insomnia/src/entry.main.ts b/packages/insomnia/src/entry.main.ts
index abcf0f8c70..59adc61664 100644
--- a/packages/insomnia/src/entry.main.ts
+++ b/packages/insomnia/src/entry.main.ts
@@ -21,7 +21,7 @@ import { registerLLMConfigServiceAPI } from '~/main/llm-config-service';
import { userDataFolder } from '../config/config.json';
import { getAppVersion, getProductName, isDevelopment } from './common/constants';
import { isMac } from './common/platform';
-import { SegmentEvent, trackSegmentEvent } from './main/analytics';
+import { AnalyticsEvent, trackAnalyticsEvent } from './main/analytics';
import { registerInsomniaProtocols } from './main/api.protocol';
import { backupIfNewerVersionAvailable } from './main/backup';
import { registerSyncHandlers } from './main/cloud-sync/ipc';
@@ -289,7 +289,7 @@ async function _createModelInstances() {
await services.stats.get();
await services.settings.getOrCreate();
try {
- const scratchpadProject = await services.project.getById(models.project.SCRATCHPAD_PROJECT_ID);
+ const scratchpadProject = await services.project.get(models.project.SCRATCHPAD_PROJECT_ID);
const scratchPad = await services.workspace.getById(models.workspace.SCRATCHPAD_WORKSPACE_ID);
if (!scratchpadProject) {
console.log('[main] Initializing Scratch Pad Project');
@@ -365,7 +365,7 @@ async function _trackStats() {
const settings = await services.settings.get();
- trackSegmentEvent(SegmentEvent.appStarted, {
+ trackAnalyticsEvent(AnalyticsEvent.appStarted, {
localProjects,
remoteProjects,
createdRequests: stats.createdRequests,
diff --git a/packages/insomnia/src/entry.preload.ts b/packages/insomnia/src/entry.preload.ts
index ed7b80188a..6ad8416d4e 100644
--- a/packages/insomnia/src/entry.preload.ts
+++ b/packages/insomnia/src/entry.preload.ts
@@ -289,7 +289,7 @@ const main: Window['main'] = {
secretStorage,
electronStorage,
sync,
- trackSegmentEvent: options => ipcRenderer.send('trackSegmentEvent', options),
+ trackAnalyticsEvent: options => ipcRenderer.send('trackAnalyticsEvent', options),
trackPageView: options => ipcRenderer.send('trackPageView', options),
setCurrentOrganizationId: organizationId => ipcRenderer.send('analytics.setOrganizationId', organizationId),
showNunjucksContextMenu: options => ipcRenderer.send('show-nunjucks-context-menu', options),
diff --git a/packages/insomnia/src/insomnia-data/__tests__/request.test.ts b/packages/insomnia/src/insomnia-data/__tests__/request.test.ts
index e9da89d417..c641d16069 100644
--- a/packages/insomnia/src/insomnia-data/__tests__/request.test.ts
+++ b/packages/insomnia/src/insomnia-data/__tests__/request.test.ts
@@ -30,7 +30,6 @@ import { services } from '~/insomnia-data';
// @vitest-environment jsdom
describe('Request Model - Comprehensive Tests', () => {
beforeEach(async () => {
- await services.project.all();
await services.settings.getOrCreate();
// Create test project for all tests
diff --git a/packages/insomnia/src/insomnia-data/node-src/services/index.ts b/packages/insomnia/src/insomnia-data/node-src/services/index.ts
index f69c65abde..b53542b4f2 100644
--- a/packages/insomnia/src/insomnia-data/node-src/services/index.ts
+++ b/packages/insomnia/src/insomnia-data/node-src/services/index.ts
@@ -15,6 +15,7 @@ import * as mcpResponseService from './mcp-response';
import * as mockRouteService from './mock-route';
import * as mockServerService from './mock-server';
import * as oAuth2TokenService from './o-auth-2-token';
+import * as organizationService from './organization';
import * as pluginDataService from './plugin-data';
import * as projectService from './project';
import * as protoDirectoryService from './proto-directory';
@@ -59,6 +60,7 @@ export const servicesNodeImpl = {
mcpRequest: mcpRequestService,
mcpResponse: mcpResponseService,
oAuth2Token: oAuth2TokenService,
+ organization: organizationService,
pluginData: pluginDataService,
protoDirectory: protoDirectoryService,
protoFile: protoFileService,
diff --git a/packages/insomnia/src/insomnia-data/node-src/services/organization.ts b/packages/insomnia/src/insomnia-data/node-src/services/organization.ts
new file mode 100644
index 0000000000..48141388db
--- /dev/null
+++ b/packages/insomnia/src/insomnia-data/node-src/services/organization.ts
@@ -0,0 +1,60 @@
+import { getOrganizations, type Organization } from 'insomnia-api';
+
+import { models } from '~/insomnia-data';
+
+import * as userSessionService from './user-session';
+
+function sortOrganizations(accountId: string, organizations: Organization[]): Organization[] {
+ const home = organizations.find(
+ organization =>
+ models.organization.isPersonalOrganization(organization) &&
+ models.organization.isOwnerOfOrganization({
+ organization,
+ accountId,
+ }),
+ );
+ const myOrgs = organizations
+ .filter(
+ organization =>
+ !models.organization.isPersonalOrganization(organization) &&
+ models.organization.isOwnerOfOrganization({
+ organization,
+ accountId,
+ }),
+ )
+ .sort((a, b) => a.name.localeCompare(b.name));
+ const notMyOrgs = organizations
+ .filter(
+ organization =>
+ !models.organization.isOwnerOfOrganization({
+ organization,
+ accountId,
+ }),
+ )
+ .sort((a, b) => a.name.localeCompare(b.name));
+ return [...(home ? [home] : []), ...myOrgs, ...notMyOrgs];
+}
+
+/**
+ * List organizations from the Insomnia cloud API.
+ */
+export async function list(): Promise {
+ const { id: sessionId, accountId } = await userSessionService.get();
+
+ if (!sessionId || !accountId) {
+ return [];
+ }
+
+ const result = await getOrganizations({ sessionId });
+ const organizations = result?.organizations ?? [];
+
+ return sortOrganizations(accountId, organizations);
+}
+
+/**
+ * Get a single organization by ID.
+ */
+export async function get(id: string): Promise {
+ const all = await list();
+ return all.find(org => org.id === id);
+}
diff --git a/packages/insomnia/src/insomnia-data/node-src/services/project.ts b/packages/insomnia/src/insomnia-data/node-src/services/project.ts
index 41d57097ce..a6ca41a3b5 100644
--- a/packages/insomnia/src/insomnia-data/node-src/services/project.ts
+++ b/packages/insomnia/src/insomnia-data/node-src/services/project.ts
@@ -1,4 +1,4 @@
-import type { Project } from '~/insomnia-data';
+import type { Project, Query } from '~/insomnia-data';
import { database as db, models } from '~/insomnia-data';
const { type } = models.project;
@@ -7,30 +7,42 @@ export function create(patch: Partial = {}) {
return db.docCreate(type, patch);
}
-export function getById(_id: string) {
- return db.findOne(type, { _id });
+export function list(options?: { gitRepositoryIds?: string[]; organizationId?: string }) {
+ const query: Query = {};
+ if (options?.organizationId) {
+ query.parentId = options.organizationId;
+ }
+ if (options?.gitRepositoryIds) {
+ const queryIds = options.gitRepositoryIds.flatMap(id => models.project.getQueryableGitRepositoryIds(id));
+ query.gitRepositoryId = { $in: queryIds };
+ }
+ return db.find(type, query);
+}
+
+export function get(id: string) {
+ return db.findOne(type, { _id: id });
}
export function getByRemoteId(remoteId: string) {
return db.findOne(type, { remoteId });
}
-export function getAllByGitRepositoryIds(gitRepositoryIds: string[]) {
- const queryIds = gitRepositoryIds.flatMap(id => models.project.getQueryableGitRepositoryIds(id));
- return db.find(type, {
- gitRepositoryId: { $in: queryIds },
- });
-}
+const getProjectByIdOrProject = async (idOrProject: string | Project) => {
+ const project = typeof idOrProject === 'string' ? await get(idOrProject) : idOrProject;
+ if (!project) {
+ throw new Error(
+ `Project not found: ${typeof idOrProject === 'string' ? idOrProject : `_id=${idOrProject._id}, name=${idOrProject.name}`}`,
+ );
+ }
+ return project;
+};
-export function remove(project: Project) {
- return db.remove(project);
-}
-
-export function update(project: Project, patch: Partial) {
+export async function update(idOrProject: string | Project, patch: Partial) {
+ const project = await getProjectByIdOrProject(idOrProject);
return db.docUpdate(project, patch);
}
-export async function all() {
- const projects = await db.find(type);
- return projects;
+export async function remove(idOrProject: string | Project) {
+ const project = await getProjectByIdOrProject(idOrProject);
+ return db.remove(project);
}
diff --git a/packages/insomnia/src/insomnia-data/node-src/services/user-session.ts b/packages/insomnia/src/insomnia-data/node-src/services/user-session.ts
index 7eac158caa..7d44e8e702 100644
--- a/packages/insomnia/src/insomnia-data/node-src/services/user-session.ts
+++ b/packages/insomnia/src/insomnia-data/node-src/services/user-session.ts
@@ -3,41 +3,23 @@ import { database as db, models } from '~/insomnia-data';
const { type } = models.userSession;
-export async function all() {
- let userList = await db.find(type);
-
- if (userList?.length === 0) {
- userList = [await getOrCreate()];
- }
-
- return userList;
-}
-
-async function create() {
- const user = await db.docCreate(type);
- return user;
-}
-
-export async function update(user: UserSession, patch: Partial) {
- const updatedUser = await db.docUpdate(user, patch);
- return updatedUser;
-}
-
-export async function patch(patch: Partial) {
- const user = await getOrCreate();
- const updatedUser = await db.docUpdate(user, patch);
- return updatedUser;
-}
-
-export async function getOrCreate() {
+export async function get() {
const result = await db.findOne(type);
if (!result) {
- return await create();
+ const user = await db.docCreate(type);
+ return user;
}
return result;
}
-export async function get() {
- return getOrCreate();
+export async function update(patch: Partial) {
+ const user = await get();
+ const updatedUser = await db.docUpdate(user, patch);
+ return updatedUser;
+}
+
+export async function remove() {
+ const user = await get();
+ await db.remove(user);
}
diff --git a/packages/insomnia/src/konnect/__tests__/sync.test.ts b/packages/insomnia/src/konnect/__tests__/sync.test.ts
index 376f66a490..9bdbc2695d 100644
--- a/packages/insomnia/src/konnect/__tests__/sync.test.ts
+++ b/packages/insomnia/src/konnect/__tests__/sync.test.ts
@@ -1,7 +1,7 @@
/**
* Tests run against the in-memory NeDB initialized by setup-vitest.ts.
* fetch is mocked per-test to return shaped Konnect API responses.
- * window.main is stubbed globally so trackSegmentEvent calls don't throw.
+ * window.main is stubbed globally so trackAnalyticsEvent calls don't throw.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
@@ -101,13 +101,13 @@ const konnectRequests = (docs: any[]) => docs.filter((r: any) => r.konnectRouteK
const konnectWorkspaces = (docs: any[]) => docs.filter((w: any) => w.konnectServiceId != null);
const konnectProjects = (docs: any[]) => docs.filter((p: any) => p.konnectControlPlaneId != null);
-const trackSegmentEvent = vi.fn();
+const trackAnalyticsEvent = vi.fn();
beforeEach(async () => {
// Re-init with fresh in-memory NeDB buckets — clean slate for every test.
await initDatabase(mainDatabase, { inMemoryOnly: true }, true);
resetV4Counter();
- vi.stubGlobal('window', { main: { trackSegmentEvent } });
+ vi.stubGlobal('window', { main: { trackAnalyticsEvent } });
});
afterEach(() => {
diff --git a/packages/insomnia/src/main/__tests__/sync-initialization.test.ts b/packages/insomnia/src/main/__tests__/sync-initialization.test.ts
index 4aecefa039..477a57a8db 100644
--- a/packages/insomnia/src/main/__tests__/sync-initialization.test.ts
+++ b/packages/insomnia/src/main/__tests__/sync-initialization.test.ts
@@ -20,10 +20,10 @@ vi.mock('~/insomnia-data', () => ({
getById: vi.fn(),
},
project: {
- getById: vi.fn(),
+ get: vi.fn(),
},
userSession: {
- getOrCreate: vi.fn(),
+ get: vi.fn(),
},
workspaceMeta: {
getOrCreateByParentId: vi.fn(),
@@ -68,8 +68,8 @@ describe('sync-initialization', () => {
vi.clearAllMocks();
vi.mocked(services.workspace.getById).mockResolvedValue(workspace);
- vi.mocked(services.project.getById).mockResolvedValue(project);
- vi.mocked(services.userSession.getOrCreate).mockResolvedValue({ id: 'sess_123' } as any);
+ vi.mocked(services.project.get).mockResolvedValue(project);
+ vi.mocked(services.userSession.get).mockResolvedValue({ id: 'sess_123' } as any);
vi.mocked(services.workspaceMeta.getOrCreateByParentId).mockResolvedValue({ gitRepositoryId: null } as any);
vi.mocked(services.environment.getOrCreateForParentId).mockResolvedValue({} as any);
vi.mocked(services.cookieJar.getOrCreateForParentId).mockResolvedValue({} as any);
@@ -83,7 +83,7 @@ describe('sync-initialization', () => {
});
it('returns early when initializing a workspace backend project without a session', async () => {
- vi.mocked(services.userSession.getOrCreate).mockResolvedValue({ id: null } as any);
+ vi.mocked(services.userSession.get).mockResolvedValue({ id: null } as any);
await initializeWorkspaceBackendProject({ workspaceId: workspace._id });
diff --git a/packages/insomnia/src/main/analytics.ts b/packages/insomnia/src/main/analytics.ts
index b78061c767..dd71185315 100644
--- a/packages/insomnia/src/main/analytics.ts
+++ b/packages/insomnia/src/main/analytics.ts
@@ -1,8 +1,8 @@
import crypto from 'node:crypto';
-import { Analytics } from '@segment/analytics-node';
import * as Sentry from '@sentry/electron/main';
import { net } from 'electron';
+import { AnalyticsEvent, InsomniaAnalytics } from 'insomnia-analytics';
import { v4 as uuidv4 } from 'uuid';
import { services } from '~/insomnia-data';
@@ -15,7 +15,8 @@ import {
getSegmentWriteKey,
PLAYWRIGHT_TEST,
} from '../common/constants';
-import { platform } from '../common/platform';
+
+export { AnalyticsEvent };
let _currentOrganizationId: string | undefined;
@@ -23,18 +24,29 @@ export function setCurrentOrganizationId(id: string | undefined): void {
_currentOrganizationId = id;
}
-const analytics = new Analytics({
+const analytics = new InsomniaAnalytics({
writeKey: getSegmentWriteKey(),
- httpClient: {
- makeRequest(_options) {
- return net.fetch(_options.url, {
- method: _options.method,
- headers: _options.headers,
- body: _options.body,
- signal: AbortSignal.timeout(_options.httpRequestTimeout),
- });
+ app: {
+ appName: getProductName(),
+ appVersion: getAppVersion(),
+ osVersion: () => process.getSystemVersion(),
+ platform: 'app',
+ },
+ settings: {
+ httpClient: {
+ makeRequest(_options) {
+ return net.fetch(_options.url, {
+ method: _options.method,
+ headers: _options.headers,
+ body: _options.body,
+ signal: AbortSignal.timeout(_options.httpRequestTimeout),
+ });
+ },
},
},
+ onError: error => {
+ console.warn('[analytics] Error sending analytics event', error);
+ },
});
const getDeviceId = async () => {
@@ -42,97 +54,48 @@ const getDeviceId = async () => {
return settings.deviceId || (await services.settings.update(settings, { deviceId: uuidv4() })).deviceId;
};
-export enum SegmentEvent {
- appStarted = 'App Started',
- collectionCreate = 'Collection Created',
- dataExport = 'Data Exported',
- dataImport = 'Data Imported',
- loginSuccess = 'Login Success',
- documentCreate = 'Document Created',
- kongConnected = 'Kong Connected',
- kongSync = 'Kong Synced',
- requestBodyTypeSelect = 'Request Body Type Selected',
- requestCreated = 'Request Created',
- requestExecuted = 'Request Executed',
- collectionRunExecute = 'Collection Run Executed',
- projectLocalCreate = 'Local Project Created',
- projectLocalDelete = 'Local Project Deleted',
- testSuiteCreate = 'Test Suite Created',
- testSuiteDelete = 'Test Suite Deleted',
- unitTestCreate = 'Unit Test Created',
- unitTestDelete = 'Unit Test Deleted',
- unitTestRun = 'Ran Individual Unit Test',
- unitTestRunAll = 'Ran All Unit Tests',
- vcsSyncStart = 'VCS Sync Started',
- vcsSyncComplete = 'VCS Sync Completed',
- vcsAction = 'VCS Action Executed',
- gitAuthenticationCompleted = 'Git Authentication Completed',
- gitAuthenticationUpdated = 'Git Authentication Updated',
- buttonClick = 'Button Clicked',
- aiFeatureEnabled = 'AI Feature Enabled',
- aiFeatureDisabled = 'AI Feature Disabled',
- mcpClientConnected = 'MCP Client Connected',
- mcpClientDisconnected = 'MCP Client Disconnected',
- mcpToolCalled = 'MCP Tool Called',
- mcpResourceRead = 'MCP Resource Read',
- mcpPromptCalled = 'MCP Prompt Called',
- installPlugin = 'Plugin Installed',
-}
-
function hashString(input: string) {
return crypto.createHash('sha256').update(input).digest('hex');
}
-export async function trackSegmentEvent(event: SegmentEvent, properties?: Record) {
+export async function trackAnalyticsEvent(event: AnalyticsEvent, properties?: Record) {
if (PLAYWRIGHT_TEST) {
return;
}
const settings = await services.settings.getOrCreate();
- const userSession = await services.userSession.getOrCreate();
+ const userSession = await services.userSession.get();
if (!userSession?.hashedAccountId) {
userSession.hashedAccountId = userSession?.accountId ? hashString(userSession.accountId) : '';
}
const allowAnalytics = settings.enableAnalytics || userSession?.hashedAccountId;
- if (allowAnalytics) {
- try {
- const anonymousId = (await getDeviceId()) ?? '';
- const context = {
- app: { name: getProductName(), version: getAppVersion() },
- os: { name: _getOsName(), version: process.getSystemVersion() },
- };
+ if (!allowAnalytics) {
+ return;
+ }
- analytics.track(
- {
- event,
- properties: {
- ...(_currentOrganizationId && { organization_id: _currentOrganizationId }),
- ...properties,
- platform: 'app',
- },
- context,
- anonymousId,
- userId: userSession?.hashedAccountId || '',
+ try {
+ const anonymousId = (await getDeviceId()) ?? '';
+ analytics.track({
+ event,
+ properties: {
+ ...(_currentOrganizationId && { organization_id: _currentOrganizationId }),
+ ...properties,
+ },
+ anonymousId,
+ userId: userSession?.hashedAccountId || '',
+ });
+ } catch (error: unknown) {
+ console.warn('[analytics] Unexpected error while sending analytics event', error);
+ } finally {
+ if (!userSession?.hashedAccountId && [AnalyticsEvent.unitTestRun, AnalyticsEvent.unitTestRunAll].includes(event)) {
+ Sentry.captureException(`Run tests by anonymous`, {
+ tags: {
+ source: 'main/analytics',
},
- error => {
- if (error) {
- console.warn('[analytics] Error sending segment event', error);
- }
+ extra: {
+ organizationId: properties?.organizationId || '',
+ projectId: properties?.projectId || '',
},
- );
- } catch (error: unknown) {
- console.warn('[analytics] Unexpected error while sending segment event', error);
- } finally {
- if (!userSession?.hashedAccountId && [SegmentEvent.unitTestRun, SegmentEvent.unitTestRunAll].includes(event)) {
- Sentry.captureException(`Run tests by anonymous`, {
- tags: {
- source: 'main/analytics',
- },
- extra: {
- organizationId: properties?.organizationId || '',
- projectId: properties?.projectId || '',
- },
- });
- }
+ });
}
}
}
@@ -142,54 +105,30 @@ export async function trackPageView(name: string) {
return;
}
const settings = await services.settings.getOrCreate();
- const userSession = await services.userSession.getOrCreate();
+ const userSession = await services.userSession.get();
if (!userSession?.hashedAccountId) {
userSession.hashedAccountId = userSession?.accountId ? hashString(userSession.accountId) : '';
}
const allowAnalytics = settings.enableAnalytics || userSession?.hashedAccountId;
- if (allowAnalytics) {
- try {
- const anonymousId = (await getDeviceId()) ?? '';
- const context = {
- app: { name: getProductName(), version: getAppVersion() },
- os: { name: _getOsName(), version: process.getSystemVersion() },
- };
+ if (!allowAnalytics) {
+ return;
+ }
- analytics.page({ name, context, anonymousId, userId: userSession?.hashedAccountId }, error => {
- if (error) {
- console.warn('[analytics] Error sending segment event', error);
- }
+ try {
+ const anonymousId = (await getDeviceId()) ?? '';
+ analytics.page({ name, anonymousId, userId: userSession?.hashedAccountId });
+
+ if (userSession?.id) {
+ net.fetch(getApiBaseURL() + '/v1/telemetry/', {
+ method: 'POST',
+ headers: new Headers({
+ 'X-Session-Id': userSession?.id,
+ 'X-Insomnia-Client': getClientString(),
+ }),
});
-
- if (userSession?.id) {
- net.fetch(getApiBaseURL() + '/v1/telemetry/', {
- method: 'POST',
- headers: new Headers({
- 'X-Session-Id': userSession?.id,
- 'X-Insomnia-Client': getClientString(),
- }),
- });
- }
- } catch (error: unknown) {
- console.warn('[analytics] Unexpected error while sending segment event', error);
- }
- }
-}
-
-// ~~~~~~~~~~~~~~~~~ //
-// Private Functions //
-// ~~~~~~~~~~~~~~~~~ //
-function _getOsName() {
- switch (platform) {
- case 'darwin': {
- return 'mac';
- }
- case 'win32': {
- return 'windows';
- }
- default: {
- return platform;
}
+ } catch (error: unknown) {
+ console.warn('[analytics] Unexpected error while sending analytics event', error);
}
}
diff --git a/packages/insomnia/src/main/cloud-sync/initialization.ts b/packages/insomnia/src/main/cloud-sync/initialization.ts
index 436ae72ec5..19d341ef98 100644
--- a/packages/insomnia/src/main/cloud-sync/initialization.ts
+++ b/packages/insomnia/src/main/cloud-sync/initialization.ts
@@ -11,7 +11,7 @@ export const initializeWorkspaceBackendProject = async ({ workspaceId }: { works
const workspace = await services.workspace.getById(workspaceId);
invariant(workspace, 'Workspace not found');
- const { id } = await services.userSession.getOrCreate();
+ const { id } = await services.userSession.get();
if (!id) {
return;
}
@@ -32,10 +32,10 @@ export const syncNewWorkspaceIfNeeded = async ({ workspaceId }: { workspaceId: s
const workspace = await services.workspace.getById(workspaceId);
invariant(workspace, 'Workspace not found');
- const project = await services.project.getById(workspace.parentId);
+ const project = await services.project.get(workspace.parentId);
invariant(project, 'Project not found');
- const userSession = await services.userSession.getOrCreate();
+ const userSession = await services.userSession.get();
if (!userSession.id || !models.project.isRemoteProject(project)) {
return;
}
diff --git a/packages/insomnia/src/main/git-service.ts b/packages/insomnia/src/main/git-service.ts
index 19499f86c1..58c56d1715 100644
--- a/packages/insomnia/src/main/git-service.ts
+++ b/packages/insomnia/src/main/git-service.ts
@@ -66,7 +66,7 @@ import { routableFSClient } from '../sync/git/routable-fs-client';
import { shallowClone } from '../sync/git/shallow-clone';
import type { AutoResolvedConflict, MergeConflict } from '../sync/types';
import { invariant } from '../utils/invariant';
-import { SegmentEvent, trackSegmentEvent } from './analytics';
+import { AnalyticsEvent, trackAnalyticsEvent } from './analytics';
import { ipcMainHandle } from './ipc/electron';
// Initialize Git Remote Providers on module load
@@ -153,7 +153,7 @@ export function getErrorMessage(error: unknown): string {
// Non-Error objects
return 'Unknown Error';
}
-export function vcsSegmentEventProperties(type: 'git', action: VCSAction, error?: string) {
+export function vcsEventProperties(type: 'git', action: VCSAction, error?: string) {
return { type, action, error };
}
@@ -216,7 +216,7 @@ async function getGitRepository({ projectId, workspaceId }: { projectId: string;
}
invariant(projectId, 'Project ID is required');
- const project = await services.project.getById(projectId);
+ const project = await services.project.get(projectId);
invariant(project, 'Project not found');
invariant(models.project.isConnectedGitProject(project), 'Project is not linked to a git repository');
const repoId = models.project.getEffectiveRepoId(project);
@@ -290,7 +290,7 @@ export async function getProjectGitFileIssues({
workspaceId,
gitRepositoryId,
}: GetProjectGitFileIssuesOptions): Promise {
- const project = await services.project.getById(projectId);
+ const project = await services.project.get(projectId);
if (!project || !models.project.isConnectedGitProject(project)) {
return [];
}
@@ -1097,8 +1097,8 @@ export const cloneGitRepoAction = async ({
}
if (!projectId) {
- trackSegmentEvent(SegmentEvent.vcsSyncStart, {
- ...vcsSegmentEventProperties('git', 'clone'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsSyncStart, {
+ ...vcsEventProperties('git', 'clone'),
provider,
repoId: repoSettingsPatch._id,
});
@@ -1157,7 +1157,7 @@ export const cloneGitRepoAction = async ({
async function getProject() {
if (cloneIntoProjectId) {
- const project = await services.project.getById(cloneIntoProjectId);
+ const project = await services.project.get(cloneIntoProjectId);
invariant(project, 'Project not found');
await services.project.update(project, {
@@ -1230,8 +1230,8 @@ export const cloneGitRepoAction = async ({
});
await database.flushChanges(bufferId);
- trackSegmentEvent(SegmentEvent.vcsSyncComplete, {
- ...vcsSegmentEventProperties('git', 'clone'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsSyncComplete, {
+ ...vcsEventProperties('git', 'clone'),
providerName,
repoId: repoSettingsPatch._id,
});
@@ -1242,11 +1242,11 @@ export const cloneGitRepoAction = async ({
};
}
- const project = await services.project.getById(projectId);
+ const project = await services.project.get(projectId);
invariant(project, 'Project not found');
- trackSegmentEvent(SegmentEvent.vcsSyncStart, {
- ...vcsSegmentEventProperties('git', 'clone'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsSyncStart, {
+ ...vcsEventProperties('git', 'clone'),
provider,
repoId: repoSettingsPatch._id,
});
@@ -1304,8 +1304,8 @@ export const cloneGitRepoAction = async ({
});
await services.apiSpec.getOrCreateForParentId(workspace._id);
- trackSegmentEvent(SegmentEvent.vcsSyncComplete, {
- ...vcsSegmentEventProperties('git', 'clone', 'no directory found'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsSyncComplete, {
+ ...vcsEventProperties('git', 'clone', 'no directory found'),
providerName: provider,
repoId: repoSettingsPatch._id,
});
@@ -1323,8 +1323,8 @@ export const cloneGitRepoAction = async ({
const workspaces = await inMemoryFsClient.promises.readdir(workspaceBase);
if (workspaces.length === 0) {
- trackSegmentEvent(SegmentEvent.vcsSyncComplete, {
- ...vcsSegmentEventProperties('git', 'clone', 'no workspaces found'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsSyncComplete, {
+ ...vcsEventProperties('git', 'clone', 'no workspaces found'),
providerName: provider,
repoId: repoSettingsPatch._id,
});
@@ -1335,8 +1335,8 @@ export const cloneGitRepoAction = async ({
}
if (workspaces.length > 1) {
- trackSegmentEvent(SegmentEvent.vcsSyncComplete, {
- ...vcsSegmentEventProperties('git', 'clone', 'multiple workspaces found'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsSyncComplete, {
+ ...vcsEventProperties('git', 'clone', 'multiple workspaces found'),
providerName: provider,
repoId: repoSettingsPatch._id,
});
@@ -1359,7 +1359,7 @@ export const cloneGitRepoAction = async ({
const existingWorkspace = await services.workspace.getById(workspace._id);
if (existingWorkspace) {
- const project = await services.project.getById(existingWorkspace.parentId);
+ const project = await services.project.get(existingWorkspace.parentId);
if (!project) {
return {
errors: [
@@ -1424,8 +1424,8 @@ export const cloneGitRepoAction = async ({
// Flush DB changes
await database.flushChanges(bufferId);
- trackSegmentEvent(SegmentEvent.vcsSyncComplete, {
- ...vcsSegmentEventProperties('git', 'clone'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsSyncComplete, {
+ ...vcsEventProperties('git', 'clone'),
providerName: provider,
repoId: repoSettingsPatch._id,
});
@@ -1469,7 +1469,7 @@ export const updateGitRepoAction = async ({
const workspaceMeta = await services.workspaceMeta.getByParentId(workspaceId);
gitRepositoryId = workspaceMeta?.gitRepositoryId;
} else if (projectId) {
- const project = await services.project.getById(projectId);
+ const project = await services.project.get(projectId);
invariant(project, 'Project not found');
gitRepositoryId = project.gitRepositoryId;
}
@@ -1497,7 +1497,7 @@ export const updateGitRepoAction = async ({
gitRepositoryId: gitRepository._id,
});
} else if (projectId) {
- const project = await services.project.getById(projectId);
+ const project = await services.project.get(projectId);
invariant(project, 'Project not found');
await services.project.update(project, {
gitRepositoryId: models.project.toProtectedRepoId(gitRepository._id),
@@ -1557,7 +1557,7 @@ export const resetGitRepoAction = async ({ projectId, workspaceId }: { projectId
gitRepositoryId: null,
});
} else if (projectId) {
- const project = await services.project.getById(projectId);
+ const project = await services.project.get(projectId);
invariant(project, 'Project not found');
await services.project.update(project, {
gitRepositoryId: models.project.EMPTY_GIT_PROJECT_ID,
@@ -1601,8 +1601,8 @@ export const commitToGitRepoAction = async ({
providerName = credentials.provider;
}
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', 'commit'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', 'commit'),
providerName,
repoId: gitRepository._id,
});
@@ -1730,8 +1730,8 @@ export const commitAndPushToGitRepoAction = async ({
providerName = credentials.provider;
}
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', 'commit'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', 'commit'),
providerName,
repoId: repo._id,
});
@@ -1770,8 +1770,8 @@ export const commitAndPushToGitRepoAction = async ({
try {
await GitVCS.push(repo.credentialsId);
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', 'push'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', 'push'),
providerName,
repoId: repo._id,
});
@@ -1804,8 +1804,8 @@ export const commitAndPushToGitRepoAction = async ({
}
const errorMessage = getErrorMessage(err);
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', 'push', errorMessage),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', 'push', errorMessage),
providerName,
repoId: repo._id,
});
@@ -1846,8 +1846,8 @@ export const createNewGitBranchAction = async ({
providerName = credentials.provider;
}
await GitVCS.checkout(branch);
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', 'create_branch'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', 'create_branch'),
providerName,
repoId: gitRepository._id,
});
@@ -2021,8 +2021,8 @@ export const mergeGitBranch = async ({
await repoFileWatcherRegistry.importAllFiles(gitRepoId);
clearConflictSuppression(gitRepository._id);
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', 'merge_branch'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', 'merge_branch'),
providerName,
repoId: gitRepository._id,
});
@@ -2050,8 +2050,8 @@ export const mergeGitBranch = async ({
errorMessage = `${err.message}, ${err.data.response}`;
}
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', 'merge_branch', errorMessage),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', 'merge_branch', errorMessage),
providerName,
repoId: gitRepository._id,
});
@@ -2086,8 +2086,8 @@ export const deleteGitBranchAction = async ({
providerName = credentials.provider;
}
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', 'delete_branch'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', 'delete_branch'),
providerName,
repoId: repo._id,
});
@@ -2163,8 +2163,8 @@ export const pushToGitRemoteAction = async ({
const bufferId = await database.bufferChanges();
await GitVCS.push(gitRepository.credentialsId);
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', force ? 'force_push' : 'push'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', force ? 'force_push' : 'push'),
providerName,
repoId: gitRepository._id,
});
@@ -2208,8 +2208,8 @@ export const pushToGitRemoteAction = async ({
}
const errorMessage = getErrorMessage(err);
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', 'push', errorMessage),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', 'push', errorMessage),
providerName,
repoId: gitRepository._id,
});
@@ -2277,8 +2277,8 @@ export async function pullFromGitRemote({ projectId, workspaceId }: { projectId:
await repoFileWatcherRegistry.importAllFiles(gitRepository._id);
clearConflictSuppression(repoId);
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', 'pull'),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', 'pull'),
providerName: credentials.provider,
repoId: gitRepository._id,
});
@@ -2330,8 +2330,8 @@ export async function pullFromGitRemote({ projectId, workspaceId }: { projectId:
providerName = credentials.provider;
}
- trackSegmentEvent(SegmentEvent.vcsAction, {
- ...vcsSegmentEventProperties('git', 'pull', errorMessage),
+ trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
+ ...vcsEventProperties('git', 'pull', errorMessage),
providerName,
repoId: gitRepository._id,
});
@@ -2679,7 +2679,7 @@ const getRepositoryDirectoryTree = async ({
repositoryTree: FileTree;
folderList: Record;
}> => {
- const project = await services.project.getById(projectId);
+ const project = await services.project.get(projectId);
if (project && models.project.isEmptyGitProject(project)) {
return {
@@ -2788,9 +2788,9 @@ async function completeSignInToGitProvider({
}
if (isEditing) {
- trackSegmentEvent(SegmentEvent.gitAuthenticationUpdated, { provider });
+ trackAnalyticsEvent(AnalyticsEvent.gitAuthenticationUpdated, { provider });
} else {
- trackSegmentEvent(SegmentEvent.gitAuthenticationCompleted, { provider });
+ trackAnalyticsEvent(AnalyticsEvent.gitAuthenticationCompleted, { provider });
}
return {};
@@ -2893,7 +2893,7 @@ export async function runAllGitRepoMigrations(): Promise {
const logs: string[] = [];
const failedProjects: { id: string; name: string }[] = [];
- const allProjects = await services.project.all();
+ const allProjects = await services.project.list();
const gitProjects = allProjects.filter((p): p is GitProject => models.project.isConnectedGitProject(p));
if (gitProjects.length === 0) return { logs, failedProjects, totalProjects: 0 };
@@ -2947,7 +2947,7 @@ export async function runAllGitRepoMigrations(): Promise {
failedProjects.map(async ({ id, name }) => {
logs.push(`${ts()} [INFO] ["${name}"] Converting to local project`);
try {
- const project = await services.project.getById(id);
+ const project = await services.project.get(id);
if (!project || !models.project.isConnectedGitProject(project)) {
logs.push(`${ts()} [WARN] ["${name}"] Project not found or already local — skipping`);
return;
diff --git a/packages/insomnia/src/main/install-plugin.ts b/packages/insomnia/src/main/install-plugin.ts
index f9f5d53732..cea45872d0 100644
--- a/packages/insomnia/src/main/install-plugin.ts
+++ b/packages/insomnia/src/main/install-plugin.ts
@@ -7,7 +7,7 @@ import { promisify } from 'node:util';
import { app, net } from 'electron';
import { services } from '~/insomnia-data';
-import { SegmentEvent, trackSegmentEvent } from '~/main/analytics';
+import { AnalyticsEvent, trackAnalyticsEvent } from '~/main/analytics';
import { isDevelopment } from '../common/constants';
import { validatePluginName } from '../utils/plugin';
@@ -160,7 +160,7 @@ export default async function installPlugin(pluginName: string, allowScopedPacka
}),
);
- trackSegmentEvent(SegmentEvent.installPlugin, {
+ trackAnalyticsEvent(AnalyticsEvent.installPlugin, {
pluginName: moduleName,
pluginVersion: info.version,
});
diff --git a/packages/insomnia/src/main/ipc/electron.ts b/packages/insomnia/src/main/ipc/electron.ts
index e3c8344e29..64cd849ef5 100644
--- a/packages/insomnia/src/main/ipc/electron.ts
+++ b/packages/insomnia/src/main/ipc/electron.ts
@@ -192,7 +192,7 @@ export type MainOnChannels =
| 'socketIO.event.on'
| 'startExecution'
| 'trackPageView'
- | 'trackSegmentEvent'
+ | 'trackAnalyticsEvent'
| 'updateLatestStepName'
| 'webSocket.close'
| 'webSocket.closeAll'
diff --git a/packages/insomnia/src/main/ipc/main.ts b/packages/insomnia/src/main/ipc/main.ts
index e32d45ae6e..a5fa996fa2 100644
--- a/packages/insomnia/src/main/ipc/main.ts
+++ b/packages/insomnia/src/main/ipc/main.ts
@@ -37,8 +37,8 @@ import type {
import type { HiddenBrowserWindowBridgeAPI } from '../../entry.hidden-window';
import type { PluginTemplateTag, RenderedRequest } from '../../templating/types';
-import type { SegmentEvent } from '../analytics';
-import { setCurrentOrganizationId, trackPageView, trackSegmentEvent } from '../analytics';
+import type { AnalyticsEvent } from '../analytics';
+import { setCurrentOrganizationId, trackAnalyticsEvent, trackPageView } from '../analytics';
import {
authorizeUserInDefaultBrowser,
cancelAuthorizationInDefaultBrowser,
@@ -186,7 +186,7 @@ export interface RendererToMainBridgeAPI {
secretStorage: secretStorageBridgeAPI;
electronStorage: electronStorageBridgeAPI;
sync: SyncBridgeAPI;
- trackSegmentEvent: (options: { event: string; properties?: Record }) => void;
+ trackAnalyticsEvent: (options: { event: string; properties?: Record }) => void;
trackPageView: (options: { name: string }) => void;
setCurrentOrganizationId: (organizationId: string | undefined) => void;
showNunjucksContextMenu: (options: {
@@ -399,10 +399,13 @@ export function registerMainHandlers() {
ipcMainHandle('readDir', readDir);
ipcMainHandle('readOrCreateDataDir', async (_, options: { folder: string }) => {
- const dataPath = app.getPath('userData');
- const folderPath = path.join(dataPath, options.folder);
+ const folderPath = path.join(app.getPath('userData'), options.folder);
mkdirSync(folderPath, { recursive: true });
- return readDir(_, { path: folderPath });
+ try {
+ return await readDir(_, { path: folderPath });
+ } catch {
+ return [];
+ }
});
ipcMainHandle('curlRequest', (_, options: Parameters[0]) => {
@@ -413,9 +416,12 @@ export function registerMainHandlers() {
cancelCurlRequest(requestId);
});
- ipcMainOn('trackSegmentEvent', (_, options: { event: SegmentEvent; properties?: Record }): void => {
- trackSegmentEvent(options.event, options.properties);
- });
+ ipcMainOn(
+ 'trackAnalyticsEvent',
+ (_, options: { event: AnalyticsEvent; properties?: Record }): void => {
+ trackAnalyticsEvent(options.event, options.properties);
+ },
+ );
ipcMainOn('trackPageView', (_, options: { name: string }): void => {
trackPageView(options.name);
});
diff --git a/packages/insomnia/src/main/llm-config-service.ts b/packages/insomnia/src/main/llm-config-service.ts
index 42d7f8f4ce..1e17a800a7 100644
--- a/packages/insomnia/src/main/llm-config-service.ts
+++ b/packages/insomnia/src/main/llm-config-service.ts
@@ -4,7 +4,7 @@ import { app } from 'electron';
import { LLM_BACKENDS } from '~/common/constants';
import { services } from '~/insomnia-data';
-import { SegmentEvent, trackSegmentEvent } from '~/main/analytics';
+import { AnalyticsEvent, trackAnalyticsEvent } from '~/main/analytics';
import { ipcMainHandle } from '~/main/ipc/electron';
const LLM_PLUGIN_NAME = 'insomnia-llm';
@@ -123,7 +123,7 @@ export const getAIFeatureEnabled = async (feature: AIFeatureNames): Promise => {
await services.pluginData.upsertByKey(LLM_PLUGIN_NAME, `feature.${feature}`, String(enabled));
- trackSegmentEvent(enabled ? SegmentEvent.aiFeatureEnabled : SegmentEvent.aiFeatureDisabled, {
+ trackAnalyticsEvent(enabled ? AnalyticsEvent.aiFeatureEnabled : AnalyticsEvent.aiFeatureDisabled, {
feature: feature,
set_for: 'user',
});
diff --git a/packages/insomnia/src/main/mcp/client-requests.ts b/packages/insomnia/src/main/mcp/client-requests.ts
index 4b3f6093d9..24d09d9777 100644
--- a/packages/insomnia/src/main/mcp/client-requests.ts
+++ b/packages/insomnia/src/main/mcp/client-requests.ts
@@ -11,7 +11,7 @@ import {
} from '@modelcontextprotocol/sdk/types.js';
import { METHOD_SUBSCRIBE_RESOURCE, METHOD_UNSUBSCRIBE_RESOURCE } from '~/common/mcp-utils';
-import { SegmentEvent, trackSegmentEvent } from '~/main/analytics';
+import { AnalyticsEvent, trackAnalyticsEvent } from '~/main/analytics';
import { getActiveMcpClient, getReadyActiveMcpConnectionContext, writeEventLogAndNotify } from '~/main/mcp/common';
import type { CommonMcpOptions, McpMessageEventWithoutBase } from '~/main/mcp/types';
@@ -29,7 +29,7 @@ export const callTool = async (options: CommonMcpOptions & CallToolRequest['para
const mcpClient = getActiveMcpClient(requestId);
if (mcpClient) {
const response = await mcpClient.callTool(params, CompatibilityCallToolResultSchema);
- trackSegmentEvent(SegmentEvent.mcpToolCalled);
+ trackAnalyticsEvent(AnalyticsEvent.mcpToolCalled);
return response.content;
}
return null;
@@ -49,7 +49,7 @@ export const getPrompt = async (options: CommonMcpOptions & GetPromptRequest['pa
const mcpClient = getActiveMcpClient(options.requestId);
if (mcpClient) {
const prompt = await mcpClient.getPrompt(params);
- trackSegmentEvent(SegmentEvent.mcpPromptCalled);
+ trackAnalyticsEvent(AnalyticsEvent.mcpPromptCalled);
return prompt;
}
return null;
@@ -80,7 +80,7 @@ export const readResource = async (options: CommonMcpOptions & ReadResourceReque
const mcpClient = getActiveMcpClient(requestId);
if (mcpClient) {
const resource = await mcpClient.readResource(params);
- trackSegmentEvent(SegmentEvent.mcpResourceRead);
+ trackAnalyticsEvent(AnalyticsEvent.mcpResourceRead);
return resource;
}
return null;
diff --git a/packages/insomnia/src/main/network/mcp.ts b/packages/insomnia/src/main/network/mcp.ts
index 28378a696d..c17dfbf78c 100644
--- a/packages/insomnia/src/main/network/mcp.ts
+++ b/packages/insomnia/src/main/network/mcp.ts
@@ -21,7 +21,7 @@ import electron from 'electron';
import { getAppVersion, getProductName, REALTIME_EVENTS_CHANNELS } from '~/common/constants';
import { getMcpMethodFromMessage, METHOD_NOTIFICATION_CANCELLED } from '~/common/mcp-utils';
import { models, services } from '~/insomnia-data';
-import { SegmentEvent, trackSegmentEvent } from '~/main/analytics';
+import { AnalyticsEvent, trackAnalyticsEvent } from '~/main/analytics';
import {
callTool,
getPrompt,
@@ -257,7 +257,7 @@ const createTransportAndConnect = async (context: ConnectionContext, mcpClient:
}
const authDisabled = 'disabled' in mcpRequest.authentication && mcpRequest.authentication.disabled;
const isFirstConnection = !mcpRequest.connected;
- trackSegmentEvent(SegmentEvent.mcpClientConnected, {
+ trackAnalyticsEvent(AnalyticsEvent.mcpClientConnected, {
transportType: connectionOptions.transportType,
firstTime: isFirstConnection,
...(connectionOptions.transportType === models.mcpRequest.TRANSPORT_TYPES.HTTP
@@ -455,7 +455,7 @@ const closeMcpConnection = async (options: CommonMcpOptions) => {
// Execute clear resource subscription in main process rather than UI to make sure closeAllMcpConnections method will clear subscriptions
await services.mcpRequest.clearResourceSubscriptions(requestId);
}
- trackSegmentEvent(SegmentEvent.mcpClientDisconnected);
+ trackAnalyticsEvent(AnalyticsEvent.mcpClientDisconnected);
};
const closeAllMcpConnections = () => {
diff --git a/packages/insomnia/src/network/__tests__/network.test.ts b/packages/insomnia/src/network/__tests__/network.test.ts
index f3815d583b..0c8a1d3ca2 100644
--- a/packages/insomnia/src/network/__tests__/network.test.ts
+++ b/packages/insomnia/src/network/__tests__/network.test.ts
@@ -35,10 +35,6 @@ describe('getAuthQueryParams', () => {
});
});
describe('sendCurlAndWriteTimeline()', () => {
- beforeEach(async () => {
- await services.project.all();
- });
-
it('sends a generic request', async () => {
const workspace = await services.workspace.create();
const settings = await services.settings.getOrCreate();
diff --git a/packages/insomnia/src/root.tsx b/packages/insomnia/src/root.tsx
index ea09034463..d38e004925 100644
--- a/packages/insomnia/src/root.tsx
+++ b/packages/insomnia/src/root.tsx
@@ -35,7 +35,7 @@ import {
GIT_PROVIDER_COMPLETE_SIGN_IN_FETCHER_KEY,
useGitProviderCompleteSignInFetcher,
} from '~/routes/git-credentials.complete-sign-in';
-import { PENDING_IMPORT_ATTRIBUTION_KEY, SegmentEvent, trackImportEvent } from '~/ui/analytics';
+import { AnalyticsEvent, PENDING_IMPORT_ATTRIBUTION_KEY, trackImportEvent } from '~/ui/analytics';
import { getLoginUrl } from '~/ui/auth-session-provider.client';
import { CopyButton } from '~/ui/components/base/copy-button';
import { Link } from '~/ui/components/base/link';
@@ -161,7 +161,7 @@ export const useRootLoaderData = () => {
export async function clientLoader(_args: Route.ClientLoaderArgs) {
const settings = await services.settings.get();
const workspaceCount = await services.workspace.count();
- const userSession = await services.userSession.getOrCreate();
+ const userSession = await services.userSession.get();
const cloudCredentials = await services.cloudCredential.all();
return {
@@ -203,6 +203,11 @@ export const Layout = ({ children }: { children: React.ReactNode }) => {
*
insomnia://*
;
+ frame-src
+ blob:
+ *
+ insomnia://*
+ ;
script-src
'self'
'unsafe-eval'
@@ -381,15 +386,14 @@ const Root = () => {
JSON.stringify({ importSource, importSourceUrl }),
);
}
-
- const userSession = await services.userSession.getOrCreate();
+ const userSession = await services.userSession.get();
if (!userSession.id) {
window.sessionStorage.setItem('pendingDeepLinkAfterAuthorize', url);
window.localStorage.setItem('logoutMessage', 'Please log in to import this resource.');
- trackImportEvent(SegmentEvent.importLoginRequired);
+ trackImportEvent(AnalyticsEvent.importLoginRequired);
return navigate(href('/auth/login'));
}
- trackImportEvent(SegmentEvent.importStarted, { source: 'import-url' });
+ trackImportEvent(AnalyticsEvent.importStarted, { source: 'import-url' });
if (params.uri) {
return setImportObject({
@@ -511,7 +515,7 @@ const Root = () => {
if (urlWithoutParams === 'insomnia://app/open/organization') {
// if user is logged out, navigate to authorize instead
// gracefully handle open org in app from browser
- const userSession = await services.userSession.getOrCreate();
+ const userSession = await services.userSession.get();
if (!userSession.id || userSession.id === '') {
const url = new URL(getLoginUrl());
window.main.openInBrowser(url.toString());
@@ -635,7 +639,7 @@ const Root = () => {
if (pendingDeepLink && organizationId && organizationId !== models.organization.SCRATCHPAD_ORGANIZATION_ID) {
window.sessionStorage.removeItem('pendingDeepLinkAfterAuthorize');
window.sessionStorage.setItem('suppressWelcomeModals', 'true');
- trackImportEvent(SegmentEvent.importResumedAfterLogin);
+ trackImportEvent(AnalyticsEvent.importResumedAfterLogin);
window.main.openDeepLink(pendingDeepLink);
}
}, [organizationId]);
diff --git a/packages/insomnia/src/routes/ai.generate-commit-messages.tsx b/packages/insomnia/src/routes/ai.generate-commit-messages.tsx
index 1264f0c071..2db596ad68 100644
--- a/packages/insomnia/src/routes/ai.generate-commit-messages.tsx
+++ b/packages/insomnia/src/routes/ai.generate-commit-messages.tsx
@@ -1,6 +1,6 @@
import { href } from 'react-router';
-import { SegmentEvent } from '~/ui/analytics';
+import { AnalyticsEvent } from '~/ui/analytics';
import { showToast } from '~/ui/components/toast-notification';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -38,8 +38,8 @@ export async function clientAction(args: Route.ClientActionArgs) {
.join('\n'),
});
- window.main.trackSegmentEvent({
- event: SegmentEvent.recommendCommitsGenerated,
+ window.main.trackAnalyticsEvent({
+ event: AnalyticsEvent.recommendCommitsGenerated,
properties: {
file_count: commits?.map(commit => commit.files?.length || 0)?.reduce((a, b) => a + b, 0),
group_count: commits?.length || 0,
diff --git a/packages/insomnia/src/routes/auth.authorize.tsx b/packages/insomnia/src/routes/auth.authorize.tsx
index e4e6f7255e..a7532fce33 100644
--- a/packages/insomnia/src/routes/auth.authorize.tsx
+++ b/packages/insomnia/src/routes/auth.authorize.tsx
@@ -4,7 +4,7 @@ import { Button, Heading } from 'react-aria-components';
import { href, redirect, useFetchers, useNavigate } from 'react-router';
import { services } from '~/insomnia-data';
-import { SegmentEvent } from '~/ui/analytics';
+import { AnalyticsEvent } from '~/ui/analytics';
import { getLoginUrl, submitAuthCode } from '~/ui/auth-session-provider.client';
import { Icon } from '~/ui/components/icon';
import { validateVaultKey } from '~/ui/vault-key.client';
@@ -32,18 +32,18 @@ export async function clientAction({ request }: Route.ClientActionArgs) {
};
}
console.log('Login successful');
- window.main.trackSegmentEvent({
- event: SegmentEvent.loginSuccess,
+ window.main.trackAnalyticsEvent({
+ event: AnalyticsEvent.loginSuccess,
});
window.localStorage.setItem('hasUserLoggedInBefore', 'true');
- const userSession = await services.userSession.getOrCreate();
+ const userSession = await services.userSession.get();
const { accountId, id: sessionId } = userSession;
try {
// check vault salt exists in server
const { salt: vaultSalt } = await getVault({ sessionId });
if (vaultSalt) {
// save vault salt to session
- await services.userSession.update(userSession, { vaultSalt });
+ await services.userSession.update({ vaultSalt });
// get vault key saved in local
const localVaultKey = await getVaultKeyFromStorage(accountId);
if (localVaultKey) {
@@ -52,7 +52,7 @@ export async function clientAction({ request }: Route.ClientActionArgs) {
if (validateResult) {
// Encrypt vault key and save encrypted vault key & raw vault salt to session
const encryptedVaultKey = await window.main.secretStorage.encryptString(localVaultKey);
- await services.userSession.update(userSession, { vaultKey: encryptedVaultKey, vaultSalt });
+ await services.userSession.update({ vaultKey: encryptedVaultKey, vaultSalt });
}
}
}
diff --git a/packages/insomnia/src/routes/auth.clear-vault-key.tsx b/packages/insomnia/src/routes/auth.clear-vault-key.tsx
index 5ec363e44e..eede8df665 100644
--- a/packages/insomnia/src/routes/auth.clear-vault-key.tsx
+++ b/packages/insomnia/src/routes/auth.clear-vault-key.tsx
@@ -10,7 +10,7 @@ import type { Route } from './+types/auth.clear-vault-key';
export async function clientAction({ request }: Route.ClientActionArgs) {
const { organizations = [], sessionId: resetVaultClientSessionId } = await request.json();
- const userSession = await services.userSession.getOrCreate();
+ const userSession = await services.userSession.get();
const { id: sessionId } = userSession;
const { salt: newVaultSalt } =
(await getVault({ sessionId }).catch(error => {
@@ -21,7 +21,7 @@ export async function clientAction({ request }: Route.ClientActionArgs) {
// remove all secret environment variables
await services.environment.removeAllSecrets(organizations);
// Update vault salt and delete vault key from session
- await services.userSession.update(userSession, { vaultSalt: newVaultSalt, vaultKey: '' });
+ await services.userSession.update({ vaultSalt: newVaultSalt, vaultKey: '' });
// show notification
electron.ipcRenderer.emit('show-toast', null, {
content: {
diff --git a/packages/insomnia/src/routes/auth.login.tsx b/packages/insomnia/src/routes/auth.login.tsx
index fbd5439ca9..ad5651f5cf 100644
--- a/packages/insomnia/src/routes/auth.login.tsx
+++ b/packages/insomnia/src/routes/auth.login.tsx
@@ -3,7 +3,7 @@ import { Button } from 'react-aria-components';
import { href, redirect, useNavigate } from 'react-router';
import { models } from '~/insomnia-data';
-import { SegmentEvent } from '~/ui/analytics';
+import { AnalyticsEvent } from '~/ui/analytics';
import { getLoginUrl } from '~/ui/auth-session-provider.client';
import { Icon } from '~/ui/components/icon';
import { Tooltip } from '~/ui/components/tooltip';
@@ -152,8 +152,8 @@ const Component = () => {
Note: This change is backwards compatible, but we strongly recommend{' '}
-
+
following these best practices
{' '}
when returning to an earlier version of Insomnia.
diff --git a/packages/insomnia/src/routes/git.all-connected-repos.tsx b/packages/insomnia/src/routes/git.all-connected-repos.tsx
index 2f9749865f..5b4762d307 100644
--- a/packages/insomnia/src/routes/git.all-connected-repos.tsx
+++ b/packages/insomnia/src/routes/git.all-connected-repos.tsx
@@ -7,7 +7,7 @@ import { models, services } from '~/insomnia-data';
import { createFetcherLoadHook } from '~/utils/router';
export async function clientLoader() {
- const { accountId } = await services.userSession.getOrCreate();
+ const { accountId } = await services.userSession.get();
const organizations = JSON.parse(localStorage.getItem(`${accountId}:organizations`) || '[]') as Organization[];
const allProjects = (
await Promise.all(
diff --git a/packages/insomnia/src/routes/import.resources.tsx b/packages/insomnia/src/routes/import.resources.tsx
index 232ee26a27..92c73befb1 100644
--- a/packages/insomnia/src/routes/import.resources.tsx
+++ b/packages/insomnia/src/routes/import.resources.tsx
@@ -35,7 +35,7 @@ export const importScannedResources = async ({
invariant(organizationId && typeof organizationId === 'string', 'OrganizationId is required.');
invariant(projectId && typeof projectId === 'string', 'ProjectId is required.');
- const project = await services.project.getById(projectId);
+ const project = await services.project.get(projectId);
invariant(project, 'Project not found.');
return await (typeof workspaceId === 'string' && workspaceId
diff --git a/packages/insomnia/src/routes/import.scan.tsx b/packages/insomnia/src/routes/import.scan.tsx
index 8d7dae62bf..d59d2c9b06 100644
--- a/packages/insomnia/src/routes/import.scan.tsx
+++ b/packages/insomnia/src/routes/import.scan.tsx
@@ -9,7 +9,7 @@ import {
scanResources,
} from '~/common/import';
import type { ImportEntry } from '~/main/importers/entities';
-import { SegmentEvent, trackImportEvent } from '~/ui/analytics';
+import { AnalyticsEvent, trackImportEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -27,7 +27,7 @@ export const scanImportResources = async (data: {
invariant(typeof source === 'string', 'Source is required.');
invariant(IMPORT_SOURCE_TYPES.includes(source), 'Unsupported import type');
- trackImportEvent(SegmentEvent.importScanned, { source });
+ trackImportEvent(AnalyticsEvent.importScanned, { source });
const contentList: ImportEntry[] = [];
diff --git a/packages/insomnia/src/routes/organization.$organizationId.collaborators.invites.$invitationId.reinvite.tsx b/packages/insomnia/src/routes/organization.$organizationId.collaborators.invites.$invitationId.reinvite.tsx
index 6cd137af9d..ec19c2ed8f 100644
--- a/packages/insomnia/src/routes/organization.$organizationId.collaborators.invites.$invitationId.reinvite.tsx
+++ b/packages/insomnia/src/routes/organization.$organizationId.collaborators.invites.$invitationId.reinvite.tsx
@@ -10,7 +10,7 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
const { organizationId, invitationId } = params;
try {
- const user = await services.userSession.getOrCreate();
+ const user = await services.userSession.get();
const sessionId = user.id;
const response = await reinvite({
diff --git a/packages/insomnia/src/routes/organization.$organizationId.collaborators.invites.$invitationId.tsx b/packages/insomnia/src/routes/organization.$organizationId.collaborators.invites.$invitationId.tsx
index e236906471..c27bcdfabd 100644
--- a/packages/insomnia/src/routes/organization.$organizationId.collaborators.invites.$invitationId.tsx
+++ b/packages/insomnia/src/routes/organization.$organizationId.collaborators.invites.$invitationId.tsx
@@ -16,7 +16,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
invariant(typeof roleId === 'string', 'Role ID is required');
try {
- const user = await services.userSession.getOrCreate();
+ const user = await services.userSession.get();
const sessionId = user.id;
const response = await updateInvitationRole({
diff --git a/packages/insomnia/src/routes/organization.$organizationId.members.$userId.roles.tsx b/packages/insomnia/src/routes/organization.$organizationId.members.$userId.roles.tsx
index 5f3201d63e..50aa9188b4 100644
--- a/packages/insomnia/src/routes/organization.$organizationId.members.$userId.roles.tsx
+++ b/packages/insomnia/src/routes/organization.$organizationId.members.$userId.roles.tsx
@@ -16,7 +16,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
invariant(typeof roleId === 'string', 'Role ID is required');
try {
- const user = await services.userSession.getOrCreate();
+ const user = await services.userSession.get();
const sessionId = user.id;
const response = await updateUserRoles({
organizationId,
diff --git a/packages/insomnia/src/routes/organization.$organizationId.permissions.tsx b/packages/insomnia/src/routes/organization.$organizationId.permissions.tsx
index c0f9f61db3..a75245ce06 100644
--- a/packages/insomnia/src/routes/organization.$organizationId.permissions.tsx
+++ b/packages/insomnia/src/routes/organization.$organizationId.permissions.tsx
@@ -26,7 +26,7 @@ export const fallbackBilling = Object.freeze({
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
const { organizationId } = params;
- const { id: sessionId, accountId } = await services.userSession.getOrCreate();
+ const { id: sessionId, accountId } = await services.userSession.get();
if (models.organization.isScratchpadOrganizationId(organizationId)) {
return {
diff --git a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId._index.tsx b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId._index.tsx
index 755cd93c8c..372846b896 100644
--- a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId._index.tsx
+++ b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId._index.tsx
@@ -37,7 +37,7 @@ import { useInsomniaSyncPullRemoteFileActionFetcher } from '~/routes/organizatio
import { useProjectLoaderData } from '~/routes/organization.$organizationId.project.$projectId';
import { useWorkspaceNewActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.new';
import { useStorageRulesLoaderFetcher } from '~/routes/organization.$organizationId.storage-rules';
-import { SegmentEvent, trackOnceDaily } from '~/ui/analytics';
+import { AnalyticsEvent, trackOnceDaily } from '~/ui/analytics';
import { AvatarGroup } from '~/ui/components/avatar';
import { WorkspaceCardDropdown } from '~/ui/components/dropdowns/workspace-card-dropdown';
import { ErrorBoundary } from '~/ui/components/error-boundary';
@@ -389,7 +389,7 @@ const Component = () => {
onChange={filter => {
setWorkspaceListFilter(filter);
if (filter.trim() !== '') {
- trackOnceDaily(SegmentEvent.homepageFiltered);
+ trackOnceDaily(AnalyticsEvent.homepageFiltered);
}
}}
>
@@ -483,8 +483,8 @@ const Component = () => {