2021-06-14 15:18:35 +00:00
import { Insomnia4Data } from 'insomnia-importers' ;
import clone from 'clone' ;
import { database as db } from './database' ;
import * as har from './har' ;
import type { BaseModel } from '../models/index' ;
import * as models from '../models/index' ;
import {
EXPORT_TYPE_API_SPEC ,
EXPORT_TYPE_COOKIE_JAR ,
EXPORT_TYPE_ENVIRONMENT ,
EXPORT_TYPE_GRPC_REQUEST ,
EXPORT_TYPE_PROTO_DIRECTORY ,
EXPORT_TYPE_PROTO_FILE ,
EXPORT_TYPE_REQUEST ,
EXPORT_TYPE_REQUEST_GROUP ,
EXPORT_TYPE_UNIT_TEST ,
EXPORT_TYPE_UNIT_TEST_SUITE ,
EXPORT_TYPE_WORKSPACE ,
getAppVersion ,
} from './constants' ;
import YAML from 'yaml' ;
import { trackEvent } from './analytics' ;
2021-06-16 19:19:00 +00:00
import { isGrpcRequest } from '../models/grpc-request' ;
import { isRequest } from '../models/request' ;
import { isRequestGroup } from '../models/request-group' ;
import { isProtoDirectory } from '../models/proto-directory' ;
import { isProtoFile } from '../models/proto-file' ;
2021-06-30 15:33:02 +00:00
import { isWorkspace , Workspace } from '../models/workspace' ;
2021-06-16 19:19:00 +00:00
import { isApiSpec } from '../models/api-spec' ;
import { isCookieJar } from '../models/cookie-jar' ;
import { isEnvironment } from '../models/environment' ;
import { isUnitTestSuite } from '../models/unit-test-suite' ;
import { isUnitTest } from '../models/unit-test' ;
2021-06-14 15:18:35 +00:00
const EXPORT_FORMAT = 4 ;
2021-06-30 15:33:02 +00:00
const getDocWithDescendants = ( includePrivateDocs = false ) = > async ( parentDoc : BaseModel | null ) = > {
2021-06-14 15:18:35 +00:00
const docs = await db . withDescendants ( parentDoc ) ;
return docs . filter (
// Don't include if private, except if we want to
doc = > ! doc ? . isPrivate || includePrivateDocs ,
) ;
2021-06-30 15:33:02 +00:00
} ;
2021-06-14 15:18:35 +00:00
export async function exportWorkspacesHAR (
2021-06-30 15:33:02 +00:00
workspaces : Workspace [ ] ,
2021-06-14 15:18:35 +00:00
includePrivateDocs = false ,
) {
2021-06-30 15:33:02 +00:00
// regarding `[null]`, see the comment here in `exportWorkspacesData`
const rootDocs = workspaces . length === 0 ? [ null ] : workspaces ;
const promises = rootDocs . map ( getDocWithDescendants ( includePrivateDocs ) ) ;
const docs = ( await Promise . all ( promises ) ) . flat ( ) ;
2021-06-14 15:18:35 +00:00
const requests = docs . filter ( isRequest ) ;
return exportRequestsHAR ( requests , includePrivateDocs ) ;
}
export async function exportRequestsHAR (
requests : BaseModel [ ] ,
includePrivateDocs = false ,
) {
const workspaces : BaseModel [ ] = [ ] ;
const mapRequestIdToWorkspace : Record < string , any > = { } ;
const workspaceLookup : Record < string , any > = { } ;
for ( const request of requests ) {
const ancestors : BaseModel [ ] = await db . withAncestors ( request , [
models . workspace . type ,
models . requestGroup . type ,
] ) ;
const workspace = ancestors . find ( isWorkspace ) ;
mapRequestIdToWorkspace [ request . _id ] = workspace ;
if ( workspace == null || workspaceLookup . hasOwnProperty ( workspace . _id ) ) {
continue ;
}
workspaceLookup [ workspace . _id ] = true ;
workspaces . push ( workspace ) ;
}
const mapWorkspaceIdToEnvironmentId : Record < string , any > = { } ;
for ( const workspace of workspaces ) {
const workspaceMeta = await models . workspaceMeta . getByParentId ( workspace . _id ) ;
let environmentId = workspaceMeta ? workspaceMeta.activeEnvironmentId : null ;
const environment = await models . environment . getById ( environmentId || 'n/a' ) ;
if ( ! environment || ( environment . isPrivate && ! includePrivateDocs ) ) {
environmentId = 'n/a' ;
}
mapWorkspaceIdToEnvironmentId [ workspace . _id ] = environmentId ;
}
requests = requests . sort ( ( a : Record < string , any > , b : Record < string , any > ) = >
a . metaSortKey < b . metaSortKey ? - 1 : 1 ,
) ;
const harRequests : har.ExportRequest [ ] = [ ] ;
for ( const request of requests ) {
const workspace = mapRequestIdToWorkspace [ request . _id ] ;
if ( workspace == null ) {
// Workspace not found for request, so don't export it.
continue ;
}
const environmentId = mapWorkspaceIdToEnvironmentId [ workspace . _id ] ;
harRequests . push ( {
requestId : request._id ,
environmentId : environmentId ,
} ) ;
}
const data = await har . exportHar ( harRequests ) ;
trackEvent ( 'Data' , 'Export' , 'HAR' ) ;
return JSON . stringify ( data , null , '\t' ) ;
}
export async function exportWorkspacesData (
2021-06-30 15:33:02 +00:00
workspaces : Workspace [ ] ,
2021-06-14 15:18:35 +00:00
includePrivateDocs : boolean ,
format : 'json' | 'yaml' ,
) {
2021-06-30 15:33:02 +00:00
// Semantically, if an empty array is passed, then nothing will be returned. What an empty array really signifies is "no parent", which, at the database layer is the same as "parentId === null", hence we add null in ourselves.
const rootDocs = workspaces . length === 0 ? [ null ] : workspaces ;
const promises = rootDocs . map ( getDocWithDescendants ( includePrivateDocs ) ) ;
const docs = ( await Promise . all ( promises ) ) . flat ( ) ;
2021-06-14 15:18:35 +00:00
const requests = docs . filter ( doc = > isRequest ( doc ) || isGrpcRequest ( doc ) ) ;
return exportRequestsData ( requests , includePrivateDocs , format ) ;
}
export async function exportRequestsData (
requests : BaseModel [ ] ,
includePrivateDocs : boolean ,
format : 'json' | 'yaml' ,
) {
const data : Insomnia4Data = {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
_type : 'export' ,
__export_format : EXPORT_FORMAT ,
__export_date : new Date ( ) ,
__export_source : ` insomnia.desktop.app:v ${ getAppVersion ( ) } ` ,
resources : [ ] ,
} ;
const docs : BaseModel [ ] = [ ] ;
2021-06-30 15:33:02 +00:00
const workspaces : Workspace [ ] = [ ] ;
const mapTypeAndIdToDoc : Record < string , BaseModel > = { } ;
2021-06-14 15:18:35 +00:00
2021-06-30 15:33:02 +00:00
for ( const request of requests ) {
const ancestors = clone < BaseModel [ ] > ( await db . withAncestors ( request ) ) ;
2021-06-14 15:18:35 +00:00
for ( const ancestor of ancestors ) {
const key = ancestor . type + '___' + ancestor . _id ;
if ( mapTypeAndIdToDoc . hasOwnProperty ( key ) ) {
continue ;
}
mapTypeAndIdToDoc [ key ] = ancestor ;
docs . push ( ancestor ) ;
if ( isWorkspace ( ancestor ) ) {
workspaces . push ( ancestor ) ;
}
}
}
for ( const workspace of workspaces ) {
2021-06-30 15:33:02 +00:00
const descendants = ( await db . withDescendants ( workspace ) ) . filter ( d = > {
2021-06-14 15:18:35 +00:00
// Only interested in these additional model types.
return (
2021-06-16 19:19:00 +00:00
isCookieJar ( d ) ||
isEnvironment ( d ) ||
isApiSpec ( d ) ||
isUnitTestSuite ( d ) ||
isUnitTest ( d ) ||
2021-06-14 15:18:35 +00:00
isProtoFile ( d ) ||
isProtoDirectory ( d )
) ;
} ) ;
docs . push ( . . . descendants ) ;
}
data . resources = docs
. filter ( d = > {
// Only export these model types.
if (
! (
2021-06-16 19:19:00 +00:00
isUnitTestSuite ( d ) ||
isUnitTest ( d ) ||
2021-06-14 15:18:35 +00:00
isRequest ( d ) ||
isGrpcRequest ( d ) ||
isRequestGroup ( d ) ||
isProtoFile ( d ) ||
isProtoDirectory ( d ) ||
isWorkspace ( d ) ||
2021-06-16 19:19:00 +00:00
isCookieJar ( d ) ||
isEnvironment ( d ) ||
isApiSpec ( d )
2021-06-14 15:18:35 +00:00
)
) {
return false ;
}
// BaseModel doesn't have isPrivate, so cast it first.
return ! d . isPrivate || includePrivateDocs ;
} )
. map ( d = > {
if ( isWorkspace ( d ) ) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d . _type = EXPORT_TYPE_WORKSPACE ;
2021-06-16 19:19:00 +00:00
} else if ( isCookieJar ( d ) ) {
2021-06-14 15:18:35 +00:00
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d . _type = EXPORT_TYPE_COOKIE_JAR ;
2021-06-16 19:19:00 +00:00
} else if ( isEnvironment ( d ) ) {
2021-06-14 15:18:35 +00:00
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d . _type = EXPORT_TYPE_ENVIRONMENT ;
2021-06-16 19:19:00 +00:00
} else if ( isUnitTestSuite ( d ) ) {
2021-06-14 15:18:35 +00:00
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d . _type = EXPORT_TYPE_UNIT_TEST_SUITE ;
2021-06-16 19:19:00 +00:00
} else if ( isUnitTest ( d ) ) {
2021-06-14 15:18:35 +00:00
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d . _type = EXPORT_TYPE_UNIT_TEST ;
} else if ( isRequestGroup ( d ) ) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d . _type = EXPORT_TYPE_REQUEST_GROUP ;
} else if ( isRequest ( d ) ) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d . _type = EXPORT_TYPE_REQUEST ;
} else if ( isGrpcRequest ( d ) ) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d . _type = EXPORT_TYPE_GRPC_REQUEST ;
} else if ( isProtoFile ( d ) ) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d . _type = EXPORT_TYPE_PROTO_FILE ;
} else if ( isProtoDirectory ( d ) ) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d . _type = EXPORT_TYPE_PROTO_DIRECTORY ;
2021-06-16 19:19:00 +00:00
} else if ( isApiSpec ( d ) ) {
2021-06-14 15:18:35 +00:00
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d . _type = EXPORT_TYPE_API_SPEC ;
}
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
// Delete the things we don't want to export
delete d . type ;
return d ;
} ) ;
trackEvent ( 'Data' , 'Export' , ` Insomnia ${ format } ` ) ;
if ( format . toLowerCase ( ) === 'yaml' ) {
return YAML . stringify ( data ) ;
} else if ( format . toLowerCase ( ) === 'json' ) {
return JSON . stringify ( data ) ;
} else {
throw new Error ( ` Invalid export format ${ format } . Must be "json" or "yaml" ` ) ;
}
}