refactor: monorepo+pnpm (removed husky)
This commit is contained in:
149
packages/hoppscotch-app/helpers/utils/EffectiveURL.ts
Normal file
149
packages/hoppscotch-app/helpers/utils/EffectiveURL.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { combineLatest, Observable } from "rxjs"
|
||||
import { map } from "rxjs/operators"
|
||||
import { FormDataKeyValue, HoppRESTRequest } from "../types/HoppRESTRequest"
|
||||
import parseTemplateString from "../templating"
|
||||
import { Environment, getGlobalVariables } from "~/newstore/environments"
|
||||
|
||||
export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
||||
/**
|
||||
* The effective final URL.
|
||||
*
|
||||
* This contains path, params and environment variables all applied to it
|
||||
*/
|
||||
effectiveFinalURL: string
|
||||
effectiveFinalHeaders: { key: string; value: string }[]
|
||||
effectiveFinalParams: { key: string; value: string }[]
|
||||
effectiveFinalBody: FormData | string | null
|
||||
}
|
||||
|
||||
function getFinalBodyFromRequest(
|
||||
request: HoppRESTRequest,
|
||||
env: Environment
|
||||
): FormData | string | null {
|
||||
if (request.body.contentType === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (request.body.contentType === "multipart/form-data") {
|
||||
const formData = new FormData()
|
||||
|
||||
request.body.body
|
||||
.filter((x) => x.key !== "" && x.active) // Remove empty keys
|
||||
.map(
|
||||
(x) =>
|
||||
<FormDataKeyValue>{
|
||||
active: x.active,
|
||||
isFile: x.isFile,
|
||||
key: parseTemplateString(x.key, env.variables),
|
||||
value: x.isFile
|
||||
? x.value
|
||||
: parseTemplateString(x.value, env.variables),
|
||||
}
|
||||
)
|
||||
.forEach((entry) => {
|
||||
if (!entry.isFile) formData.append(entry.key, entry.value)
|
||||
else entry.value.forEach((blob) => formData.append(entry.key, blob))
|
||||
})
|
||||
|
||||
return formData
|
||||
} else return request.body.body
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs an executable request format with environment variables applied
|
||||
*
|
||||
* @param request The request to source from
|
||||
* @param environment The environment to apply
|
||||
*
|
||||
* @returns An object with extra fields defining a complete request
|
||||
*/
|
||||
export function getEffectiveRESTRequest(
|
||||
request: HoppRESTRequest,
|
||||
environment: Environment
|
||||
): EffectiveHoppRESTRequest {
|
||||
const envVariables = [...environment.variables, ...getGlobalVariables()]
|
||||
|
||||
const effectiveFinalHeaders = request.headers
|
||||
.filter(
|
||||
(x) =>
|
||||
x.key !== "" && // Remove empty keys
|
||||
x.active // Only active
|
||||
)
|
||||
.map((x) => ({
|
||||
// Parse out environment template strings
|
||||
active: true,
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: parseTemplateString(x.value, envVariables),
|
||||
}))
|
||||
|
||||
// Authentication
|
||||
if (request.auth.authActive) {
|
||||
// TODO: Support a better b64 implementation than btoa ?
|
||||
if (request.auth.authType === "basic") {
|
||||
const username = parseTemplateString(request.auth.username, envVariables)
|
||||
const password = parseTemplateString(request.auth.password, envVariables)
|
||||
|
||||
effectiveFinalHeaders.push({
|
||||
active: true,
|
||||
key: "Authorization",
|
||||
value: `Basic ${btoa(`${username}:${password}`)}`,
|
||||
})
|
||||
} else if (
|
||||
request.auth.authType === "bearer" ||
|
||||
request.auth.authType === "oauth-2"
|
||||
) {
|
||||
effectiveFinalHeaders.push({
|
||||
active: true,
|
||||
key: "Authorization",
|
||||
value: `Bearer ${parseTemplateString(
|
||||
request.auth.token,
|
||||
envVariables
|
||||
)}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const effectiveFinalBody = getFinalBodyFromRequest(request, environment)
|
||||
if (request.body.contentType)
|
||||
effectiveFinalHeaders.push({
|
||||
active: true,
|
||||
key: "content-type",
|
||||
value: request.body.contentType,
|
||||
})
|
||||
|
||||
return {
|
||||
...request,
|
||||
effectiveFinalURL: parseTemplateString(request.endpoint, envVariables),
|
||||
effectiveFinalHeaders,
|
||||
effectiveFinalParams: request.params
|
||||
.filter(
|
||||
(x) =>
|
||||
x.key !== "" && // Remove empty keys
|
||||
x.active // Only active
|
||||
)
|
||||
.map((x) => ({
|
||||
active: true,
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: parseTemplateString(x.value, envVariables),
|
||||
})),
|
||||
effectiveFinalBody,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Observable Stream that emits HoppRESTRequests whenever
|
||||
* the input streams emit a value
|
||||
*
|
||||
* @param request$ The request stream containing request data
|
||||
* @param environment$ The environment stream containing environment data to apply
|
||||
*
|
||||
* @returns Observable Stream for the Effective Request Object
|
||||
*/
|
||||
export function getEffectiveRESTRequestStream(
|
||||
request$: Observable<HoppRESTRequest>,
|
||||
environment$: Observable<Environment>
|
||||
): Observable<EffectiveHoppRESTRequest> {
|
||||
return combineLatest([request$, environment$]).pipe(
|
||||
map(([request, env]) => getEffectiveRESTRequest(request, env))
|
||||
)
|
||||
}
|
||||
24
packages/hoppscotch-app/helpers/utils/StreamUtils.ts
Normal file
24
packages/hoppscotch-app/helpers/utils/StreamUtils.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { combineLatest, Observable } from "rxjs"
|
||||
import { map } from "rxjs/operators"
|
||||
|
||||
/**
|
||||
* Constructs a stream of a object from a collection of other observables
|
||||
*
|
||||
* @param streamObj The object containing key of observables to assemble from
|
||||
*
|
||||
* @returns The constructed object observable
|
||||
*/
|
||||
export function constructFromStreams<T>(
|
||||
streamObj: { [key in keyof T]: Observable<T[key]> }
|
||||
): Observable<T> {
|
||||
return combineLatest(Object.values<Observable<T[keyof T]>>(streamObj)).pipe(
|
||||
map((streams) => {
|
||||
const keys = Object.keys(streamObj) as (keyof T)[]
|
||||
|
||||
return keys.reduce(
|
||||
(acc, s, i) => Object.assign(acc, { [s]: streams[i] }),
|
||||
{}
|
||||
) as T
|
||||
})
|
||||
)
|
||||
}
|
||||
15
packages/hoppscotch-app/helpers/utils/__tests__/b64.spec.js
Normal file
15
packages/hoppscotch-app/helpers/utils/__tests__/b64.spec.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { TextDecoder } from "util"
|
||||
import { decodeB64StringToArrayBuffer } from "../b64"
|
||||
|
||||
describe("decodeB64StringToArrayBuffer", () => {
|
||||
test("decodes content correctly", () => {
|
||||
const decoder = new TextDecoder("utf-8")
|
||||
expect(
|
||||
decoder.decode(
|
||||
decodeB64StringToArrayBuffer("aG9wcHNjb3RjaCBpcyBhd2Vzb21lIQ==")
|
||||
)
|
||||
).toMatch("hoppscotch is awesome!")
|
||||
})
|
||||
|
||||
// TODO : More tests for binary data ?
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
import { isJSONContentType } from "../contenttypes"
|
||||
|
||||
describe("isJSONContentType", () => {
|
||||
test("returns true for JSON content types", () => {
|
||||
expect(isJSONContentType("application/json")).toBe(true)
|
||||
expect(isJSONContentType("application/vnd.api+json")).toBe(true)
|
||||
expect(isJSONContentType("application/hal+json")).toBe(true)
|
||||
expect(isJSONContentType("application/ld+json")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for JSON types with charset specified", () => {
|
||||
expect(isJSONContentType("application/json; charset=utf-8")).toBe(true)
|
||||
expect(isJSONContentType("application/vnd.api+json; charset=utf-8")).toBe(
|
||||
true
|
||||
)
|
||||
expect(isJSONContentType("application/hal+json; charset=utf-8")).toBe(true)
|
||||
expect(isJSONContentType("application/ld+json; charset=utf-8")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns false for non-JSON content types", () => {
|
||||
expect(isJSONContentType("application/xml")).toBe(false)
|
||||
expect(isJSONContentType("text/html")).toBe(false)
|
||||
expect(isJSONContentType("application/x-www-form-urlencoded")).toBe(false)
|
||||
expect(isJSONContentType("foo/jsoninword")).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for non-JSON content types with charset", () => {
|
||||
expect(isJSONContentType("application/xml; charset=utf-8")).toBe(false)
|
||||
expect(isJSONContentType("text/html; charset=utf-8")).toBe(false)
|
||||
expect(
|
||||
isJSONContentType("application/x-www-form-urlencoded; charset=utf-8")
|
||||
).toBe(false)
|
||||
expect(isJSONContentType("foo/jsoninword; charset=utf-8")).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for null/undefined", () => {
|
||||
expect(isJSONContentType(null)).toBe(false)
|
||||
expect(isJSONContentType(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,38 @@
|
||||
import debounce from "../debounce"
|
||||
|
||||
describe("debounce", () => {
|
||||
test("doesn't call function right after calling", () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
const debFunc = debounce(fn, 100)
|
||||
debFunc()
|
||||
|
||||
expect(fn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("calls the function after the given timeout", () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
jest.useFakeTimers()
|
||||
|
||||
const debFunc = debounce(fn, 100)
|
||||
debFunc()
|
||||
|
||||
jest.runAllTimers()
|
||||
|
||||
expect(fn).toHaveBeenCalled()
|
||||
// expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 100)
|
||||
})
|
||||
|
||||
test("calls the function only one time within the timeframe", () => {
|
||||
const fn = jest.fn()
|
||||
|
||||
const debFunc = debounce(fn, 1000)
|
||||
|
||||
for (let i = 0; i < 100; i++) debFunc()
|
||||
|
||||
jest.runAllTimers()
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
21
packages/hoppscotch-app/helpers/utils/__tests__/uri.spec.js
Normal file
21
packages/hoppscotch-app/helpers/utils/__tests__/uri.spec.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { parseUrlAndPath } from "../uri"
|
||||
|
||||
describe("parseUrlAndPath", () => {
|
||||
test("has url and path fields", () => {
|
||||
const result = parseUrlAndPath("https://hoppscotch.io/")
|
||||
|
||||
expect(result).toHaveProperty("url")
|
||||
expect(result).toHaveProperty("path")
|
||||
})
|
||||
|
||||
test("parses out URL correctly", () => {
|
||||
const result = parseUrlAndPath("https://hoppscotch.io/test/page")
|
||||
|
||||
expect(result.url).toBe("https://hoppscotch.io")
|
||||
})
|
||||
test("parses out Path correctly", () => {
|
||||
const result = parseUrlAndPath("https://hoppscotch.io/test/page")
|
||||
|
||||
expect(result.path).toBe("/test/page")
|
||||
})
|
||||
})
|
||||
144
packages/hoppscotch-app/helpers/utils/__tests__/valid.spec.js
Normal file
144
packages/hoppscotch-app/helpers/utils/__tests__/valid.spec.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import { wsValid, httpValid, socketioValid } from "../valid"
|
||||
|
||||
describe("wsValid", () => {
|
||||
test("returns true for valid URL with IP address", () => {
|
||||
expect(wsValid("wss://174.129.224.73/")).toBe(true)
|
||||
expect(wsValid("wss://174.129.224.73")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for valid URL with Hostname", () => {
|
||||
expect(wsValid("wss://echo.websocket.org/")).toBe(true)
|
||||
expect(wsValid("wss://echo.websocket.org")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns false for invalid URL with IP address", () => {
|
||||
expect(wsValid("wss://174.129.")).toBe(false)
|
||||
expect(wsValid("wss://174.129./")).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for invalid URL with hostname", () => {
|
||||
expect(wsValid("wss://echo.websocket./")).toBe(false)
|
||||
expect(wsValid("wss://echo.websocket.")).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for non-wss protocol URLs", () => {
|
||||
expect(wsValid("http://echo.websocket.org/")).toBe(false)
|
||||
expect(wsValid("http://echo.websocket.org")).toBe(false)
|
||||
expect(wsValid("http://174.129.224.73/")).toBe(false)
|
||||
expect(wsValid("http://174.129.224.73")).toBe(false)
|
||||
})
|
||||
|
||||
test("returns true for wss protocol URLs", () => {
|
||||
expect(wsValid("wss://echo.websocket.org/")).toBe(true)
|
||||
expect(wsValid("wss://echo.websocket.org")).toBe(true)
|
||||
expect(wsValid("wss://174.129.224.73/")).toBe(true)
|
||||
expect(wsValid("wss://174.129.224.73")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for ws protocol URLs", () => {
|
||||
expect(wsValid("ws://echo.websocket.org/")).toBe(true)
|
||||
expect(wsValid("ws://echo.websocket.org")).toBe(true)
|
||||
expect(wsValid("ws://174.129.224.73/")).toBe(true)
|
||||
expect(wsValid("ws://174.129.224.73")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("httpValid", () => {
|
||||
test("returns true for valid URL with IP address", () => {
|
||||
expect(httpValid("http://174.129.224.73/")).toBe(true)
|
||||
expect(httpValid("http://174.129.224.73")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for valid URL with Hostname", () => {
|
||||
expect(httpValid("http://echo.websocket.org/")).toBe(true)
|
||||
expect(httpValid("http://echo.websocket.org")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns false for invalid URL with IP address", () => {
|
||||
expect(httpValid("http://174.129./")).toBe(false)
|
||||
expect(httpValid("http://174.129.")).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for invalid URL with hostname", () => {
|
||||
expect(httpValid("http://echo.websocket./")).toBe(false)
|
||||
expect(httpValid("http://echo.websocket.")).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for non-http(s) protocol URLs", () => {
|
||||
expect(httpValid("wss://echo.websocket.org/")).toBe(false)
|
||||
expect(httpValid("wss://echo.websocket.org")).toBe(false)
|
||||
expect(httpValid("wss://174.129.224.73/")).toBe(false)
|
||||
expect(httpValid("wss://174.129.224.73")).toBe(false)
|
||||
})
|
||||
|
||||
test("returns true for HTTP protocol URLs", () => {
|
||||
expect(httpValid("http://echo.websocket.org/")).toBe(true)
|
||||
expect(httpValid("http://echo.websocket.org")).toBe(true)
|
||||
expect(httpValid("http://174.129.224.73/")).toBe(true)
|
||||
expect(httpValid("http://174.129.224.73")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for HTTPS protocol URLs", () => {
|
||||
expect(httpValid("https://echo.websocket.org/")).toBe(true)
|
||||
expect(httpValid("https://echo.websocket.org")).toBe(true)
|
||||
expect(httpValid("https://174.129.224.73/")).toBe(true)
|
||||
expect(httpValid("https://174.129.224.73")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("socketioValid", () => {
|
||||
test("returns true for valid URL with IP address", () => {
|
||||
expect(socketioValid("http://174.129.224.73/")).toBe(true)
|
||||
expect(socketioValid("http://174.129.224.73")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for valid URL with Hostname", () => {
|
||||
expect(socketioValid("http://echo.websocket.org/")).toBe(true)
|
||||
expect(socketioValid("http://echo.websocket.org")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns false for invalid URL with IP address", () => {
|
||||
expect(socketioValid("http://174.129./")).toBe(false)
|
||||
expect(socketioValid("http://174.129.")).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for invalid URL with hostname", () => {
|
||||
expect(socketioValid("http://echo.websocket./")).toBe(false)
|
||||
expect(socketioValid("http://echo.websocket.")).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for non-http(s) and non-wss protocol URLs", () => {
|
||||
expect(socketioValid("ftp://echo.websocket.org/")).toBe(false)
|
||||
expect(socketioValid("ftp://echo.websocket.org")).toBe(false)
|
||||
expect(socketioValid("ftp://174.129.224.73/")).toBe(false)
|
||||
expect(socketioValid("ftp://174.129.224.73")).toBe(false)
|
||||
})
|
||||
|
||||
test("returns true for HTTP protocol URLs", () => {
|
||||
expect(socketioValid("http://echo.websocket.org/")).toBe(true)
|
||||
expect(socketioValid("http://echo.websocket.org")).toBe(true)
|
||||
expect(socketioValid("http://174.129.224.73/")).toBe(true)
|
||||
expect(socketioValid("http://174.129.224.73")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for HTTPS protocol URLs", () => {
|
||||
expect(socketioValid("https://echo.websocket.org/")).toBe(true)
|
||||
expect(socketioValid("https://echo.websocket.org")).toBe(true)
|
||||
expect(socketioValid("https://174.129.224.73/")).toBe(true)
|
||||
expect(socketioValid("https://174.129.224.73")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for wss protocol URLs", () => {
|
||||
expect(socketioValid("wss://echo.websocket.org/")).toBe(true)
|
||||
expect(socketioValid("wss://echo.websocket.org")).toBe(true)
|
||||
expect(socketioValid("wss://174.129.224.73/")).toBe(true)
|
||||
expect(socketioValid("wss://174.129.224.73")).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for ws protocol URLs", () => {
|
||||
expect(socketioValid("ws://echo.websocket.org/")).toBe(true)
|
||||
expect(socketioValid("ws://echo.websocket.org")).toBe(true)
|
||||
expect(socketioValid("ws://174.129.224.73/")).toBe(true)
|
||||
expect(socketioValid("ws://174.129.224.73")).toBe(true)
|
||||
})
|
||||
})
|
||||
32
packages/hoppscotch-app/helpers/utils/b64.js
Normal file
32
packages/hoppscotch-app/helpers/utils/b64.js
Normal file
@@ -0,0 +1,32 @@
|
||||
export const decodeB64StringToArrayBuffer = (input) => {
|
||||
const bytes = Math.floor((input.length / 4) * 3)
|
||||
const ab = new ArrayBuffer(bytes)
|
||||
const uarray = new Uint8Array(ab)
|
||||
|
||||
const keyStr =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
|
||||
|
||||
let chr1, chr2, chr3
|
||||
let enc1, enc2, enc3, enc4
|
||||
let j = 0
|
||||
|
||||
input = input.replace(/[^A-Za-z0-9+/=]/g, "")
|
||||
|
||||
for (let i = 0; i < bytes; i += 3) {
|
||||
// get the 3 octets in 4 ASCII chars
|
||||
enc1 = keyStr.indexOf(input.charAt(j++))
|
||||
enc2 = keyStr.indexOf(input.charAt(j++))
|
||||
enc3 = keyStr.indexOf(input.charAt(j++))
|
||||
enc4 = keyStr.indexOf(input.charAt(j++))
|
||||
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4)
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2)
|
||||
chr3 = ((enc3 & 3) << 6) | enc4
|
||||
|
||||
uarray[i] = chr1
|
||||
if (enc3 !== 64) uarray[i + 1] = chr2
|
||||
if (enc4 !== 64) uarray[i + 2] = chr3
|
||||
}
|
||||
|
||||
return ab
|
||||
}
|
||||
18
packages/hoppscotch-app/helpers/utils/clipboard.ts
Normal file
18
packages/hoppscotch-app/helpers/utils/clipboard.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copies a given string to the clipboard using
|
||||
* the legacy exec method
|
||||
*
|
||||
* @param content The content to be copied
|
||||
*/
|
||||
export function copyToClipboard(content: string) {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(content)
|
||||
} else {
|
||||
const dummy = document.createElement("textarea")
|
||||
document.body.appendChild(dummy)
|
||||
dummy.value = content
|
||||
dummy.select()
|
||||
document.execCommand("copy")
|
||||
document.body.removeChild(dummy)
|
||||
}
|
||||
}
|
||||
134
packages/hoppscotch-app/helpers/utils/composables.ts
Normal file
134
packages/hoppscotch-app/helpers/utils/composables.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import {
|
||||
customRef,
|
||||
DeepReadonly,
|
||||
onBeforeUnmount,
|
||||
readonly,
|
||||
Ref,
|
||||
ref,
|
||||
watch,
|
||||
wrapProperty,
|
||||
} from "@nuxtjs/composition-api"
|
||||
import { Observable, Subscription } from "rxjs"
|
||||
|
||||
export const useNuxt = wrapProperty("$nuxt")
|
||||
|
||||
export function useReadonlyStream<T>(
|
||||
stream$: Observable<T>,
|
||||
initialValue: T
|
||||
): Ref<DeepReadonly<T>> {
|
||||
let sub: Subscription | null = null
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (sub) {
|
||||
sub.unsubscribe()
|
||||
}
|
||||
})
|
||||
|
||||
const targetRef = ref(initialValue) as Ref<T>
|
||||
|
||||
sub = stream$.subscribe((value) => {
|
||||
targetRef.value = value
|
||||
})
|
||||
|
||||
return readonly(targetRef)
|
||||
}
|
||||
|
||||
export function useStream<T>(
|
||||
stream$: Observable<T>,
|
||||
initialValue: T,
|
||||
setter: (val: T) => void
|
||||
) {
|
||||
let sub: Subscription | null = null
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (sub) {
|
||||
sub.unsubscribe()
|
||||
}
|
||||
})
|
||||
|
||||
return customRef((track, trigger) => {
|
||||
let value = initialValue
|
||||
|
||||
sub = stream$.subscribe((val) => {
|
||||
value = val
|
||||
trigger()
|
||||
})
|
||||
|
||||
return {
|
||||
get() {
|
||||
track()
|
||||
return value
|
||||
},
|
||||
set(value: T) {
|
||||
trigger()
|
||||
setter(value)
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function pluckRef<T, K extends keyof T>(ref: Ref<T>, key: K): Ref<T[K]> {
|
||||
return customRef((track, trigger) => {
|
||||
const stopWatching = watch(ref, (newVal, oldVal) => {
|
||||
if (newVal[key] !== oldVal[key]) {
|
||||
trigger()
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopWatching()
|
||||
})
|
||||
|
||||
return {
|
||||
get() {
|
||||
track()
|
||||
return ref.value[key]
|
||||
},
|
||||
set(value: T[K]) {
|
||||
trigger()
|
||||
ref.value = Object.assign(ref.value, { [key]: value })
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function pluckMultipleFromRef<T, K extends Array<keyof T>>(
|
||||
sourceRef: Ref<T>,
|
||||
keys: K
|
||||
): { [key in K[number]]: Ref<T[key]> } {
|
||||
return Object.fromEntries(keys.map((x) => [x, pluckRef(sourceRef, x)])) as any
|
||||
}
|
||||
|
||||
/**
|
||||
* A composable that provides the ability to run streams
|
||||
* and subscribe to them and respect the component lifecycle.
|
||||
*/
|
||||
export function useStreamSubscriber() {
|
||||
const subs: Subscription[] = []
|
||||
|
||||
const runAndSubscribe = <T>(
|
||||
stream: Observable<T>,
|
||||
next?: (value: T) => void,
|
||||
error?: (e: any) => void,
|
||||
complete?: () => void
|
||||
) => {
|
||||
const sub = stream.subscribe({
|
||||
next,
|
||||
error,
|
||||
complete: () => {
|
||||
if (complete) complete()
|
||||
subs.splice(subs.indexOf(sub), 1)
|
||||
},
|
||||
})
|
||||
|
||||
subs.push(sub)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
subs.forEach((sub) => sub.unsubscribe())
|
||||
})
|
||||
|
||||
return {
|
||||
subscribeToStream: runAndSubscribe,
|
||||
}
|
||||
}
|
||||
17
packages/hoppscotch-app/helpers/utils/contenttypes.ts
Normal file
17
packages/hoppscotch-app/helpers/utils/contenttypes.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export const knownContentTypes = {
|
||||
"application/json": "json",
|
||||
"application/ld+json": "json",
|
||||
"application/hal+json": "json",
|
||||
"application/vnd.api+json": "json",
|
||||
"application/xml": "xml",
|
||||
"application/x-www-form-urlencoded": "multipart",
|
||||
"multipart/form-data": "multipart",
|
||||
"text/html": "html",
|
||||
"text/plain": "plain",
|
||||
}
|
||||
|
||||
export type ValidContentTypes = keyof typeof knownContentTypes
|
||||
|
||||
export function isJSONContentType(contentType: string) {
|
||||
return /\bjson\b/i.test(contentType)
|
||||
}
|
||||
15
packages/hoppscotch-app/helpers/utils/debounce.js
Normal file
15
packages/hoppscotch-app/helpers/utils/debounce.js
Normal file
@@ -0,0 +1,15 @@
|
||||
// Debounce is a higher order function which makes its enclosed function be executed
|
||||
// only if the function wasn't called again till 'delay' time has passed, this helps reduce impact of heavy working
|
||||
// functions which might be called frequently
|
||||
// NOTE : Don't use lambda functions as this doesn't get bound properly in them, use the 'function (args) {}' format
|
||||
const debounce = (func, delay) => {
|
||||
let inDebounce
|
||||
return function () {
|
||||
const context = this
|
||||
const args = arguments
|
||||
clearTimeout(inDebounce)
|
||||
inDebounce = setTimeout(() => func.apply(context, args), delay)
|
||||
}
|
||||
}
|
||||
|
||||
export default debounce
|
||||
19
packages/hoppscotch-app/helpers/utils/dom.ts
Normal file
19
packages/hoppscotch-app/helpers/utils/dom.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export function isDOMElement(el: any): el is HTMLElement {
|
||||
return !!el && (el instanceof Element || el instanceof HTMLElement)
|
||||
}
|
||||
|
||||
export function isTypableElement(el: HTMLElement): boolean {
|
||||
// If content editable, then it is editable
|
||||
if (el.isContentEditable) return true
|
||||
|
||||
// If element is an input and the input is enabled, then it is typable
|
||||
if (el.tagName === "INPUT") {
|
||||
return !(el as HTMLInputElement).disabled
|
||||
}
|
||||
// If element is a textarea and the input is enabled, then it is typable
|
||||
if (el.tagName === "TEXTAREA") {
|
||||
return !(el as HTMLTextAreaElement).disabled
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
12
packages/hoppscotch-app/helpers/utils/string.js
Normal file
12
packages/hoppscotch-app/helpers/utils/string.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export function getSourcePrefix(source) {
|
||||
const sourceEmojis = {
|
||||
// Source used for info messages.
|
||||
info: "\tℹ️ [INFO]:\t",
|
||||
// Source used for client to server messages.
|
||||
client: "\t⬅️ [SENT]:\t",
|
||||
// Source used for server to client messages.
|
||||
server: "\t➡️ [RECEIVED]:\t",
|
||||
}
|
||||
if (Object.keys(sourceEmojis).includes(source)) return sourceEmojis[source]
|
||||
return ""
|
||||
}
|
||||
15
packages/hoppscotch-app/helpers/utils/uri.js
Normal file
15
packages/hoppscotch-app/helpers/utils/uri.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export function parseUrlAndPath(value) {
|
||||
const result = {}
|
||||
try {
|
||||
const url = new URL(value)
|
||||
result.url = url.origin
|
||||
result.path = url.pathname
|
||||
} catch (e) {
|
||||
const uriRegex = value.match(
|
||||
/^((http[s]?:\/\/)?(<<[^/]+>>)?[^/]*|)(\/?.*)$/
|
||||
)
|
||||
result.url = uriRegex[1]
|
||||
result.path = uriRegex[4]
|
||||
}
|
||||
return result
|
||||
}
|
||||
25
packages/hoppscotch-app/helpers/utils/useWindowSize.ts
Normal file
25
packages/hoppscotch-app/helpers/utils/useWindowSize.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
Ref,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
reactive,
|
||||
toRefs,
|
||||
} from "@nuxtjs/composition-api"
|
||||
|
||||
interface WindowSize {
|
||||
x: Ref<number>
|
||||
y: Ref<number>
|
||||
}
|
||||
|
||||
export function useWindowSize(): WindowSize {
|
||||
const windowSize = reactive({ x: 0, y: 0 })
|
||||
const resizeListener = () => {
|
||||
;({ innerWidth: windowSize.x, innerHeight: windowSize.y } = window)
|
||||
}
|
||||
onMounted(() => window.addEventListener("resize", resizeListener))
|
||||
onUnmounted(() => window.removeEventListener("resize", resizeListener))
|
||||
resizeListener()
|
||||
return toRefs(windowSize)
|
||||
}
|
||||
|
||||
export default useWindowSize
|
||||
38
packages/hoppscotch-app/helpers/utils/valid.js
Normal file
38
packages/hoppscotch-app/helpers/utils/valid.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const [wsRegexIP, wsRegexHostname] = generateREForProtocol("^(wss?:\\/\\/)?")
|
||||
const [sseRegexIP, sseRegexHostname] =
|
||||
generateREForProtocol("^(https?:\\/\\/)?")
|
||||
const [socketioRegexIP, socketioRegexHostname] = generateREForProtocol(
|
||||
"^((wss?:\\/\\/)|(https?:\\/\\/))?"
|
||||
)
|
||||
|
||||
function generateREForProtocol(protocol) {
|
||||
return [
|
||||
new RegExp(
|
||||
`${protocol}(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`
|
||||
),
|
||||
new RegExp(
|
||||
`${protocol}(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9/])$`
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* valid url for ws/wss
|
||||
*/
|
||||
export function wsValid(url) {
|
||||
return wsRegexIP.test(url) || wsRegexHostname.test(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* valid url for http/https
|
||||
*/
|
||||
export function httpValid(url) {
|
||||
return sseRegexIP.test(url) || sseRegexHostname.test(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* valid url for ws/wss/http/https
|
||||
*/
|
||||
export function socketioValid(url) {
|
||||
return socketioRegexIP.test(url) || socketioRegexHostname.test(url)
|
||||
}
|
||||
Reference in New Issue
Block a user