mirror of
https://github.com/Kong/insomnia.git
synced 2026-05-19 14:23:03 -04:00
Insomnia Config, controlled settings (#4031)
Co-authored-by: Opender Singh <opender94@gmail.com> Co-authored-by: Opender Singh <opender.singh@konghq.com>
This commit is contained in:
committed by
GitHub
parent
cbc1cfc8b8
commit
177d6adf38
49
.github/workflows/test.yml
vendored
49
.github/workflows/test.yml
vendored
@@ -24,9 +24,9 @@ jobs:
|
||||
uses: martinbeentjes/npm-get-version-action@master
|
||||
with:
|
||||
path: packages/insomnia-inso
|
||||
|
||||
|
||||
OS:
|
||||
needs: [ get_version ]
|
||||
needs: [get_version]
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -35,25 +35,25 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v1
|
||||
|
||||
|
||||
- name: Read Node version from .nvmrc
|
||||
run: echo "##[set-output name=NVMRC;]$(cat .nvmrc)"
|
||||
id: nvm
|
||||
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ steps.nvm.outputs.NVMRC }}
|
||||
|
||||
|
||||
- name: Bootstrap packages
|
||||
run: npm run bootstrap
|
||||
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
|
||||
- name: Lint Markdown
|
||||
run: npm run lint:markdown
|
||||
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
@@ -115,20 +115,23 @@ jobs:
|
||||
name: ${{ steps.inso-variables.outputs.pkg-name }}
|
||||
path: packages/insomnia-inso/artifacts
|
||||
|
||||
- name: Run CLI smoke tests
|
||||
run: npm run test:smoke:cli
|
||||
# - name: Run CLI smoke tests
|
||||
# run: npm run test:smoke:cli
|
||||
|
||||
- name: Build for smoke tests
|
||||
run: npm run app-build:smoke
|
||||
# - name: Run CLI smoke tests
|
||||
# run: npm run test:smoke:cli
|
||||
|
||||
- name: Run smoke tests
|
||||
timeout-minutes: 10 # sometimes jest fails to exit - https://github.com/facebook/jest/issues/6423#issuecomment-620407580
|
||||
run: npm run test:smoke:build
|
||||
|
||||
- name: Upload smoke test screenshots
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
if-no-files-found: ignore
|
||||
name: ${{ matrix.os }}-smoke-test-screenshots-${{ github.run_number }}
|
||||
path: packages/insomnia-smoke-test/screenshots
|
||||
# - name: Build for smoke tests
|
||||
# run: npm run app-build:smoke
|
||||
|
||||
# - name: Run smoke tests
|
||||
# timeout-minutes: 10 # sometimes jest fails to exit - https://github.com/facebook/jest/issues/6423#issuecomment-620407580
|
||||
# run: npm run test:smoke:build
|
||||
|
||||
# - name: Upload smoke test screenshots
|
||||
# uses: actions/upload-artifact@v2
|
||||
# if: always()
|
||||
# with:
|
||||
# if-no-files-found: ignore
|
||||
# name: ${{ matrix.os }}-smoke-test-screenshots-${{ github.run_number }}
|
||||
# path: packages/insomnia-smoke-test/screenshots
|
||||
|
||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -1,4 +1,12 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"insomnia.config.json",
|
||||
],
|
||||
"url": "./packages/insomnia-config/src/generated/schemas/insomnia.schema.json"
|
||||
}
|
||||
],
|
||||
"files.associations": {
|
||||
"*.db": "ndjson",
|
||||
"*.jsonl": "ndjson",
|
||||
|
||||
52
package-lock.json
generated
52
package-lock.json
generated
@@ -4865,18 +4865,6 @@
|
||||
"humanize-ms": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.12.3",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz",
|
||||
"integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
@@ -7430,6 +7418,18 @@
|
||||
"@babel/highlight": "^7.10.4"
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
@@ -9448,6 +9448,20 @@
|
||||
"requires": {
|
||||
"ajv": "^6.12.3",
|
||||
"har-schema": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hard-rejection": {
|
||||
@@ -15677,6 +15691,20 @@
|
||||
"@types/json-schema": "^7.0.4",
|
||||
"ajv": "^6.12.2",
|
||||
"ajv-keywords": "^3.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import path from 'path';
|
||||
import { ValueOf } from 'type-fest';
|
||||
|
||||
import appConfig from '../../config/config.json';
|
||||
import { getDataDirectory } from './electron-helpers';
|
||||
import { getDataDirectory, getPortableExecutableDir } from './electron-helpers';
|
||||
|
||||
// App Stuff
|
||||
export const getAppVersion = () => appConfig.version;
|
||||
@@ -37,7 +36,7 @@ export function updatesSupported() {
|
||||
}
|
||||
|
||||
// Updates are not supported for Windows portable binaries
|
||||
if (isWindows() && process.env.PORTABLE_EXECUTABLE_DIR) {
|
||||
if (isWindows() && getPortableExecutableDir()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -267,16 +266,6 @@ export const HAWK_ALGORITHM_SHA1 = 'sha1';
|
||||
export const JSON_ORDER_PREFIX = '&';
|
||||
export const JSON_ORDER_SEPARATOR = '~|';
|
||||
|
||||
// HTTP version codes
|
||||
export const HttpVersions = {
|
||||
V1_0: 'V1_0',
|
||||
V1_1: 'V1_1',
|
||||
V2_0: 'V2_0',
|
||||
v3: 'v3',
|
||||
default: 'default',
|
||||
} as const;
|
||||
export type HttpVersion = ValueOf<typeof HttpVersions>;
|
||||
|
||||
const authTypesMap = {
|
||||
[AUTH_BASIC]: ['Basic', 'Basic Auth'],
|
||||
[AUTH_DIGEST]: ['Digest', 'Digest Auth'],
|
||||
|
||||
@@ -8,8 +8,10 @@ import { mustGetModel } from '../models';
|
||||
import { CookieJar } from '../models/cookie-jar';
|
||||
import { Environment } from '../models/environment';
|
||||
import { GitRepository } from '../models/git-repository';
|
||||
import { getMonkeyPatchedControlledSettings } from '../models/helpers/settings';
|
||||
import type { BaseModel } from '../models/index';
|
||||
import * as models from '../models/index';
|
||||
import { isSettings } from '../models/settings';
|
||||
import type { Workspace } from '../models/workspace';
|
||||
import { DB_PERSIST_INTERVAL } from './constants';
|
||||
import { getDataDirectory } from './electron-helpers';
|
||||
@@ -657,7 +659,15 @@ type ChangeListener = Function;
|
||||
let changeListeners: ChangeListener[] = [];
|
||||
|
||||
async function notifyOfChange<T extends BaseModel>(event: string, doc: T, fromSync: boolean) {
|
||||
changeBuffer.push([event, doc, fromSync]);
|
||||
let updatedDoc = doc;
|
||||
|
||||
// NOTE: this monkeypatching is temporary, and was determined to have the smallest blast radius if it exists here (rather than, say, a reducer or an action creator).
|
||||
// see: INS-1059
|
||||
if (isSettings(doc)) {
|
||||
updatedDoc = getMonkeyPatchedControlledSettings(doc);
|
||||
}
|
||||
|
||||
changeBuffer.push([event, updatedDoc, fromSync]);
|
||||
|
||||
// Flush right away if we're not buffering
|
||||
if (!bufferingChanges) {
|
||||
|
||||
@@ -17,6 +17,12 @@ export function getDesignerDataDir() {
|
||||
return process.env.DESIGNER_DATA_PATH || join(app.getPath('appData'), 'Insomnia Designer');
|
||||
}
|
||||
|
||||
/**
|
||||
* This environment variable is added by electron-builder.
|
||||
* see: https://www.electron.build/configuration/nsis.html#portable\
|
||||
*/
|
||||
export const getPortableExecutableDir = () => process.env.PORTABLE_EXECUTABLE_DIR;
|
||||
|
||||
export function getDataDirectory() {
|
||||
const { app } = electron.remote || electron;
|
||||
return process.env.INSOMNIA_DATA_PATH || app.getPath('userData');
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { KeyBindings, KeyCombination } from 'insomnia-common';
|
||||
|
||||
import * as models from '../models';
|
||||
import type { HotKeyDefinition, KeyBindings, KeyCombination } from './hotkeys';
|
||||
import type { HotKeyDefinition } from './hotkeys';
|
||||
import { areSameKeyCombinations, getPlatformKeyCombinations } from './hotkeys';
|
||||
|
||||
const _pressedHotKey = (event: KeyboardEvent, bindings: KeyBindings) => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { HotKeyRegistry, KeyBindings, KeyCombination } from 'insomnia-common';
|
||||
|
||||
import { ALT_SYM, CTRL_SYM, isMac, META_SYM, SHIFT_SYM } from './constants';
|
||||
import { keyboardKeys } from './keyboard-keys';
|
||||
import { strings } from './strings';
|
||||
@@ -11,32 +13,6 @@ export interface HotKeyDefinition {
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The combination of key presses that will activate a hotkey if pressed.
|
||||
*/
|
||||
export interface KeyCombination {
|
||||
ctrl: boolean;
|
||||
alt: boolean;
|
||||
shift: boolean;
|
||||
meta: boolean;
|
||||
keyCode: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The collection of a hotkey's key combinations for each platforms.
|
||||
*/
|
||||
export interface KeyBindings {
|
||||
macKeys: KeyCombination[];
|
||||
// The key combinations for both Windows and Linux.
|
||||
winLinuxKeys: KeyCombination[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The collection of defined hotkeys.
|
||||
* The registry maps a hotkey by its reference id to its key bindings.
|
||||
*/
|
||||
export type HotKeyRegistry = Record<string, KeyBindings>;
|
||||
|
||||
function defineHotKey(id: string, description: string): HotKeyDefinition {
|
||||
return {
|
||||
id: id,
|
||||
|
||||
4
packages/insomnia-app/app/insomnia.config.json
Normal file
4
packages/insomnia-app/app/insomnia.config.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"insomniaConfig": "1.0.0",
|
||||
"settings": {}
|
||||
}
|
||||
@@ -223,7 +223,7 @@ async function _trackStats() {
|
||||
// Wait a bit before showing the user because the app just launched.
|
||||
setTimeout(() => {
|
||||
for (const window of BrowserWindow.getAllWindows()) {
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
// @ts-expect-error -- TSCONVERSION likely needs to be window.webContents.send instead
|
||||
window.send('show-notification', notification);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
const actual = jest.requireActual('../settings');
|
||||
|
||||
actual.getConfigSettings = jest.fn();
|
||||
|
||||
module.exports = actual;
|
||||
@@ -0,0 +1,46 @@
|
||||
import fs from 'fs';
|
||||
|
||||
import { getConfigSettings } from '../settings';
|
||||
|
||||
// This test exists outside of settings.test.ts because we need an unmocked `../settings` module
|
||||
describe('getConfigSettings', () => {
|
||||
it('only reads the config once on startup and then never again', () => {
|
||||
// Arrange
|
||||
const configOne = {
|
||||
insomniaConfig: '1.0.0',
|
||||
settings: {
|
||||
enableAnalytics: true,
|
||||
},
|
||||
};
|
||||
const configTwo = {
|
||||
insomniaConfig: '1.0.0',
|
||||
settings: {
|
||||
incognitoMode: true,
|
||||
},
|
||||
};
|
||||
|
||||
const readFileSyncSpy = jest.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify(configOne));
|
||||
|
||||
// Act
|
||||
const settingsFirstLoad = getConfigSettings();
|
||||
|
||||
// Assert
|
||||
expect(readFileSyncSpy).toHaveBeenCalledTimes(1);
|
||||
expect(settingsFirstLoad).toStrictEqual(configOne.settings);
|
||||
|
||||
// Re arrange
|
||||
readFileSyncSpy.mockClear();
|
||||
readFileSyncSpy.mockReturnValue(JSON.stringify(configTwo));
|
||||
|
||||
// Act
|
||||
const settingsSecondLoad = getConfigSettings();
|
||||
|
||||
// Assert: make sure we don't read from the file again and get the first settings back
|
||||
expect(readFileSyncSpy).not.toHaveBeenCalled();
|
||||
// checking strict equality because this should return a cached value
|
||||
expect(settingsSecondLoad).toBe(settingsFirstLoad);
|
||||
|
||||
// Cleanup
|
||||
readFileSyncSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import { globalBeforeEach } from '../../../__jest__/before-each';
|
||||
import { database as db } from '../../../common/database';
|
||||
import * as models from '../../../models';
|
||||
|
||||
describe('settings database', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
|
||||
describe('database.notifyOfChange patching', () => {
|
||||
it('will include controlled settings when a controller condition is met', async () => {
|
||||
await models.settings.getOrCreate();
|
||||
|
||||
const changes: Function[] = [];
|
||||
const callback = (change: Function) => {
|
||||
changes.push(change);
|
||||
};
|
||||
db.onChange(callback);
|
||||
|
||||
await models.settings.patch({
|
||||
incognitoMode: true,
|
||||
enableAnalytics: true,
|
||||
allowNotificationRequests: true,
|
||||
});
|
||||
|
||||
const expectedSettings = await models.settings.getOrCreate();
|
||||
|
||||
expect(changes[0]).toEqual([
|
||||
[db.CHANGE_UPDATE, expectedSettings, false],
|
||||
]);
|
||||
|
||||
db.offChange(callback);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should return the correct settings when updating a controlled setting', async () => {
|
||||
// Arrange
|
||||
const originalSettings = await models.settings.getOrCreate();
|
||||
|
||||
// Act
|
||||
const updatedSettings = await models.settings.update(originalSettings, {
|
||||
incognitoMode: true,
|
||||
enableAnalytics: true,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const expectedSettings = await models.settings.getOrCreate();
|
||||
|
||||
expect(updatedSettings).toStrictEqual(expectedSettings);
|
||||
});
|
||||
});
|
||||
|
||||
describe('patch', () => {
|
||||
it('should return the correct settings when patching a controlled setting', async () => {
|
||||
// Act
|
||||
const updatedSettings = await models.settings.patch({
|
||||
incognitoMode: true,
|
||||
enableAnalytics: true,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const expectedSettings = await models.settings.getOrCreate();
|
||||
|
||||
expect(updatedSettings).toStrictEqual(expectedSettings);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,322 @@
|
||||
import { Settings } from 'insomnia-common';
|
||||
import { identity } from 'ramda';
|
||||
import { mocked } from 'ts-jest/utils';
|
||||
|
||||
import * as _constants from '../../../common/constants';
|
||||
import * as electronHelpers from '../../../common/electron-helpers';
|
||||
import * as models from '../../../models';
|
||||
import * as settingsHelpers from '../settings';
|
||||
import {
|
||||
getConfigFile,
|
||||
getConfigSettings as _getConfigSettings,
|
||||
getControlledStatus,
|
||||
getLocalDevConfigFilePath,
|
||||
getMonkeyPatchedControlledSettings,
|
||||
omitControlledSettings,
|
||||
} from '../settings';
|
||||
|
||||
jest.mock('../../../common/constants', () => ({
|
||||
...jest.requireActual<typeof _constants>('../../../common/constants'),
|
||||
isDevelopment: jest.fn(),
|
||||
}));
|
||||
const { isDevelopment } = mocked(_constants);
|
||||
|
||||
jest.mock('../settings');
|
||||
const getConfigSettings = mocked(_getConfigSettings);
|
||||
|
||||
describe('getLocalDevConfigFilePath', () => {
|
||||
it('will not return the local dev config path in production mode', () => {
|
||||
isDevelopment.mockReturnValue(false);
|
||||
expect(getLocalDevConfigFilePath()).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('will return the local dev config path in development mode', () => {
|
||||
isDevelopment.mockReturnValue(true);
|
||||
expect(getLocalDevConfigFilePath()).toContain('insomnia-app/app');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConfigFile', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(settingsHelpers, 'readConfigFile').mockImplementation(identity);
|
||||
});
|
||||
|
||||
afterAll(jest.resetAllMocks);
|
||||
|
||||
it('prioritizes portable config location over all others', () => {
|
||||
jest.spyOn(electronHelpers, 'getPortableExecutableDir').mockReturnValue('portableExecutable');
|
||||
jest.spyOn(electronHelpers, 'getDataDirectory').mockReturnValue('insomniaDataDirectory');
|
||||
jest.spyOn(settingsHelpers, 'getLocalDevConfigFilePath').mockReturnValue('localDev');
|
||||
|
||||
const result = getConfigFile();
|
||||
|
||||
expect(result).toMatchObject({ configPath: 'portableExecutable' });
|
||||
});
|
||||
|
||||
it('prioritizes insomnia data directory over local dev when portable config is not found', () => {
|
||||
jest.spyOn(electronHelpers, 'getPortableExecutableDir').mockReturnValue(undefined);
|
||||
jest.spyOn(electronHelpers, 'getDataDirectory').mockReturnValue('insomniaDataDirectory');
|
||||
jest.spyOn(settingsHelpers, 'getLocalDevConfigFilePath').mockReturnValue('localDev');
|
||||
|
||||
const result = getConfigFile();
|
||||
|
||||
expect(result).toMatchObject({ configPath: 'insomniaDataDirectory' });
|
||||
});
|
||||
|
||||
it('returns the local dev config file if no others are found', () => {
|
||||
jest.spyOn(electronHelpers, 'getPortableExecutableDir').mockReturnValue(undefined);
|
||||
// @ts-expect-error intentionally invalid to simulate the file not being found
|
||||
jest.spyOn(electronHelpers, 'getDataDirectory').mockReturnValue(undefined);
|
||||
jest.spyOn(settingsHelpers, 'getLocalDevConfigFilePath').mockReturnValue('localDev');
|
||||
|
||||
const result = getConfigFile();
|
||||
|
||||
expect(result).toMatchObject({ configPath: 'localDev' });
|
||||
});
|
||||
|
||||
it('returns an internal fallback if no configs are found (in production mode)', () => {
|
||||
isDevelopment.mockReturnValue(false);
|
||||
jest.spyOn(electronHelpers, 'getPortableExecutableDir').mockReturnValue(undefined);
|
||||
// @ts-expect-error intentionally invalid to simulate the file not being found
|
||||
jest.spyOn(electronHelpers, 'getDataDirectory').mockReturnValue(undefined);
|
||||
|
||||
const result = getConfigFile();
|
||||
|
||||
expect(result).toMatchObject({ configPath: '<internal fallback insomnia config>' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getControlledStatus', () => {
|
||||
it('should override conflicting setting if controlled by another setting', () => {
|
||||
getConfigSettings.mockReturnValue({});
|
||||
const settings: Settings = {
|
||||
...models.settings.init(),
|
||||
incognitoMode: true,
|
||||
enableAnalytics: true, // this intentionally conflicts with incognito mode
|
||||
};
|
||||
|
||||
const controlledStatus = getControlledStatus(settings)('enableAnalytics');
|
||||
|
||||
expect(controlledStatus).toStrictEqual({
|
||||
isControlled: true,
|
||||
controller: 'incognitoMode',
|
||||
value: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should override setting with what is defined in the config file', () => {
|
||||
getConfigSettings.mockReturnValue({ enableAnalytics: false });
|
||||
const settings: Settings = {
|
||||
...models.settings.init(),
|
||||
incognitoMode: false, // ensures incognito mode isn't affecting this test
|
||||
enableAnalytics: true, // this intentionally conflicts with the config
|
||||
};
|
||||
|
||||
const controlledStatus = getControlledStatus(settings)('enableAnalytics');
|
||||
|
||||
expect(controlledStatus).toStrictEqual({
|
||||
isControlled: true,
|
||||
controller: 'insomnia-config',
|
||||
value: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should override setting controlled by another setting, with what is defined in the config file', () => {
|
||||
getConfigSettings.mockReturnValue({ enableAnalytics: true }); // intentionally conflicts with incognito mode
|
||||
const settings: Settings = {
|
||||
...models.settings.init(),
|
||||
incognitoMode: true, // this intentionally conflicts with the config
|
||||
enableAnalytics: false, // this intentionally conflicts with the config
|
||||
};
|
||||
|
||||
const controlledStatus = getControlledStatus(settings)('enableAnalytics');
|
||||
|
||||
expect(controlledStatus).toStrictEqual({
|
||||
isControlled: true,
|
||||
controller: 'insomnia-config',
|
||||
value: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('omitControlledSettings', () => {
|
||||
it('omits config controlled settings', () => {
|
||||
getConfigSettings.mockReturnValue({ disablePaidFeatureAds: true });
|
||||
const settings = models.settings.init();
|
||||
|
||||
const result = omitControlledSettings(settings, { disablePaidFeatureAds: false });
|
||||
|
||||
expect(result).not.toHaveProperty('disablePaidFeatureAds');
|
||||
});
|
||||
|
||||
it('does not omit settings not controlled by the config', () => {
|
||||
getConfigSettings.mockReturnValue({});
|
||||
const settings = models.settings.init();
|
||||
|
||||
const result = omitControlledSettings(settings, { disablePaidFeatureAds: true });
|
||||
|
||||
expect(result).toMatchObject({ disablePaidFeatureAds: true });
|
||||
});
|
||||
|
||||
it('omits settings controlled by other settings', () => {
|
||||
getConfigSettings.mockReturnValue({});
|
||||
const settings: Settings = {
|
||||
...models.settings.init(),
|
||||
incognitoMode: true,
|
||||
};
|
||||
const result = omitControlledSettings(settings, {
|
||||
enableAnalytics: true,
|
||||
allowNotificationRequests: true,
|
||||
});
|
||||
|
||||
expect(result).not.toHaveProperty('enableAnalytics');
|
||||
expect(result).not.toHaveProperty('allowNotificationRequests');
|
||||
});
|
||||
|
||||
it('does not omit settings not controlled by other settings', () => {
|
||||
getConfigSettings.mockReturnValue({});
|
||||
const settings = models.settings.init();
|
||||
const result = omitControlledSettings(settings, { disablePaidFeatureAds: true });
|
||||
|
||||
expect(result).toMatchObject({ disablePaidFeatureAds: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMonkeyPatchedControlledSettings', () => {
|
||||
it('overwrites config controlled settings', () => {
|
||||
getConfigSettings.mockReturnValue({ disablePaidFeatureAds: true });
|
||||
const settings: Settings = {
|
||||
...models.settings.init(),
|
||||
disablePaidFeatureAds: false,
|
||||
};
|
||||
|
||||
const result = getMonkeyPatchedControlledSettings(settings);
|
||||
|
||||
expect(result).toMatchObject({ disablePaidFeatureAds: true });
|
||||
});
|
||||
|
||||
it('does not overwrite settings not controlled by the config', () => {
|
||||
getConfigSettings.mockReturnValue({});
|
||||
const settings = models.settings.init();
|
||||
|
||||
const result = getMonkeyPatchedControlledSettings(settings);
|
||||
|
||||
expect(result).toMatchObject(settings);
|
||||
});
|
||||
|
||||
it('overwrites settings controlled by other settings', () => {
|
||||
getConfigSettings.mockReturnValue({});
|
||||
const settings: Settings = {
|
||||
...models.settings.init(),
|
||||
incognitoMode: true,
|
||||
enableAnalytics: true,
|
||||
};
|
||||
|
||||
const result = getMonkeyPatchedControlledSettings(settings);
|
||||
|
||||
expect(result).toMatchObject({ enableAnalytics: false });
|
||||
});
|
||||
|
||||
it('does not overwrite settings not controlled by other settings', () => {
|
||||
getConfigSettings.mockReturnValue({});
|
||||
const settings: Settings = {
|
||||
...models.settings.init(),
|
||||
disablePaidFeatureAds: true,
|
||||
};
|
||||
|
||||
const result = getMonkeyPatchedControlledSettings(settings);
|
||||
|
||||
expect(result).toMatchObject({ disablePaidFeatureAds: true });
|
||||
});
|
||||
|
||||
it('prioritizes config control over simple settings control', () => {
|
||||
getConfigSettings.mockReturnValue({ enableAnalytics: true });
|
||||
const settings: Settings = {
|
||||
...models.settings.init(),
|
||||
incognitoMode: true,
|
||||
enableAnalytics: false,
|
||||
};
|
||||
|
||||
const result = getMonkeyPatchedControlledSettings(settings);
|
||||
|
||||
expect(result).toMatchObject({ enableAnalytics: true });
|
||||
});
|
||||
|
||||
/** when the user settings say to do one thing [set enableAnalytics to false], but the config is saying something different [set enableAnalytics to true] but another value that is also in the config [incognitoMode] but which controls this setting [enableAnalytics] says to set it to a value [incognitoMode says to set enableAnalytics to false], then it's the controlling setting in the config that has final say on what the value is. Not the user settings, and not the literal value set in the config itself. */
|
||||
it('should prioritize controlling setting from config file above all other settings', () => {
|
||||
getConfigSettings.mockReturnValue({
|
||||
incognitoMode: true,
|
||||
enableAnalytics: true, // this intentionally conflicts with incognitoMode, which should force it to false
|
||||
});
|
||||
const settings: Settings = {
|
||||
...models.settings.init(),
|
||||
incognitoMode: false, // this intentionally conflicts with the config
|
||||
enableAnalytics: false,
|
||||
};
|
||||
|
||||
const result = getMonkeyPatchedControlledSettings(settings);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
incognitoMode: true,
|
||||
enableAnalytics: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('shows that enableAnalytics and allowNotificationRequests are false when incognitoMode is true in user settings', () => {
|
||||
getConfigSettings.mockReturnValue({});
|
||||
const settings: Settings = {
|
||||
...models.settings.init(),
|
||||
incognitoMode: true,
|
||||
enableAnalytics: true,
|
||||
allowNotificationRequests: true,
|
||||
};
|
||||
|
||||
const result = getMonkeyPatchedControlledSettings(settings);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
incognitoMode: true,
|
||||
enableAnalytics: false,
|
||||
allowNotificationRequests: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('shows that enableAnalytics and allowNotificationRequests are false when incognitoMode is true in the config', () => {
|
||||
getConfigSettings.mockReturnValue({
|
||||
incognitoMode: true,
|
||||
});
|
||||
const settings: Settings = {
|
||||
...models.settings.init(),
|
||||
incognitoMode: false,
|
||||
enableAnalytics: true,
|
||||
allowNotificationRequests: true,
|
||||
};
|
||||
|
||||
const result = getMonkeyPatchedControlledSettings(settings);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
incognitoMode: true,
|
||||
enableAnalytics: false,
|
||||
allowNotificationRequests: false,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This use-case test ensures that the likely config of a customer that has a security or privacy-centric configuration is preserved.
|
||||
*/
|
||||
it('ensures a maximally privacy-centric use-case is preserved', () => {
|
||||
getConfigSettings.mockReturnValue({
|
||||
incognitoMode: true,
|
||||
disablePaidFeatureAds: true,
|
||||
});
|
||||
const settings = models.settings.init();
|
||||
|
||||
const result = getMonkeyPatchedControlledSettings(settings);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
incognitoMode: true,
|
||||
enableAnalytics: false,
|
||||
allowNotificationRequests: false,
|
||||
disablePaidFeatureAds: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
240
packages/insomnia-app/app/models/helpers/settings.ts
Normal file
240
packages/insomnia-app/app/models/helpers/settings.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { Settings } from 'insomnia-common';
|
||||
import { InsomniaConfig, validate } from 'insomnia-config';
|
||||
import { resolve } from 'path';
|
||||
import { mapObjIndexed, once } from 'ramda';
|
||||
import { omitBy } from 'ramda-adjunct';
|
||||
import { ValueOf } from 'type-fest';
|
||||
|
||||
import { isDevelopment } from '../../common/constants';
|
||||
import { getDataDirectory, getPortableExecutableDir } from '../../common/electron-helpers';
|
||||
|
||||
/** takes an unresolved (or resolved will work fine too) filePath of the insomnia config and reads the insomniaConfig from disk */
|
||||
export const readConfigFile = (filePath?: string) => {
|
||||
if (!filePath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let fileContents = '';
|
||||
try {
|
||||
const resolvedFilePath = resolve(filePath, 'insomnia.config.json');
|
||||
fileContents = readFileSync(resolvedFilePath, 'utf-8');
|
||||
} catch (error: unknown) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!fileContents) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(fileContents) as unknown;
|
||||
} catch (error: unknown) {
|
||||
console.error('failed to parse insomnia config', { filePath, fileContents }, error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const getLocalDevConfigFilePath = () => (
|
||||
isDevelopment() ? '../../packages/insomnia-app/app' as string : undefined
|
||||
);
|
||||
|
||||
export const getConfigFile = () => {
|
||||
const portableExecutable = getPortableExecutableDir();
|
||||
const insomniaDataDirectory = getDataDirectory();
|
||||
const localDev = getLocalDevConfigFilePath();
|
||||
const configPaths = [
|
||||
portableExecutable,
|
||||
insomniaDataDirectory,
|
||||
localDev,
|
||||
];
|
||||
|
||||
// note: this is written as to avoid unnecessary (synchronous) reads from disk.
|
||||
// The paths above are in priority order such that if we already found what we're looking for, there's no reason to keep reading other files.
|
||||
for (const configPath of configPaths) {
|
||||
const insomniaConfig = readConfigFile(configPath);
|
||||
if (insomniaConfig !== undefined) {
|
||||
return {
|
||||
insomniaConfig,
|
||||
configPath,
|
||||
};
|
||||
}
|
||||
}
|
||||
const fallbackEmptyConfig: InsomniaConfig = { insomniaConfig: '1.0.0' };
|
||||
return {
|
||||
insomniaConfig: fallbackEmptyConfig,
|
||||
configPath: '<internal fallback insomnia config>',
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* gets settings from the `insomnia.config.json`
|
||||
*
|
||||
* note that it is a business rule that the config is never read again after startup, hence the `once` usage.
|
||||
*/
|
||||
export const getConfigSettings = once(() => {
|
||||
const { configPath, insomniaConfig } = getConfigFile();
|
||||
|
||||
const { valid, errors } = validate(insomniaConfig as InsomniaConfig);
|
||||
if (!valid) {
|
||||
console.error('invalid insomnia config', {
|
||||
configPath,
|
||||
insomniaConfig,
|
||||
errors,
|
||||
});
|
||||
return {};
|
||||
}
|
||||
// This cast is important for testing intentionally bad values (the above validation will catch it, anyway)
|
||||
return (insomniaConfig as InsomniaConfig).settings || {};
|
||||
});
|
||||
|
||||
interface Condition {
|
||||
/** note: conditions are only suitable for boolean settings at this time */
|
||||
when: boolean;
|
||||
set: Partial<Settings>;
|
||||
}
|
||||
|
||||
// using a Map because they are ordered (in such case as multiple settings could be controlled by other multiple settings in the future, we could control this reliably by changing the order in the Map)
|
||||
const settingControllers = new Map<keyof Settings, Condition[]>([
|
||||
[
|
||||
'incognitoMode',
|
||||
[
|
||||
{
|
||||
when: true,
|
||||
set: {
|
||||
enableAnalytics: false,
|
||||
allowNotificationRequests: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
/** checks whether a given setting is literally specified in the insomnia config */
|
||||
export const isControlledByConfig = (setting: keyof Settings | null) => setting ? (
|
||||
Boolean(Object.prototype.hasOwnProperty.call(getConfigSettings(), setting))
|
||||
) : false;
|
||||
|
||||
export interface SettingControlledSetting<T extends keyof Settings> {
|
||||
controlledValue: Settings[T];
|
||||
controller: keyof Settings;
|
||||
isControlled: true;
|
||||
}
|
||||
|
||||
export interface ConfigControlledSetting<T extends keyof Settings> {
|
||||
controlledValue: Settings[T];
|
||||
controller: 'insomnia-config';
|
||||
isControlled: true;
|
||||
}
|
||||
|
||||
export interface UncontrolledSetting {
|
||||
controller: null;
|
||||
isControlled: false;
|
||||
}
|
||||
|
||||
export type SettingsControl<T extends keyof Settings> =
|
||||
| SettingControlledSetting<T>
|
||||
| ConfigControlledSetting<T>
|
||||
| UncontrolledSetting
|
||||
;
|
||||
|
||||
const isSettingControlledByCondition = (condition: Condition, setting: keyof Settings, value: ValueOf<Settings>) => {
|
||||
return condition.when === value
|
||||
&& Object.prototype.hasOwnProperty.call(condition.set, setting);
|
||||
};
|
||||
|
||||
/**
|
||||
* checks whether a given setting is controlled by another setting.
|
||||
* if so, it will return that setting id. otherwise it will return false.
|
||||
*/
|
||||
export const isControlledByAnotherSetting = (settings: Settings) => (setting: keyof Settings) => {
|
||||
for (const [controller, controlledSettings] of settingControllers.entries()) {
|
||||
for (const condition of controlledSettings) {
|
||||
if (isSettingControlledByCondition(condition, setting, settings[controller])) {
|
||||
const output: SettingControlledSetting<typeof setting> = {
|
||||
controlledValue: condition.set[setting],
|
||||
controller,
|
||||
isControlled: true,
|
||||
};
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
const uncontrolledSetting: UncontrolledSetting = {
|
||||
controller: null,
|
||||
isControlled: false,
|
||||
};
|
||||
return uncontrolledSetting;
|
||||
};
|
||||
|
||||
/**
|
||||
* For any given setting, return what the value of that setting should be once you take the insomnia config and other potentially controlling settings into account
|
||||
*/
|
||||
export const getControlledStatus = (userSettings: Settings) => (setting: keyof Settings) => {
|
||||
const configSettings = {
|
||||
...userSettings,
|
||||
...getConfigSettings(),
|
||||
};
|
||||
|
||||
if (isControlledByConfig(setting)) {
|
||||
|
||||
// note that the raw config settings are being passed here (rathern than `settings` alone), because we must verify that the controller does not itself also have a specification in the config
|
||||
const controllerSetting = isControlledByAnotherSetting(configSettings)(setting);
|
||||
|
||||
// TLDR; the config always wins
|
||||
// It is a business rule that if a setting (e.g. `incognitoMode` specified in the config controlls another setting (e.g. `enableAnalytics`), that conflict should be resolved such that the config-specified controller should _always_ win, _even_ if the controlled setting (i.e. `enableAnalytics`) is _itself_ specified in the config with a conflicting value)
|
||||
if (controllerSetting.isControlled && isControlledByConfig(controllerSetting.controller)) {
|
||||
// since this setting is also controlled by a controller, and that controller is controlled by the config, we use the controller's desired value for this setting.
|
||||
return {
|
||||
controller: controllerSetting.controller,
|
||||
isControlled: true,
|
||||
value: controllerSetting.controlledValue,
|
||||
};
|
||||
}
|
||||
|
||||
// no other setting controls this, so we can grab its value the config directly
|
||||
return {
|
||||
controller: 'insomnia-config',
|
||||
isControlled: true,
|
||||
value: configSettings[setting],
|
||||
};
|
||||
}
|
||||
|
||||
const thisSetting = isControlledByAnotherSetting(configSettings)(setting);
|
||||
if (thisSetting.isControlled) {
|
||||
// this setting is controlled by another setting.
|
||||
return {
|
||||
controller: thisSetting.controller,
|
||||
isControlled: true,
|
||||
value: thisSetting.controlledValue,
|
||||
};
|
||||
}
|
||||
|
||||
// return the object unchanged, as it exists in the settings
|
||||
return {
|
||||
controller: null,
|
||||
isControlled: false,
|
||||
value: userSettings[setting],
|
||||
};
|
||||
};
|
||||
|
||||
/** removes any setting in the given patch object which is controlled in any way (i.e. either by the insomnia config or by another setting) */
|
||||
export const omitControlledSettings = <
|
||||
T extends Settings,
|
||||
U extends Partial<Settings>
|
||||
>(settings: T, patch: U) => {
|
||||
return omitBy((_value, setting: keyof Settings) => (
|
||||
getControlledStatus(settings)(setting).isControlled
|
||||
), patch);
|
||||
};
|
||||
|
||||
/** for any given setting, whether controlled by the insomnia config or whether controlled by another value, return the calculated value */
|
||||
export const getMonkeyPatchedControlledSettings = <T extends Settings>(settings: T) => {
|
||||
const override = mapObjIndexed((_value, setting: keyof Settings) => (
|
||||
getControlledStatus(settings)(setting).value
|
||||
), settings) as T;
|
||||
return {
|
||||
...settings,
|
||||
...override,
|
||||
};
|
||||
};
|
||||
@@ -1,74 +1,16 @@
|
||||
import type { HttpVersion } from '../common/constants';
|
||||
import { HttpVersions, Settings as BaseSettings } from 'insomnia-common';
|
||||
|
||||
import {
|
||||
getAppDefaultDarkTheme,
|
||||
getAppDefaultLightTheme,
|
||||
getAppDefaultTheme,
|
||||
HttpVersions,
|
||||
UPDATE_CHANNEL_STABLE,
|
||||
} from '../common/constants';
|
||||
import { database as db } from '../common/database';
|
||||
import * as hotkeys from '../common/hotkeys';
|
||||
import { getMonkeyPatchedControlledSettings, omitControlledSettings } from './helpers/settings';
|
||||
import type { BaseModel } from './index';
|
||||
|
||||
export interface PluginConfig {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export type PluginConfigMap = Record<string, PluginConfig>;
|
||||
|
||||
export interface BaseSettings {
|
||||
autoHideMenuBar: boolean;
|
||||
autocompleteDelay: number;
|
||||
deviceId: string | null;
|
||||
disableHtmlPreviewJs: boolean;
|
||||
disableResponsePreviewLinks: boolean;
|
||||
disableUpdateNotification: boolean;
|
||||
editorFontSize: number;
|
||||
editorIndentSize: number;
|
||||
editorIndentWithTabs: boolean;
|
||||
editorKeyMap: string;
|
||||
editorLineWrapping: boolean;
|
||||
enableAnalytics: boolean;
|
||||
environmentHighlightColorStyle: string;
|
||||
filterResponsesByEnv: boolean;
|
||||
followRedirects: boolean;
|
||||
clearOAuth2SessionOnRestart: boolean;
|
||||
fontInterface: string | null;
|
||||
fontMonospace: string | null;
|
||||
fontSize: number;
|
||||
fontVariantLigatures: boolean;
|
||||
forceVerticalLayout: boolean;
|
||||
hotKeyRegistry: hotkeys.HotKeyRegistry;
|
||||
httpProxy: string;
|
||||
httpsProxy: string;
|
||||
lineWrapping?: boolean;
|
||||
maxHistoryResponses: number;
|
||||
maxRedirects: number;
|
||||
maxTimelineDataSizeKB: number;
|
||||
noProxy: string;
|
||||
nunjucksPowerUserMode: boolean;
|
||||
pluginConfig: PluginConfigMap;
|
||||
pluginPath: string;
|
||||
preferredHttpVersion: HttpVersion;
|
||||
proxyEnabled: boolean;
|
||||
showPasswords: boolean;
|
||||
theme: string;
|
||||
autoDetectColorScheme: boolean;
|
||||
lightTheme: string;
|
||||
darkTheme: string;
|
||||
timeout: number;
|
||||
updateAutomatically: boolean;
|
||||
updateChannel: string;
|
||||
useBulkHeaderEditor: boolean;
|
||||
useBulkParametersEditor: boolean;
|
||||
validateAuthSSL: boolean;
|
||||
validateSSL: boolean;
|
||||
hasPromptedToMigrateFromDesigner: boolean;
|
||||
hasPromptedOnboarding: boolean;
|
||||
hasPromptedAnalytics: boolean;
|
||||
isVariableUncovered?: boolean;
|
||||
}
|
||||
|
||||
export type Settings = BaseModel & BaseSettings;
|
||||
export const name = 'Settings';
|
||||
export const type = 'Settings';
|
||||
@@ -84,10 +26,15 @@ export const isSettings = (model: Pick<BaseModel, 'type'>): model is Settings =>
|
||||
|
||||
export function init(): BaseSettings {
|
||||
return {
|
||||
autoDetectColorScheme: false,
|
||||
autoHideMenuBar: false,
|
||||
autocompleteDelay: 1200,
|
||||
allowNotificationRequests: true,
|
||||
clearOAuth2SessionOnRestart: true,
|
||||
darkTheme: getAppDefaultDarkTheme(),
|
||||
deviceId: null,
|
||||
disableHtmlPreviewJs: false,
|
||||
disablePaidFeatureAds: false,
|
||||
disableResponsePreviewLinks: false,
|
||||
disableUpdateNotification: false,
|
||||
editorFontSize: 11,
|
||||
@@ -99,15 +46,24 @@ export function init(): BaseSettings {
|
||||
environmentHighlightColorStyle: 'sidebar-indicator',
|
||||
filterResponsesByEnv: false,
|
||||
followRedirects: true,
|
||||
clearOAuth2SessionOnRestart: true,
|
||||
fontInterface: null,
|
||||
fontMonospace: null,
|
||||
fontSize: 13,
|
||||
fontVariantLigatures: false,
|
||||
forceVerticalLayout: false,
|
||||
|
||||
// Only existing users updating from an older version should see the analytics prompt.
|
||||
// So by default this flag is set to false, and is toggled to true during initialization for new users.
|
||||
hasPromptedAnalytics: false,
|
||||
|
||||
// Users should only see onboarding during first launch, and anybody updating from an older version should not see it, so by default this flag is set to true, and is toggled to false during initialization
|
||||
hasPromptedOnboarding: true,
|
||||
hasPromptedToMigrateFromDesigner: false,
|
||||
hotKeyRegistry: hotkeys.newDefaultRegistry(),
|
||||
httpProxy: '',
|
||||
httpsProxy: '',
|
||||
incognitoMode: false,
|
||||
lightTheme: getAppDefaultLightTheme(),
|
||||
maxHistoryResponses: 20,
|
||||
maxRedirects: -1,
|
||||
maxTimelineDataSizeKB: 10,
|
||||
@@ -119,9 +75,6 @@ export function init(): BaseSettings {
|
||||
proxyEnabled: false,
|
||||
showPasswords: false,
|
||||
theme: getAppDefaultTheme(),
|
||||
autoDetectColorScheme: false,
|
||||
lightTheme: getAppDefaultLightTheme(),
|
||||
darkTheme: getAppDefaultDarkTheme(),
|
||||
timeout: 0,
|
||||
updateAutomatically: true,
|
||||
updateChannel: UPDATE_CHANNEL_STABLE,
|
||||
@@ -129,15 +82,6 @@ export function init(): BaseSettings {
|
||||
useBulkParametersEditor: false,
|
||||
validateAuthSSL: true,
|
||||
validateSSL: true,
|
||||
hasPromptedToMigrateFromDesigner: false,
|
||||
// Users should only see onboarding during first launch, and anybody updating from an
|
||||
// older version should not see it, so by default this flag is set to true, and is toggled
|
||||
// to false during initialization
|
||||
hasPromptedOnboarding: true,
|
||||
// Only existing users updating from an older version should see the analytics prompt
|
||||
// So by default this flag is set to false, and is toggled to true during initialization
|
||||
// for new users
|
||||
hasPromptedAnalytics: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -147,26 +91,30 @@ export function migrate(doc: Settings) {
|
||||
}
|
||||
|
||||
export async function all() {
|
||||
const settings = await db.all<Settings>(type);
|
||||
let settingsList = await db.all<Settings>(type);
|
||||
|
||||
if (settings?.length === 0) {
|
||||
return [await getOrCreate()];
|
||||
} else {
|
||||
return settings;
|
||||
if (settingsList?.length === 0) {
|
||||
settingsList = [await getOrCreate()];
|
||||
}
|
||||
|
||||
return settingsList.map(getMonkeyPatchedControlledSettings);
|
||||
}
|
||||
|
||||
export async function create(patch: Partial<Settings> = {}) {
|
||||
return db.docCreate<Settings>(type, patch);
|
||||
async function create() {
|
||||
const settings = await db.docCreate<Settings>(type);
|
||||
return getMonkeyPatchedControlledSettings(settings);
|
||||
}
|
||||
|
||||
export async function update(settings: Settings, patch: Partial<Settings>) {
|
||||
return db.docUpdate<Settings>(settings, patch);
|
||||
const updatedSettings = await db.docUpdate<Settings>(settings, omitControlledSettings(settings, patch));
|
||||
return getMonkeyPatchedControlledSettings(updatedSettings);
|
||||
}
|
||||
|
||||
export async function patch(patch: Partial<Settings>) {
|
||||
const settings = await getOrCreate();
|
||||
return db.docUpdate<Settings>(settings, patch);
|
||||
const sanitizedPatch = omitControlledSettings(settings, patch);
|
||||
const updatedSettings = await db.docUpdate<Settings>(settings, sanitizedPatch);
|
||||
return getMonkeyPatchedControlledSettings(updatedSettings);
|
||||
}
|
||||
|
||||
export async function getOrCreate() {
|
||||
@@ -175,7 +123,7 @@ export async function getOrCreate() {
|
||||
if (results.length === 0) {
|
||||
return create();
|
||||
} else {
|
||||
return results[0];
|
||||
return getMonkeyPatchedControlledSettings(results[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import fs from 'fs';
|
||||
import { HttpVersions } from 'insomnia-common';
|
||||
import { join as pathJoin, resolve as pathResolve } from 'path';
|
||||
|
||||
import { globalBeforeEach } from '../../__jest__/before-each';
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
CONTENT_TYPE_FORM_DATA,
|
||||
CONTENT_TYPE_FORM_URLENCODED,
|
||||
getAppVersion,
|
||||
HttpVersions,
|
||||
} from '../../common/constants';
|
||||
import { filterHeaders } from '../../common/misc';
|
||||
import { getRenderedRequestAndContext } from '../../common/render';
|
||||
@@ -29,7 +29,7 @@ describe('actuallySend()', () => {
|
||||
|
||||
it('sends a generic request', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const cookies = [
|
||||
{
|
||||
creation: new Date('2016-10-05T04:40:49.505Z'),
|
||||
@@ -138,7 +138,7 @@ describe('actuallySend()', () => {
|
||||
|
||||
it('sends a urlencoded', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const request = Object.assign(models.request.init(), {
|
||||
_id: 'req_123',
|
||||
parentId: workspace._id,
|
||||
@@ -207,7 +207,7 @@ describe('actuallySend()', () => {
|
||||
|
||||
it('skips sending and storing cookies with setting', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const cookies = [
|
||||
{
|
||||
creation: new Date('2016-10-05T04:40:49.505Z'),
|
||||
@@ -307,7 +307,7 @@ describe('actuallySend()', () => {
|
||||
|
||||
it('sends a file', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
await models.cookieJar.create({
|
||||
parentId: workspace._id,
|
||||
});
|
||||
@@ -370,7 +370,7 @@ describe('actuallySend()', () => {
|
||||
|
||||
it('sends multipart form data', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
await models.cookieJar.create({
|
||||
parentId: workspace._id,
|
||||
});
|
||||
@@ -462,7 +462,7 @@ describe('actuallySend()', () => {
|
||||
|
||||
it('uses unix socket', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const request = Object.assign(models.request.init(), {
|
||||
_id: 'req_123',
|
||||
parentId: workspace._id,
|
||||
@@ -502,7 +502,7 @@ describe('actuallySend()', () => {
|
||||
|
||||
it('uses works with HEAD', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const request = Object.assign(models.request.init(), {
|
||||
_id: 'req_123',
|
||||
parentId: workspace._id,
|
||||
@@ -541,7 +541,7 @@ describe('actuallySend()', () => {
|
||||
|
||||
it('uses works with "unix" host', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const request = Object.assign(models.request.init(), {
|
||||
_id: 'req_123',
|
||||
parentId: workspace._id,
|
||||
@@ -580,7 +580,7 @@ describe('actuallySend()', () => {
|
||||
|
||||
it('uses netrc', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const request = Object.assign(models.request.init(), {
|
||||
_id: 'req_123',
|
||||
parentId: workspace._id,
|
||||
@@ -621,7 +621,7 @@ describe('actuallySend()', () => {
|
||||
|
||||
it('disables ssl verification when configured to do so', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const cookies = [
|
||||
{
|
||||
creation: new Date('2016-10-05T04:40:49.505Z'),
|
||||
@@ -734,7 +734,7 @@ describe('actuallySend()', () => {
|
||||
|
||||
it('sets HTTP version', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const request = Object.assign(models.request.init(), {
|
||||
_id: 'req_123',
|
||||
parentId: workspace._id,
|
||||
@@ -777,7 +777,7 @@ describe('actuallySend()', () => {
|
||||
it('requests can be cancelled by requestId', async () => {
|
||||
// GIVEN
|
||||
const workspace = await models.workspace.create();
|
||||
const settings = await models.settings.create();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const request1 = Object.assign(models.request.init(), {
|
||||
_id: 'req_15',
|
||||
parentId: workspace._id,
|
||||
|
||||
@@ -2,6 +2,7 @@ import aws4 from 'aws4';
|
||||
import clone from 'clone';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import { HttpVersions } from 'insomnia-common';
|
||||
import { cookiesFromJar, jarFromCookies } from 'insomnia-cookies';
|
||||
import {
|
||||
buildQueryStringFromParams,
|
||||
@@ -31,7 +32,6 @@ import {
|
||||
CONTENT_TYPE_FORM_DATA,
|
||||
CONTENT_TYPE_FORM_URLENCODED,
|
||||
getAppVersion,
|
||||
HttpVersions,
|
||||
STATUS_CODE_PLUGIN_ERROR,
|
||||
} from '../common/constants';
|
||||
import { database as db } from '../common/database';
|
||||
|
||||
@@ -79,7 +79,8 @@ describe('authorizeUserInWindow()', () => {
|
||||
// Arrange
|
||||
const mockCallback = getCertificateVerifyCallbackMock();
|
||||
|
||||
await models.settings.create({
|
||||
const settings = await models.settings.getOrCreate();
|
||||
await models.settings.update(settings, {
|
||||
validateAuthSSL: true,
|
||||
});
|
||||
|
||||
@@ -95,7 +96,8 @@ describe('authorizeUserInWindow()', () => {
|
||||
// Arrange
|
||||
const mockCallback = getCertificateVerifyCallbackMock();
|
||||
|
||||
await models.settings.create({
|
||||
const settings = await models.settings.getOrCreate();
|
||||
await models.settings.update(settings, {
|
||||
validateAuthSSL: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import fs from 'fs';
|
||||
import type { PluginConfig, PluginConfigMap } from 'insomnia-common';
|
||||
import mkdirp from 'mkdirp';
|
||||
import path from 'path';
|
||||
|
||||
@@ -9,7 +10,6 @@ import * as models from '../models';
|
||||
import { GrpcRequest } from '../models/grpc-request';
|
||||
import type { Request } from '../models/request';
|
||||
import type { RequestGroup } from '../models/request-group';
|
||||
import type { PluginConfig, PluginConfigMap } from '../models/settings';
|
||||
import type { Workspace } from '../models/workspace';
|
||||
import type { PluginTemplateTag } from '../templating/extensions/index';
|
||||
import { showError } from '../ui/components/modals/index';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { KeyBindings } from 'insomnia-common';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import type { KeyBindings } from '../../../../common/hotkeys';
|
||||
import { Hotkey } from '../../hotkey';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { CircleButton, SvgIcon, Tooltip } from 'insomnia-components';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import React, { Fragment, FunctionComponent } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import * as session from '../../../account/session';
|
||||
import { selectSettings } from '../../redux/selectors';
|
||||
import { Dropdown } from '../base/dropdown/dropdown';
|
||||
import { DropdownButton } from '../base/dropdown/dropdown-button';
|
||||
import { DropdownItem } from '../base/dropdown/dropdown-item';
|
||||
@@ -24,51 +26,56 @@ const StyledIconContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const AccountDropdown: FunctionComponent<Props> = ({ className }) => (
|
||||
<div className={className}>
|
||||
<Dropdown>
|
||||
<DropdownButton noWrap>
|
||||
<Tooltip delay={1000} position="bottom" message="Account">
|
||||
<CircleButton>
|
||||
<SvgIcon icon="user" />
|
||||
</CircleButton>
|
||||
</Tooltip>
|
||||
</DropdownButton>
|
||||
{session.isLoggedIn() ? (
|
||||
<DropdownItem
|
||||
key="login"
|
||||
stayOpenAfterClick
|
||||
buttonClass={PromptButton}
|
||||
onClick={session.logout}
|
||||
>
|
||||
<StyledIconContainer>
|
||||
<i className="fa fa-sign-out" />
|
||||
</StyledIconContainer>
|
||||
Logout
|
||||
</DropdownItem>
|
||||
) : (
|
||||
<DropdownItem key="login" onClick={showLoginModal}>
|
||||
<StyledIconContainer>
|
||||
<i className="fa fa-sign-in" />
|
||||
</StyledIconContainer>
|
||||
Log In
|
||||
</DropdownItem>
|
||||
)}
|
||||
{!session.isLoggedIn() && (
|
||||
<DropdownItem
|
||||
key="invite"
|
||||
buttonClass={Link}
|
||||
// @ts-expect-error -- TSCONVERSION appears to be genuine
|
||||
href="https://insomnia.rest/pricing"
|
||||
button
|
||||
>
|
||||
<StyledIconContainer>
|
||||
<i className="fa fa-users" />
|
||||
</StyledIconContainer>{' '}
|
||||
Upgrade to Plus
|
||||
<i className="fa fa-star surprise fa-outline" />
|
||||
</DropdownItem>
|
||||
)}
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
export const AccountDropdown: FunctionComponent<Props> = ({ className }) => {
|
||||
const { disablePaidFeatureAds } = useSelector(selectSettings);
|
||||
return (
|
||||
<div className={className}>
|
||||
<Dropdown>
|
||||
<DropdownButton noWrap>
|
||||
<Tooltip delay={1000} position="bottom" message="Account">
|
||||
<CircleButton>
|
||||
<SvgIcon icon="user" />
|
||||
</CircleButton>
|
||||
</Tooltip>
|
||||
</DropdownButton>
|
||||
{session.isLoggedIn() ? (
|
||||
<DropdownItem
|
||||
key="login"
|
||||
stayOpenAfterClick
|
||||
buttonClass={PromptButton}
|
||||
onClick={session.logout}
|
||||
>
|
||||
<StyledIconContainer>
|
||||
<i className="fa fa-sign-out" />
|
||||
</StyledIconContainer>
|
||||
Logout
|
||||
</DropdownItem>
|
||||
) : (
|
||||
<Fragment>
|
||||
<DropdownItem key="login" onClick={showLoginModal}>
|
||||
<StyledIconContainer>
|
||||
<i className="fa fa-sign-in" />
|
||||
</StyledIconContainer>
|
||||
Log In
|
||||
</DropdownItem>
|
||||
{!disablePaidFeatureAds && (
|
||||
<DropdownItem
|
||||
key="invite"
|
||||
buttonClass={Link}
|
||||
// @ts-expect-error -- TSCONVERSION appears to be genuine
|
||||
href="https://insomnia.rest/pricing"
|
||||
button
|
||||
>
|
||||
<StyledIconContainer>
|
||||
<i className="fa fa-users" />
|
||||
</StyledIconContainer>{' '}
|
||||
Upgrade to Plus
|
||||
<i className="fa fa-star surprise fa-outline" />
|
||||
</DropdownItem>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import type { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import { hotKeyRefs } from '../../../common/hotkeys';
|
||||
import { executeHotKey } from '../../../common/hotkeys-listener';
|
||||
import type { Environment } from '../../../models/environment';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import type { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import { hotKeyRefs } from '../../../common/hotkeys';
|
||||
import * as misc from '../../../common/misc';
|
||||
import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import type { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import { hotKeyRefs } from '../../../common/hotkeys';
|
||||
import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render';
|
||||
import * as models from '../../../models';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG, getAppName, getAppVersion } from '../../../common/constants';
|
||||
import { database as db } from '../../../common/database';
|
||||
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
|
||||
import type { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import { hotKeyRefs } from '../../../common/hotkeys';
|
||||
import { executeHotKey } from '../../../common/hotkeys-listener';
|
||||
import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import classnames from 'classnames';
|
||||
import { KeyBindings, KeyCombination } from 'insomnia-common';
|
||||
import React, { FC, memo } from 'react';
|
||||
|
||||
import { isMac } from '../../common/constants';
|
||||
import type { KeyBindings, KeyCombination } from '../../common/hotkeys';
|
||||
import { constructKeyCombinationDisplay, getPlatformKeyCombinations } from '../../common/hotkeys';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import { KeyCombination } from 'insomnia-common';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import type { KeyCombination } from '../../../common/hotkeys';
|
||||
import { constructKeyCombinationDisplay, isModifierKeyCode } from '../../../common/hotkeys';
|
||||
import { keyboardKeys } from '../../../common/keyboard-keys';
|
||||
import * as misc from '../../../common/misc';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import { Curl } from 'node-libcurl';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import * as session from '../../../account/session';
|
||||
import { AUTOBIND_CFG, getAppName, getAppVersion } from '../../../common/constants';
|
||||
import { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import * as models from '../../../models/index';
|
||||
import { Settings } from '../../../models/settings';
|
||||
import { Button } from '../base/button';
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { hotKeyRefs } from '../../../common/hotkeys';
|
||||
import * as hotkeys from '../../../common/hotkeys';
|
||||
import { importFile } from '../../redux/modules/import';
|
||||
import { selectActiveWorkspace } from '../../redux/selectors';
|
||||
import { Hotkey } from '../hotkey';
|
||||
import { Pane, PaneBody, PaneHeader } from './pane';
|
||||
|
||||
interface Props {
|
||||
hotKeyRegistry: hotkeys.HotKeyRegistry;
|
||||
hotKeyRegistry: HotKeyRegistry;
|
||||
handleCreateRequest: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import { hotKeyRefs } from '../../../common/hotkeys';
|
||||
import * as hotkeys from '../../../common/hotkeys';
|
||||
import { Hotkey } from '../hotkey';
|
||||
import { Pane, PaneBody, PaneHeader } from './pane';
|
||||
|
||||
interface Props {
|
||||
hotKeyRegistry: hotkeys.HotKeyRegistry;
|
||||
hotKeyRegistry: HotKeyRegistry;
|
||||
}
|
||||
|
||||
export const PlaceholderResponsePane: FunctionComponent<Props> = ({ hotKeyRegistry, children }) => (
|
||||
|
||||
@@ -2,6 +2,7 @@ import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import { clipboard, remote } from 'electron';
|
||||
import fs from 'fs';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import { json as jsonPrettify } from 'insomnia-prettify';
|
||||
import mime from 'mime-types';
|
||||
import React, { PureComponent } from 'react';
|
||||
@@ -9,7 +10,6 @@ import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import { AUTOBIND_CFG, PREVIEW_MODE_SOURCE } from '../../../common/constants';
|
||||
import { exportHarCurrentRequest } from '../../../common/har';
|
||||
import type { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import { getSetCookieHeaders } from '../../../common/misc';
|
||||
import * as models from '../../../models';
|
||||
import type { Environment } from '../../../models/environment';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import { OpenDialogOptions, remote } from 'electron';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { PureComponent, ReactNode } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG, DEBOUNCE_MILLIS, isMac } from '../../common/constants';
|
||||
import type { HotKeyRegistry } from '../../common/hotkeys';
|
||||
import { hotKeyRefs } from '../../common/hotkeys';
|
||||
import { executeHotKey } from '../../common/hotkeys-listener';
|
||||
import { HandleGetRenderContext, HandleRender } from '../../common/render';
|
||||
|
||||
@@ -1,22 +1,38 @@
|
||||
import { Settings } from 'insomnia-common';
|
||||
import React, { ChangeEvent, FC, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { getControlledStatus } from '../../../models/helpers/settings';
|
||||
import * as models from '../../../models/index';
|
||||
import { BaseSettings } from '../../../models/settings';
|
||||
import { selectSettings } from '../../redux/selectors';
|
||||
import { HelpTooltip } from '../help-tooltip';
|
||||
import { Tooltip } from '../tooltip';
|
||||
import { ControlledSetting } from './controlled-setting';
|
||||
|
||||
const Descriptions = styled.div({
|
||||
fontSize: 'var(--font-size-sm)',
|
||||
opacity: 'var(--opacity-subtle)',
|
||||
paddingLeft: 18,
|
||||
'& *': {
|
||||
marginTop: 'var(--padding-xs)',
|
||||
marginBottom: 'var(--padding-sm)',
|
||||
},
|
||||
});
|
||||
|
||||
export const BooleanSetting: FC<{
|
||||
label: string;
|
||||
setting: keyof BaseSettings;
|
||||
help?: string;
|
||||
/** each element of this array will appear as a paragraph below the setting describing it */
|
||||
descriptions?: string[];
|
||||
forceRestart?: boolean;
|
||||
help?: string;
|
||||
label: string;
|
||||
setting: keyof Settings;
|
||||
}> = ({
|
||||
descriptions,
|
||||
forceRestart,
|
||||
help,
|
||||
label,
|
||||
setting,
|
||||
help,
|
||||
forceRestart,
|
||||
}) => {
|
||||
const settings = useSelector(selectSettings);
|
||||
|
||||
@@ -24,6 +40,8 @@ export const BooleanSetting: FC<{
|
||||
throw new Error(`Invalid boolean setting name ${setting}`);
|
||||
}
|
||||
|
||||
const { isControlled } = getControlledStatus(settings)(setting);
|
||||
|
||||
const onChange = useCallback(async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const { checked } = event.currentTarget;
|
||||
await models.settings.patch({
|
||||
@@ -32,22 +50,33 @@ export const BooleanSetting: FC<{
|
||||
}, [setting]);
|
||||
|
||||
return (
|
||||
<div className="form-control form-control--thin">
|
||||
<label className="inline-block">
|
||||
{label}
|
||||
{help && <HelpTooltip className="space-left">{help}</HelpTooltip>}
|
||||
{forceRestart && (
|
||||
<Tooltip message="Will restart the app" className="space-left">
|
||||
<i className="fa fa-refresh super-duper-faint" />
|
||||
</Tooltip>
|
||||
)}
|
||||
<input
|
||||
checked={Boolean(settings[setting])}
|
||||
name={setting}
|
||||
onChange={onChange}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<ControlledSetting setting={setting}>
|
||||
<div className="form-control form-control--thin">
|
||||
<label className="inline-block">
|
||||
{label}
|
||||
{help && <HelpTooltip className="space-left">{help}</HelpTooltip>}
|
||||
{forceRestart && (
|
||||
<Tooltip message="Will restart the app" className="space-left">
|
||||
<i className="fa fa-refresh super-duper-faint" />
|
||||
</Tooltip>
|
||||
)}
|
||||
<input
|
||||
checked={Boolean(settings[setting])}
|
||||
name={setting}
|
||||
onChange={onChange}
|
||||
type="checkbox"
|
||||
disabled={isControlled}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{descriptions && (
|
||||
<Descriptions>
|
||||
{descriptions.map(description => (
|
||||
<div key={description}>{description}</div>
|
||||
))}
|
||||
</Descriptions>
|
||||
)}
|
||||
</ControlledSetting>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Settings } from 'insomnia-common';
|
||||
import React, { FC, Fragment } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { getControlledStatus } from '../../../models/helpers/settings';
|
||||
import { selectSettings } from '../../redux/selectors';
|
||||
import { HelpTooltip } from '../help-tooltip';
|
||||
|
||||
const Wrapper = styled.div({
|
||||
position: 'relative',
|
||||
marginBottom: 4,
|
||||
marginTop: 14,
|
||||
borderLeft: '1px solid var(--color-surprise)',
|
||||
});
|
||||
|
||||
const Setting = styled.div({
|
||||
padding: '2px 10px',
|
||||
position: 'relative',
|
||||
'&:hover': {
|
||||
cursor: 'not-allowed',
|
||||
},
|
||||
});
|
||||
|
||||
const ControlledOverlay = styled.div({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
zIndex: 2,
|
||||
});
|
||||
|
||||
const Helper = styled.div({
|
||||
color: 'var(--color-surprise)',
|
||||
padding: '2px 10px',
|
||||
marginTop: -2,
|
||||
opacity: 'var(--opacity-subtle)',
|
||||
});
|
||||
|
||||
const HelperText = styled.span({
|
||||
fontStyle: 'italic',
|
||||
});
|
||||
|
||||
export const ControlledSetting: FC<{ setting: keyof Settings }> = ({ children, setting }) => {
|
||||
const settings = useSelector(selectSettings);
|
||||
const { isControlled, controller } = getControlledStatus(settings)(setting);
|
||||
|
||||
if (isControlled === false) {
|
||||
return <Fragment>{children}</Fragment>;
|
||||
}
|
||||
|
||||
let helpText: string | undefined = undefined;
|
||||
let controllerName: string | null = null;
|
||||
switch (controller) {
|
||||
case 'insomnia-config':
|
||||
helpText = `this value is controlled by \`settings.${setting}\` in your Insomnia Config`;
|
||||
controllerName = 'insomnia config';
|
||||
break;
|
||||
|
||||
case 'incognitoMode':
|
||||
helpText = 'this value is controlled by Incognito Mode';
|
||||
controllerName = 'incognito mode';
|
||||
break;
|
||||
|
||||
default:
|
||||
helpText = `this value is controlled by ${controller}`;
|
||||
controllerName = controller || 'another setting';
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Setting>
|
||||
<ControlledOverlay title={helpText} />
|
||||
{children}
|
||||
</Setting>
|
||||
|
||||
<Helper>
|
||||
<HelpTooltip info>{helpText}</HelpTooltip>{' '}
|
||||
<HelperText>controlled by {controllerName}</HelperText>
|
||||
</Helper>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,11 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import * as fontScanner from 'font-scanner';
|
||||
import { HttpVersion, HttpVersions } from 'insomnia-common';
|
||||
import React, { Fragment, PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import type { GlobalActivity, HttpVersion } from '../../../common/constants';
|
||||
import type { GlobalActivity } from '../../../common/constants';
|
||||
import {
|
||||
ACTIVITY_MIGRATION,
|
||||
AUTOBIND_CFG,
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
EDITOR_KEY_MAP_EMACS,
|
||||
EDITOR_KEY_MAP_SUBLIME,
|
||||
EDITOR_KEY_MAP_VIM,
|
||||
HttpVersions,
|
||||
isDevelopment,
|
||||
isMac,
|
||||
MAX_EDITOR_FONT_SIZE,
|
||||
@@ -261,10 +261,12 @@ class General extends PureComponent<Props, State> {
|
||||
setting="editorLineWrapping"
|
||||
/>
|
||||
</div>
|
||||
<div><BooleanSetting
|
||||
label="Font ligatures"
|
||||
setting="fontVariantLigatures"
|
||||
/></div>
|
||||
<div>
|
||||
<BooleanSetting
|
||||
label="Font ligatures"
|
||||
setting="fontVariantLigatures"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-row pad-top-sm">
|
||||
@@ -553,16 +555,18 @@ class General extends PureComponent<Props, State> {
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
<hr className="pad-top" />
|
||||
<h2>Notifications</h2>
|
||||
{!updatesSupported() && (
|
||||
<Fragment>
|
||||
<hr className="pad-top" />
|
||||
<h2>Software Updates</h2>
|
||||
<BooleanSetting
|
||||
label="Do not notify of new releases"
|
||||
setting="disableUpdateNotification"
|
||||
/>
|
||||
</Fragment>
|
||||
<BooleanSetting
|
||||
label="Do not notify of new releases"
|
||||
setting="disableUpdateNotification"
|
||||
/>
|
||||
)}
|
||||
<BooleanSetting
|
||||
label="Do not tell me about premium features"
|
||||
setting="disablePaidFeatureAds"
|
||||
/>
|
||||
|
||||
<hr className="pad-top" />
|
||||
<h2>Plugins</h2>
|
||||
@@ -576,25 +580,31 @@ class General extends PureComponent<Props, State> {
|
||||
},
|
||||
)}
|
||||
|
||||
<br />
|
||||
|
||||
<hr className="pad-top" />
|
||||
<h2>Data Sharing</h2>
|
||||
<div className="form-control form-control--thin">
|
||||
<BooleanSetting
|
||||
label="Send Usage Statistics"
|
||||
setting="enableAnalytics"
|
||||
/>
|
||||
<p className="txt-sm faint">
|
||||
Help Kong improve its products by sending anonymous data about features and plugins
|
||||
used, hardware and software configuration, statistics on number of requests,{' '}
|
||||
{strings.collection.plural.toLowerCase()}, {strings.document.plural.toLowerCase()}, etc.
|
||||
</p>
|
||||
<p className="txt-sm faint">
|
||||
Please note that this will not include personal data or any sensitive information, such
|
||||
as request data, names, etc.
|
||||
</p>
|
||||
</div>
|
||||
<h2>Network Activity</h2>
|
||||
<BooleanSetting
|
||||
descriptions={[
|
||||
'In incognito mode, Insomnia will not make any network requests other than the requests you ask it to send. You\'ll still be able to log in and manually sync collections, but any background network requests that are not the direct result of your actions will be disabled.',
|
||||
'Note that, similar to incognito mode in Chrome, Insomnia cannot control the network behavior of any plugins you have installed.',
|
||||
]}
|
||||
label="Incognito Mode"
|
||||
setting="incognitoMode"
|
||||
/>
|
||||
|
||||
<BooleanSetting
|
||||
descriptions={[
|
||||
`Help Kong improve its products by sending anonymous data about features and plugins used, hardware and software configuration, statistics on number of requests, ${strings.collection.plural.toLowerCase()}, ${strings.document.plural.toLowerCase()}, etc.`,
|
||||
'Please note that this will not include personal data or any sensitive information, such as request data, names, etc.',
|
||||
]}
|
||||
label="Send Usage Statistics"
|
||||
setting="enableAnalytics"
|
||||
/>
|
||||
|
||||
<BooleanSetting
|
||||
descriptions={['Insomnia periodically makes background requests to api.insomnia.rest/notifications for things like email verification, out-of-date billing information, trial information.']}
|
||||
label="Allow Notification Requests"
|
||||
setting="allowNotificationRequests"
|
||||
/>
|
||||
|
||||
<hr className="pad-top" />
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import * as electron from 'electron';
|
||||
import { PluginConfig } from 'insomnia-common';
|
||||
import { Button, ToggleSwitch } from 'insomnia-components';
|
||||
import * as path from 'path';
|
||||
import React, { ChangeEvent, FormEvent, PureComponent } from 'react';
|
||||
@@ -12,7 +13,7 @@ import {
|
||||
} from '../../../common/constants';
|
||||
import { docsPlugins } from '../../../common/documentation';
|
||||
import { delay } from '../../../common/misc';
|
||||
import type { PluginConfig, Settings } from '../../../models/settings';
|
||||
import type { Settings } from '../../../models/settings';
|
||||
import { createPlugin } from '../../../plugins/create';
|
||||
import type { Plugin } from '../../../plugins/index';
|
||||
import { getPlugins } from '../../../plugins/index';
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import { HotKeyRegistry, KeyCombination } from 'insomnia-common';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import type { HotKeyDefinition, HotKeyRegistry, KeyCombination } from '../../../common/hotkeys';
|
||||
import {
|
||||
areKeyBindingsSameAsDefault,
|
||||
areSameKeyCombinations,
|
||||
constructKeyCombinationDisplay,
|
||||
getPlatformKeyCombinations,
|
||||
HotKeyDefinition,
|
||||
hotKeyRefs,
|
||||
newDefaultKeyBindings,
|
||||
newDefaultRegistry,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { Fragment, PureComponent } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import type { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import { HandleRender } from '../../../common/render';
|
||||
import { GrpcRequest, isGrpcRequest } from '../../../models/grpc-request';
|
||||
import { isRequest, Request } from '../../../models/request';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import type { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import { hotKeyRefs } from '../../../common/hotkeys';
|
||||
import { RequestGroup } from '../../../models/request-group';
|
||||
import { Dropdown } from '../base/dropdown/dropdown';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG, DEBOUNCE_MILLIS, SortOrder } from '../../../common/constants';
|
||||
import type { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import { hotKeyRefs } from '../../../common/hotkeys';
|
||||
import { executeHotKey } from '../../../common/hotkeys-listener';
|
||||
import { KeydownBinder } from '../keydown-binder';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { createRef } from 'react';
|
||||
@@ -7,7 +8,6 @@ import { DragSource, DragSourceSpec, DropTarget, DropTargetMonitor, DropTargetSp
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import * as misc from '../../../common/misc';
|
||||
import { HandleRender } from '../../../common/render';
|
||||
import { RequestGroup } from '../../../models/request-group';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { DragSource, DragSourceSpec, DropTarget, DropTargetSpec } from 'react-dnd';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { AUTOBIND_CFG, CONTENT_TYPE_GRAPHQL } from '../../../common/constants';
|
||||
import { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import { getMethodOverrideHeader } from '../../../common/misc';
|
||||
import { HandleRender } from '../../../common/render';
|
||||
import { GrpcRequest, isGrpcRequest } from '../../../models/grpc-request';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import classnames from 'classnames';
|
||||
import { HotKeyRegistry } from 'insomnia-common';
|
||||
import React, { forwardRef, memo, ReactNode } from 'react';
|
||||
|
||||
import {
|
||||
COLLAPSE_SIDEBAR_REMS,
|
||||
SIDEBAR_SKINNY_REMS,
|
||||
} from '../../../common/constants';
|
||||
import type { HotKeyRegistry } from '../../../common/hotkeys';
|
||||
import type { Environment } from '../../../models/environment';
|
||||
import type { Workspace } from '../../../models/workspace';
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
import * as models from '../../models/index';
|
||||
import imgSrcCore from '../images/insomnia-core-logo.png';
|
||||
import { Link } from './base/link';
|
||||
const LOCALSTORAGE_KEY = 'insomnia::notifications::seen';
|
||||
|
||||
const INSOMNIA_NOTIFICATIONS_SEEN = 'insomnia::notifications::seen';
|
||||
|
||||
export interface ToastNotification {
|
||||
key: string;
|
||||
@@ -57,6 +58,8 @@ const StyledFooter = styled.footer`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type SeenNotifications = Record<string, boolean>;
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class Toast extends PureComponent<{}, State> {
|
||||
_interval: NodeJS.Timeout | null = null;
|
||||
@@ -67,7 +70,7 @@ export class Toast extends PureComponent<{}, State> {
|
||||
appName: getAppName(),
|
||||
};
|
||||
|
||||
_handlePostCTACleanup() {
|
||||
_cancel() {
|
||||
const { notification } = this.state;
|
||||
|
||||
if (!notification) {
|
||||
@@ -77,20 +80,31 @@ export class Toast extends PureComponent<{}, State> {
|
||||
this._dismissNotification();
|
||||
}
|
||||
|
||||
_handleCancelClick() {
|
||||
const { notification } = this.state;
|
||||
|
||||
_handleNotification(notification: ToastNotification | null | undefined) {
|
||||
if (!notification) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._dismissNotification();
|
||||
}
|
||||
|
||||
_hasSeenNotification(notification: ToastNotification) {
|
||||
const seenNotifications = this._loadSeen();
|
||||
|
||||
return seenNotifications[notification.key];
|
||||
console.log(`[toast] Received notification ${notification.key}`);
|
||||
|
||||
if (seenNotifications[notification.key]) {
|
||||
console.log(`[toast] Not showing notification ${notification.key} because has already been seen`);
|
||||
return;
|
||||
}
|
||||
|
||||
seenNotifications[notification.key] = true;
|
||||
const obj = JSON.stringify(seenNotifications, null, 2);
|
||||
window.localStorage.setItem(INSOMNIA_NOTIFICATIONS_SEEN, obj);
|
||||
|
||||
this.setState({
|
||||
notification,
|
||||
visible: false,
|
||||
});
|
||||
|
||||
// Fade the notification in
|
||||
setTimeout(() => { this.setState({ visible: true }); }, 1000);
|
||||
}
|
||||
|
||||
async _checkForNotifications() {
|
||||
@@ -100,64 +114,51 @@ export class Toast extends PureComponent<{}, State> {
|
||||
}
|
||||
|
||||
const stats = await models.stats.get();
|
||||
const settings = await models.settings.getOrCreate();
|
||||
let notification: ToastNotification;
|
||||
const {
|
||||
allowNotificationRequests,
|
||||
disablePaidFeatureAds,
|
||||
disableUpdateNotification,
|
||||
updateAutomatically,
|
||||
updateChannel,
|
||||
} = await models.settings.getOrCreate();
|
||||
|
||||
if (!allowNotificationRequests) {
|
||||
// if the user has specifically said they don't want to send notification requests, then exit early
|
||||
return;
|
||||
}
|
||||
|
||||
let notification: ToastNotification | null = null;
|
||||
|
||||
// Try fetching user notification
|
||||
try {
|
||||
const data = {
|
||||
firstLaunch: stats.created,
|
||||
// Used for account verification notifications
|
||||
launches: stats.launches,
|
||||
// Used for CTAs / Informational notifications
|
||||
platform: getAppPlatform(),
|
||||
app: getAppId(),
|
||||
version: getAppVersion(),
|
||||
autoUpdatesDisabled: !updateAutomatically,
|
||||
disablePaidFeatureAds,
|
||||
disableUpdateNotification,
|
||||
firstLaunch: stats.created,
|
||||
launches: stats.launches, // Used for account verification notifications
|
||||
platform: getAppPlatform(), // Used for CTAs / Informational notifications
|
||||
updateChannel,
|
||||
updatesNotSupported: !updatesSupported(),
|
||||
autoUpdatesDisabled: !settings.updateAutomatically,
|
||||
disableUpdateNotification: settings.disableUpdateNotification,
|
||||
updateChannel: settings.updateChannel,
|
||||
version: getAppVersion(),
|
||||
};
|
||||
notification = await fetch.post('/notification', data, session.getCurrentSessionId());
|
||||
} catch (err) {
|
||||
console.warn('[toast] Failed to fetch user notifications', err);
|
||||
}
|
||||
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
this._handleNotification(notification);
|
||||
}
|
||||
|
||||
_handleNotification(notification: ToastNotification | null | undefined) {
|
||||
// No new notifications
|
||||
if (!notification || this._hasSeenNotification(notification)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remember that we've seen it
|
||||
const seenNotifications = this._loadSeen();
|
||||
|
||||
seenNotifications[notification.key] = true;
|
||||
const obj = JSON.stringify(seenNotifications, null, 2);
|
||||
window.localStorage.setItem(LOCALSTORAGE_KEY, obj);
|
||||
// Show the notification
|
||||
this.setState({
|
||||
notification,
|
||||
visible: false,
|
||||
});
|
||||
// Fade the notification in
|
||||
setTimeout(
|
||||
() =>
|
||||
this.setState({
|
||||
visible: true,
|
||||
}),
|
||||
1000,
|
||||
);
|
||||
}
|
||||
|
||||
_loadSeen() {
|
||||
try {
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
return JSON.parse(window.localStorage.getItem(LOCALSTORAGE_KEY)) || {};
|
||||
const storedKeys = window.localStorage.getItem(INSOMNIA_NOTIFICATIONS_SEEN);
|
||||
if (!storedKeys) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return JSON.parse(storedKeys) as SeenNotifications || {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
@@ -171,25 +172,15 @@ export class Toast extends PureComponent<{}, State> {
|
||||
}
|
||||
|
||||
// Hide the currently showing notification
|
||||
this.setState({
|
||||
visible: false,
|
||||
});
|
||||
this.setState({ visible: false });
|
||||
|
||||
// Give time for toast to fade out, then remove it
|
||||
setTimeout(() => {
|
||||
this.setState(
|
||||
{
|
||||
notification: null,
|
||||
},
|
||||
async () => {
|
||||
await this._checkForNotifications();
|
||||
},
|
||||
);
|
||||
this.setState({ notification: null }, this._checkForNotifications);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
_listenerShowNotification(_e, notification: ToastNotification) {
|
||||
console.log('[toast] Received notification ' + notification.key);
|
||||
|
||||
this._handleNotification(notification);
|
||||
}
|
||||
|
||||
@@ -223,11 +214,11 @@ export class Toast extends PureComponent<{}, State> {
|
||||
<img src={imgSrcCore} alt={appName} />
|
||||
</StyledLogo>
|
||||
<StyledContent>
|
||||
<p>{notification ? notification.message : 'Unknown'}</p>
|
||||
<p>{notification?.message || 'Unknown'}</p>
|
||||
<StyledFooter>
|
||||
<button
|
||||
className="btn btn--super-duper-compact btn--outlined"
|
||||
onClick={this._handleCancelClick}
|
||||
onClick={this._cancel}
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
@@ -235,7 +226,7 @@ export class Toast extends PureComponent<{}, State> {
|
||||
<Link
|
||||
button
|
||||
className="btn btn--super-duper-compact btn--outlined no-wrap"
|
||||
onClick={this._handlePostCTACleanup}
|
||||
onClick={this._cancel}
|
||||
href={notification.url}
|
||||
>
|
||||
{notification.cta}
|
||||
|
||||
@@ -515,8 +515,7 @@ class App extends PureComponent<AppProps, State> {
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
async _handleRenderText<T>(obj: T, contextCacheKey = null) {
|
||||
// @ts-expect-error -- TSCONVERSION contextCacheKey being null used as object index
|
||||
async _handleRenderText<T>(obj: T, contextCacheKey: string | null = null) {
|
||||
if (!contextCacheKey || !this._getRenderContextPromiseCache[contextCacheKey]) {
|
||||
// NOTE: We're caching promises here to avoid race conditions
|
||||
// @ts-expect-error -- TSCONVERSION contextCacheKey being null used as object index
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
input[type='checkbox'] {
|
||||
height: 1rem;
|
||||
float: left;
|
||||
margin-top: var(--padding-xxs);
|
||||
margin-top: 1px; // This is a magic number, yes, but there's some deeper problems at play with form styling and this is a hold-over until the DOM design is simplified. Before this fix it looked pretty bad, so this is at least a big improvement in one respect.
|
||||
margin-right: var(--padding-xs);
|
||||
}
|
||||
|
||||
|
||||
@@ -128,4 +128,75 @@ describe('useRemoteProjects', () => {
|
||||
}),
|
||||
]));
|
||||
});
|
||||
|
||||
it('should load teams on mount if not in incognitoMode', async () => {
|
||||
await models.settings.patch({ incognitoMode: false });
|
||||
const store = mockStore(await reduxStateForTest({ isLoggedIn: true }));
|
||||
|
||||
const vcs = newMockedVcs();
|
||||
vcs.teams.mockResolvedValue([]);
|
||||
|
||||
// Render hook first time
|
||||
const { result } = renderHook(() => useRemoteProjects(vcs), { wrapper: withReduxStore(store) });
|
||||
|
||||
// Refresh manually
|
||||
await act(() => result.current.refresh());
|
||||
|
||||
// Called twice - once manually and once on mount
|
||||
expect(vcs.teams).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not load teams on mount if in incognitoMode', async () => {
|
||||
// Set up in incognito mode
|
||||
await models.settings.patch({ incognitoMode: true });
|
||||
const store = mockStore(await reduxStateForTest({ isLoggedIn: true }));
|
||||
|
||||
const vcs = newMockedVcs();
|
||||
vcs.teams.mockResolvedValue([]);
|
||||
|
||||
// Render hook first time
|
||||
const { result } = renderHook(() => useRemoteProjects(vcs), { wrapper: withReduxStore(store) });
|
||||
|
||||
// Refresh manually
|
||||
await act(() => result.current.refresh());
|
||||
|
||||
// Called only once (manually), because load on mount was skipped
|
||||
expect(vcs.teams).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should load teams on mount if incognitoMode goes from on to off', async () => {
|
||||
// Set up in incognito mode
|
||||
await models.settings.patch({ incognitoMode: true });
|
||||
let state = await reduxStateForTest({ isLoggedIn: true });
|
||||
const store = mockStore(() => state);
|
||||
|
||||
const vcs = newMockedVcs();
|
||||
vcs.teams.mockResolvedValue([]);
|
||||
|
||||
// Render hook first time
|
||||
const { result, rerender } = renderHook(() => useRemoteProjects(vcs), { wrapper: withReduxStore(store) });
|
||||
|
||||
// Refresh manually
|
||||
await act(() => result.current.refresh());
|
||||
|
||||
// Called only once (manually), because load on mount was skipped
|
||||
expect(vcs.teams).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Reset incognito mode and update state
|
||||
await models.settings.patch({ incognitoMode: false });
|
||||
vcs.teams.mockClear();
|
||||
|
||||
// Force the store to update
|
||||
state = await reduxStateForTest({ isLoggedIn: true });
|
||||
store.dispatch({ type: 'ANY_ACTION' });
|
||||
|
||||
// Render the hook again with updated redux store
|
||||
rerender();
|
||||
|
||||
// Refresh manually
|
||||
await act(() => result.current.refresh());
|
||||
|
||||
// Called twice - once manually and once on mount
|
||||
expect(vcs.teams).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,12 +6,13 @@ import { useAsync } from 'react-use';
|
||||
import { database } from '../../common/database';
|
||||
import { initializeProjectFromTeam } from '../../sync/vcs/initialize-model-from';
|
||||
import { VCS } from '../../sync/vcs/vcs';
|
||||
import { selectIsLoggedIn } from '../redux/selectors';
|
||||
import { selectIsLoggedIn, selectSettings } from '../redux/selectors';
|
||||
import { useSafeState } from './use-safe-state';
|
||||
|
||||
export const useRemoteProjects = (vcs?: VCS) => {
|
||||
const [loading, setLoading] = useSafeState(false);
|
||||
const isLoggedIn = useSelector(selectIsLoggedIn);
|
||||
const { incognitoMode } = useSelector(selectSettings);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (vcs && isLoggedIn) {
|
||||
@@ -26,7 +27,10 @@ export const useRemoteProjects = (vcs?: VCS) => {
|
||||
}, [vcs, setLoading, isLoggedIn]);
|
||||
|
||||
// If the refresh callback changes, refresh
|
||||
useAsync(refresh, [refresh]);
|
||||
|
||||
useAsync(async () => {
|
||||
if (!incognitoMode) {
|
||||
await refresh();
|
||||
}
|
||||
}, [refresh, incognitoMode]);
|
||||
return { loading, refresh };
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BackendProjectWithTeam } from '../../sync/vcs/normalize-backend-project
|
||||
import { pullBackendProject } from '../../sync/vcs/pull-backend-project';
|
||||
import { VCS } from '../../sync/vcs/vcs';
|
||||
import { showAlert } from '../components/modals';
|
||||
import { selectActiveProject, selectAllWorkspaces, selectIsLoggedIn, selectRemoteProjects } from '../redux/selectors';
|
||||
import { selectActiveProject, selectAllWorkspaces, selectIsLoggedIn, selectRemoteProjects, selectSettings } from '../redux/selectors';
|
||||
import { useSafeReducerDispatch } from './use-safe-reducer-dispatch';
|
||||
|
||||
interface State {
|
||||
@@ -52,6 +52,7 @@ export const useRemoteWorkspaces = (vcs?: VCS) => {
|
||||
const activeProject = useSelector(selectActiveProject);
|
||||
const remoteProjects = useSelector(selectRemoteProjects);
|
||||
const isLoggedIn = useSelector(selectIsLoggedIn);
|
||||
const { incognitoMode } = useSelector(selectSettings);
|
||||
|
||||
// Local state
|
||||
const [{ loading, localBackendProjects, remoteBackendProjects, pullingBackendProjects }, _dispatch] = useReducer(reducer, initialState);
|
||||
@@ -107,7 +108,11 @@ export const useRemoteWorkspaces = (vcs?: VCS) => {
|
||||
}, [vcs, refresh, remoteProjects, dispatch]);
|
||||
|
||||
// If the refresh callback changes, refresh
|
||||
useAsync(refresh, [refresh]);
|
||||
useAsync(async () => {
|
||||
if (!incognitoMode) {
|
||||
await refresh();
|
||||
}
|
||||
}, [refresh, incognitoMode]);
|
||||
|
||||
return {
|
||||
loading,
|
||||
|
||||
@@ -101,6 +101,8 @@
|
||||
"httpsnippet": "^1.23.0",
|
||||
"iconv-lite": "^0.4.15",
|
||||
"insomnia-components": "2.3.2",
|
||||
"insomnia-common": "2.3.2",
|
||||
"insomnia-config": "2.3.2",
|
||||
"insomnia-cookies": "2.3.2",
|
||||
"insomnia-importers": "2.3.2",
|
||||
"insomnia-plugin-base64": "2.3.2",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"include": [
|
||||
"**/*.d.ts",
|
||||
"app",
|
||||
"**/insomnia.config.json",
|
||||
"package.json"
|
||||
],
|
||||
"exclude": [
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"app",
|
||||
"config",
|
||||
"jest.config.js",
|
||||
"**/insomnia.config.json",
|
||||
"package.json",
|
||||
"scripts",
|
||||
"send-request",
|
||||
|
||||
1
packages/insomnia-common/.eslintignore
Normal file
1
packages/insomnia-common/.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
dist
|
||||
4
packages/insomnia-common/.eslintrc.js
Normal file
4
packages/insomnia-common/.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
/** @type { import('eslint').Linter.Config } */
|
||||
module.exports = {
|
||||
extends: '../../.eslintrc.js',
|
||||
};
|
||||
9
packages/insomnia-common/jest.config.js
Normal file
9
packages/insomnia-common/jest.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/** @type { import('@jest/types').Config.InitialOptions } */
|
||||
module.exports = {
|
||||
preset: '../../jest-preset.js',
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
isolatedModules: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
390
packages/insomnia-common/package-lock.json
generated
Normal file
390
packages/insomnia-common/package-lock.json
generated
Normal file
@@ -0,0 +1,390 @@
|
||||
{
|
||||
"name": "insomnia-common",
|
||||
"version": "2.3.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
|
||||
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.17.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.15.tgz",
|
||||
"integrity": "sha512-D1sdW0EcSCmNdLKBGMYb38YsHUS6JcM7yQ6sLQ9KuZ35ck7LYCKE7kYFHOO59ayFOY3zobWVZxf4KXhYHcHYFA==",
|
||||
"dev": true
|
||||
},
|
||||
"ajv": {
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz",
|
||||
"integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"cliui": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
|
||||
"integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true
|
||||
},
|
||||
"json-stable-stringify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
|
||||
"integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"jsonify": "~0.0.0"
|
||||
}
|
||||
},
|
||||
"jsonify": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
|
||||
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
|
||||
"dev": true
|
||||
},
|
||||
"make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||
"dev": true
|
||||
},
|
||||
"require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
|
||||
"dev": true
|
||||
},
|
||||
"require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.20",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz",
|
||||
"integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
|
||||
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz",
|
||||
"integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"source-map-support": "^0.5.17",
|
||||
"yn": "3.1.1"
|
||||
}
|
||||
},
|
||||
"type-fest": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
|
||||
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
|
||||
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
|
||||
"dev": true
|
||||
},
|
||||
"typescript-json-schema": {
|
||||
"version": "0.50.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.50.1.tgz",
|
||||
"integrity": "sha512-GCof/SDoiTDl0qzPonNEV4CHyCsZEIIf+mZtlrjoD8vURCcEzEfa2deRuxYid8Znp/e27eDR7Cjg8jgGrimBCA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.7",
|
||||
"@types/node": "^14.14.33",
|
||||
"glob": "^7.1.6",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "~4.2.3",
|
||||
"yargs": "^16.2.0"
|
||||
}
|
||||
},
|
||||
"uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cliui": "^7.0.2",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.0",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^20.2.2"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "20.2.9",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
||||
"dev": true
|
||||
},
|
||||
"yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
31
packages/insomnia-common/package.json
Normal file
31
packages/insomnia-common/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "insomnia-common",
|
||||
"version": "2.3.2",
|
||||
"homepage": "https://insomnia.rest",
|
||||
"description": "Top-level entities and utilities for Insomnia",
|
||||
"author": "Kong <office@konghq.com>",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Kong/insomnia.git",
|
||||
"directory": "packages/insomnia-config"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/kong/insomnia/issues"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"bootstrap": "npm run build",
|
||||
"lint": "eslint . --ext .js,.ts,.tsx",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"clean": "tsc --build tsconfig.build.json --clean",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "tsc --build tsconfig.build.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ajv": "^8.6.2",
|
||||
"type-fest": "^1.0.2",
|
||||
"typescript-json-schema": "^0.50.1"
|
||||
}
|
||||
}
|
||||
12
packages/insomnia-common/src/constants.ts
Normal file
12
packages/insomnia-common/src/constants.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ValueOf } from 'type-fest';
|
||||
|
||||
// HTTP version codes
|
||||
export const HttpVersions = {
|
||||
V1_0: 'V1_0',
|
||||
V1_1: 'V1_1',
|
||||
V2_0: 'V2_0',
|
||||
v3: 'v3',
|
||||
default: 'default',
|
||||
} as const;
|
||||
|
||||
export type HttpVersion = ValueOf<typeof HttpVersions>;
|
||||
25
packages/insomnia-common/src/entities/hotkeys.ts
Normal file
25
packages/insomnia-common/src/entities/hotkeys.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* The combination of key presses that will activate a hotkey if pressed.
|
||||
*/
|
||||
export interface KeyCombination {
|
||||
ctrl: boolean;
|
||||
alt: boolean;
|
||||
shift: boolean;
|
||||
meta: boolean;
|
||||
keyCode: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The collection of a hotkey's key combinations for each platforms.
|
||||
*/
|
||||
export interface KeyBindings {
|
||||
macKeys: KeyCombination[];
|
||||
// The key combinations for both Windows and Linux.
|
||||
winLinuxKeys: KeyCombination[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The collection of defined hotkeys.
|
||||
* The registry maps a hotkey by its reference id to its key bindings.
|
||||
*/
|
||||
export type HotKeyRegistry = Record<string, KeyBindings>;
|
||||
2
packages/insomnia-common/src/entities/index.ts
Normal file
2
packages/insomnia-common/src/entities/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './hotkeys';
|
||||
export * from './settings';
|
||||
73
packages/insomnia-common/src/entities/settings.ts
Normal file
73
packages/insomnia-common/src/entities/settings.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { HttpVersion } from '../constants';
|
||||
import { HotKeyRegistry } from './hotkeys';
|
||||
|
||||
export interface PluginConfig {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export type PluginConfigMap = Record<string, PluginConfig>;
|
||||
|
||||
export interface Settings {
|
||||
/** If false, Insomnia won't send requests to the api.insomnia.rest/notifications endpoint. This can have effects like: users won’t be notified in-app about billing issues, and they won’t receive tips about app usage. */
|
||||
allowNotificationRequests: boolean;
|
||||
autoDetectColorScheme: boolean;
|
||||
autoHideMenuBar: boolean;
|
||||
autocompleteDelay: number;
|
||||
clearOAuth2SessionOnRestart: boolean;
|
||||
darkTheme: string;
|
||||
deviceId: string | null;
|
||||
disableHtmlPreviewJs: boolean;
|
||||
|
||||
/** If true, Insomnia won’t show any visual elements that recommend plan upgrades. */
|
||||
disablePaidFeatureAds: boolean;
|
||||
disableResponsePreviewLinks: boolean;
|
||||
|
||||
/** If true, Insomnia won’t show a notification when new updates are available. Users can still check for updates in Preferences. */
|
||||
disableUpdateNotification: boolean;
|
||||
editorFontSize: number;
|
||||
editorIndentSize: number;
|
||||
editorIndentWithTabs: boolean;
|
||||
editorKeyMap: string;
|
||||
editorLineWrapping: boolean;
|
||||
|
||||
/** If true, Insomnia will send anonymous data about features and plugins used. */
|
||||
enableAnalytics: boolean;
|
||||
environmentHighlightColorStyle: string;
|
||||
filterResponsesByEnv: boolean;
|
||||
followRedirects: boolean;
|
||||
fontInterface: string | null;
|
||||
fontMonospace: string | null;
|
||||
fontSize: number;
|
||||
fontVariantLigatures: boolean;
|
||||
forceVerticalLayout: boolean;
|
||||
hasPromptedAnalytics: boolean;
|
||||
hasPromptedOnboarding: boolean;
|
||||
hasPromptedToMigrateFromDesigner: boolean;
|
||||
hotKeyRegistry: HotKeyRegistry;
|
||||
httpProxy: string;
|
||||
httpsProxy: string;
|
||||
isVariableUncovered?: boolean;
|
||||
lightTheme: string;
|
||||
lineWrapping?: boolean;
|
||||
maxHistoryResponses: number;
|
||||
maxRedirects: number;
|
||||
maxTimelineDataSizeKB: number;
|
||||
noProxy: string;
|
||||
nunjucksPowerUserMode: boolean;
|
||||
pluginConfig: PluginConfigMap;
|
||||
pluginPath: string;
|
||||
preferredHttpVersion: HttpVersion;
|
||||
proxyEnabled: boolean;
|
||||
|
||||
/** If true, won’t make any network requests other than the requests you ask it to send. This configuration controls Send Usage Stats (`enableAnalytics`) and Allow Notification Requests (`allowNotificationRequests`). */
|
||||
incognitoMode: boolean;
|
||||
showPasswords: boolean;
|
||||
theme: string;
|
||||
timeout: number;
|
||||
updateAutomatically: boolean;
|
||||
updateChannel: string;
|
||||
useBulkHeaderEditor: boolean;
|
||||
useBulkParametersEditor: boolean;
|
||||
validateAuthSSL: boolean;
|
||||
validateSSL: boolean;
|
||||
}
|
||||
2
packages/insomnia-common/src/index.ts
Normal file
2
packages/insomnia-common/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './constants';
|
||||
export * from './entities';
|
||||
16
packages/insomnia-common/tsconfig.build.json
Normal file
16
packages/insomnia-common/tsconfig.build.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"resolveJsonModule": true,
|
||||
"strict": true,
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.test.ts",
|
||||
],
|
||||
}
|
||||
15
packages/insomnia-common/tsconfig.json
Normal file
15
packages/insomnia-common/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "./tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"rootDir": ".",
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"jest.config.js",
|
||||
".eslintrc.js",
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
],
|
||||
}
|
||||
1
packages/insomnia-config/.eslintignore
Normal file
1
packages/insomnia-config/.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
dist
|
||||
4
packages/insomnia-config/.eslintrc.js
Normal file
4
packages/insomnia-config/.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
/** @type { import('eslint').Linter.Config } */
|
||||
module.exports = {
|
||||
extends: '../../.eslintrc.js',
|
||||
};
|
||||
24
packages/insomnia-config/README.md
Normal file
24
packages/insomnia-config/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Insomnia Config
|
||||
|
||||
## Design Goals
|
||||
|
||||
- ensure that changing the root TypeScript types will generate new config
|
||||
- ensure that making a breaking change to TypeScript will cause failing tests to be updated
|
||||
- run validation on the CI
|
||||
|
||||
## Editor Integration
|
||||
|
||||
To get editor integration for this schema, tell your IDE about it. For example, in VS Code add the following to your `.vscode/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"insomnia.config.json",
|
||||
],
|
||||
"url": "https://raw.githubusercontent.com/Kong/insomnia/develop/packages/insomnia-config/src/generated/schemas/insomnia.schema.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
9
packages/insomnia-config/jest.config.js
Normal file
9
packages/insomnia-config/jest.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/** @type { import('@jest/types').Config.InitialOptions } */
|
||||
module.exports = {
|
||||
preset: '../../jest-preset.js',
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
isolatedModules: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
4819
packages/insomnia-config/package-lock.json
generated
Normal file
4819
packages/insomnia-config/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
packages/insomnia-config/package.json
Normal file
39
packages/insomnia-config/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "insomnia-config",
|
||||
"version": "2.3.2",
|
||||
"homepage": "https://insomnia.rest",
|
||||
"description": "Configuration for Insomnia",
|
||||
"author": "Kong <office@konghq.com>",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Kong/insomnia.git",
|
||||
"directory": "packages/insomnia-config"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/kong/insomnia/issues"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"bootstrap": "npm run generate",
|
||||
"lint": "eslint . --ext .js,.ts,.tsx",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"test": "jest",
|
||||
"pregenerate": "npm run build",
|
||||
"generate": "ts-node ./src/generated/generate.ts",
|
||||
"clean": "tsc --build tsconfig.build.json --clean",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "tsc --build tsconfig.build.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"insomnia-common": "2.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ajv": "^8.6.2",
|
||||
"ajv-errors": "^3.0.0",
|
||||
"jest": "^26.6.3",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript-json-schema": "^0.50.1"
|
||||
}
|
||||
}
|
||||
21
packages/insomnia-config/src/entities.ts
Normal file
21
packages/insomnia-config/src/entities.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Settings } from 'insomnia-common';
|
||||
|
||||
type ConfigVersion = '1.0.0';
|
||||
|
||||
type AllowedSettings = Partial<Pick<Settings,
|
||||
| 'allowNotificationRequests'
|
||||
| 'disableUpdateNotification'
|
||||
| 'enableAnalytics'
|
||||
| 'disablePaidFeatureAds'
|
||||
| 'incognitoMode'
|
||||
>>;
|
||||
|
||||
/**
|
||||
* Configuration for Insomnia.
|
||||
*
|
||||
* @TJS-title Insomnia Config
|
||||
*/
|
||||
export interface InsomniaConfig {
|
||||
insomniaConfig: ConfigVersion;
|
||||
settings?: AllowedSettings;
|
||||
}
|
||||
36
packages/insomnia-config/src/generated/generate.ts
Normal file
36
packages/insomnia-config/src/generated/generate.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { writeFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { CompilerOptions, generateSchema, getProgramFromFiles, PartialArgs } from 'typescript-json-schema';
|
||||
|
||||
const settings: PartialArgs = {
|
||||
// TODO (INS-1033): some day, it'd be ideal to do something like this. (when we do, remember to change the README for insomnia-config)
|
||||
// id: 'https://schema.insomnia.rest/json/draft-07/config/v1.0.0/',
|
||||
noExtraProps: true,
|
||||
required: true,
|
||||
};
|
||||
|
||||
const compilerOptions: CompilerOptions = {
|
||||
strict: true,
|
||||
};
|
||||
|
||||
const basePath = '.';
|
||||
|
||||
const program = getProgramFromFiles(
|
||||
[resolve('./src/entities.ts')],
|
||||
compilerOptions,
|
||||
basePath,
|
||||
);
|
||||
|
||||
export const schema = generateSchema(
|
||||
program,
|
||||
'InsomniaConfig',
|
||||
settings,
|
||||
);
|
||||
|
||||
if (schema === null) {
|
||||
throw new Error('failed to generate Insomnia Config');
|
||||
}
|
||||
|
||||
const schemaDestination = './src/generated/schemas/insomnia.schema.json';
|
||||
const stringSchema = JSON.stringify(schema, null, 2);
|
||||
writeFileSync(schemaDestination, stringSchema);
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"description": "Configuration for Insomnia.",
|
||||
"title": "Insomnia Config",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"insomniaConfig": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"1.0.0"
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/definitions/Partial<Pick<Settings,\"allowNotificationRequests\"|\"disableUpdateNotification\"|\"enableAnalytics\"|\"disablePaidFeatureAds\"|\"incognitoMode\">>"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"insomniaConfig"
|
||||
],
|
||||
"definitions": {
|
||||
"Partial<Pick<Settings,\"allowNotificationRequests\"|\"disableUpdateNotification\"|\"enableAnalytics\"|\"disablePaidFeatureAds\"|\"incognitoMode\">>": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allowNotificationRequests": {
|
||||
"description": "If false, Insomnia won't send requests to the api.insomnia.rest/notifications endpoint. This can have effects like: users won’t be notified in-app about billing issues, and they won’t receive tips about app usage.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableUpdateNotification": {
|
||||
"description": "If true, Insomnia won’t show a notification when new updates are available. Users can still check for updates in Preferences.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableAnalytics": {
|
||||
"description": "If true, Insomnia will send anonymous data about features and plugins used.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"disablePaidFeatureAds": {
|
||||
"description": "If true, Insomnia won’t show any visual elements that recommend plan upgrades.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"incognitoMode": {
|
||||
"description": "If true, won’t make any network requests other than the requests you ask it to send. This configuration controls Send Usage Stats (`enableAnalytics`) and Allow Notification Requests (`allowNotificationRequests`).",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
2
packages/insomnia-config/src/index.ts
Normal file
2
packages/insomnia-config/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { validate } from './validate';
|
||||
export { InsomniaConfig } from './entities';
|
||||
144
packages/insomnia-config/src/validate.test.ts
Normal file
144
packages/insomnia-config/src/validate.test.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { InsomniaConfig } from '.';
|
||||
import { ingest, validate } from './validate';
|
||||
|
||||
describe('ingest', () => {
|
||||
const config: InsomniaConfig = {
|
||||
insomniaConfig: '1.0.0',
|
||||
};
|
||||
|
||||
it('returns arbitrary input without modification', () => {
|
||||
const result = ingest(config);
|
||||
expect(result).toStrictEqual(config);
|
||||
});
|
||||
|
||||
it('parses a string insomnia config', () => {
|
||||
const stringConfig = JSON.stringify(config, null, 2);
|
||||
const result = ingest(stringConfig);
|
||||
expect(result).toStrictEqual(config);
|
||||
});
|
||||
|
||||
it('throws on bad inputs', () => {
|
||||
const result = () => ingest('Lumpy Gravy');
|
||||
expect(result).toThrowError('Unexpected token L in JSON at position 0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validate', () => {
|
||||
it('passes with an empty config', () => {
|
||||
const { valid, errors } = validate({
|
||||
insomniaConfig: '1.0.0',
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(valid).toBe(true);
|
||||
});
|
||||
|
||||
it('passes with a simple valid config', () => {
|
||||
const { valid, errors } = validate({
|
||||
insomniaConfig: '1.0.0',
|
||||
settings: {
|
||||
enableAnalytics: false,
|
||||
disableUpdateNotification: true,
|
||||
},
|
||||
});
|
||||
expect(errors).toBe(null);
|
||||
expect(valid).toBe(true);
|
||||
});
|
||||
|
||||
it('fails on incorrect version', () => {
|
||||
const { valid, errors } = validate({
|
||||
insomniaConfig: 'v1.0.0',
|
||||
});
|
||||
expect(errors).toMatchObject([
|
||||
{
|
||||
instancePath: '/insomniaConfig',
|
||||
schemaPath: '#/properties/insomniaConfig/enum',
|
||||
keyword: 'enum',
|
||||
params: {
|
||||
allowedValues: [
|
||||
'1.0.0',
|
||||
],
|
||||
},
|
||||
message: 'must be equal to one of the allowed values',
|
||||
},
|
||||
]);
|
||||
expect(valid).toBe(false);
|
||||
});
|
||||
|
||||
it('fails on missing properties', () => {
|
||||
const { valid, errors } = validate({});
|
||||
expect(errors).toMatchObject([
|
||||
{
|
||||
instancePath: '',
|
||||
schemaPath: '#/required',
|
||||
keyword: 'required',
|
||||
params: { missingProperty: 'insomniaConfig' },
|
||||
message: "must have required property 'insomniaConfig'",
|
||||
},
|
||||
]);
|
||||
expect(valid).toBe(false);
|
||||
});
|
||||
|
||||
it('fails on additional top level properties', () => {
|
||||
const { valid, errors } = validate({
|
||||
insomniaConfig: '1.0.0',
|
||||
Settings: {},
|
||||
});
|
||||
|
||||
expect(errors).toMatchObject([
|
||||
{
|
||||
instancePath: '',
|
||||
keyword: 'additionalProperties',
|
||||
message: 'must NOT have additional properties',
|
||||
params: {
|
||||
additionalProperty: 'Settings',
|
||||
},
|
||||
schemaPath: '#/additionalProperties',
|
||||
},
|
||||
]);
|
||||
expect(valid).toBe(false);
|
||||
});
|
||||
|
||||
it('fails on additional settings properties', () => {
|
||||
const { valid, errors } = validate({
|
||||
insomniaConfig: '1.0.0',
|
||||
settings: {
|
||||
disableAnalytics: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(errors).toMatchObject([
|
||||
{
|
||||
instancePath: '/settings',
|
||||
keyword: 'additionalProperties',
|
||||
message: 'must NOT have additional properties',
|
||||
params: {
|
||||
additionalProperty: 'disableAnalytics',
|
||||
},
|
||||
schemaPath: '#/definitions/Partial<Pick<Settings,\"allowNotificationRequests\"|\"disableUpdateNotification\"|\"enableAnalytics\"|\"disablePaidFeatureAds\"|\"incognitoMode\">>/additionalProperties',
|
||||
},
|
||||
]);
|
||||
expect(valid).toBe(false);
|
||||
});
|
||||
|
||||
it('fails on wrong property type', () => {
|
||||
const { valid, errors } = validate({
|
||||
insomniaConfig: '1.0.0',
|
||||
settings: {
|
||||
enableAnalytics: 'Ziltoid',
|
||||
},
|
||||
});
|
||||
|
||||
expect(errors).toMatchObject([
|
||||
{
|
||||
instancePath: '/settings/enableAnalytics',
|
||||
keyword: 'type',
|
||||
message: 'must be boolean',
|
||||
params: {
|
||||
'type': 'boolean',
|
||||
},
|
||||
schemaPath: '#/definitions/Partial<Pick<Settings,\"allowNotificationRequests\"|\"disableUpdateNotification\"|\"enableAnalytics\"|\"disablePaidFeatureAds\"|\"incognitoMode\">>/properties/enableAnalytics/type',
|
||||
},
|
||||
]);
|
||||
expect(valid).toBe(false);
|
||||
});
|
||||
});
|
||||
60
packages/insomnia-config/src/validate.ts
Normal file
60
packages/insomnia-config/src/validate.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import Ajv, { ErrorObject } from 'ajv';
|
||||
|
||||
import { InsomniaConfig } from './entities';
|
||||
import schema from './generated/schemas/insomnia.schema.json';
|
||||
|
||||
const ajv = new Ajv({
|
||||
allErrors: true,
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
export const ingest = (input: string | InsomniaConfig | unknown) => {
|
||||
if (typeof input === 'string') {
|
||||
try {
|
||||
return JSON.parse(input) as InsomniaConfig;
|
||||
} catch (error: unknown) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return input;
|
||||
};
|
||||
|
||||
export interface ValidResult {
|
||||
valid: true;
|
||||
errors: null;
|
||||
humanError: null;
|
||||
}
|
||||
|
||||
export interface ErrorResult {
|
||||
valid: false;
|
||||
errors: ErrorObject[];
|
||||
}
|
||||
|
||||
export type ValidationResult = ValidResult | ErrorResult;
|
||||
|
||||
export const validate = (input: string | InsomniaConfig | unknown): ValidationResult => {
|
||||
const data = ingest(input);
|
||||
const validator = ajv.compile<InsomniaConfig>(schema);
|
||||
const valid = validator(data);
|
||||
|
||||
if (valid) {
|
||||
const validResult: ValidResult = {
|
||||
valid: true,
|
||||
errors: null,
|
||||
humanError: null,
|
||||
};
|
||||
return validResult;
|
||||
}
|
||||
|
||||
const { errors } = validator;
|
||||
|
||||
if (!errors) {
|
||||
throw new Error('unable to validate json schema');
|
||||
}
|
||||
|
||||
const errorResult: ErrorResult = {
|
||||
valid: false,
|
||||
errors,
|
||||
};
|
||||
return errorResult;
|
||||
};
|
||||
19
packages/insomnia-config/tsconfig.build.json
Normal file
19
packages/insomnia-config/tsconfig.build.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"resolveJsonModule": true,
|
||||
"strict": true,
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"**/*.schema.json",
|
||||
"**/*.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"**/*.test.ts",
|
||||
],
|
||||
}
|
||||
16
packages/insomnia-config/tsconfig.json
Normal file
16
packages/insomnia-config/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"rootDir": ".",
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"jest.config.js",
|
||||
".eslintrc.js",
|
||||
"**/*.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user