From a1fe7fb5149dff2a0931e45b27b239da4e3bed4a Mon Sep 17 00:00:00 2001 From: George He Date: Fri, 12 Jan 2024 15:37:15 +0800 Subject: [PATCH] feat: isolate major DOM handlers --- .../renderers/hidden-browser-window/index.ts | 13 +- .../hidden-browser-window/inso-object.ts | 22 +- .../hidden-browser-window/intepolator.ts | 34 -- .../hidden-browser-window/object-auth.ts | 336 ----------- .../hidden-browser-window/object-base.ts | 388 ------------ .../object-certificates.ts | 58 -- .../hidden-browser-window/object-cookies.ts | 153 ----- .../hidden-browser-window/object-headers.ts | 106 ---- .../object-proxy-configs.ts | 168 ------ .../hidden-browser-window/object-req-resp.ts | 526 ---------------- .../hidden-browser-window/object-urls.ts | 563 ------------------ .../hidden-browser-window/object-variables.ts | 59 -- 12 files changed, 13 insertions(+), 2413 deletions(-) delete mode 100644 packages/insomnia/src/renderers/hidden-browser-window/intepolator.ts delete mode 100644 packages/insomnia/src/renderers/hidden-browser-window/object-auth.ts delete mode 100644 packages/insomnia/src/renderers/hidden-browser-window/object-base.ts delete mode 100644 packages/insomnia/src/renderers/hidden-browser-window/object-certificates.ts delete mode 100644 packages/insomnia/src/renderers/hidden-browser-window/object-cookies.ts delete mode 100644 packages/insomnia/src/renderers/hidden-browser-window/object-headers.ts delete mode 100644 packages/insomnia/src/renderers/hidden-browser-window/object-proxy-configs.ts delete mode 100644 packages/insomnia/src/renderers/hidden-browser-window/object-req-resp.ts delete mode 100644 packages/insomnia/src/renderers/hidden-browser-window/object-urls.ts delete mode 100644 packages/insomnia/src/renderers/hidden-browser-window/object-variables.ts diff --git a/packages/insomnia/src/renderers/hidden-browser-window/index.ts b/packages/insomnia/src/renderers/hidden-browser-window/index.ts index 870c31f251..e1f9a5063a 100644 --- a/packages/insomnia/src/renderers/hidden-browser-window/index.ts +++ b/packages/insomnia/src/renderers/hidden-browser-window/index.ts @@ -1,4 +1,5 @@ -import { initGlobalObject, InsomniaObject, require } from './inso-object'; +import { initGlobalObject, InsomniaObject } from './inso-object'; +import { require } from './sdk-objects/require'; const ErrorTimeout = 'executing script timeout'; const ErrorInvalidResult = 'result is invalid, null or custom value may be returned'; @@ -22,6 +23,11 @@ async function init() { const executeScript = AsyncFunction( 'insomnia', 'require', + // isolate DOM objects + // window properties + 'closed', 'console', 'credentialless', 'customElements', 'devicePixelRatio', 'document', 'documentPictureInPicture', 'event', 'external', 'frameElement', 'frames', 'fullScreen', 'history', 'innerHeight', 'innerWidth', 'launchQueue', 'length', 'localStorage', 'location', 'locationbar', 'menubar', 'name', 'navigation', 'navigator', 'opener', 'orientation', 'originAgentCluster', 'outerHeight', 'outerWidth', 'parent', 'personalbar', 'screen', 'screenLeft', 'screenTop', 'screenX', 'screenY', 'scrollbars', 'scrollMaxX', 'scrollMaxY', 'scrollX', 'scrollY', 'self', 'sessionStorage', 'sharedStorage', 'sidebar', 'speechSynthesis', 'status', 'statusbar', 'toolbar', 'top', 'visualViewport', 'window', + // window methods + 'alert', 'backalert', 'bluralert', 'cancelAnimationFramealert', 'cancelIdleCallbackalert', 'captureEventsalert', 'clearImmediatealert', 'closealert', 'confirmalert', 'dumpalert', 'findalert', 'focusalert', 'forwardalert', 'getComputedStylealert', 'getDefaultComputedStylealert', 'getScreenDetailsalert', 'getSelectionalert', 'matchMediaalert', 'moveByalert', 'moveToalert', 'openalert', 'postMessagealert', 'printalert', 'promptalert', 'queryLocalFontsalert', 'releaseEventsalert', 'requestAnimationFramealert', 'requestFileSystemalert', 'requestIdleCallbackalert', 'resizeByalert', 'resizeToalert', 'scrollalert', 'scrollByalert', 'scrollByLinesalert', 'scrollByPagesalert', 'scrollToalert', 'setImmediatealert', 'setResizablealert', 'showDirectoryPickeralert', 'showModalDialogalert', 'showOpenFilePickeralert', 'showSaveFilePickeralert', 'sizeToContentalert', 'stopalert', 'updateCommandsalert', 'webkitConvertPointFromNodeToPagealert', 'webkitConvertPointFromPageToNodealert', // if possible, avoid adding code to the following part ` const $ = insomnia, pm = insomnia; @@ -39,6 +45,10 @@ async function init() { const insoObject = await executeScript( insomniaObject, require, + // window properties + undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, + // window methods + undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, ); clearTimeout(timeoutChecker); if (insoObject instanceof InsomniaObject) { @@ -46,6 +56,7 @@ async function init() { } else { throw { message: ErrorInvalidResult }; } + } catch (e) { reject(e); } diff --git a/packages/insomnia/src/renderers/hidden-browser-window/inso-object.ts b/packages/insomnia/src/renderers/hidden-browser-window/inso-object.ts index 7bcaae1183..91e7319801 100644 --- a/packages/insomnia/src/renderers/hidden-browser-window/inso-object.ts +++ b/packages/insomnia/src/renderers/hidden-browser-window/inso-object.ts @@ -1,6 +1,4 @@ -import * as uuid from 'uuid'; - -import { getIntepolator } from './intepolator'; +import { getIntepolator } from './sdk-objects/intepolator'; export type EventName = 'prerequest' | 'test'; @@ -225,21 +223,3 @@ export function initGlobalObject(rawObj: RawObject) { requestInfo, }); }; - -const builtinModules = new Map([ - ['uuid', uuid], -]); - -const nodeModules = new Map([]); - -export function require(moduleName: string) { - if (builtinModules.has(moduleName)) { - return builtinModules.get(moduleName); - } - - if (nodeModules.has(moduleName)) { - // invoke main.js - } - - throw Error(`no module is found for "${moduleName}"`); -} diff --git a/packages/insomnia/src/renderers/hidden-browser-window/intepolator.ts b/packages/insomnia/src/renderers/hidden-browser-window/intepolator.ts deleted file mode 100644 index 7d665aaaf6..0000000000 --- a/packages/insomnia/src/renderers/hidden-browser-window/intepolator.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { configure, type ConfigureOptions, type Environment as NunjuncksEnv } from 'nunjucks'; - -class Intepolator { - private engine: NunjuncksEnv; - - constructor(config: ConfigureOptions) { - this.engine = configure(config); - } - - render = (template: string, context: object) => { - // TODO: handle timeout - // TODO: support plugin? - return this.engine.renderString(template, context); - }; -} - -const intepolator = new Intepolator({ - autoescape: false, - // Don't escape HTML - throwOnUndefined: true, - // Strict mode - tags: { - blockStart: '{%', - blockEnd: '%}', - variableStart: '{{', - variableEnd: '}}', - commentStart: '{#', - commentEnd: '#}', - }, -}); - -export function getIntepolator() { - return intepolator; -} diff --git a/packages/insomnia/src/renderers/hidden-browser-window/object-auth.ts b/packages/insomnia/src/renderers/hidden-browser-window/object-auth.ts deleted file mode 100644 index 9ac6284470..0000000000 --- a/packages/insomnia/src/renderers/hidden-browser-window/object-auth.ts +++ /dev/null @@ -1,336 +0,0 @@ -import { Property } from './object-base'; -import { Variable, VariableList } from './object-variables'; - -const AuthTypes = new Set([ - 'noauth', - 'basic', - 'bearer', - 'jwt', - 'digest', - 'oauth1', - 'oauth2', - 'hawk', - 'awsv4', - 'ntlm', - 'apikey', - 'edgegrid', - 'asap', -]); - -export interface AuthOption { - type: string; - key: string; - value: string; -} - -// export interface AuthBasic { -// password: string; -// username: string; -// id: string; -// } - -// export interface AuthBearer { -// token: string; -// id: string; -// } - -// export interface AuthJWT { -// secret: string; -// algorithm: string; -// isSecretBase64Encoded: boolean; -// payload: string; // e.g. "{}" -// addTokenTo: string; -// headerPrefix: string; -// queryParamKey: string; -// header: string; // e.g. "{}" -// id: string; -// } - -// export interface AuthDigest { -// opaque: string; -// clientNonce: string; -// nonceCount: string; -// qop: string; -// nonce: string; -// realm: string; -// password: string; -// username: string; -// algorithm: string; -// id: string; -// } - -// export interface AuthOAuth1 { -// addEmptyParamsToSign: boolean; -// includeBodyHash: boolean; -// realm: string; -// nonce: string; -// timestamp: string; -// verifier: string; -// callback: string; -// tokenSecret: string; -// token: string; -// consumerSecret: string; -// consumerKey: string; -// signatureMethod: string; // "HMAC-SHA1" -// version: string; -// addParamsToHeader: string; -// id: string; -// } - -// export interface OAuth2Param { -// key: string; -// value: string; -// enabled: boolean; -// send_as: string; // it follows exising naming -// } - -// export interface AuthOAuth2 { -// accessToken: string; -// refreshRequestParams: OAuth2Param[]; -// tokenRequestParams: OAuth2Param[]; -// authRequestParams: OAuth2Param[]; -// refreshTokenUrl: string; -// state: string; -// scope: string; -// clientSecret: string; -// clientId: string; -// tokenName: string; -// addTokenTo: string; -// id: string; -// } - -// export interface AuthHAWK { -// includePayloadHash: boolean; -// timestamp: string; -// delegation: string; -// app: string; -// extraData: string; -// nonce: string; -// user: string; -// authKey: string; -// authId: string; -// algorithm: string; -// id: string; -// } - -// export interface AuthAWSV4 { -// sessionToken: string; -// service: string; -// region: string; -// secretKey: string; -// accessKey: string; -// id: string; -// } - -// export interface AuthNTLM { -// workstation: string; -// domain: string; -// password: string; -// username: string; -// id: string; -// } - -// export interface AuthAPIKey { -// key: string; -// value: string; -// id: string; -// } - -// export interface AuthEdgegrid { -// headersToSign: string; -// baseURL: string; -// timestamp: string; -// nonce: string; -// clientSecret: string; -// clientToken: string; -// accessToken: string; -// id: string; -// } - -// export interface AuthASAP { -// exp: string; // expiry -// claims: string; // e.g., { "additional claim": "claim value" } -// sub: string; // subject -// privateKey: string; // private key -// kid: string; // key id -// aud: string; // audience -// iss: string; // issuer -// alg: string; // e.g., RS256 -// id: string; -// } - -// function AuthMethodToParams(authMethod: AuthNoAuth | AuthBasic | AuthBasic | AuthBearer | AuthJWT | AuthDigest | AuthOAuth1 | AuthOAuth2 | AuthHAWK | AuthAWSV4 | AuthNTLM | AuthAPIKey | AuthEdgegrid | AuthASAP) { -// return Object.entries(authMethod). -// map(entry => ({ -// type: 'any', -// key: entry[0], -// value: entry[1], -// })); -// } - -export interface AuthOptions { - type: string; - basic?: AuthOption[]; - bearer?: AuthOption[]; - jwt?: AuthOption[]; - digest?: AuthOption[]; - oauth1?: AuthOption[]; - oauth2?: AuthOption[]; - hawk?: AuthOption[]; - awsv4?: AuthOption[]; - ntlm?: AuthOption[]; - apikey?: AuthOption[]; - edgegrid?: AuthOption[]; - asap?: AuthOption[]; -} - -function rawOptionsToVariables(options: VariableList | Variable[] | object, targetType?: string): VariableList[] { - if (VariableList.isVariableList(options)) { - return [options as VariableList]; - } else if ('type' in options) { // object - const optsObj = options as AuthOptions; - const optsVarLists = Object.entries(optsObj) - .filter(optsObjEntry => optsObjEntry[0] === targetType) - .map(optsEntry => { - return new VariableList( - undefined, - optsEntry.map(opt => new Variable({ - key: opt.key, - value: opt.value, - type: opt.type, - })), - ); - }); - - return optsVarLists; - } else if ('length' in options) { // array - return [new VariableList(undefined, options)]; - } - - throw Error('options is not valid: it must be VariableList | Variable[] | object'); -} - -export class RequestAuth extends Property { - private type: string; - private authOptions: Map> = new Map(); - - constructor(options: AuthOptions, parent?: Property) { - super(); - - if (!RequestAuth.isValidType(options.type)) { - throw Error(`invalid auth type ${options.type}`); - } - this.type = options.type; - const optsObj = options as AuthOptions; - Object.entries(optsObj) - .filter(optsObjEntry => optsObjEntry[0] !== 'type') - .map(optsEntry => { - return { - type: optsEntry[0], - options: new VariableList( - undefined, - optsEntry.map(opt => new Variable({ - key: opt.key, - value: opt.value, - type: opt.type, - })), - ), - }; - }) - .forEach(authOpts => { - this.authOptions.set(authOpts.type, authOpts.options); - }); - - this._parent = parent; - } - - static isValidType(authType: string) { - return AuthTypes.has(authType); - } - - clear(type: string) { - if (RequestAuth.isValidType(type)) { - this.authOptions.delete(type); - } - } - - parameters() { - return this.authOptions.get(this.type); - } - - toJSON() { - const obj: AuthOptions = { type: this.type }; - const authOption = this.authOptions.get(this.type); - if (!authOption) { - return obj; - } - - const authOptionJSON = authOption.map(optValue => optValue.toJSON(), {}); - - switch (this.type) { - case 'basic': - obj.basic = authOptionJSON; - break; - case 'bearer': - obj.bearer = authOptionJSON; - break; - case 'jwt': - obj.jwt = authOptionJSON; - break; - case 'digest': - obj.digest = authOptionJSON; - break; - case 'oauth1': - obj.oauth1 = authOptionJSON; - break; - case 'oauth2': - obj.oauth2 = authOptionJSON; - break; - case 'hawk': - obj.hawk = authOptionJSON; - break; - case 'awsv4': - obj.awsv4 = authOptionJSON; - break; - case 'ntlm': - obj.ntlm = authOptionJSON; - break; - case 'apikey': - obj.apikey = authOptionJSON; - break; - case 'edgegrid': - obj.edgegrid = authOptionJSON; - break; - case 'asap': - obj.asap = authOptionJSON; - break; - default: // noauth, no op - } - - return obj; - } - - update(options: VariableList | Variable[] | object, type?: string) { - const currentType = type ? type : this.type; - const authOpts = rawOptionsToVariables(options, currentType); - if (authOpts.length > 0) { - this.authOptions.set(currentType, authOpts[0]); - } else { - throw Error('no valid RequestAuth options is found'); - } - } - - use(type: string, options: VariableList | Variable[] | object) { - if (!RequestAuth.isValidType(type)) { - throw Error(`invalid type (${type}), it must be noauth | basic | bearer | jwt | digest | oauth1 | oauth2 | hawk | awsv4 | ntlm | apikey | edgegrid | asap.`); - } - - const authOpts = rawOptionsToVariables(options, type); - if (authOpts.length > 0) { - this.type = type; - this.authOptions.set(type, authOpts[0]); - } else { - throw Error('no valid RequestAuth options is found'); - } - } -} diff --git a/packages/insomnia/src/renderers/hidden-browser-window/object-base.ts b/packages/insomnia/src/renderers/hidden-browser-window/object-base.ts deleted file mode 100644 index 5d2f1d223c..0000000000 --- a/packages/insomnia/src/renderers/hidden-browser-window/object-base.ts +++ /dev/null @@ -1,388 +0,0 @@ -import clone from 'clone'; -import equal from 'deep-equal'; - -export interface JSONer { - toJSON: () => object; -} - -export class PropertyBase { - public kind = 'PropertyBase'; - protected description: string; - protected _parent: PropertyBase | undefined = undefined; - - constructor(def: { description: string }) { - this.description = def.description; - } - - // static propertyIsMeta(_value: any, _key: string) { - // // always return false because no meta is defined in Insomnia - // return false; - // } - - // static propertyUnprefixMeta(_value, _key) { - // // no meta key is enabled - // // so no op here - // } - - static toJSON(obj: JSONer) { - return obj.toJSON(); - } - - meta() { - return {}; - }; - - parent() { - return this._parent; - } - - forEachParent(_options: { withRoot?: boolean }, iterator: (obj: PropertyBase) => boolean) { - const currentParent = this.parent(); - if (!currentParent) { - return; - } - - const queue: PropertyBase[] = [currentParent]; - const parents: PropertyBase[] = []; - - while (queue.length > 0) { - const ancester = queue.shift(); - if (!ancester) { - continue; - } - - // TODO: check options - const cloned = clone(ancester); - const keepIterating = iterator(cloned); - parents.push(cloned); - if (!keepIterating) { - break; - } - - const olderAncester = ancester.parent(); - if (olderAncester) { - queue.push(olderAncester); - } - } - - return parents; - } - - findInParents(property: string, customizer?: (ancester: PropertyBase) => boolean): PropertyBase | undefined { - const currentParent = this.parent(); - if (!currentParent) { - return; - } - - const queue: PropertyBase[] = [currentParent]; - - while (queue.length > 0) { - const ancester = queue.shift(); - if (!ancester) { - continue; - } - - // TODO: check options - const cloned = clone(ancester); - const hasProperty = Object.keys(cloned.meta()).includes(property); - if (!hasProperty) { - // keep traversing until parent has the property - // no op - } else { - if (customizer) { - if (customizer(cloned)) { - // continue until customizer returns a truthy value - return cloned; - } - } else { - // customizer is not specified, stop at the first parent that contains the property - return cloned; - } - } - - const olderAncester = ancester.parent(); - if (olderAncester) { - queue.push(olderAncester); - } - } - - return undefined; - } - - toJSON() { - const entriesToExport = Object - .entries(this) - .filter((kv: [string, any]) => - typeof kv[1] !== 'function' && typeof kv[1] !== 'undefined' - ); - - return Object.fromEntries(entriesToExport); - } - - toObject() { - return this.toJSON(); - } - - toString() { - return JSON.stringify(this.toJSON()); - } -} - -export class Property extends PropertyBase { - id?: string; - name?: string; - disabled?: boolean; - info?: object; - - constructor(def?: { - id?: string; - name?: string; - disabled?: boolean; - info?: object; - }) { - super({ description: 'Property' }); - this.id = def?.id || ''; - this.name = def?.name || ''; - this.disabled = def?.disabled || false; - this.info = def?.info || {}; - } - - // static replaceSubstitutions(_str: string, _variables: object): string { - // // TODO: unsupported - // return ''; - // } - - // static replaceSubstitutionsIn(obj: string, variables: object): object { - // // TODO: unsupported - // return {}; - // } - - describe(content: string, typeName: string) { - this.kind = typeName; - this.description = content; - } -} - -export class PropertyList { - kind: string = 'PropertyList'; - protected _parent: PropertyList | undefined = undefined; - protected list: T[] = []; - - constructor( - // public readonly typeClass: { new(...arg: any): T }, - // public readonly parent: string, - populate: T[], - ) { - this.list = populate; - } - - static isPropertyList(obj: object) { - return 'kind' in obj && obj.kind === 'PropertyList'; - } - - add(item: T) { - this.list.push(item); - } - - all() { - return new Map( - this.list.map( - pp => [pp.id, pp.toJSON()] - ), - ); - } - - append(item: T) { - // it doesn't move item to the end of list for avoiding side effect - this.add(item); - } - - assimilate(source: T[] | PropertyList, prune?: boolean) { - // it doesn't update values from a source list - if (prune) { - this.clear(); - } - if ('list' in source) { // it is PropertyList - this.list.push(...source.list); - } else { - this.list.push(...source); - } - } - - clear() { - this.list = []; - } - - count() { - return this.list.length; - } - - each(iterator: (item: T) => void, context: object) { - interface Iterator { - context?: object; - (item: T): void; - } - const it: Iterator = iterator; - it.context = context; - - this.list.forEach(it); - } - - // TODO: unsupported - // eachParent(iterator, contextopt) {} - - filter(rule: (item: T) => boolean, context: object) { - interface Iterator { - context?: object; - (item: T): boolean; - } - const it: Iterator = rule; - it.context = context; - - return this.list.filter(it); - } - - // TODO: support returning {Item|ItemGroup} - find(rule: (item: T) => boolean, context?: object) { - interface Finder { - context?: object; - (item: T): boolean; - } - const finder: Finder = rule; - finder.context = context; - - return this.list.find(finder); - } - - // it does not return underlying type of the item because they are not supported - get(key: string) { - return this.one(key); - } - - // TODO: value is not used as its usage is unknown - // eslint-disable-next-line @typescript-eslint/no-unused-vars - has(item: T, _value: any) { - return this.indexOf(item) >= 0; - } - - idx(index: number) { - if (index <= this.list.length - 1) { - return this.list[index]; - } - return undefined; - } - - indexOf(item: string | T) { - for (let i = 0; i < this.list.length; i++) { - if (typeof item === 'string') { - if (item === this.list[i].id) { - return i; - } - } else { - if (equal(item, this.list[i])) { - return i; - } - } - } - return -1; - } - - insert(item: T, before?: number) { - if (before && before <= this.list.length - 1) { - this.list = [...this.list.slice(0, before), item, ...this.list.slice(before)]; - } else { - this.append(item); - } - } - - insertAfter(item: T, after?: number) { - if (after && after <= this.list.length - 1) { - this.list = [...this.list.slice(0, after + 1), item, ...this.list.slice(after + 1)]; - } else { - this.append(item); - } - } - - map(iterator: (item: T) => any, context: object) { - interface Iterator { - context?: object; - (item: T): any; - } - const it: Iterator = iterator; - it.context = context; - - return this.list.map(it); - } - - one(id: string) { - for (let i = this.list.length - 1; i >= 0; i--) { - if (this.list[i].id === id) { - return this.list[i]; - } - } - - return undefined; - } - - populate(items: T[]) { - this.list = [...this.list, ...items]; - } - - prepend(item: T) { - this.list = [item, ...this.list]; - } - - reduce(iterator: ((acc: any, item: T) => any), accumulator: any, context: object) { - interface Iterator { - context?: object; - (acc: any, item: T): any; - } - const it: Iterator = iterator; - it.context = context; - - this.list.reduce(it, accumulator); - } - - remove(predicate: T | ((item: T) => boolean), context: object) { - if (typeof predicate === 'function') { - this.list = this.filter(predicate, context); - } else { - this.list = this.filter(item => equal(predicate, item), context); - } - } - - repopulate(items: T[]) { - this.clear(); - this.populate(items); - } - - // unsupportd as _postman_propertyIndexKey is not supported - // toObject(excludeDisabled?: boolean, caseSensitive?: boolean, multiValue?: boolean, sanitizeKeys?: boolean) { - // const itemObjects = this.list - // .filter(item => { - // if (excludeDisabled) { - // return !item.disabled; - // } - // return true; - // }) - // .map(item => { - // return item.toJSON(); - // }); - // } - - toString() { - const itemStrs = this.list.map(item => item.toString()); - return `[${itemStrs.join(',')}]`; - } - - upsert(item: T): boolean { - const itemIdx = this.indexOf(item); - if (itemIdx >= 0) { - this.list = [...this.list.splice(0, itemIdx), item, ...this.list.splice(itemIdx + 1)]; - return false; - } - - this.add(item); - return true; - } -} diff --git a/packages/insomnia/src/renderers/hidden-browser-window/object-certificates.ts b/packages/insomnia/src/renderers/hidden-browser-window/object-certificates.ts deleted file mode 100644 index 206f210a9d..0000000000 --- a/packages/insomnia/src/renderers/hidden-browser-window/object-certificates.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Property } from './object-base'; -import { UrlMatchPattern, UrlMatchPatternList } from './object-urls'; - -export interface CertificateOptions { - name?: string; - matches?: string[]; - key?: object; - cert?: object; - passphrase?: string; - pfx?: object; // PFX or PKCS12 Certificate -} - -export class Certificate extends Property { - name?: string; - matches?: UrlMatchPatternList; - key?: object; - cert?: object; - passphrase?: string; - pfx?: object; // PFX or PKCS12 Certificate - - constructor(options: CertificateOptions) { - super(); - this.kind = 'Certificate'; - this.name = options.name; - this.matches = new UrlMatchPatternList( - undefined, - options.matches ? - options.matches.map(matchStr => new UrlMatchPattern(matchStr)) : - [], - ); - this.key = options.key; - this.cert = options.cert; - this.passphrase = options.passphrase; - this.pfx = options.pfx; - } - - static isCertificate(obj: object) { - return 'kind' in obj && obj.kind === 'Certificate'; - } - - canApplyTo(url: string) { - return this.matches ? this.matches.test(url) : false; - } - - update(options: CertificateOptions) { - this.name = options.name; - this.matches = new UrlMatchPatternList( - undefined, - options.matches ? - options.matches.map(matchStr => new UrlMatchPattern(matchStr)) : - [], - ); - this.key = options.key; - this.cert = options.cert; - this.passphrase = options.passphrase; - this.pfx = options.pfx; - } -} diff --git a/packages/insomnia/src/renderers/hidden-browser-window/object-cookies.ts b/packages/insomnia/src/renderers/hidden-browser-window/object-cookies.ts deleted file mode 100644 index 21fe92ee28..0000000000 --- a/packages/insomnia/src/renderers/hidden-browser-window/object-cookies.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { Property, PropertyList } from './object-base'; - -export interface CookieOptions { - key: string; - value: string; - expires?: Date | string; - maxAge?: Number; - domain?: string; - path?: string; - secure?: Boolean; - httpOnly?: Boolean; - hostOnly?: Boolean; - session?: Boolean; - extensions?: { key: string; value: string }[]; -} - -export class Cookie extends Property { - private def: object; - - constructor(cookieDef: CookieOptions | string) { - super(); - this.kind = 'Cookie'; - this.description = 'Cookie'; - - if (typeof cookieDef === 'string') { - this.def = Cookie.parse(cookieDef); - } else { - this.def = cookieDef; - } - } - - static isCookie(obj: Property) { - return obj.kind === 'Cookie'; - } - - static parse(cookieStr: string) { - const parts = cookieStr.split(';'); - - const def: CookieOptions = { key: '', value: '' }; - const extensions: { key: string; value: string }[] = []; - - parts.forEach((part, i) => { - const kvParts = part.split('='); - const key = kvParts[0]; - - if (i === 0) { - const value = kvParts.length > 1 ? kvParts[1] : ''; - def.key, def.value = key, value; - } else { - switch (key) { - case 'Expires': - // TODO: it should be timestamp - const expireVal = kvParts.length > 1 ? kvParts[1] : '0'; - def.expires = expireVal; - break; - case 'Max-Age': - let maxAgeVal = 0; - if (kvParts.length > 1) { - maxAgeVal = parseInt(kvParts[1], 10); - } - def.maxAge = maxAgeVal; - break; - case 'Domain': - const domainVal = kvParts.length > 1 ? kvParts[1] : ''; - def.domain = domainVal; - break; - case 'Path': - const pathVal = kvParts.length > 1 ? kvParts[1] : ''; - def.path = pathVal; - break; - case 'Secure': - def.secure = true; - break; - case 'HttpOnly': - def.httpOnly = true; - break; - case 'HostOnly': - def.hostOnly = true; - break; - case 'Session': - def.session = true; - break; - default: - const value = kvParts.length > 1 ? kvParts[1] : ''; - extensions.push({ key, value }); - def.extensions = extensions; - } - } - }); - - return def; - } - - static stringify(cookie: Cookie) { - return cookie.toString(); - } - - static unparseSingle(cookie: Cookie) { - return cookie.toString(); - } - - // TODO: support PropertyList - static unparse(cookies: Cookie[]) { - const cookieStrs = cookies.map(cookie => cookie.toString()); - return cookieStrs.join(';'); - } - - toString = () => { - // Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie - const cookieDef = this.def as CookieOptions; - const kvPair = `${cookieDef.key}=${cookieDef.value};`; - const expires = cookieDef.expires ? `Expires=${cookieDef.expires?.toString()};` : ''; - const maxAge = cookieDef.maxAge ? `Max-Age=${cookieDef.maxAge};` : ''; - const domain = cookieDef.domain ? `Domain=${cookieDef.domain};` : ''; - const path = cookieDef.path ? `Path=${cookieDef.path};` : ''; - const secure = cookieDef.secure ? 'Secure;' : ''; - const httpOnly = cookieDef.httpOnly ? 'HttpOnly;' : ''; - // TODO: SameSite, Partitioned is not suported - - const hostOnly = cookieDef.hostOnly ? 'HostOnly;' : ''; - const session = cookieDef.session ? 'Session;' : ''; - - // TODO: extension key may be conflict with pre-defined keys - const extensions = cookieDef.extensions ? - cookieDef.extensions - .map((kv: { key: string; value: string }) => `${kv.key}=${kv.value}`) - .join(';') : ''; // the last field doesn't have ';' - - return `${kvPair} ${expires} ${maxAge} ${domain} ${path} ${secure} ${httpOnly} ${hostOnly} ${session} ${extensions}`; - }; - - valueOf = () => { - return (this.def as CookieOptions).value; - }; -} - -export class CookieList extends PropertyList { - kind: string = 'CookieList'; - cookies: Cookie[]; - - constructor(parent: CookieList | undefined, cookies: Cookie[]) { - super( - // Cookie, parent.toString() - cookies - ); - this._parent = parent; - this.cookies = cookies; - } - - static isCookieList(obj: object) { - return 'kind' in obj && obj.kind === 'CookieList'; - } -} diff --git a/packages/insomnia/src/renderers/hidden-browser-window/object-headers.ts b/packages/insomnia/src/renderers/hidden-browser-window/object-headers.ts deleted file mode 100644 index 08143070a6..0000000000 --- a/packages/insomnia/src/renderers/hidden-browser-window/object-headers.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Property, PropertyList } from './object-base'; - -export interface HeaderOptions { - id?: string; - name?: string; - type?: string; - disabled?: boolean; - key: string; - value: string; -} - -export class Header extends Property { - kind: string = 'Header'; - type: string = ''; - key: string; - value: string; - - constructor( - opts: HeaderOptions | string, - name?: string, // if it is defined, it overrides 'key' (not 'name') - ) { - super(); - - if (typeof opts === 'string') { - const obj = Header.parseSingle(opts); - this.key = obj.key; - this.value = obj.value; - } else { - this.id = opts.id ? opts.id : ''; - this.key = opts.key ? opts.key : ''; - this.name = name ? name : (opts.name ? opts.name : ''); - this.value = opts.value ? opts.value : ''; - this.type = opts.type ? opts.type : ''; - this.disabled = opts ? opts.disabled : false; - } - } - - static create(input?: { key: string; value: string } | string, name?: string): Header { - return new Header(input || { key: '', value: '' }, name); - } - - static isHeader(obj: object) { - return 'kind' in obj && obj.kind === 'Header'; - } - - // example: 'Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n' - static parse(headerString: string): { key: string; value: string }[] { - return headerString - .split('\n') - .map(kvPart => Header.parseSingle(kvPart)); - } - - static parseSingle(headerStr: string): { key: string; value: string } { - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers - // the first colon is the separator - const separatorPos = headerStr.indexOf(':'); - const key = headerStr.slice(0, separatorPos); - const value = headerStr.slice(separatorPos + 1); - - return { key, value }; - } - - static unparse(headers: { key: string; value: string }[] | PropertyList
, separator?: string): string { - const headerStrs = headers.map( - header => this.unparseSingle(header), {} - ); - - return headerStrs.join(separator || '\n'); - } - - static unparseSingle(header: { key: string; value: string } | Header): string { - // both PropertyList and object contains 'key' and 'value' - return `${header.key}: ${header.value}`; - } - - update(newHeader: { key: string; value: string }) { - this.key = newHeader.key; - this.value = newHeader.value; - } - - valueOf() { - return this.value; - } -} - -export class HeaderList extends PropertyList { - constructor(parent: PropertyList | undefined, populate: T[]) { - super(populate); - this._parent = parent; - } - - static isHeaderList(obj: any) { - return 'kind' in obj && obj.kind === 'HeaderList'; - } - - // unsupported - // eachParent(iterator, contextopt) - // toObject(excludeDisabledopt, nullable, caseSensitiveopt, nullable, multiValueopt, nullable, sanitizeKeysopt) → {Object} - - contentSize(): number { - return this.list - .map(header => header.toString()) - .map(headerStr => headerStr.length) // TODO: handle special characters - .reduce((totalSize, headerSize) => totalSize + headerSize, 0); - } -} diff --git a/packages/insomnia/src/renderers/hidden-browser-window/object-proxy-configs.ts b/packages/insomnia/src/renderers/hidden-browser-window/object-proxy-configs.ts deleted file mode 100644 index f07a7773b4..0000000000 --- a/packages/insomnia/src/renderers/hidden-browser-window/object-proxy-configs.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { Property, PropertyList } from './object-base'; - -export interface ProxyConfigOptions { - match: string; - host: string; - port: number; - tunnel: boolean; - disabled?: boolean; - authenticate: boolean; - username: string; - password: string; -} - -export class ProxyConfig extends Property { - kind: string = 'ProxyConfig'; - type: string; - - host: string; - match: string; - port: number; - tunnel: boolean; - authenticate: boolean; - username: string; - password: string; - - static authenticate: boolean; - // static bypass: UrlMatchPatternList; - static host: string; - static match: string; - static password: string; - static port: number; - static tunnel: boolean; // unsupported - static username: string; - - static { - ProxyConfig.authenticate = false; - // ProxyConfig.bypass: UrlMatchPatternList; - ProxyConfig.host = ''; - ProxyConfig.match = ''; - ProxyConfig.password = ''; - ProxyConfig.port = 0; - ProxyConfig.tunnel = false; - ProxyConfig.username = ''; - } - - constructor(def: { - id?: string; - name?: string; - type?: string; - - match: string; - host: string; - port: number; - tunnel: boolean; - disabled?: boolean; - authenticate: boolean; - username: string; - password: string; - - }) { - super(); - - this.id = def.id ? def.id : ''; - this.name = def.name ? def.name : ''; - this.type = def.type ? def.type : ''; - this.disabled = def.disabled ? def.disabled : false; - - this.host = def.host; - this.match = def.match; - this.port = def.port; - this.tunnel = def.tunnel; - this.authenticate = def.authenticate; - this.username = def.username; - this.password = def.password; - } - - static isProxyConfig(obj: object) { - return 'kind' in obj && obj.kind === 'ProxyConfig'; - } - - // TODO: should not read from match? - getProtocols(): string[] { - // match field example: 'http+https://example.com/*' - const protoSeparator = this.match.indexOf('://'); - if (protoSeparator <= 0 || protoSeparator >= this.match.length) { - return []; // invalid match value - } - - return this.match - .slice(0, protoSeparator) - .split('+'); - } - - getProxyUrl(): string { - const protos = this.getProtocols(); - // TODO: how to pick up a protocol? - if (protos.length === 0) { - return ''; - } - - // http://proxy_username:proxy_password@proxy.com:8080 - if (this.authenticate) { - return `protos[0]://${this.username}:${this.password}@${this.host}:${this.port}`; - } - return `protos[0]://${this.host}:${this.port}`; - } - - // TODO: unsupported yet - // test(urlStropt) - - update(options: { - host: string; - match: string; - port: number; - tunnel: boolean; - authenticate: boolean; - username: string; - password: string; - }) { - this.host = options.host; - this.match = options.match; - this.port = options.port; - this.tunnel = options.tunnel; - this.authenticate = options.authenticate; - this.username = options.username; - this.password = options.password; - } - - updateProtocols(protocols: string[]) { - const protoSeparator = this.match.indexOf('://'); - if (protoSeparator <= 0 || protoSeparator >= this.match.length) { - return; // invalid match value - } - - this.match = protocols.join('+') + this.match.slice(protoSeparator); - } -} - -// myProxyConfig = new ProxyConfigList({}, [ -// {match: 'https://example.com/*', host: 'proxy.com', port: 8080, tunnel: true}, -// {match: 'http+https://example2.com/*', host: 'proxy2.com'}, -// ]); - -export class ProxyConfigList extends PropertyList { - constructor(parent: PropertyList | undefined, populate: T[]) { - super(populate); - this._parent = parent; - } - - static isProxyConfigList(obj: any) { - return 'kind' in obj && obj.kind === 'ProxyConfigList'; - } - - // TODO: need support URL at first - // resolve(url?: URL) { - // return { - // host: string; - // match: string; - // port: number; - // tunnel: boolean; - // authenticate: boolean; - // username: string; - // password: string; - // } - // } - - // toObject(excludeDisabledopt, nullable, caseSensitiveopt, nullable, multiValueopt, nullable, sanitizeKeysopt) → {Object} -} diff --git a/packages/insomnia/src/renderers/hidden-browser-window/object-req-resp.ts b/packages/insomnia/src/renderers/hidden-browser-window/object-req-resp.ts deleted file mode 100644 index 4af1ddc92e..0000000000 --- a/packages/insomnia/src/renderers/hidden-browser-window/object-req-resp.ts +++ /dev/null @@ -1,526 +0,0 @@ -import { RESPONSE_CODE_REASONS } from '../../common/constants'; -import { AuthOptions, RequestAuth } from './object-auth'; -import { Property, PropertyBase, PropertyList } from './object-base'; -import { CertificateOptions } from './object-certificates'; -import { Certificate } from './object-certificates'; -import { Cookie, CookieList, CookieOptions } from './object-cookies'; -import { HeaderOptions } from './object-headers'; -import { Header, HeaderList } from './object-headers'; -import { ProxyConfig, ProxyConfigOptions } from './object-proxy-configs'; -import { QueryParam, Url } from './object-urls'; -import { Variable, VariableList } from './object-variables'; - - -// export type RequestBodyMode = -// file string -// formdata string -// graphql string -// raw string -// urlencoded string - -export type RequestBodyMode = undefined | 'formdata' | 'urlencoded' | 'raw' | 'file' | 'graphql'; - -export interface RequestBodyOptions { - mode: RequestBodyMode; - file?: string; - formdata?: { key: string; value: string }[]; - graphql?: object; - raw?: string; - urlencoded?: { key: string; value: string }[]; -} - -class FormParam { - key: string; - value: string; - - constructor(options: { key: string; value: string }) { - this.key = options.key; - this.value = options.value; - } - - // TODO - // (static) _postman_propertyAllowsMultipleValues :Boolean - // (static) _postman_propertyIndexKey :String - - // not implemented either - // static parse(_: FormParam) { - // throw Error('unimplemented yet'); - // } - - toJSON() { - return { key: this.key, value: this.value }; - } - - toString() { - return `${this.key}=${this.value}`; // validate key, value contains '=' - } - - valueOf() { - return this.value; - } -} - -export class RequestBody extends PropertyBase { - mode: RequestBodyMode; // type of request data - file?: string; // It can be a file path (when used with Node.js) or a unique ID (when used with the browser). - formdata?: PropertyList; - graphql?: object; // raw graphql data - options?: object; // request body options - raw?: string; // raw body - urlencoded?: PropertyList; // URL encoded body params - - constructor(opts: RequestBodyOptions) { - super({ description: '' }); - - this.file = opts.file; - this.formdata = opts.formdata ? - new PropertyList( - opts.formdata. - map(formParamObj => new FormParam({ key: formParamObj.key, value: formParamObj.value })) - ) : - undefined; - this.graphql = opts.graphql; - this.mode = opts.mode; - // this.options = opts.options; - this.raw = opts.raw; - - if (typeof opts.urlencoded === 'string') { - const queryParamObj = QueryParam.parse(opts.urlencoded); - this.urlencoded = opts.urlencoded ? - new PropertyList( - Object.entries(queryParamObj) - .map(entry => ({ key: entry[0], value: JSON.stringify(entry[1]) })) - .map(kv => new QueryParam(kv)), - ) : - undefined; - } else { - // TODO: validate key, value in each entry - this.urlencoded = opts.urlencoded ? - new PropertyList( - opts.urlencoded - .map(entry => ({ key: entry.key, value: entry.value })) - .map(kv => new QueryParam(kv)), - ) : - undefined; - } - } - - isEmpty() { - switch (this.mode) { - case 'formdata': - return this.formdata == null; - case 'urlencoded': - return this.urlencoded == null; - case 'raw': - return this.raw == null; - case 'file': - return this.file == null; - case 'graphql': - return this.graphql == null; - default: - throw Error(`mode (${this.mode}) is unexpected`); - } - } - - toString() { - try { - switch (this.mode) { - case 'formdata': - return this.formdata ? this.formdata?.toString() : ''; - case 'urlencoded': - return this.urlencoded ? this.urlencoded.toString() : ''; - case 'raw': - return this.raw ? this.raw.toString() : ''; - case 'file': - return this.file || ''; // TODO: check file id or file content - case 'graphql': - return this.graphql ? JSON.stringify(this.graphql) : ''; - default: - throw Error(`mode (${this.mode}) is unexpected`); - } - } catch (e) { - return ''; - } - } - - update(opts: RequestBodyOptions) { - this.file = opts.file; - this.formdata = opts.formdata ? - new PropertyList( - opts.formdata. - map(formParamObj => new FormParam({ key: formParamObj.key, value: formParamObj.value })) - ) : - undefined; - this.graphql = opts.graphql; - this.mode = opts.mode; - // this.options = opts.options; - this.raw = opts.raw; - - if (typeof opts.urlencoded === 'string') { - const queryParamObj = QueryParam.parse(opts.urlencoded); - this.urlencoded = opts.urlencoded ? - new PropertyList( - Object.entries(queryParamObj) - .map(entry => ({ key: entry[0], value: JSON.stringify(entry[1]) })) - .map(kv => new QueryParam(kv)), - ) : - undefined; - } else { - // TODO: validate key, value in each entry - this.urlencoded = opts.urlencoded ? - new PropertyList( - opts.urlencoded - .map(entry => ({ key: entry.key, value: JSON.stringify(entry.value) })) - .map(kv => new QueryParam(kv)), - ) : - undefined; - } - } -} - -export interface RequestOptions { - url: string | Url; - method: string; - header: HeaderOptions[]; - body: RequestBodyOptions; - auth: AuthOptions; - proxy: ProxyConfigOptions; - certificate: CertificateOptions; -} - -export interface RequestSize { - body: number; - header: number; - total: number; - source: string; -} - -export class Request extends Property { - kind: string = 'Request'; - - url: Url; - method: string; - headers: HeaderList
; - body?: RequestBody; - auth: RequestAuth; - proxy: ProxyConfig; - certificate?: Certificate; - - constructor(options: RequestOptions) { - super(); - - this.url = typeof options.url === 'string' ? new Url(options.url) : options.url; - this.method = options.method; - this.headers = new HeaderList( - undefined, - options.header.map(header => new Header(header)), - ); - this.body = new RequestBody(options.body); - this.auth = new RequestAuth(options.auth); - this.proxy = new ProxyConfig(options.proxy); - this.certificate = new Certificate(options.certificate); - } - - static isRequest(obj: object) { - return 'kind' in obj && obj.kind === 'Request'; - } - - addHeader(header: Header | object) { - if (Header.isHeader(header)) { - const headerInstance = header as Header; - this.headers.add(headerInstance); - } else if ('key' in header && 'value' in header) { - const headerInstance = new Header(header); - this.headers.add(headerInstance); - } else { - throw Error('header must be Header | object'); - } - } - - addQueryParams(params: QueryParam[] | string) { - this.url.addQueryParams(params); - } - - authorizeUsing(authType: string | AuthOptions, options?: VariableList) { - const selectedAuth = typeof authType === 'string' ? authType : authType.type; - this.auth.use(selectedAuth, options || {}); - } - - clone() { - return new Request({ - url: this.url, - method: this.method, - header: this.headers.map(header => header.toJSON(), {}), - body: { - mode: this.body?.mode, - file: this.body?.file, - formdata: this.body?.formdata?.map(formParam => formParam.toJSON(), {}), - graphql: this.body?.graphql, - raw: this.body?.raw, - urlencoded: this.body?.urlencoded?.map(queryParam => queryParam.toJSON(), {}), - }, - auth: this.auth.toJSON(), - proxy: { - match: this.proxy.match, - host: this.proxy.host, - port: this.proxy.port, - tunnel: this.proxy.tunnel, - disabled: this.proxy.disabled, - authenticate: this.proxy.authenticate, - username: this.proxy.username, - password: this.proxy.password, - }, - certificate: { - name: this.certificate?.name, - matches: this.certificate?.matches?.map(match => match.toString(), {}), - key: this.certificate?.key, - cert: this.certificate?.cert, - passphrase: this.certificate?.passphrase, - pfx: this.certificate?.pfx, - }, - }); - } - - forEachHeader(callback: (header: Header, context?: object) => void) { - this.headers.each(callback, {}); - } - - getHeaders(options?: { - ignoreCase: boolean; - enabled: boolean; - multiValue: boolean; - sanitizeKeys: boolean; - }) { - // merge headers with same key into an array - const headerMap = new Map(); - this.headers.each(header => { - const enabled = options?.enabled ? !header.disabled : true; - const sanitized = options?.sanitizeKeys ? !!header.value : true; - const hasName = !!header.key; - - if (!enabled || !sanitized || !hasName) { - return; - } - - header.key = options?.ignoreCase ? header.key?.toLocaleLowerCase() : header.key; - - if (headerMap.has(header.key)) { - const existingHeader = headerMap.get(header.key) || []; - headerMap.set(header.key, [...existingHeader, header.value]); - } else { - headerMap.set(header.key, [header.value]); - } - }, {}); - - const obj: Record = {}; - Array.from(headerMap.entries()) - .forEach(headerEntry => { - obj[headerEntry[0]] = headerEntry[1]; - }); - - return obj; - } - - removeHeader(toRemove: string | Header, options: { ignoreCase: boolean }) { - const filteredHeaders = this.headers.filter( - header => { - if (!header.key) { - return false; - } - - if (typeof toRemove === 'string') { - return options.ignoreCase ? - header.key.toLocaleLowerCase() !== toRemove.toLocaleLowerCase() : - header.key !== toRemove; - } else if ('name' in toRemove) { - if (!toRemove.key) { - return false; - } - - return options.ignoreCase ? - header.key.toLocaleLowerCase() !== toRemove.key.toLocaleLowerCase() : - header.key !== toRemove.key; - } else { - throw Error('type of the "toRemove" must be: string | Header'); - } - }, - {}, - ); - - this.headers = new HeaderList(undefined, filteredHeaders); - } - - removeQueryParams(params: string | string[] | QueryParam[]) { - this.url.removeQueryParams(params); - } - - // TODO: - // size() → { Object } - - toJSON() { - return { - url: this.url, - method: this.method, - header: this.headers.map(header => header.toJSON(), {}), - body: { - mode: this.body?.mode, - file: this.body?.file, - formdata: this.body?.formdata?.map(formParam => formParam.toJSON(), {}), - graphql: this.body?.graphql, - raw: this.body?.raw, - urlencoded: this.body?.urlencoded?.map(queryParam => queryParam.toJSON(), {}), - }, - auth: this.auth.toJSON(), - proxy: { - match: this.proxy.match, - host: this.proxy.host, - port: this.proxy.port, - tunnel: this.proxy.tunnel, - disabled: this.proxy.disabled, - authenticate: this.proxy.authenticate, - username: this.proxy.username, - password: this.proxy.password, - }, - certificate: { - name: this.certificate?.name, - matches: this.certificate?.matches?.map(match => match.toString(), {}), - key: this.certificate?.key, - cert: this.certificate?.cert, - passphrase: this.certificate?.passphrase, - pfx: this.certificate?.pfx, - }, - }; - } - - update(options: RequestOptions) { - this.url = typeof options.url === 'string' ? new Url(options.url) : options.url; - this.method = options.method; - this.headers = new HeaderList( - undefined, - options.header.map(header => new Header(header)), - ); - this.body = new RequestBody(options.body); - this.auth = new RequestAuth(options.auth); - this.proxy = new ProxyConfig(options.proxy); - this.certificate = new Certificate(options.certificate); - } - - upsertHeader(header: HeaderOptions) { - // remove keys with same name - this.headers = new HeaderList( - undefined, - this.headers - .filter( - existingHeader => existingHeader.key !== header.key, - {}, - ) - ); - - // append new - this.headers.append(new Header(header)); - } -} - -export interface ResponseOptions { - code: number; - reason?: string; - header?: HeaderOptions[]; - cookie?: CookieOptions[]; - body?: string; - stream?: Buffer | ArrayBuffer; // TODO: check if it works in both node.js and browser - responseTime: number; - status?: string; -} - -export interface ResponseContentInfo { - mimeType: string; - mimeFormat: string; - charset: string; - fileExtension: string; - fileName: string; - contentType: string; -} - -export class Response extends Property { - kind: string = 'Response'; - - body: string; - code: number; - cookies: CookieList; - headers: HeaderList
; - // originalRequest: Request; - responseTime: number; - status: string; - - constructor(options: ResponseOptions) { - super(); - - this.body = options.body || ''; - this.code = options.code; - this.cookies = new CookieList( - undefined, - options.cookie?.map(cookie => new Cookie(cookie)) || [], - ); - this.headers = new HeaderList( - undefined, - options.header?.map(headerOpt => new Header(headerOpt)) || [], - ); - // TODO: how to init request? - // this.originalRequest = options.originalRequest; - this.responseTime = options.responseTime; - this.status = RESPONSE_CODE_REASONS[options.code]; - } - - // TODO: accurate type of response should be given - // static createFromNode(response: object, cookies: CookieOptions[]) { - // return new Response({ - // cookie: cookies, - // body: response.body.toString(), - // stream: response.body, - // header: response.headers, - // code: response.statusCode, - // status: response.statusMessage, - // responseTime: response.elapsedTime, - // }); - // } - - static isResponse(obj: object) { - return 'kind' in obj && obj.kind === 'Response'; - } - - // TODO: need a library for this - // contentInfo(): ResponseContentInfo { - // return { - // mimeType: string; - // mimeFormat: string; - // charset: string; - // fileExtension: string; - // fileName: string; - // contentType: string; - // }; - // } - - // dataURI() { - - // } - - // need a library for this - // json(reviver?, strict?) { - - // } - - // jsonp(reviver?, strict?) { - - // } - - reason() { - return this.status; - } - - // TODO: - // size() → {Number} - - text() { - return this.body; - } -} diff --git a/packages/insomnia/src/renderers/hidden-browser-window/object-urls.ts b/packages/insomnia/src/renderers/hidden-browser-window/object-urls.ts deleted file mode 100644 index ca805bed10..0000000000 --- a/packages/insomnia/src/renderers/hidden-browser-window/object-urls.ts +++ /dev/null @@ -1,563 +0,0 @@ -import queryString from 'query-string'; - -import { Property, PropertyBase, PropertyList } from './object-base'; -import { Variable, VariableList } from './object-variables'; - -// export class QueryParam extends Property { -// key: string = ''; -// value: string = ''; - -// constructor(options: { -// id?: string; -// name?: string; -// key: string; -// value: string; -// }) { -// super(); - -// this.id = options.id ? options.id : ''; -// this.name = options.name ? options.name : ''; -// this.key = options.key; -// this.value = options.value; -// } - -// // TODO: improve following fields -// static _postman_propertyAllowsMultipleValues: boolean = true; -// static _postman_propertyIndexKey: string = 'formData'; - -// // parse a form data string into an array of objects, where each object contains a key and a value. -// static parse(query: string): { key: string; value: string }[] { -// try { -// const keyValues = JSON.parse(query); -// return keyValues.filter((keyValue: object) => { -// if (!('key' in keyValue) || !('value' in keyValue)) { -// console.error('ignored some formdata as "key" or "value" is not found in it'); -// return false; -// } -// return true; -// }); -// } catch (e) { -// console.error(`failed to parse QueryParams: ${e.message}`); -// return []; -// } -// } - -// valueOf() { -// return this.value; -// } -// } - -export class QueryParam extends Property { - key: string; - value: string; - - constructor(options: { key: string; value: string } | string) { - super(); - - if (typeof options === 'string') { - try { - const optionsObj = JSON.parse(options); - // validate key and value fields - this.key = optionsObj.key; - this.value = optionsObj.value; - } catch (e) { - throw Error(`invalid QueryParam options ${e}`); - } - } else if (typeof options === 'object' && ('key' in options) && ('value' in options)) { - this.key = options.key; - this.value = options.value; - } else { - throw Error('unknown options for new QueryParam'); - } - } - - // TODO: - // (static) _postman_propertyAllowsMultipleValues :Boolean - // (static) _postman_propertyIndexKey :String - - static parse(queryStr: string) { - // this may not always be executed in the browser - return queryString.parse(queryStr); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - static parseSingle(param: string, _idx?: number, _all?: string[]) { - // it seems that _idx and _all are not useful - return queryString.parse(param); - - } - - static unparse(params: object) { - return Object.entries(params) - .map(entry => `${entry[0]}=${entry[1] || ''}`) - .join('&'); - } - - static unparseSingle(obj: { key: string; value: string }) { - if ('key' in obj && 'value' in obj) { - // TODO: validate and unescape - return `${obj.key}=${obj.value}`; - } - return {}; - } - - toString() { - return `${this.key}=${this.value}`; // validate key, value contains '=' - } - - update(param: string | { key: string; value: string }) { - if (typeof param === 'string') { - const paramObj = QueryParam.parse(param); - this.key = typeof paramObj.key === 'string' ? paramObj.key : ''; - this.value = typeof paramObj.value === 'string' ? paramObj.value : ''; - } else if ('key' in param && 'value' in param) { - this.key = param.key; - this.value = param.value; - } else { - throw Error('the param for update must be: string | { key: string; value: string }'); - } - } -} - -export interface UrlObject { - auth: { - username: string; - password: string; - } | undefined; - hash: string; - host: string[]; - path: string[]; - port: string; - protocol: string; - query: { key: string; value: string }[]; - variables: { key: string; value: string }[]; -} - -export class Url extends PropertyBase { - kind: string = 'Url'; - - auth?: { username: string; password: string }; - hash?: string; - host: string[]; - path?: string[]; - port?: string; - protocol?: string; - query: PropertyList; - variables: VariableList; - - // TODO: user could pass anything - constructor(def: { - auth?: { username: string; password: string }; // TODO: should be related to RequestAuth - hash?: string; - host: string[]; - path?: string[]; - port?: string; - protocol: string; - query: PropertyList; - variables: VariableList; - } | string) { - super({ description: 'Url' }); - - if (typeof def === 'string') { - const urlObj = Url.parse(def); - - if (urlObj) { - this.auth = urlObj.auth; - this.hash = urlObj.hash; - this.host = urlObj.host; - this.path = urlObj.path; - this.port = urlObj.port; - this.protocol = urlObj.protocol; - - const queryList = urlObj.query ? urlObj.query.map(kvObj => new QueryParam(kvObj)) : []; - this.query = new PropertyList(queryList); - const varList = urlObj.variables ? - urlObj.variables - .map((kvObj: { key: string; value: string }) => new Variable(kvObj)) : - []; - this.variables = new VariableList(undefined, varList); - } else { - throw Error(`url is invalid: ${def}`); // TODO: - } - } else { - this.auth = def.auth ? { username: def.auth.username, password: def.auth.password } : undefined; - this.hash = def.hash ? def.hash : ''; - this.host = def.host; - this.path = def.path ? def.path : []; - this.port = def.port ? def.port : ''; - this.protocol = def.protocol ? def.protocol : ''; - this.query = def.query ? def.query : new PropertyList([]); - this.variables = def.variables ? def.variables : new VariableList(undefined, new Array()); - } - } - - static isUrl(obj: object) { - return 'kind' in obj && obj.kind === 'Url'; - } - - static parse(urlStr: string): UrlObject | undefined { - if (!URL.canParse(urlStr)) { - console.error(`invalid URL string ${urlStr}`); - return undefined; - } - - const url = new URL(urlStr); - const query = Array.from(url.searchParams.entries()) - .map((kv: [string, string]) => { - return { key: kv[0], value: kv[1] }; - }); - - return { - auth: url.username !== '' ? { // TODO: make it compatible with RequestAuth - username: url.username, - password: url.password, - } : undefined, - hash: url.hash, - host: url.hostname.split('/'), - path: url.pathname.split('/'), - port: url.port, - protocol: url.protocol, // e.g. https: - query, - variables: [], - }; - } - - addQueryParams(params: { key: string; value: string }[] | string) { - let queryParams: { key: string; value: string }[]; - - if (typeof params === 'string') { - queryParams = QueryParam.parse(params); - } else { - queryParams = params; - } - - queryParams.forEach((param: { key: string; value: string }) => { - this.query.append(new QueryParam({ key: param.key, value: param.value })); - }); - } - - getHost() { - return this.host.join('/'); - } - - getPath(unresolved?: boolean) { - const path = this.path ? this.path.join('/') : ''; - - if (unresolved) { - return '/' + path; - } - // TODO: handle variables - return path; - } - - getPathWithQuery() { - const path = this.path ? this.path.join('/') : ''; - const pathHasPrefix = path.startsWith('/') ? path : '/' + path; - - const query = this.query - .map(param => `${param.key}=${param.value}`, {}) - .join('&'); - - return `${pathHasPrefix}?${query}`; - } - - getQueryString() { - return this.query - .map(queryParam => (`${queryParam.key}=${queryParam.key}`), {}) - .join('&'); - } - - // TODO: - getRemote(forcePort?: boolean) { - const host = this.host.join('/'); - if (forcePort) { - const port = this.protocol && (this.protocol === 'https:') ? 443 : 80; // TODO: support ws, gql, grpc - return `${host}:${port}`; - } - return this.port ? `${host}:${this.port}` : `${host}`; - } - - removeQueryParams(params: QueryParam[] | string[] | string) { - if (typeof params === 'string') { - // it is a string - this.query = new PropertyList( - this.query.filter(queryParam => queryParam.key === params, {}) - ); - } else if (params.length > 0) { - let toBeRemoved: Set; - - if (typeof params[0] === 'string') { - // it is a string[] - toBeRemoved = new Set(params as string[]); - } else { - // it is a QueryParam[] - toBeRemoved = new Set( - (params as QueryParam[]) - .map(param => param.key) - ); - } - - this.query = new PropertyList( - this.query.filter(queryParam => !toBeRemoved.has(queryParam.key), {}) - ); - } else { - console.error('failed to remove query params: unknown params type, only supports QueryParam[], string[] or string'); - } - } - - toString(forceProtocol?: boolean) { - const queryStr = this.query - .map(param => `${param.key}=${param.value}`, {}) - .join('&'); - - const auth = this.auth ? `${this.auth.username}:${this.auth.password}@` : ''; - if (forceProtocol) { - return `${this.protocol}//${auth}${this.host}:${this.port}${this.path}?${queryStr}#${this.hash}`; // TODO: improve auth - } - return `${auth}${this.host}:${this.port}${this.path}?${queryStr}#${this.hash}`; - } - - update(url: string | object) { - // user could pass anything in script - - if (typeof url === 'string') { - const urlObj = Url.parse(url); - if (urlObj) { - this.auth = urlObj.auth ? { - username: urlObj.auth.username, - password: urlObj.auth.password, - } : { - username: '', - password: '', - }; - this.hash = urlObj.hash ? urlObj.hash : ''; - this.host = urlObj.host ? urlObj.host : []; - this.path = urlObj.path ? urlObj.path : []; - this.port = urlObj.port ? urlObj.port : ''; - this.protocol = urlObj.protocol ? urlObj.protocol : ''; - this.query = urlObj.query ? - new PropertyList(urlObj.query.map(kv => new QueryParam(kv))) : - new PropertyList([]); - // TODO: update variables - // this.variables = new VariableList(undefined, new Array()); - } else { - throw Error(`failed to parse url: ${url}`); - } - } else { - if ('auth' in url && typeof url.auth === 'object' && url.auth) { - if ('username' in url.auth - && typeof url.auth.username === 'string' - && 'password' in url.auth - && typeof url.auth.password === 'string' - ) { - this.auth = { username: url.auth.username, password: url.auth.password }; - } else { - console.error('the auth field must have "username" and "password" fields'); - } - } - - if ('hash' in url && typeof url.hash === 'string') { - this.hash = url.hash; - } else { - this.hash = ''; - } - - if ('host' in url && Array.isArray(url.host)) { - const isStringArray = url.host.length > 0 ? typeof url.host[0] === 'string' : true; - if (isStringArray) { - this.host = url.host; - } else { - console.error('type of "host" is invalid'); - } - } else { - this.host = []; - } - - if ('path' in url && Array.isArray(url.path)) { - const isStringArray = url.path.length > 0 ? typeof url.path[0] === 'string' : true; - if (isStringArray) { - this.path = url.path; - } else { - console.error('type of "path" is invalid'); - } - } else { - this.path = []; - } - - this.port = 'port' in url && url.port && typeof url.port === 'string' ? url.port : ''; - this.protocol = 'protocol' in url && url.protocol && typeof url.protocol === 'string' ? url.protocol : ''; - - if ('query' in url && Array.isArray(url.query)) { - const queryParams = url.query - .filter(obj => 'key' in obj && 'value' in obj) - .map(kv => new QueryParam(kv)); - - this.query = new PropertyList(queryParams); - } - - // TODO: update variables - // this.variables = new VariableList(undefined, new Array()); - } - } -} - -// UrlMatchPattern implements chrome extension match patterns: https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns -export class UrlMatchPattern extends Property { - // scheme - scheme: 'http:' | 'https:' | '*' | 'file:'; - // host - // About wildcard: - // If you use a wildcard in the host pattern - // it must be the first or only character, and it must be followed by a period (.) or forward slash (/). - host: string; - // path: - // Must contain at least a forward slash - // The slash by itself matches any path. - path: string; - - private port: string; - - // Special cases: https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns#special - // "" - // "file:///" - // "http://localhost/*" - // It doesn't support match patterns for top Level domains (TLD). - - constructor(pattern: string) { - super(); - - const patternObj = UrlMatchPattern.parseAndValidate(pattern); - this.scheme = patternObj.scheme; - this.host = patternObj.host.join('/'); - this.path = patternObj.path.join('/'); - this.port = patternObj.port; - } - - static parseAndValidate(pattern: string): { - scheme: 'http:' | 'https:' | '*' | 'file:'; - host: string[]; - path: string[]; - port: string; - } { - // TODO: validate the pattern - const urlObj = Url.parse(pattern); - - if (!urlObj || urlObj.host.length === 0) { - throw Error(`match pattern (${pattern}) is invalid and failed to parse`); - } - - if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:' && urlObj.protocol !== '*' && urlObj.protocol !== 'file:') { - throw Error(`scheme (${urlObj.protocol}) is invalid and failed to parse`); - } - - return { scheme: urlObj.protocol, host: urlObj.host, path: urlObj.path, port: `${urlObj.port}` }; - } - - static readonly MATCH_ALL_URLS: string = ''; - static pattern: string | undefined = undefined; // TODO: its usage is unknown - static readonly PROTOCOL_DELIMITER: string = '+'; - - // TODO: the url can not start with - - private readonly starRegPattern = '[a-zA-Z0-9\-]*'; - - getProtocols(): string[] { - switch (this.scheme) { - case 'http:': - return ['http']; - case 'https:': - return ['https']; - case '*': - return ['http', 'https']; - case 'file:': - return ['file']; - default: - throw `invalid scheme ${this.scheme}`; - } - } - - test(urlStr: string) { - const urlObj = Url.parse(urlStr); - if (!urlObj) { - return false; - } - - return this.testProtocol(urlObj.protocol) - && this.testHost(urlObj.host.join('/')) - && this.testPort(urlObj.port, urlObj.protocol) - && this.testPath(urlObj.path.join('/')); - } - - testHost(host: string) { - const hostRegPattern = new RegExp(this.host.replace('*', this.starRegPattern), 'ig'); - return hostRegPattern.test(host); - } - - testPath(path: string) { - const pathRegPattern = new RegExp(this.path.replace('*', this.starRegPattern), 'ig'); - return pathRegPattern.test(path); - } - - // TODO: it is confused to verify both port and protocol - // testPort verifies both port and protocol, but not the relationship between them - testPort(port: string, protocol: string) { - if (!this.testProtocol(protocol)) { - return false; - } - - const portRegPattern = new RegExp(this.port.replace('*', this.starRegPattern), 'ig'); - if (!portRegPattern.test(port)) { - return false; - } - - return true; - } - - testProtocol(protocol: string) { - switch (protocol) { - case 'http:': - return this.scheme === 'http:' || this.scheme === '*'; - case 'https:': - return this.scheme === 'https:' || this.scheme === '*'; - case '*': - return this.scheme === 'http:' || this.scheme === 'https:' || this.scheme === '*'; - case 'file:': - return this.scheme === 'file:'; - default: - throw `invalid scheme ${protocol}`; - } - } - - toString() { - return `${this.scheme}//${this.host}${this.path}`; - } - - update(pattern: string) { - const patternObj = UrlMatchPattern.parseAndValidate(pattern); - this.scheme = patternObj.scheme; - this.host = patternObj.host.join('/'); - this.path = patternObj.path.join('/'); - this.port = patternObj.port; - } -} - -export class UrlMatchPatternList extends PropertyList { - kind: string = 'UrlMatchPatternList'; - - constructor(parent: PropertyList | undefined, populate: T[]) { - super(populate); - this._parent = parent; - } - - static isUrlMatchPatternList(obj: any) { - return 'kind' in obj && obj.kind === 'UrlMatchPatternList'; - } - - // TODO: unsupported yet - // toObject(excludeDisabledopt, nullable, caseSensitiveopt, nullable, multiValueopt, nullable, sanitizeKeysopt) → {Object} - - test(urlStr: string) { - return this - .filter(matchPattern => matchPattern.test(urlStr), {}) - .length > 0; - } -} diff --git a/packages/insomnia/src/renderers/hidden-browser-window/object-variables.ts b/packages/insomnia/src/renderers/hidden-browser-window/object-variables.ts deleted file mode 100644 index 9b0ba7c5f1..0000000000 --- a/packages/insomnia/src/renderers/hidden-browser-window/object-variables.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Property, PropertyList } from './object-base'; - -export class Variable extends Property { - key: string; - value: any; - type: string; - kind: string = 'Variable'; - - constructor(def?: { - id?: string; - key: string; - name?: string; - value: string; - type?: string; - disabled?: boolean; - }) { - super(); - - this.id = def ? def.id : ''; - this.key = def ? def.key : ''; - this.name = def ? def.name : ''; - this.value = def ? def.value : ''; - this.type = def && def.type ? def.type : 'Variable'; - this.disabled = def ? def.disabled : false; - } - - // unknown usage and unsupported - // static readonly types() => { - // } - - // cast typecasts a value to the Variable.types of this Variable. - cast(value: any) { - if ('kind' in value && value.kind === 'Variable') { - return value.value; - } - return undefined; - } - - get() { - return this.value; - } - - set(value: any) { - this.value = value; - } -} - -export class VariableList extends PropertyList { - kind: string = 'VariableList'; - - constructor(parent: PropertyList | undefined, populate: T[]) { - super(populate); - this._parent = parent; - } - - static isVariableList(obj: any) { - return 'kind' in obj && obj.kind === 'VariableList'; - } -}