Compare commits

..

12 Commits

Author SHA1 Message Date
Mir Arif Hasan
18449e08d0 build: conflict resolve in pnpm lock file 2023-10-19 15:01:39 +06:00
Mir Arif Hasan
8465c83ff6 chore: remove unused imports 2023-10-19 14:51:35 +06:00
Mir Arif Hasan
489370f82b feat: admin extends user with omitting some fields 2023-10-19 14:51:35 +06:00
Mir Arif Hasan
1790163df6 feat: admin extends user partially 2023-10-19 14:51:35 +06:00
Mir Arif Hasan
04aaa108f3 feat: add field in infra type 2023-10-19 14:51:35 +06:00
Mir Arif Hasan
e4030c4b4e build: update pnpm-lock file 2023-10-19 14:51:35 +06:00
Mir Arif Hasan
fad4a64445 feat: deprecated tag added in some admin ResolveFields 2023-10-19 14:51:14 +06:00
Mir Arif Hasan
156d128b41 feat: feedback resolved 2023-10-19 14:51:14 +06:00
Mir Arif Hasan
cb8444017a feat: infra-resolver added in admin module 2023-10-19 14:51:14 +06:00
Mir Arif Hasan
72a753b3d6 feat: infra type added in admin module 2023-10-19 14:51:14 +06:00
Andrew Bastin
d1c9c3583f chore: merge hoppscotch/release/2023.8.3 into hoppscotch/release/2023.12.0 2023-10-19 09:34:49 +05:30
James George
2462492c86 chore(cli): bump dependencies (#3441)
* chore: bump CLI dependencies

* chore: update package.json

Bump version and specify minimum Node.js version
2023-10-16 18:23:22 +05:30
32 changed files with 4266 additions and 6139 deletions

View File

@@ -17,12 +17,12 @@
"types": "dist/index.d.ts",
"sideEffects": false,
"dependencies": {
"@codemirror/language": "^6.9.1",
"@codemirror/language": "^6.9.0",
"@lezer/highlight": "^1.1.6",
"@lezer/lr": "^1.3.13"
"@lezer/lr": "^1.3.10"
},
"devDependencies": {
"@lezer/generator": "^1.5.1",
"@lezer/generator": "^1.5.0",
"mocha": "^9.2.2",
"rollup": "^3.29.3",
"rollup-plugin-dts": "^6.0.2",

View File

@@ -1,4 +1,9 @@
import { ObjectType } from '@nestjs/graphql';
import { ObjectType, OmitType } from '@nestjs/graphql';
import { User } from 'src/user/user.model';
@ObjectType()
export class Admin {}
export class Admin extends OmitType(User, [
'isAdmin',
'currentRESTSession',
'currentGQLSession',
]) {}

View File

@@ -10,6 +10,7 @@ import { TeamInvitationModule } from '../team-invitation/team-invitation.module'
import { TeamEnvironmentsModule } from '../team-environments/team-environments.module';
import { TeamCollectionModule } from '../team-collection/team-collection.module';
import { TeamRequestModule } from '../team-request/team-request.module';
import { InfraResolver } from './infra.resolver';
@Module({
imports: [
@@ -23,7 +24,7 @@ import { TeamRequestModule } from '../team-request/team-request.module';
TeamCollectionModule,
TeamRequestModule,
],
providers: [AdminResolver, AdminService],
providers: [InfraResolver, AdminResolver, AdminService],
exports: [AdminService],
})
export class AdminModule {}

View File

@@ -21,15 +21,15 @@ import { InvitedUser } from './invited-user.model';
import { GqlUser } from '../decorators/gql-user.decorator';
import { PubSubService } from '../pubsub/pubsub.service';
import { Team, TeamMember } from '../team/team.model';
import { User } from '../user/user.model';
import { TeamInvitation } from '../team-invitation/team-invitation.model';
import { PaginationArgs } from '../types/input-types.args';
import {
AddUserToTeamArgs,
ChangeUserRoleInTeamArgs,
} from './input-types.args';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
import { User } from 'src/user/user.model';
import { PaginationArgs } from 'src/types/input-types.args';
import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => Admin)
@@ -51,6 +51,7 @@ export class AdminResolver {
@ResolveField(() => [User], {
description: 'Returns a list of all admin users in infra',
deprecationReason: 'Use `infra` query instead',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async admins() {
@@ -59,6 +60,7 @@ export class AdminResolver {
}
@ResolveField(() => User, {
description: 'Returns a user info by UID',
deprecationReason: 'Use `infra` query instead',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async userInfo(
@@ -76,6 +78,7 @@ export class AdminResolver {
@ResolveField(() => [User], {
description: 'Returns a list of all the users in infra',
deprecationReason: 'Use `infra` query instead',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async allUsers(
@@ -88,6 +91,7 @@ export class AdminResolver {
@ResolveField(() => [InvitedUser], {
description: 'Returns a list of all the invited users',
deprecationReason: 'Use `infra` query instead',
})
async invitedUsers(@Parent() admin: Admin): Promise<InvitedUser[]> {
const users = await this.adminService.fetchInvitedUsers();
@@ -96,6 +100,7 @@ export class AdminResolver {
@ResolveField(() => [Team], {
description: 'Returns a list of all the teams in the infra',
deprecationReason: 'Use `infra` query instead',
})
async allTeams(
@Parent() admin: Admin,
@@ -106,6 +111,7 @@ export class AdminResolver {
}
@ResolveField(() => Team, {
description: 'Returns a team info by ID when requested by Admin',
deprecationReason: 'Use `infra` query instead',
})
async teamInfo(
@Parent() admin: Admin,
@@ -123,6 +129,7 @@ export class AdminResolver {
@ResolveField(() => Number, {
description: 'Return count of all the members in a team',
deprecationReason: 'Use `infra` query instead',
})
async membersCountInTeam(
@Parent() admin: Admin,
@@ -140,6 +147,7 @@ export class AdminResolver {
@ResolveField(() => Number, {
description: 'Return count of all the stored collections in a team',
deprecationReason: 'Use `infra` query instead',
})
async collectionCountInTeam(
@Parent() admin: Admin,
@@ -155,6 +163,7 @@ export class AdminResolver {
}
@ResolveField(() => Number, {
description: 'Return count of all the stored requests in a team',
deprecationReason: 'Use `infra` query instead',
})
async requestCountInTeam(
@Parent() admin: Admin,
@@ -171,6 +180,7 @@ export class AdminResolver {
@ResolveField(() => Number, {
description: 'Return count of all the stored environments in a team',
deprecationReason: 'Use `infra` query instead',
})
async environmentCountInTeam(
@Parent() admin: Admin,
@@ -187,6 +197,7 @@ export class AdminResolver {
@ResolveField(() => [TeamInvitation], {
description: 'Return all the pending invitations in a team',
deprecationReason: 'Use `infra` query instead',
})
async pendingInvitationCountInTeam(
@Parent() admin: Admin,
@@ -205,6 +216,7 @@ export class AdminResolver {
@ResolveField(() => Number, {
description: 'Return total number of Users in organization',
deprecationReason: 'Use `infra` query instead',
})
async usersCount() {
return this.adminService.getUsersCount();
@@ -212,6 +224,7 @@ export class AdminResolver {
@ResolveField(() => Number, {
description: 'Return total number of Teams in organization',
deprecationReason: 'Use `infra` query instead',
})
async teamsCount() {
return this.adminService.getTeamsCount();
@@ -219,6 +232,7 @@ export class AdminResolver {
@ResolveField(() => Number, {
description: 'Return total number of Team Collections in organization',
deprecationReason: 'Use `infra` query instead',
})
async teamCollectionsCount() {
return this.adminService.getTeamCollectionsCount();
@@ -226,6 +240,7 @@ export class AdminResolver {
@ResolveField(() => Number, {
description: 'Return total number of Team Requests in organization',
deprecationReason: 'Use `infra` query instead',
})
async teamRequestsCount() {
return this.adminService.getTeamRequestsCount();

View File

@@ -0,0 +1,10 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { Admin } from './admin.model';
@ObjectType()
export class Infra {
@Field(() => Admin, {
description: 'Admin who executed the action',
})
executedBy: Admin;
}

View File

@@ -0,0 +1,205 @@
import { UseGuards } from '@nestjs/common';
import { Args, ID, Query, ResolveField, Resolver } from '@nestjs/graphql';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { Infra } from './infra.model';
import { AdminService } from './admin.service';
import { GqlAuthGuard } from 'src/guards/gql-auth.guard';
import { GqlAdminGuard } from './guards/gql-admin.guard';
import { User } from 'src/user/user.model';
import { AuthUser } from 'src/types/AuthUser';
import { throwErr } from 'src/utils';
import * as E from 'fp-ts/Either';
import { Admin } from './admin.model';
import { PaginationArgs } from 'src/types/input-types.args';
import { InvitedUser } from './invited-user.model';
import { Team } from 'src/team/team.model';
import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
import { GqlAdmin } from './decorators/gql-admin.decorator';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => Infra)
export class InfraResolver {
constructor(private adminService: AdminService) {}
@Query(() => Infra, {
description: 'Fetch details of the Infrastructure',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
infra(@GqlAdmin() admin: Admin) {
const infra: Infra = { executedBy: admin };
return infra;
}
@ResolveField(() => [User], {
description: 'Returns a list of all admin users in infra',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async admins() {
const admins = await this.adminService.fetchAdmins();
return admins;
}
@ResolveField(() => User, {
description: 'Returns a user info by UID',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async userInfo(
@Args({
name: 'userUid',
type: () => ID,
description: 'The user UID',
})
userUid: string,
): Promise<AuthUser> {
const user = await this.adminService.fetchUserInfo(userUid);
if (E.isLeft(user)) throwErr(user.left);
return user.right;
}
@ResolveField(() => [User], {
description: 'Returns a list of all the users in infra',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async allUsers(@Args() args: PaginationArgs): Promise<AuthUser[]> {
const users = await this.adminService.fetchUsers(args.cursor, args.take);
return users;
}
@ResolveField(() => [InvitedUser], {
description: 'Returns a list of all the invited users',
})
async invitedUsers(): Promise<InvitedUser[]> {
const users = await this.adminService.fetchInvitedUsers();
return users;
}
@ResolveField(() => [Team], {
description: 'Returns a list of all the teams in the infra',
})
async allTeams(@Args() args: PaginationArgs): Promise<Team[]> {
const teams = await this.adminService.fetchAllTeams(args.cursor, args.take);
return teams;
}
@ResolveField(() => Team, {
description: 'Returns a team info by ID when requested by Admin',
})
async teamInfo(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which info to fetch',
})
teamID: string,
): Promise<Team> {
const team = await this.adminService.getTeamInfo(teamID);
if (E.isLeft(team)) throwErr(team.left);
return team.right;
}
@ResolveField(() => Number, {
description: 'Return count of all the members in a team',
})
async membersCountInTeam(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which team members to fetch',
nullable: false,
})
teamID: string,
): Promise<number> {
const teamMembersCount = await this.adminService.membersCountInTeam(teamID);
return teamMembersCount;
}
@ResolveField(() => Number, {
description: 'Return count of all the stored collections in a team',
})
async collectionCountInTeam(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which team members to fetch',
})
teamID: string,
): Promise<number> {
const teamCollCount = await this.adminService.collectionCountInTeam(teamID);
return teamCollCount;
}
@ResolveField(() => Number, {
description: 'Return count of all the stored requests in a team',
})
async requestCountInTeam(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which team members to fetch',
})
teamID: string,
): Promise<number> {
const teamReqCount = await this.adminService.requestCountInTeam(teamID);
return teamReqCount;
}
@ResolveField(() => Number, {
description: 'Return count of all the stored environments in a team',
})
async environmentCountInTeam(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which team members to fetch',
})
teamID: string,
): Promise<number> {
const envsCount = await this.adminService.environmentCountInTeam(teamID);
return envsCount;
}
@ResolveField(() => [TeamInvitation], {
description: 'Return all the pending invitations in a team',
})
async pendingInvitationCountInTeam(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which team members to fetch',
})
teamID: string,
) {
const invitations = await this.adminService.pendingInvitationCountInTeam(
teamID,
);
return invitations;
}
@ResolveField(() => Number, {
description: 'Return total number of Users in organization',
})
async usersCount() {
return this.adminService.getUsersCount();
}
@ResolveField(() => Number, {
description: 'Return total number of Teams in organization',
})
async teamsCount() {
return this.adminService.getTeamsCount();
}
@ResolveField(() => Number, {
description: 'Return total number of Team Collections in organization',
})
async teamCollectionsCount() {
return this.adminService.getTeamCollectionsCount();
}
@ResolveField(() => Number, {
description: 'Return total number of Team Requests in organization',
})
async teamRequestsCount() {
return this.adminService.getTeamRequestsCount();
}
}

View File

@@ -27,6 +27,7 @@ import { UserRequestUserCollectionResolver } from './user-request/resolvers/user
import { UserEnvsUserResolver } from './user-environment/user.resolver';
import { UserHistoryUserResolver } from './user-history/user.resolver';
import { UserSettingsUserResolver } from './user-settings/user.resolver';
import { InfraResolver } from './admin/infra.resolver';
/**
* All the resolvers present in the application.
@@ -34,6 +35,7 @@ import { UserSettingsUserResolver } from './user-settings/user.resolver';
* NOTE: This needs to be KEPT UP-TO-DATE to keep the schema accurate
*/
const RESOLVERS = [
InfraResolver,
AdminResolver,
ShortcodeResolver,
TeamResolver,

View File

@@ -1,6 +1,6 @@
{
"name": "@hoppscotch/cli",
"version": "0.3.3",
"version": "0.4.0",
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
"homepage": "https://hoppscotch.io",
"main": "dist/index.js",
@@ -10,6 +10,9 @@
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=18"
},
"scripts": {
"build": "pnpm exec tsup",
"dev": "pnpm exec tsup --watch",
@@ -38,24 +41,24 @@
"devDependencies": {
"@hoppscotch/data": "workspace:^",
"@hoppscotch/js-sandbox": "workspace:^",
"@relmify/jest-fp-ts": "^2.0.2",
"@swc/core": "^1.2.181",
"@types/jest": "^27.4.1",
"@types/lodash": "^4.14.181",
"@types/qs": "^6.9.7",
"@relmify/jest-fp-ts": "^2.1.1",
"@swc/core": "^1.3.92",
"@types/jest": "^29.5.5",
"@types/lodash": "^4.14.199",
"@types/qs": "^6.9.8",
"axios": "^0.21.4",
"chalk": "^4.1.1",
"commander": "^8.0.0",
"chalk": "^4.1.2",
"commander": "^11.0.0",
"esm": "^3.2.25",
"fp-ts": "^2.12.1",
"io-ts": "^2.2.16",
"jest": "^27.5.1",
"fp-ts": "^2.16.1",
"io-ts": "^2.2.20",
"jest": "^29.7.0",
"lodash": "^4.17.21",
"prettier": "^2.8.4",
"qs": "^6.10.3",
"ts-jest": "^27.1.4",
"tsup": "^5.12.7",
"typescript": "^4.6.4",
"zod": "^3.22.2"
"prettier": "^3.0.3",
"qs": "^6.11.2",
"ts-jest": "^29.1.1",
"tsup": "^7.2.0",
"typescript": "^5.2.2",
"zod": "^3.22.4"
}
}

View File

@@ -22,17 +22,17 @@
},
"dependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"@codemirror/autocomplete": "^6.10.2",
"@codemirror/commands": "^6.3.0",
"@codemirror/lang-javascript": "^6.2.1",
"@codemirror/autocomplete": "^6.9.0",
"@codemirror/commands": "^6.2.4",
"@codemirror/lang-javascript": "^6.1.9",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-xml": "^6.0.2",
"@codemirror/language": "^6.9.1",
"@codemirror/language": "^6.9.0",
"@codemirror/legacy-modes": "^6.3.3",
"@codemirror/lint": "^6.4.2",
"@codemirror/search": "^6.5.4",
"@codemirror/state": "^6.3.1",
"@codemirror/view": "^6.21.3",
"@codemirror/lint": "^6.4.0",
"@codemirror/search": "^6.5.1",
"@codemirror/state": "^6.2.1",
"@codemirror/view": "^6.16.0",
"@fontsource-variable/inter": "^5.0.8",
"@fontsource-variable/material-symbols-rounded": "^5.0.7",
"@fontsource-variable/roboto-mono": "^5.0.9",
@@ -42,6 +42,8 @@
"@hoppscotch/ui": "workspace:^",
"@hoppscotch/vue-toasted": "^0.1.0",
"@lezer/highlight": "^1.1.6",
"@sentry/tracing": "^7.64.0",
"@sentry/vue": "^7.64.0",
"@urql/core": "^4.1.1",
"@urql/devtools": "^2.0.3",
"@urql/exchange-auth": "^2.1.6",

View File

@@ -0,0 +1,200 @@
import { HoppModule } from "."
import * as Sentry from "@sentry/vue"
import { BrowserTracing } from "@sentry/tracing"
import { Route } from "@sentry/vue/types/router"
import { RouteLocationNormalized, Router } from "vue-router"
import { settingsStore } from "~/newstore/settings"
import { App } from "vue"
import { APP_IS_IN_DEV_MODE } from "~/helpers/dev"
import { gqlClientError$ } from "~/helpers/backend/GQLClient"
import { platform } from "~/platform"
/**
* The tag names we allow giving to Sentry
*/
type SentryTag = "BACKEND_OPERATIONS"
interface SentryVueRouter {
onError: (fn: (err: Error) => void) => void
beforeEach: (fn: (to: Route, from: Route, next: () => void) => void) => void
}
function normalizedRouteToSentryRoute(route: RouteLocationNormalized): Route {
return {
matched: route.matched,
// route.params' type translates just to a fancy version of this, hence assertion
params: route.params as Route["params"],
path: route.path,
// route.query's type translates just to a fancy version of this, hence assertion
query: route.query as Route["query"],
name: route.name,
}
}
function getInstrumentationVueRouter(router: Router): SentryVueRouter {
return <SentryVueRouter>{
onError: router.onError,
beforeEach(func) {
router.beforeEach((to, from, next) => {
func(
normalizedRouteToSentryRoute(to),
normalizedRouteToSentryRoute(from),
next
)
})
},
}
}
let sentryActive = false
function initSentry(dsn: string, router: Router, app: App) {
Sentry.init({
app,
dsn,
release: import.meta.env.VITE_SENTRY_RELEASE_TAG ?? undefined,
environment: APP_IS_IN_DEV_MODE
? "dev"
: import.meta.env.VITE_SENTRY_ENVIRONMENT,
integrations: [
new BrowserTracing({
routingInstrumentation: Sentry.vueRouterInstrumentation(
getInstrumentationVueRouter(router)
),
// TODO: We may want to limit this later on
tracingOrigins: [new URL(import.meta.env.VITE_BACKEND_GQL_URL).origin],
}),
],
tracesSampleRate: 0.8,
})
sentryActive = true
}
function deinitSentry() {
Sentry.close()
sentryActive = false
}
/**
* Reports a set of related errors to Sentry
* @param errs The errors to report
* @param tag The tag for the errord
* @param extraTags Additional tag data to add
* @param extras Extra information to attach
*/
function reportErrors(
errs: Error[],
tag: SentryTag,
extraTags: Record<string, string | number | boolean> | null = null,
extras: any = undefined
) {
if (sentryActive) {
Sentry.withScope((scope) => {
scope.setTag("tag", tag)
if (extraTags) {
Object.entries(extraTags).forEach(([key, value]) => {
scope.setTag(key, value)
})
}
if (extras !== null && extras === undefined) scope.setExtras(extras)
scope.addAttachment({
filename: "extras-dump.json",
data: JSON.stringify(extras),
contentType: "application/json",
})
errs.forEach((err) => Sentry.captureException(err))
})
}
}
/**
* Reports a specific error to Sentry
* @param err The error to report
* @param tag The tag for the error
* @param extraTags Additional tag data to add
* @param extras Extra information to attach
*/
function reportError(
err: Error,
tag: SentryTag,
extraTags: Record<string, string | number | boolean> | null = null,
extras: any = undefined
) {
reportErrors([err], tag, extraTags, extras)
}
/**
* Subscribes to events occuring in various subsystems in the app
* for personalized error reporting
*/
function subscribeToAppEventsForReporting() {
gqlClientError$.subscribe((ev) => {
switch (ev.type) {
case "SUBSCRIPTION_CONN_CALLBACK_ERR_REPORT":
reportErrors(ev.errors, "BACKEND_OPERATIONS", { from: ev.type })
break
case "CLIENT_REPORTED_ERROR":
reportError(
ev.error,
"BACKEND_OPERATIONS",
{ from: ev.type },
{ op: ev.op }
)
break
case "GQL_CLIENT_REPORTED_ERROR":
reportError(
new Error("Backend Query Failed"),
"BACKEND_OPERATIONS",
{ opType: ev.opType },
{
opResult: ev.opResult,
}
)
break
}
})
}
/**
* Subscribe to app system events for adding
* additional data tags for the error reporting
*/
function subscribeForAppDataTags() {
const currentUser$ = platform.auth.getCurrentUserStream()
currentUser$.subscribe((user) => {
if (sentryActive) {
Sentry.setTag("user_logged_in", !!user)
}
})
}
export default <HoppModule>{
onRouterInit(app, router) {
if (!import.meta.env.VITE_SENTRY_DSN) {
console.log(
"Sentry tracing is not enabled because 'VITE_SENTRY_DSN' env is not defined"
)
return
}
if (settingsStore.value.TELEMETRY_ENABLED) {
initSentry(import.meta.env.VITE_SENTRY_DSN, router, app)
}
settingsStore.subject$.subscribe(({ TELEMETRY_ENABLED }) => {
if (!TELEMETRY_ENABLED && sentryActive) {
deinitSentry()
} else if (TELEMETRY_ENABLED && !sentryActive) {
initSentry(import.meta.env.VITE_SENTRY_DSN!, router, app)
}
})
subscribeToAppEventsForReporting()
subscribeForAppDataTags()
},
}

View File

@@ -60,7 +60,6 @@
<div class="py-4 space-y-4">
<div class="flex items-center">
<HoppSmartToggle
v-if="hasPlatformTelemetry"
:on="TELEMETRY_ENABLED"
@change="showConfirmModal"
>
@@ -135,7 +134,6 @@ import { InterceptorService } from "~/services/interceptor.service"
import { pipe } from "fp-ts/function"
import * as O from "fp-ts/Option"
import * as A from "fp-ts/Array"
import { platform } from "~/platform"
const t = useI18n()
const colorMode = useColorMode()
@@ -165,8 +163,6 @@ const TELEMETRY_ENABLED = useSetting("TELEMETRY_ENABLED")
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
const SIDEBAR_ON_LEFT = useSetting("SIDEBAR_ON_LEFT")
const hasPlatformTelemetry = Boolean(platform.platformFeatureFlags.hasTelemetry)
const confirmRemove = ref(false)
const proxySettings = computed(() => ({

View File

@@ -26,7 +26,6 @@ export type PlatformDef = {
additionalInspectors?: InspectorsPlatformDef
platformFeatureFlags: {
exportAsGIST: boolean
hasTelemetry: boolean
}
}

View File

@@ -1,10 +1,10 @@
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])(:[0-9]+)?(\\/[^?#]*)?(\\?[^#]*)?(#.*)?$`
`${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/])(:[0-9]+)?(\\/[^?#]*)?(\\?[^#]*)?(#.*)?$`
`${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/])$`
),
]
}

View File

@@ -6,9 +6,7 @@
"main": "dist/hoppscotch-data.cjs",
"module": "dist/hoppscotch-data.js",
"types": "./dist/index.d.ts",
"files": [
"dist/*"
],
"files": [ "dist/*" ],
"scripts": {
"build:code": "vite build",
"build:decl": "tsc --project tsconfig.decl.json",
@@ -35,15 +33,13 @@
"homepage": "https://github.com/hoppscotch/hoppscotch#readme",
"devDependencies": {
"@types/lodash": "^4.14.181",
"typescript": "^5.2.2",
"typescript": "^4.6.3",
"vite": "^3.2.3"
},
"dependencies": {
"fp-ts": "^2.11.10",
"io-ts": "^2.2.16",
"lodash": "^4.17.21",
"parser-ts": "^0.6.16",
"verzod": "^0.1.1",
"zod": "^3.22.2"
"parser-ts": "^0.6.16"
}
}

View File

@@ -1,22 +1,14 @@
import * as E from "fp-ts/Either"
import { pipe } from "fp-ts/function"
import { InferredEntity, createVersionedEntity } from "verzod"
import * as E from "fp-ts/Either"
import V0_VERSION from "./v/0"
export const Environment = createVersionedEntity({
latestVersion: 0,
versionMap: {
0: V0_VERSION
},
getVersion(x) {
return V0_VERSION.schema.safeParse(x).success
? 0
: null
}
})
export type Environment = InferredEntity<typeof Environment>
export type Environment = {
id?: string
name: string
variables: {
key: string
value: string
}[]
}
const REGEX_ENV_VAR = /<<([^>]*)>>/g // "<<myVariable>>"

View File

@@ -1,18 +0,0 @@
import { z } from "zod"
import { defineVersion } from "verzod"
export const V0_SCHEMA = z.object({
id: z.optional(z.string()),
name: z.string(),
variables: z.array(
z.object({
key: z.string(),
value: z.string(),
})
)
})
export default defineVersion({
initial: true,
schema: V0_SCHEMA
})

View File

@@ -0,0 +1,43 @@
export type HoppGQLAuthNone = {
authType: "none"
}
export type HoppGQLAuthBasic = {
authType: "basic"
username: string
password: string
}
export type HoppGQLAuthBearer = {
authType: "bearer"
token: string
}
export type HoppGQLAuthOAuth2 = {
authType: "oauth-2"
token: string
oidcDiscoveryURL: string
authURL: string
accessTokenURL: string
clientID: string
scope: string
}
export type HoppGQLAuthAPIKey = {
authType: "api-key"
key: string
value: string
addTo: string
}
export type HoppGQLAuth = { authActive: boolean } & (
| HoppGQLAuthNone
| HoppGQLAuthBasic
| HoppGQLAuthBearer
| HoppGQLAuthOAuth2
| HoppGQLAuthAPIKey
)

View File

@@ -1,73 +1,49 @@
import { InferredEntity, createVersionedEntity } from "verzod"
import { z } from "zod"
import V1_VERSION from "./v/1"
import V2_VERSION from "./v/2"
import { HoppGQLAuth } from "./HoppGQLAuth"
export { GQLHeader } from "./v/1"
export {
HoppGQLAuth,
HoppGQLAuthAPIKey,
HoppGQLAuthBasic,
HoppGQLAuthBearer,
HoppGQLAuthNone,
HoppGQLAuthOAuth2,
} from "./v/2"
export * from "./HoppGQLAuth"
export const GQL_REQ_SCHEMA_VERSION = 2
const versionedObject = z.object({
v: z.number(),
})
export const HoppGQLRequest = createVersionedEntity({
latestVersion: 2,
versionMap: {
1: V1_VERSION,
2: V2_VERSION,
},
getVersion(x) {
const result = versionedObject.safeParse(x)
return result.success ? result.data.v : null
},
})
export type HoppGQLRequest = InferredEntity<typeof HoppGQLRequest>
const DEFAULT_QUERY = `
query Request {
method
url
headers {
key
value
}
}`.trim()
export function getDefaultGQLRequest(): HoppGQLRequest {
return {
v: GQL_REQ_SCHEMA_VERSION,
name: "Untitled",
url: "https://echo.hoppscotch.io/graphql",
headers: [],
variables: `
{
"id": "1"
}`.trim(),
query: DEFAULT_QUERY,
auth: {
authType: "none",
authActive: true,
},
}
export type GQLHeader = {
key: string
value: string
active: boolean
}
/**
* @deprecated This function is deprecated. Use `HoppGQLRequest` instead.
*/
export function translateToGQLRequest(x: unknown): HoppGQLRequest {
const result = HoppGQLRequest.safeParse(x)
return result.type === "ok" ? result.value : getDefaultGQLRequest()
export type HoppGQLRequest = {
id?: string
v: number
name: string
url: string
headers: GQLHeader[]
query: string
variables: string
auth: HoppGQLAuth
}
export function translateToGQLRequest(x: any): HoppGQLRequest {
if (x.v && x.v === GQL_REQ_SCHEMA_VERSION) return x
// Old request
const name = x.name ?? "Untitled"
const url = x.url ?? ""
const headers = x.headers ?? []
const query = x.query ?? ""
const variables = x.variables ?? []
const auth = x.auth ?? {
authType: "none",
authActive: true,
}
return {
v: GQL_REQ_SCHEMA_VERSION,
name,
url,
headers,
query,
variables,
auth
}
}
export function makeGQLRequest(x: Omit<HoppGQLRequest, "v">): HoppGQLRequest {

View File

@@ -1,24 +0,0 @@
import { z } from "zod"
import { defineVersion } from "verzod"
export const GQLHeader = z.object({
key: z.string(),
value: z.string(),
active: z.boolean()
})
export type GQLHeader = z.infer<typeof GQLHeader>
export const V1_SCHEMA = z.object({
v: z.literal(1),
name: z.string(),
url: z.string(),
headers: z.array(GQLHeader),
query: z.string(),
variables: z.string(),
})
export default defineVersion({
initial: true,
schema: V1_SCHEMA
})

View File

@@ -1,91 +0,0 @@
import { z } from "zod"
import { defineVersion } from "verzod"
import { GQLHeader, V1_SCHEMA } from "./1"
export const HoppGQLAuthNone = z.object({
authType: z.literal("none")
})
export type HoppGQLAuthNone = z.infer<typeof HoppGQLAuthNone>
export const HoppGQLAuthBasic = z.object({
authType: z.literal("basic"),
username: z.string(),
password: z.string()
})
export type HoppGQLAuthBasic = z.infer<typeof HoppGQLAuthBasic>
export const HoppGQLAuthBearer = z.object({
authType: z.literal("bearer"),
token: z.string()
})
export type HoppGQLAuthBearer = z.infer<typeof HoppGQLAuthBearer>
export const HoppGQLAuthOAuth2 = z.object({
authType: z.literal("oauth-2"),
token: z.string(),
oidcDiscoveryURL: z.string(),
authURL: z.string(),
accessTokenURL: z.string(),
clientID: z.string(),
scope: z.string()
})
export type HoppGQLAuthOAuth2 = z.infer<typeof HoppGQLAuthOAuth2>
export const HoppGQLAuthAPIKey = z.object({
authType: z.literal("api-key"),
key: z.string(),
value: z.string(),
addTo: z.string()
})
export type HoppGQLAuthAPIKey = z.infer<typeof HoppGQLAuthAPIKey>
export const HoppGQLAuth = z.discriminatedUnion("authType", [
HoppGQLAuthNone,
HoppGQLAuthBasic,
HoppGQLAuthBearer,
HoppGQLAuthOAuth2,
HoppGQLAuthAPIKey
]).and(
z.object({
authActive: z.boolean()
})
)
export type HoppGQLAuth = z.infer<typeof HoppGQLAuth>
const V2_SCHEMA = z.object({
id: z.optional(z.string()),
v: z.literal(2),
name: z.string(),
url: z.string(),
headers: z.array(GQLHeader),
query: z.string(),
variables: z.string(),
auth: HoppGQLAuth
})
export default defineVersion({
initial: false,
schema: V2_SCHEMA,
up(old: z.infer<typeof V1_SCHEMA>) {
return <z.infer<typeof V2_SCHEMA>>{
...old,
v: 2,
auth: {
authActive: true,
authType: "none",
}
}
}
})

View File

@@ -0,0 +1,43 @@
export type HoppRESTAuthNone = {
authType: "none"
}
export type HoppRESTAuthBasic = {
authType: "basic"
username: string
password: string
}
export type HoppRESTAuthBearer = {
authType: "bearer"
token: string
}
export type HoppRESTAuthOAuth2 = {
authType: "oauth-2"
token: string
oidcDiscoveryURL: string
authURL: string
accessTokenURL: string
clientID: string
scope: string
}
export type HoppRESTAuthAPIKey = {
authType: "api-key"
key: string
value: string
addTo: string
}
export type HoppRESTAuth = { authActive: boolean } & (
| HoppRESTAuthNone
| HoppRESTAuthBasic
| HoppRESTAuthBearer
| HoppRESTAuthOAuth2
| HoppRESTAuthAPIKey
)

View File

@@ -11,5 +11,3 @@ export const knownContentTypes = {
}
export type ValidContentTypes = keyof typeof knownContentTypes
export const ValidContentTypesList = Object.keys(knownContentTypes) as ValidContentTypes[]

View File

@@ -1,58 +1,66 @@
import cloneDeep from "lodash/cloneDeep"
import * as Eq from "fp-ts/Eq"
import * as S from "fp-ts/string"
import cloneDeep from "lodash/cloneDeep"
import V0_VERSION from "./v/0"
import V1_VERSION from "./v/1"
import { createVersionedEntity, InferredEntity } from "verzod"
import { ValidContentTypes } from "./content-types"
import { HoppRESTAuth } from "./HoppRESTAuth"
import { lodashIsEqualEq, mapThenEq, undefinedEq } from "../utils/eq"
import {
HoppRESTAuth,
HoppRESTReqBody,
HoppRESTHeaders,
HoppRESTParams,
} from "./v/1"
import { z } from "zod"
export * from "./content-types"
export {
FormDataKeyValue,
HoppRESTReqBodyFormData,
HoppRESTAuth,
HoppRESTAuthAPIKey,
HoppRESTAuthBasic,
HoppRESTAuthBearer,
HoppRESTAuthNone,
HoppRESTAuthOAuth2,
HoppRESTReqBody,
} from "./v/1"
export * from "./HoppRESTAuth"
const versionedObject = z.object({
// v is a stringified number
v: z.string().regex(/^\d+$/).transform(Number),
})
export const RESTReqSchemaVersion = "1"
export const HoppRESTRequest = createVersionedEntity({
latestVersion: 1,
versionMap: {
0: V0_VERSION,
1: V1_VERSION,
},
getVersion(data) {
// For V1 onwards we have the v string storing the number
const versionCheck = versionedObject.safeParse(data)
export type HoppRESTParam = {
key: string
value: string
active: boolean
}
if (versionCheck.success) return versionCheck.data.v
export type HoppRESTHeader = {
key: string
value: string
active: boolean
}
// For V0 we have to check the schema
const result = V0_VERSION.schema.safeParse(data)
export type FormDataKeyValue = {
key: string
active: boolean
} & ({ isFile: true; value: Blob[] } | { isFile: false; value: string })
return result.success ? 0 : null
},
})
export type HoppRESTReqBodyFormData = {
contentType: "multipart/form-data"
body: FormDataKeyValue[]
}
export type HoppRESTRequest = InferredEntity<typeof HoppRESTRequest>
export type HoppRESTReqBody =
| {
contentType: Exclude<ValidContentTypes, "multipart/form-data">
body: string
}
| HoppRESTReqBodyFormData
| {
contentType: null
body: null
}
const HoppRESTRequestEq = Eq.struct<HoppRESTRequest>({
export interface HoppRESTRequest {
v: string
id?: string // Firebase Firestore ID
name: string
method: string
endpoint: string
params: HoppRESTParam[]
headers: HoppRESTHeader[]
preRequestScript: string
testScript: string
auth: HoppRESTAuth
body: HoppRESTReqBody
}
export const HoppRESTRequestEq = Eq.struct<HoppRESTRequest>({
id: undefinedEq(S.Eq),
v: S.Eq,
auth: lodashIsEqualEq,
@@ -72,11 +80,6 @@ const HoppRESTRequestEq = Eq.struct<HoppRESTRequest>({
testScript: S.Eq,
})
export const RESTReqSchemaVersion = "1"
export type HoppRESTParam = HoppRESTRequest["params"][number]
export type HoppRESTHeader = HoppRESTRequest["headers"][number]
export const isEqualHoppRESTRequest = HoppRESTRequestEq.equals
/**
@@ -84,9 +87,6 @@ export const isEqualHoppRESTRequest = HoppRESTRequestEq.equals
* If we fail to detect certain bits, we just resolve it to the default value
* @param x The value to extract REST Request data from
* @param defaultReq The default REST Request to source from
*
* @deprecated Usage of this function is no longer recommended and is only here
* for legacy reasons and will be removed
*/
export function safelyExtractRESTRequest(
x: unknown,
@@ -94,53 +94,40 @@ export function safelyExtractRESTRequest(
): HoppRESTRequest {
const req = cloneDeep(defaultReq)
// TODO: A cleaner way to do this ?
if (!!x && typeof x === "object") {
if ("id" in x && typeof x.id === "string") req.id = x.id
if (x.hasOwnProperty("v") && typeof x.v === "string")
req.v = x.v
if ("name" in x && typeof x.name === "string") req.name = x.name
if (x.hasOwnProperty("id") && typeof x.id === "string")
req.id = x.id
if ("method" in x && typeof x.method === "string") req.method = x.method
if (x.hasOwnProperty("name") && typeof x.name === "string")
req.name = x.name
if ("endpoint" in x && typeof x.endpoint === "string")
if (x.hasOwnProperty("method") && typeof x.method === "string")
req.method = x.method
if (x.hasOwnProperty("endpoint") && typeof x.endpoint === "string")
req.endpoint = x.endpoint
if ("preRequestScript" in x && typeof x.preRequestScript === "string")
if (x.hasOwnProperty("preRequestScript") && typeof x.preRequestScript === "string")
req.preRequestScript = x.preRequestScript
if ("testScript" in x && typeof x.testScript === "string")
if (x.hasOwnProperty("testScript") && typeof x.testScript === "string")
req.testScript = x.testScript
if ("body" in x) {
const result = HoppRESTReqBody.safeParse(x.body)
if (x.hasOwnProperty("body") && typeof x.body === "object" && !!x.body)
req.body = x.body as any // TODO: Deep nested checks
if (result.success) {
req.body = result.data
}
}
if (x.hasOwnProperty("auth") && typeof x.auth === "object" && !!x.auth)
req.auth = x.auth as any // TODO: Deep nested checks
if ("auth" in x) {
const result = HoppRESTAuth.safeParse(x.auth)
if (x.hasOwnProperty("params") && Array.isArray(x.params))
req.params = x.params // TODO: Deep nested checks
if (result.success) {
req.auth = result.data
}
}
if ("params" in x) {
const result = HoppRESTParams.safeParse(x.params)
if (result.success) {
req.params = result.data
}
}
if ("headers" in x) {
const result = HoppRESTHeaders.safeParse(x.headers)
if (result.success) {
req.headers = result.data
}
}
if (x.hasOwnProperty("headers") && Array.isArray(x.headers))
req.headers = x.headers // TODO: Deep nested checks
}
return req
@@ -150,51 +137,105 @@ export function makeRESTRequest(
x: Omit<HoppRESTRequest, "v">
): HoppRESTRequest {
return {
v: RESTReqSchemaVersion,
...x,
v: RESTReqSchemaVersion,
}
}
export function getDefaultRESTRequest(): HoppRESTRequest {
export function isHoppRESTRequest(x: any): x is HoppRESTRequest {
return x && typeof x === "object" && "v" in x
}
function parseRequestBody(x: any): HoppRESTReqBody {
if (x.contentType === "application/json") {
return {
contentType: "application/json",
body: x.rawParams,
}
}
return {
v: "1",
endpoint: "https://echo.hoppscotch.io",
name: "Untitled",
params: [],
headers: [],
method: "GET",
auth: {
contentType: "application/json",
body: "",
}
}
export function translateToNewRequest(x: any): HoppRESTRequest {
if (isHoppRESTRequest(x)) {
return x
} else {
// Old format
const endpoint: string = `${x?.url ?? ""}${x?.path ?? ""}`
const headers: HoppRESTHeader[] = x?.headers ?? []
// Remove old keys from params
const params: HoppRESTParam[] = (x?.params ?? []).map(
({
key,
value,
active,
}: {
key: string
value: string
active: boolean
}) => ({
key,
value,
active,
})
)
const name = x?.name ?? "Untitled request"
const method = x?.method ?? ""
const preRequestScript = x?.preRequestScript ?? ""
const testScript = x?.testScript ?? ""
const body = parseRequestBody(x)
const auth = parseOldAuth(x)
const result: HoppRESTRequest = {
name,
endpoint,
headers,
params,
method,
preRequestScript,
testScript,
body,
auth,
v: RESTReqSchemaVersion,
}
if (x.id) result.id = x.id
return result
}
}
export function parseOldAuth(x: any): HoppRESTAuth {
if (!x.auth || x.auth === "None")
return {
authType: "none",
authActive: true,
},
preRequestScript: "",
testScript: "",
body: {
contentType: null,
body: null,
},
}
}
}
/**
* Checks if the given value is a HoppRESTRequest
* @param x The value to check
*
* @deprecated This function is no longer recommended and is only here for legacy reasons
* Use `HoppRESTRequest.is`/`HoppRESTRequest.isLatest` instead.
*/
export function isHoppRESTRequest(x: unknown): x is HoppRESTRequest {
return HoppRESTRequest.isLatest(x)
}
if (x.auth === "Basic Auth")
return {
authType: "basic",
authActive: true,
username: x.httpUser,
password: x.httpPassword,
}
/**
* Safely parses a value into a HoppRESTRequest.
* @param x The value to check
*
* @deprecated This function is no longer recommended and is only here for
* legacy reasons. Use `HoppRESTRequest.safeParse` instead.
*/
export function translateToNewRequest(x: unknown): HoppRESTRequest {
const result = HoppRESTRequest.safeParse(x)
return result.type === "ok" ? result.value : getDefaultRESTRequest()
if (x.auth === "Bearer Token")
return {
authType: "bearer",
authActive: true,
token: x.bearerToken,
}
return { authType: "none", authActive: true }
}

View File

@@ -1,39 +0,0 @@
import { defineVersion } from "verzod"
import { z } from "zod"
export const V0_SCHEMA = z.object({
id: z.optional(z.string()), // Firebase Firestore ID
url: z.string(),
path: z.string(),
headers: z.array(
z.object({
key: z.string(),
value: z.string(),
active: z.boolean()
})
),
params: z.array(
z.object({
key: z.string(),
value: z.string(),
active: z.boolean()
})
),
name: z.string(),
method: z.string(),
preRequestScript: z.string(),
testScript: z.string(),
contentType: z.string(),
body: z.string(),
rawParams: z.optional(z.string()),
auth: z.optional(z.string()),
httpUser: z.optional(z.string()),
httpPassword: z.optional(z.string()),
bearerToken: z.optional(z.string()),
})
export default defineVersion({
initial: true,
schema: V0_SCHEMA
})

View File

@@ -1,209 +0,0 @@
import { defineVersion } from "verzod"
import { z } from "zod"
import { V0_SCHEMA } from "./0"
export const FormDataKeyValue = z.object({
key: z.string(),
active: z.boolean()
}).and(
z.union([
z.object({
isFile: z.literal(true),
value: z.array(z.instanceof(Blob))
}),
z.object({
isFile: z.literal(false),
value: z.string()
})
])
)
export type FormDataKeyValue = z.infer<typeof FormDataKeyValue>
export const HoppRESTReqBodyFormData = z.object({
contentType: z.literal("multipart/form-data"),
body: z.array(FormDataKeyValue)
})
export type HoppRESTReqBodyFormData = z.infer<typeof HoppRESTReqBodyFormData>
export const HoppRESTReqBody = z.union([
z.object({
contentType: z.literal(null),
body: z.literal(null)
}),
z.object({
contentType: z.literal("multipart/form-data"),
body: FormDataKeyValue
}),
z.object({
contentType: z.union([
z.literal("application/json"),
z.literal("application/ld+json"),
z.literal("application/hal+json"),
z.literal("application/vnd.api+json"),
z.literal("application/xml"),
z.literal("application/x-www-form-urlencoded"),
z.literal("text/html"),
z.literal("text/plain"),
]),
body: z.string()
})
])
export type HoppRESTReqBody = z.infer<typeof HoppRESTReqBody>
export const HoppRESTAuthNone = z.object({
authType: z.literal("none")
})
export type HoppRESTAuthNone = z.infer<typeof HoppRESTAuthNone>
export const HoppRESTAuthBasic = z.object({
authType: z.literal("basic"),
username: z.string(),
password: z.string(),
})
export type HoppRESTAuthBasic = z.infer<typeof HoppRESTAuthBasic>
export const HoppRESTAuthBearer = z.object({
authType: z.literal("bearer"),
token: z.string(),
})
export type HoppRESTAuthBearer = z.infer<typeof HoppRESTAuthBearer>
export const HoppRESTAuthOAuth2 = z.object({
authType: z.literal("oauth-2"),
token: z.string(),
oidcDiscoveryURL: z.string(),
authURL: z.string(),
accessTokenURL: z.string(),
clientID: z.string(),
scope: z.string(),
})
export type HoppRESTAuthOAuth2 = z.infer<typeof HoppRESTAuthOAuth2>
export const HoppRESTAuthAPIKey = z.object({
authType: z.literal("api-key"),
key: z.string(),
value: z.string(),
addTo: z.string(),
})
export type HoppRESTAuthAPIKey = z.infer<typeof HoppRESTAuthAPIKey>
export const HoppRESTAuth = z.discriminatedUnion("authType", [
HoppRESTAuthNone,
HoppRESTAuthBasic,
HoppRESTAuthBearer,
HoppRESTAuthOAuth2,
HoppRESTAuthAPIKey
]).and(
z.object({
authActive: z.boolean(),
})
)
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
export const HoppRESTParams = z.array(
z.object({
key: z.string(),
value: z.string(),
active: z.boolean()
})
)
export type HoppRESTParams = z.infer<typeof HoppRESTParams>
export const HoppRESTHeaders = z.array(
z.object({
key: z.string(),
value: z.string(),
active: z.boolean()
})
)
export type HoppRESTHeaders = z.infer<typeof HoppRESTHeaders>
const V1_SCHEMA = z.object({
v: z.literal("1"),
id: z.optional(z.string()), // Firebase Firestore ID
name: z.string(),
method: z.string(),
endpoint: z.string(),
params: HoppRESTParams,
headers: HoppRESTHeaders,
preRequestScript: z.string(),
testScript: z.string(),
auth: HoppRESTAuth,
body: HoppRESTReqBody
})
function parseRequestBody(x: z.infer<typeof V0_SCHEMA>): z.infer<typeof V1_SCHEMA>["body"] {
return {
contentType: "application/json",
body: x.contentType === "application/json" ? x.rawParams ?? "" : "",
}
}
export function parseOldAuth(x: z.infer<typeof V0_SCHEMA>): z.infer<typeof V1_SCHEMA>["auth"] {
if (!x.auth || x.auth === "None")
return {
authType: "none",
authActive: true,
}
if (x.auth === "Basic Auth")
return {
authType: "basic",
authActive: true,
username: x.httpUser ?? "",
password: x.httpPassword ?? "",
}
if (x.auth === "Bearer Token")
return {
authType: "bearer",
authActive: true,
token: x.bearerToken ?? "",
}
return { authType: "none", authActive: true }
}
export default defineVersion({
initial: false,
schema: V1_SCHEMA,
up(old: z.infer<typeof V0_SCHEMA>) {
const { url, path, headers, params, name, method, preRequestScript, testScript } = old
const endpoint = `${url}${path}`
const body = parseRequestBody(old)
const auth = parseOldAuth(old)
const result: z.infer<typeof V1_SCHEMA> = {
v: "1",
endpoint,
headers,
params,
name,
method,
preRequestScript,
testScript,
body,
auth,
}
if (old.id) result.id = old.id
return result
},
})

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"target": "es2017",
"module": "esnext",
"lib": ["esnext", "DOM"],
"lib": ["esnext"],
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,

View File

@@ -2,13 +2,13 @@
"compilerOptions": {
"target": "es2017",
"module": "esnext",
"lib": ["esnext", "DOM"],
"lib": ["esnext"],
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"strictNullChecks": true,
"skipLibCheck": true,
"resolveJsonModule": true
"resolveJsonModule": true,
},
"include": ["src/*.ts"]
}

View File

@@ -1,6 +1,6 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "jsdom",
testEnvironment: "node",
collectCoverage: true,
setupFilesAfterEnv: ["./jest.setup.ts"],
}

View File

@@ -38,6 +38,5 @@ createHoppApp("#app", {
],
platformFeatureFlags: {
exportAsGIST: false,
hasTelemetry: false,
},
})

View File

@@ -147,7 +147,6 @@ export default defineConfig({
},
}),
VitePWA({
useCredentials: true,
manifest: {
name: APP_INFO.name,
short_name: APP_INFO.name,

View File

@@ -21,6 +21,7 @@
"@fontsource-variable/material-symbols-rounded": "^5.0.5",
"@fontsource-variable/roboto-mono": "^5.0.6",
"@hoppscotch/vue-toasted": "^0.1.0",
"@lezer/highlight": "^1.0.0",
"@vitejs/plugin-legacy": "^2.3.0",
"@vueuse/core": "^8.7.5",
"fp-ts": "^2.12.1",
@@ -80,4 +81,4 @@
"./style.css": "./dist/style.css"
},
"types": "./dist/index.d.ts"
}
}

8976
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff