Fix/fetch body (#6134)

* fix body, extract backoff

* fix two way commands

* add docs

* fix ai calls and errors
This commit is contained in:
Jack Kavanagh
2023-07-11 15:58:06 +02:00
committed by GitHub
parent 64fe218c2e
commit 2cd79b35b3
6 changed files with 67 additions and 74 deletions

View File

@@ -103,7 +103,7 @@ export async function changePasswordWithToken(rawNewPassphrase: string, confirma
return window.main.insomniaFetch({
method: 'POST',
path: '/auth/change-password',
obj: {
data: {
code: confirmationCode,
newEmail: newEmail,
encSymmetricKey: encSymmetricKey,
@@ -252,7 +252,7 @@ function _getAuthSalts(email: string) {
return window.main.insomniaFetch({
method: 'POST',
path: '/auth/login-s',
obj: { email },
data: { email },
sessionId: getCurrentSessionId(),
});
}

View File

@@ -1,75 +1,65 @@
import { parse as urlParse } from 'url';
import { BrowserWindow, net } from 'electron';
import { getApiBaseURL, getClientString } from '../common/constants';
import { delay } from '../common/misc';
import { invariant } from '../utils/invariant';
const _commandListeners: Function[] = [];
export function onCommand(callback: Function) {
_commandListeners.push(callback);
}
let _userAgent = getClientString();
let _baseUrl = getApiBaseURL();
export function setup(userAgent: string, baseUrl: string) {
_userAgent = userAgent;
_baseUrl = baseUrl;
}
interface FetchConfig {
method: 'POST' | 'PUT' | 'GET';
path: string;
sessionId: string | null;
obj?: unknown;
data?: unknown;
retries?: number;
origin?: string;
}
export async function insomniaFetch<T = any>({ method, path, obj, sessionId, retries = 0 }: FetchConfig): Promise<T | string> {
const config: {
method: string;
headers: Record<string, string>;
body?: string | Buffer;
} = {
method: method,
// internal request (insomniaFetch)
// should validate ssl certs on our server
// should only go to insomnia API
// should be able to listen for specific messages in headers
// should be able to retry on 502
// external request (axiosRequest)
// should respect settings for proxy and ssl validation
// should be for all third party APIs, github, gitlab, isometric-git
const exponentialBackOff = async (url: string, init: RequestInit, retries = 0): Promise<Response> => {
try {
const response = await net.fetch(url, init);
if (response.status === 502 && retries < 5) {
retries++;
await delay(retries * 200);
return exponentialBackOff(url, init, retries);
}
if (!response.ok) {
const err = new Error(`Response ${response.status} for ${url}`);
err.message = await response.text();
throw err;
}
return response;
} catch (err) {
throw new Error(`Failed to fetch ${url}: ${err.message}`);
}
};
export async function insomniaFetch<T = any>({ method, path, data, sessionId, origin }: FetchConfig): Promise<T | string> {
const config: RequestInit = {
method,
headers: {
'X-Insomnia-Client': getClientString(),
...(sessionId ? { 'X-Session-Id': sessionId } : {}),
...(_userAgent ? { 'X-Insomnia-Client': _userAgent } : {}),
...(obj ? { 'Content-Type': 'application/json' } : {}),
...(obj ? { body: JSON.stringify(obj) } : {}),
...(data ? { 'Content-Type': 'application/json' } : {}),
},
...(data ? { body: JSON.stringify(data) } : {}),
};
if (sessionId === undefined) {
throw new Error(`No session ID provided to ${method}:${path}`);
}
invariant(_baseUrl, 'API base URL not configured!');
const url = `${_baseUrl}${path}`;
let response: Response | undefined;
try {
response = await window.fetch(url, config);
// Exponential backoff for 502 errors
if (response.status === 502 && retries < 5) {
retries++;
await delay(retries * 200);
return insomniaFetch({ method, path, obj, sessionId, retries });
}
} catch (err) {
throw new Error(`Failed to fetch '${url}'`);
}
const response = await exponentialBackOff(`${origin || getApiBaseURL()}${path}`, config);
const uri = response.headers.get('x-insomnia-command');
if (uri) {
const parsed = urlParse(uri, true);
_commandListeners.map(fn => fn(`${parsed.hostname}${parsed.pathname}`, JSON.parse(JSON.stringify(parsed.query))));
for (const window of BrowserWindow.getAllWindows()) {
window.webContents.send('shell:open', uri);
}
}
if (!response.ok) {
const err = new Error(`Response ${response.status} for ${path}`);
err.message = await response.text();
// @ts-expect-error -- TSCONVERSION
err.statusCode = response.status;
throw err;
}
const isJson = response.headers.get('content-type') === 'application/json' || path.match(/\.json$/);
return isJson ? response.json() : response.text();
}

View File

@@ -712,7 +712,7 @@ export class VCS {
const { data, errors } = await window.main.insomniaFetch({
method: 'POST',
path: '/graphql?' + name,
obj: { query, variables },
data: { query, variables },
sessionId,
});

View File

@@ -114,7 +114,7 @@ export const Toast: FC = () => {
const notificationOrEmpty = await window.main.insomniaFetch<ToastNotification>({
method: 'POST',
path: '/notification',
obj: data,
data,
sessionId: session.getCurrentSessionId(),
});
if (notificationOrEmpty && typeof notificationOrEmpty !== 'string') {

View File

@@ -626,13 +626,11 @@ export const generateCollectionAndTestsAction: ActionFunction = async ({ params
const methodInfo = resolveComponentSchemaRefs(spec, getMethodInfo(request));
const response = await window.main.axiosRequest({
const response = await window.main.insomniaFetch({
method: 'POST',
url: 'https://ai.insomnia.rest/v1/generate-test',
headers: {
'Content-Type': 'application/json',
'X-Session-Id': session.getCurrentSessionId(),
},
origin: 'https://ai.insomnia.rest',
path: '/v1/generate-test',
sessionId: session.getCurrentSessionId(),
data: {
teamId: organizationId,
request: requests.find(r => r._id === test.requestId),
@@ -709,13 +707,11 @@ export const generateTestsAction: ActionFunction = async ({ params }) => {
for (const test of tests) {
async function generateTest() {
try {
const response = await window.main.axiosRequest({
const response = await window.main.insomniaFetch({
method: 'POST',
url: 'https://ai.insomnia.rest/v1/generate-test',
headers: {
'Content-Type': 'application/json',
'X-Session-Id': session.getCurrentSessionId(),
},
origin: 'https://ai.insomnia.rest',
path: '/v1/generate-test',
sessionId: session.getCurrentSessionId(),
data: {
teamId: organizationId,
request: requests.find(r => r._id === test.requestId),
@@ -753,13 +749,11 @@ export const accessAIApiAction: ActionFunction = async ({ params }) => {
invariant(typeof workspaceId === 'string', 'Workspace ID is required');
try {
const response = await window.main.axiosRequest({
const response = await window.main.insomniaFetch({
method: 'POST',
url: 'https://ai.insomnia.rest/v1/access',
headers: {
'Content-Type': 'application/json',
'X-Session-Id': session.getCurrentSessionId(),
},
origin: 'https://ai.insomnia.rest',
path: '/v1/access',
sessionId: session.getCurrentSessionId(),
data: {
teamId: organizationId,
},

View File

@@ -30,6 +30,7 @@ import { showError, showModal } from '../components/modals';
import { AlertModal } from '../components/modals/alert-modal';
import { AskModal } from '../components/modals/ask-modal';
import { ImportModal } from '../components/modals/import-modal';
import { LoginModal } from '../components/modals/login-modal';
import {
SettingsModal,
TAB_INDEX_PLUGINS,
@@ -133,6 +134,14 @@ const Root = () => {
});
break;
case 'insomnia://app/auth/login':
showModal(LoginModal, {
title: params.title,
message: params.message,
reauth: true,
});
break;
case 'insomnia://app/import':
setImportUri(params.uri);
break;