Compare commits
18 Commits
feat/serve
...
fix/secret
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3da14d392 | ||
|
|
5f9f9addaf | ||
|
|
63cbaabf07 | ||
|
|
40e8696a9a | ||
|
|
be7387ed19 | ||
|
|
acfb0189df | ||
|
|
8fdba760a2 | ||
|
|
bf98009abb | ||
|
|
dce396c164 | ||
|
|
07e8af7947 | ||
|
|
e69d5a6253 | ||
|
|
6d66d12a9e | ||
|
|
439cd82c88 | ||
|
|
6dbaf524ce | ||
|
|
68e439d1a4 | ||
|
|
8deba7a28e | ||
|
|
7ec8659381 | ||
|
|
3611cac241 |
14
package.json
14
package.json
@@ -23,13 +23,13 @@
|
||||
"./packages/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^16.2.3",
|
||||
"@commitlint/config-conventional": "^16.2.1",
|
||||
"@hoppscotch/ui": "^0.1.0",
|
||||
"@commitlint/cli": "16.3.0",
|
||||
"@commitlint/config-conventional": "16.2.4",
|
||||
"@hoppscotch/ui": "0.1.0",
|
||||
"@types/node": "17.0.27",
|
||||
"cross-env": "^7.0.3",
|
||||
"http-server": "^14.1.1",
|
||||
"husky": "^7.0.4",
|
||||
"cross-env": "7.0.3",
|
||||
"http-server": "14.1.1",
|
||||
"husky": "7.0.4",
|
||||
"lint-staged": "12.4.0"
|
||||
},
|
||||
"pnpm": {
|
||||
@@ -37,7 +37,7 @@
|
||||
"vue": "3.3.9"
|
||||
},
|
||||
"packageExtensions": {
|
||||
"httpsnippet@^3.0.1": {
|
||||
"httpsnippet@3.0.1": {
|
||||
"peerDependencies": {
|
||||
"ajv": "6.12.3"
|
||||
}
|
||||
|
||||
@@ -17,16 +17,16 @@
|
||||
"types": "dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@codemirror/language": "6.9.3",
|
||||
"@codemirror/language": "6.10.1",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@lezer/lr": "^1.3.14"
|
||||
"@lezer/lr": "1.3.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lezer/generator": "^1.5.1",
|
||||
"mocha": "^9.2.2",
|
||||
"rollup": "^3.29.3",
|
||||
"rollup-plugin-dts": "^6.0.2",
|
||||
"rollup-plugin-ts": "^3.4.5",
|
||||
"typescript": "^5.2.2"
|
||||
"@lezer/generator": "1.5.1",
|
||||
"mocha": "9.2.2",
|
||||
"rollup": "3.29.4",
|
||||
"rollup-plugin-dts": "6.0.2",
|
||||
"rollup-plugin-ts": "3.4.5",
|
||||
"typescript": "5.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hoppscotch-backend",
|
||||
"version": "2023.12.6",
|
||||
"version": "2024.3.0",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
@@ -24,83 +24,83 @@
|
||||
"do-test": "pnpm run test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/server": "^4.9.4",
|
||||
"@nestjs-modules/mailer": "^1.9.1",
|
||||
"@nestjs/apollo": "^12.0.9",
|
||||
"@nestjs/common": "^10.2.6",
|
||||
"@nestjs/config": "^3.1.1",
|
||||
"@nestjs/core": "^10.2.6",
|
||||
"@nestjs/graphql": "^12.0.9",
|
||||
"@nestjs/jwt": "^10.1.1",
|
||||
"@nestjs/passport": "^10.0.2",
|
||||
"@nestjs/platform-express": "^10.2.6",
|
||||
"@nestjs/schedule": "^4.0.1",
|
||||
"@nestjs/throttler": "^5.0.0",
|
||||
"@prisma/client": "^5.8.0",
|
||||
"argon2": "^0.30.3",
|
||||
"bcrypt": "^5.1.0",
|
||||
"cookie": "^0.5.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cron": "^3.1.6",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.3",
|
||||
"fp-ts": "^2.13.1",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-query-complexity": "^0.12.0",
|
||||
"graphql-redis-subscriptions": "^2.6.0",
|
||||
"graphql-subscriptions": "^2.0.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"io-ts": "^2.2.16",
|
||||
"luxon": "^3.2.1",
|
||||
"nodemailer": "^6.9.1",
|
||||
"passport": "^0.6.0",
|
||||
"passport-github2": "^0.1.12",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"passport-microsoft": "^1.0.0",
|
||||
"posthog-node": "^3.6.3",
|
||||
"prisma": "^5.8.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.6.0"
|
||||
"@apollo/server": "4.9.5",
|
||||
"@nestjs-modules/mailer": "1.9.1",
|
||||
"@nestjs/apollo": "12.0.9",
|
||||
"@nestjs/common": "10.2.7",
|
||||
"@nestjs/config": "3.1.1",
|
||||
"@nestjs/core": "10.2.7",
|
||||
"@nestjs/graphql": "12.0.9",
|
||||
"@nestjs/jwt": "10.1.1",
|
||||
"@nestjs/passport": "10.0.2",
|
||||
"@nestjs/platform-express": "10.2.7",
|
||||
"@nestjs/schedule": "4.0.1",
|
||||
"@nestjs/throttler": "5.0.1",
|
||||
"@prisma/client": "5.8.1",
|
||||
"argon2": "0.30.3",
|
||||
"bcrypt": "5.1.0",
|
||||
"cookie": "0.5.0",
|
||||
"cookie-parser": "1.4.6",
|
||||
"cron": "3.1.6",
|
||||
"express": "4.18.2",
|
||||
"express-session": "1.17.3",
|
||||
"fp-ts": "2.13.1",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-query-complexity": "0.12.0",
|
||||
"graphql-redis-subscriptions": "2.6.0",
|
||||
"graphql-subscriptions": "2.0.0",
|
||||
"handlebars": "4.7.7",
|
||||
"io-ts": "2.2.16",
|
||||
"luxon": "3.2.1",
|
||||
"nodemailer": "6.9.1",
|
||||
"passport": "0.6.0",
|
||||
"passport-github2": "0.1.12",
|
||||
"passport-google-oauth20": "2.0.0",
|
||||
"passport-jwt": "4.0.1",
|
||||
"passport-local": "1.0.0",
|
||||
"passport-microsoft": "1.0.0",
|
||||
"posthog-node": "3.6.3",
|
||||
"prisma": "5.8.1",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rimraf": "3.0.2",
|
||||
"rxjs": "7.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.1.18",
|
||||
"@nestjs/schematics": "^10.0.2",
|
||||
"@nestjs/testing": "^10.2.6",
|
||||
"@relmify/jest-fp-ts": "^2.0.2",
|
||||
"@types/argon2": "^0.15.0",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/luxon": "^3.2.0",
|
||||
"@types/node": "^18.11.10",
|
||||
"@types/nodemailer": "^6.4.7",
|
||||
"@types/passport-github2": "^1.2.5",
|
||||
"@types/passport-google-oauth20": "^2.0.11",
|
||||
"@types/passport-jwt": "^3.0.8",
|
||||
"@types/passport-microsoft": "^0.0.0",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"jest": "^29.4.1",
|
||||
"jest-mock-extended": "^3.0.1",
|
||||
"@nestjs/cli": "10.2.1",
|
||||
"@nestjs/schematics": "10.0.3",
|
||||
"@nestjs/testing": "10.2.7",
|
||||
"@relmify/jest-fp-ts": "2.0.2",
|
||||
"@types/argon2": "0.15.0",
|
||||
"@types/bcrypt": "5.0.0",
|
||||
"@types/cookie": "0.5.1",
|
||||
"@types/cookie-parser": "1.4.3",
|
||||
"@types/express": "4.17.14",
|
||||
"@types/jest": "29.4.0",
|
||||
"@types/luxon": "3.2.0",
|
||||
"@types/node": "18.11.10",
|
||||
"@types/nodemailer": "6.4.7",
|
||||
"@types/passport-github2": "1.2.5",
|
||||
"@types/passport-google-oauth20": "2.0.11",
|
||||
"@types/passport-jwt": "3.0.8",
|
||||
"@types/passport-microsoft": "0.0.0",
|
||||
"@types/supertest": "2.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "5.45.0",
|
||||
"@typescript-eslint/parser": "5.45.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.29.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"jest": "29.4.1",
|
||||
"jest-mock-extended": "3.0.1",
|
||||
"jwt": "link:@types/nestjs/jwt",
|
||||
"prettier": "^2.8.4",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.2",
|
||||
"prettier": "2.8.4",
|
||||
"source-map-support": "0.5.21",
|
||||
"supertest": "6.3.2",
|
||||
"ts-jest": "29.0.5",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"ts-loader": "9.4.2",
|
||||
"ts-node": "10.9.1",
|
||||
"tsconfig-paths": "4.1.1",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "4.9.3"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
@@ -121,4 +121,4 @@
|
||||
"^src/(.*)$": "<rootDir>/$1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
EnableAndDisableSSOArgs,
|
||||
InfraConfigArgs,
|
||||
} from 'src/infra-config/input-args';
|
||||
import { InfraConfigEnumForClient } from 'src/types/InfraConfig';
|
||||
import { InfraConfigEnum } from 'src/types/InfraConfig';
|
||||
import { ServiceStatus } from 'src/infra-config/helper';
|
||||
|
||||
@UseGuards(GqlThrottlerGuard)
|
||||
@@ -274,10 +274,10 @@ export class InfraResolver {
|
||||
async infraConfigs(
|
||||
@Args({
|
||||
name: 'configNames',
|
||||
type: () => [InfraConfigEnumForClient],
|
||||
type: () => [InfraConfigEnum],
|
||||
description: 'Configs to fetch',
|
||||
})
|
||||
names: InfraConfigEnumForClient[],
|
||||
names: InfraConfigEnum[],
|
||||
) {
|
||||
const infraConfigs = await this.infraConfigService.getMany(names);
|
||||
if (E.isLeft(infraConfigs)) throwErr(infraConfigs.left);
|
||||
|
||||
@@ -12,7 +12,10 @@ import { GithubStrategy } from './strategies/github.strategy';
|
||||
import { MicrosoftStrategy } from './strategies/microsoft.strategy';
|
||||
import { AuthProvider, authProviderCheck } from './helper';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { loadInfraConfiguration } from 'src/infra-config/helper';
|
||||
import {
|
||||
isInfraConfigTablePopulated,
|
||||
loadInfraConfiguration,
|
||||
} from 'src/infra-config/helper';
|
||||
import { InfraConfigModule } from 'src/infra-config/infra-config.module';
|
||||
|
||||
@Module({
|
||||
@@ -34,6 +37,11 @@ import { InfraConfigModule } from 'src/infra-config/infra-config.module';
|
||||
})
|
||||
export class AuthModule {
|
||||
static async register() {
|
||||
const isInfraConfigPopulated = await isInfraConfigTablePopulated();
|
||||
if (!isInfraConfigPopulated) {
|
||||
return { module: AuthModule };
|
||||
}
|
||||
|
||||
const env = await loadInfraConfiguration();
|
||||
const allowedAuthProviders = env.INFRA.VITE_ALLOWED_AUTH_PROVIDERS;
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ export class GithubStrategy extends PassportStrategy(Strategy) {
|
||||
super({
|
||||
clientID: configService.get('INFRA.GITHUB_CLIENT_ID'),
|
||||
clientSecret: configService.get('INFRA.GITHUB_CLIENT_SECRET'),
|
||||
callbackURL: configService.get('GITHUB_CALLBACK_URL'),
|
||||
scope: [configService.get('GITHUB_SCOPE')],
|
||||
callbackURL: configService.get('INFRA.GITHUB_CALLBACK_URL'),
|
||||
scope: [configService.get('INFRA.GITHUB_SCOPE')],
|
||||
store: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ export class GoogleStrategy extends PassportStrategy(Strategy) {
|
||||
super({
|
||||
clientID: configService.get('INFRA.GOOGLE_CLIENT_ID'),
|
||||
clientSecret: configService.get('INFRA.GOOGLE_CLIENT_SECRET'),
|
||||
callbackURL: configService.get('GOOGLE_CALLBACK_URL'),
|
||||
scope: configService.get('GOOGLE_SCOPE').split(','),
|
||||
callbackURL: configService.get('INFRA.GOOGLE_CALLBACK_URL'),
|
||||
scope: configService.get('INFRA.GOOGLE_SCOPE').split(','),
|
||||
passReqToCallback: true,
|
||||
store: true,
|
||||
});
|
||||
|
||||
@@ -17,9 +17,9 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy) {
|
||||
super({
|
||||
clientID: configService.get('INFRA.MICROSOFT_CLIENT_ID'),
|
||||
clientSecret: configService.get('INFRA.MICROSOFT_CLIENT_SECRET'),
|
||||
callbackURL: configService.get('MICROSOFT_CALLBACK_URL'),
|
||||
scope: [configService.get('MICROSOFT_SCOPE')],
|
||||
tenant: configService.get('MICROSOFT_TENANT'),
|
||||
callbackURL: configService.get('INFRA.MICROSOFT_CALLBACK_URL'),
|
||||
scope: [configService.get('INFRA.MICROSOFT_SCOPE')],
|
||||
tenant: configService.get('INFRA.MICROSOFT_TENANT'),
|
||||
store: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -731,6 +731,13 @@ export const INFRA_CONFIG_INVALID_INPUT = 'infra_config/invalid_input' as const;
|
||||
export const INFRA_CONFIG_SERVICE_NOT_CONFIGURED =
|
||||
'infra_config/service_not_configured' as const;
|
||||
|
||||
/**
|
||||
* Infra Config update/fetch operation not allowed
|
||||
* (InfraConfigService)
|
||||
*/
|
||||
export const INFRA_CONFIG_OPERATION_NOT_ALLOWED =
|
||||
'infra_config/operation_not_allowed';
|
||||
|
||||
/**
|
||||
* Error message for when the database table does not exist
|
||||
* (InfraConfigService)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { AuthProvider } from 'src/auth/helper';
|
||||
import { AUTH_PROVIDER_NOT_CONFIGURED } from 'src/errors';
|
||||
import {
|
||||
AUTH_PROVIDER_NOT_CONFIGURED,
|
||||
DATABASE_TABLE_NOT_EXIST,
|
||||
} from 'src/errors';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { InfraConfigEnum } from 'src/types/InfraConfig';
|
||||
import { throwErr } from 'src/utils';
|
||||
@@ -14,14 +17,21 @@ const AuthProviderConfigurations = {
|
||||
[AuthProvider.GOOGLE]: [
|
||||
InfraConfigEnum.GOOGLE_CLIENT_ID,
|
||||
InfraConfigEnum.GOOGLE_CLIENT_SECRET,
|
||||
InfraConfigEnum.GOOGLE_CALLBACK_URL,
|
||||
InfraConfigEnum.GOOGLE_SCOPE,
|
||||
],
|
||||
[AuthProvider.GITHUB]: [
|
||||
InfraConfigEnum.GITHUB_CLIENT_ID,
|
||||
InfraConfigEnum.GITHUB_CLIENT_SECRET,
|
||||
InfraConfigEnum.GITHUB_CALLBACK_URL,
|
||||
InfraConfigEnum.GITHUB_SCOPE,
|
||||
],
|
||||
[AuthProvider.MICROSOFT]: [
|
||||
InfraConfigEnum.MICROSOFT_CLIENT_ID,
|
||||
InfraConfigEnum.MICROSOFT_CLIENT_SECRET,
|
||||
InfraConfigEnum.MICROSOFT_CALLBACK_URL,
|
||||
InfraConfigEnum.MICROSOFT_SCOPE,
|
||||
InfraConfigEnum.MICROSOFT_TENANT,
|
||||
],
|
||||
[AuthProvider.EMAIL]: [
|
||||
InfraConfigEnum.MAILER_SMTP_URL,
|
||||
@@ -54,6 +64,125 @@ export async function loadInfraConfiguration() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the default values from .env file and return them as an array
|
||||
* @returns Array of default infra configs
|
||||
*/
|
||||
export async function getDefaultInfraConfigs(): Promise<
|
||||
{ name: InfraConfigEnum; value: string }[]
|
||||
> {
|
||||
const prisma = new PrismaService();
|
||||
|
||||
// Prepare rows for 'infra_config' table with default values (from .env) for each 'name'
|
||||
const infraConfigDefaultObjs: { name: InfraConfigEnum; value: string }[] = [
|
||||
{
|
||||
name: InfraConfigEnum.MAILER_SMTP_URL,
|
||||
value: process.env.MAILER_SMTP_URL,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MAILER_ADDRESS_FROM,
|
||||
value: process.env.MAILER_ADDRESS_FROM,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GOOGLE_CLIENT_ID,
|
||||
value: process.env.GOOGLE_CLIENT_ID,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GOOGLE_CLIENT_SECRET,
|
||||
value: process.env.GOOGLE_CLIENT_SECRET,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GOOGLE_CALLBACK_URL,
|
||||
value: process.env.GOOGLE_CALLBACK_URL,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GOOGLE_SCOPE,
|
||||
value: process.env.GOOGLE_SCOPE,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GITHUB_CLIENT_ID,
|
||||
value: process.env.GITHUB_CLIENT_ID,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GITHUB_CLIENT_SECRET,
|
||||
value: process.env.GITHUB_CLIENT_SECRET,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GITHUB_CALLBACK_URL,
|
||||
value: process.env.GITHUB_CALLBACK_URL,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GITHUB_SCOPE,
|
||||
value: process.env.GITHUB_SCOPE,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MICROSOFT_CLIENT_ID,
|
||||
value: process.env.MICROSOFT_CLIENT_ID,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MICROSOFT_CLIENT_SECRET,
|
||||
value: process.env.MICROSOFT_CLIENT_SECRET,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MICROSOFT_CALLBACK_URL,
|
||||
value: process.env.MICROSOFT_CALLBACK_URL,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MICROSOFT_SCOPE,
|
||||
value: process.env.MICROSOFT_SCOPE,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MICROSOFT_TENANT,
|
||||
value: process.env.MICROSOFT_TENANT,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS,
|
||||
value: getConfiguredSSOProviders(),
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.ALLOW_ANALYTICS_COLLECTION,
|
||||
value: false.toString(),
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.ANALYTICS_USER_ID,
|
||||
value: generateAnalyticsUserId(),
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
||||
value: (await prisma.infraConfig.count()) === 0 ? 'true' : 'false',
|
||||
},
|
||||
];
|
||||
|
||||
return infraConfigDefaultObjs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if 'infra_config' table is loaded with all entries
|
||||
* @returns boolean
|
||||
*/
|
||||
export async function isInfraConfigTablePopulated(): Promise<boolean> {
|
||||
const prisma = new PrismaService();
|
||||
try {
|
||||
const dbInfraConfigs = await prisma.infraConfig.findMany();
|
||||
const infraConfigDefaultObjs = await getDefaultInfraConfigs();
|
||||
|
||||
const propsRemainingToInsert = infraConfigDefaultObjs.filter(
|
||||
(p) => !dbInfraConfigs.find((e) => e.name === p.name),
|
||||
);
|
||||
|
||||
if (propsRemainingToInsert.length > 0) {
|
||||
console.log(
|
||||
'Infra Config table is not populated with all entries. Populating now...',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the app after 5 seconds
|
||||
* (Docker will re-start the app)
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as E from 'fp-ts/Either';
|
||||
import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard';
|
||||
import { RESTAdminGuard } from 'src/admin/guards/rest-admin.guard';
|
||||
import { RESTError } from 'src/types/RESTError';
|
||||
import { InfraConfigEnumForClient } from 'src/types/InfraConfig';
|
||||
import { InfraConfigEnum } from 'src/types/InfraConfig';
|
||||
import { throwHTTPErr } from 'src/utils';
|
||||
|
||||
@UseGuards(ThrottlerBehindProxyGuard)
|
||||
@@ -17,7 +17,7 @@ export class SiteController {
|
||||
@UseGuards(JwtAuthGuard, RESTAdminGuard)
|
||||
async fetchSetupInfo() {
|
||||
const status = await this.infraConfigService.get(
|
||||
InfraConfigEnumForClient.IS_FIRST_TIME_INFRA_SETUP,
|
||||
InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
||||
);
|
||||
|
||||
if (E.isLeft(status))
|
||||
@@ -32,7 +32,7 @@ export class SiteController {
|
||||
@UseGuards(JwtAuthGuard, RESTAdminGuard)
|
||||
async setSetupAsComplete() {
|
||||
const res = await this.infraConfigService.update(
|
||||
InfraConfigEnumForClient.IS_FIRST_TIME_INFRA_SETUP,
|
||||
InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
||||
false.toString(),
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
import { AuthProvider } from 'src/auth/helper';
|
||||
import { InfraConfigEnumForClient } from 'src/types/InfraConfig';
|
||||
import { InfraConfigEnum } from 'src/types/InfraConfig';
|
||||
import { ServiceStatus } from './helper';
|
||||
|
||||
@ObjectType()
|
||||
@@ -8,7 +8,7 @@ export class InfraConfig {
|
||||
@Field({
|
||||
description: 'Infra Config Name',
|
||||
})
|
||||
name: InfraConfigEnumForClient;
|
||||
name: InfraConfigEnum;
|
||||
|
||||
@Field({
|
||||
description: 'Infra Config Value',
|
||||
@@ -16,7 +16,7 @@ export class InfraConfig {
|
||||
value: string;
|
||||
}
|
||||
|
||||
registerEnumType(InfraConfigEnumForClient, {
|
||||
registerEnumType(InfraConfigEnum, {
|
||||
name: 'InfraConfigEnum',
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { InfraConfigService } from './infra-config.service';
|
||||
import { InfraConfigEnum } from 'src/types/InfraConfig';
|
||||
import {
|
||||
InfraConfigEnum,
|
||||
InfraConfigEnumForClient,
|
||||
} from 'src/types/InfraConfig';
|
||||
import { INFRA_CONFIG_NOT_FOUND, INFRA_CONFIG_UPDATE_FAILED } from 'src/errors';
|
||||
INFRA_CONFIG_NOT_FOUND,
|
||||
INFRA_CONFIG_OPERATION_NOT_ALLOWED,
|
||||
INFRA_CONFIG_UPDATE_FAILED,
|
||||
} from 'src/errors';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as helper from './helper';
|
||||
import { InfraConfig as dbInfraConfig } from '@prisma/client';
|
||||
import { InfraConfig } from './infra-config.model';
|
||||
|
||||
const mockPrisma = mockDeep<PrismaService>();
|
||||
const mockConfigService = mockDeep<ConfigService>();
|
||||
@@ -19,12 +22,82 @@ const infraConfigService = new InfraConfigService(
|
||||
mockConfigService,
|
||||
);
|
||||
|
||||
const INITIALIZED_DATE_CONST = new Date();
|
||||
const dbInfraConfigs: dbInfraConfig[] = [
|
||||
{
|
||||
id: '3',
|
||||
name: InfraConfigEnum.GOOGLE_CLIENT_ID,
|
||||
value: 'abcdefghijkl',
|
||||
active: true,
|
||||
createdOn: INITIALIZED_DATE_CONST,
|
||||
updatedOn: INITIALIZED_DATE_CONST,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS,
|
||||
value: 'google',
|
||||
active: true,
|
||||
createdOn: INITIALIZED_DATE_CONST,
|
||||
updatedOn: INITIALIZED_DATE_CONST,
|
||||
},
|
||||
];
|
||||
const infraConfigs: InfraConfig[] = [
|
||||
{
|
||||
name: dbInfraConfigs[0].name as InfraConfigEnum,
|
||||
value: dbInfraConfigs[0].value,
|
||||
},
|
||||
{
|
||||
name: dbInfraConfigs[1].name as InfraConfigEnum,
|
||||
value: dbInfraConfigs[1].value,
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
mockReset(mockPrisma);
|
||||
});
|
||||
|
||||
describe('InfraConfigService', () => {
|
||||
describe('update', () => {
|
||||
it('should update the infra config without backend server restart', async () => {
|
||||
const name = InfraConfigEnum.GOOGLE_CLIENT_ID;
|
||||
const value = 'true';
|
||||
|
||||
mockPrisma.infraConfig.update.mockResolvedValueOnce({
|
||||
id: '',
|
||||
name,
|
||||
value,
|
||||
active: true,
|
||||
createdOn: new Date(),
|
||||
updatedOn: new Date(),
|
||||
});
|
||||
|
||||
jest.spyOn(helper, 'stopApp').mockReturnValueOnce();
|
||||
const result = await infraConfigService.update(name, value);
|
||||
|
||||
expect(helper.stopApp).not.toHaveBeenCalled();
|
||||
expect(result).toEqualRight({ name, value });
|
||||
});
|
||||
|
||||
it('should update the infra config with backend server restart', async () => {
|
||||
const name = InfraConfigEnum.GOOGLE_CLIENT_ID;
|
||||
const value = 'true';
|
||||
|
||||
mockPrisma.infraConfig.update.mockResolvedValueOnce({
|
||||
id: '',
|
||||
name,
|
||||
value,
|
||||
active: true,
|
||||
createdOn: new Date(),
|
||||
updatedOn: new Date(),
|
||||
});
|
||||
jest.spyOn(helper, 'stopApp').mockReturnValueOnce();
|
||||
|
||||
const result = await infraConfigService.update(name, value, true);
|
||||
|
||||
expect(helper.stopApp).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqualRight({ name, value });
|
||||
});
|
||||
|
||||
it('should update the infra config', async () => {
|
||||
const name = InfraConfigEnum.GOOGLE_CLIENT_ID;
|
||||
const value = 'true';
|
||||
@@ -71,7 +144,7 @@ describe('InfraConfigService', () => {
|
||||
|
||||
describe('get', () => {
|
||||
it('should get the infra config', async () => {
|
||||
const name = InfraConfigEnumForClient.GOOGLE_CLIENT_ID;
|
||||
const name = InfraConfigEnum.GOOGLE_CLIENT_ID;
|
||||
const value = 'true';
|
||||
|
||||
mockPrisma.infraConfig.findUniqueOrThrow.mockResolvedValueOnce({
|
||||
@@ -87,7 +160,7 @@ describe('InfraConfigService', () => {
|
||||
});
|
||||
|
||||
it('should pass correct params to prisma findUnique', async () => {
|
||||
const name = InfraConfigEnumForClient.GOOGLE_CLIENT_ID;
|
||||
const name = InfraConfigEnum.GOOGLE_CLIENT_ID;
|
||||
|
||||
await infraConfigService.get(name);
|
||||
|
||||
@@ -98,7 +171,7 @@ describe('InfraConfigService', () => {
|
||||
});
|
||||
|
||||
it('should throw an error if the infra config does not exist', async () => {
|
||||
const name = InfraConfigEnumForClient.GOOGLE_CLIENT_ID;
|
||||
const name = InfraConfigEnum.GOOGLE_CLIENT_ID;
|
||||
|
||||
mockPrisma.infraConfig.findUniqueOrThrow.mockRejectedValueOnce('null');
|
||||
|
||||
@@ -106,4 +179,45 @@ describe('InfraConfigService', () => {
|
||||
expect(result).toEqualLeft(INFRA_CONFIG_NOT_FOUND);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMany', () => {
|
||||
it('should throw error if any disallowed names are provided', async () => {
|
||||
const disallowedNames = [InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS];
|
||||
const result = await infraConfigService.getMany(disallowedNames);
|
||||
|
||||
expect(result).toEqualLeft(INFRA_CONFIG_OPERATION_NOT_ALLOWED);
|
||||
});
|
||||
it('should resolve right with disallowed names if `checkDisallowed` parameter passed', async () => {
|
||||
const disallowedNames = [InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS];
|
||||
|
||||
const dbInfraConfigResponses = dbInfraConfigs.filter((dbConfig) =>
|
||||
disallowedNames.includes(dbConfig.name as InfraConfigEnum),
|
||||
);
|
||||
mockPrisma.infraConfig.findMany.mockResolvedValueOnce(
|
||||
dbInfraConfigResponses,
|
||||
);
|
||||
|
||||
const result = await infraConfigService.getMany(disallowedNames, false);
|
||||
|
||||
expect(result).toEqualRight(
|
||||
infraConfigs.filter((i) => disallowedNames.includes(i.name)),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return right with infraConfigs if Prisma query succeeds', async () => {
|
||||
const allowedNames = [InfraConfigEnum.GOOGLE_CLIENT_ID];
|
||||
|
||||
const dbInfraConfigResponses = dbInfraConfigs.filter((dbConfig) =>
|
||||
allowedNames.includes(dbConfig.name as InfraConfigEnum),
|
||||
);
|
||||
mockPrisma.infraConfig.findMany.mockResolvedValueOnce(
|
||||
dbInfraConfigResponses,
|
||||
);
|
||||
|
||||
const result = await infraConfigService.getMany(allowedNames);
|
||||
expect(result).toEqualRight(
|
||||
infraConfigs.filter((i) => allowedNames.includes(i.name)),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,28 +3,25 @@ import { InfraConfig } from './infra-config.model';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { InfraConfig as DBInfraConfig } from '@prisma/client';
|
||||
import * as E from 'fp-ts/Either';
|
||||
import {
|
||||
InfraConfigEnum,
|
||||
InfraConfigEnumForClient,
|
||||
} from 'src/types/InfraConfig';
|
||||
import { InfraConfigEnum } from 'src/types/InfraConfig';
|
||||
import {
|
||||
AUTH_PROVIDER_NOT_SPECIFIED,
|
||||
DATABASE_TABLE_NOT_EXIST,
|
||||
INFRA_CONFIG_INVALID_INPUT,
|
||||
INFRA_CONFIG_NOT_FOUND,
|
||||
INFRA_CONFIG_NOT_LISTED,
|
||||
INFRA_CONFIG_RESET_FAILED,
|
||||
INFRA_CONFIG_UPDATE_FAILED,
|
||||
INFRA_CONFIG_SERVICE_NOT_CONFIGURED,
|
||||
INFRA_CONFIG_OPERATION_NOT_ALLOWED,
|
||||
} from 'src/errors';
|
||||
import { throwErr, validateSMTPEmail, validateSMTPUrl } from 'src/utils';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
ServiceStatus,
|
||||
generateAnalyticsUserId,
|
||||
getConfiguredSSOProviders,
|
||||
stopApp,
|
||||
} from './helper';
|
||||
throwErr,
|
||||
validateSMTPEmail,
|
||||
validateSMTPUrl,
|
||||
validateUrl,
|
||||
} from 'src/utils';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ServiceStatus, getDefaultInfraConfigs, stopApp } from './helper';
|
||||
import { EnableAndDisableSSOArgs, InfraConfigArgs } from './input-args';
|
||||
import { AuthProvider } from 'src/auth/helper';
|
||||
|
||||
@@ -35,84 +32,32 @@ export class InfraConfigService implements OnModuleInit {
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
// Following fields are not updatable by `infraConfigs` Mutation. Use dedicated mutations for these fields instead.
|
||||
EXCLUDE_FROM_UPDATE_CONFIGS = [
|
||||
InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS,
|
||||
InfraConfigEnum.ALLOW_ANALYTICS_COLLECTION,
|
||||
InfraConfigEnum.ANALYTICS_USER_ID,
|
||||
InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
||||
];
|
||||
// Following fields can not be fetched by `infraConfigs` Query. Use dedicated queries for these fields instead.
|
||||
EXCLUDE_FROM_FETCH_CONFIGS = [
|
||||
InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS,
|
||||
InfraConfigEnum.ANALYTICS_USER_ID,
|
||||
InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
||||
];
|
||||
|
||||
async onModuleInit() {
|
||||
await this.initializeInfraConfigTable();
|
||||
}
|
||||
|
||||
async getDefaultInfraConfigs(): Promise<
|
||||
{ name: InfraConfigEnum; value: string }[]
|
||||
> {
|
||||
// Prepare rows for 'infra_config' table with default values (from .env) for each 'name'
|
||||
const infraConfigDefaultObjs: { name: InfraConfigEnum; value: string }[] = [
|
||||
{
|
||||
name: InfraConfigEnum.MAILER_SMTP_URL,
|
||||
value: process.env.MAILER_SMTP_URL,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MAILER_ADDRESS_FROM,
|
||||
value: process.env.MAILER_ADDRESS_FROM,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GOOGLE_CLIENT_ID,
|
||||
value: process.env.GOOGLE_CLIENT_ID,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GOOGLE_CLIENT_SECRET,
|
||||
value: process.env.GOOGLE_CLIENT_SECRET,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GITHUB_CLIENT_ID,
|
||||
value: process.env.GITHUB_CLIENT_ID,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GITHUB_CLIENT_SECRET,
|
||||
value: process.env.GITHUB_CLIENT_SECRET,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MICROSOFT_CLIENT_ID,
|
||||
value: process.env.MICROSOFT_CLIENT_ID,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MICROSOFT_CLIENT_SECRET,
|
||||
value: process.env.MICROSOFT_CLIENT_SECRET,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS,
|
||||
value: getConfiguredSSOProviders(),
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.ALLOW_ANALYTICS_COLLECTION,
|
||||
value: false.toString(),
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.ANALYTICS_USER_ID,
|
||||
value: generateAnalyticsUserId(),
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
||||
value: (await this.prisma.infraConfig.count()) === 0 ? 'true' : 'false',
|
||||
},
|
||||
];
|
||||
|
||||
return infraConfigDefaultObjs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the 'infra_config' table with values from .env
|
||||
* @description This function create rows 'infra_config' in very first time (only once)
|
||||
*/
|
||||
async initializeInfraConfigTable() {
|
||||
try {
|
||||
// Get all the 'names' of the properties to be saved in the 'infra_config' table
|
||||
const enumValues = Object.values(InfraConfigEnum);
|
||||
|
||||
// Fetch the default values (value in .env) for configs to be saved in 'infra_config' table
|
||||
const infraConfigDefaultObjs = await this.getDefaultInfraConfigs();
|
||||
|
||||
// Check if all the 'names' are listed in the default values
|
||||
if (enumValues.length !== infraConfigDefaultObjs.length) {
|
||||
throw new Error(INFRA_CONFIG_NOT_LISTED);
|
||||
}
|
||||
const infraConfigDefaultObjs = await getDefaultInfraConfigs();
|
||||
|
||||
// Eliminate the rows (from 'infraConfigDefaultObjs') that are already present in the database table
|
||||
const dbInfraConfigs = await this.prisma.infraConfig.findMany();
|
||||
@@ -169,11 +114,7 @@ export class InfraConfigService implements OnModuleInit {
|
||||
* @param restartEnabled If true, restart the app after updating the InfraConfig
|
||||
* @returns InfraConfig model
|
||||
*/
|
||||
async update(
|
||||
name: InfraConfigEnumForClient | InfraConfigEnum,
|
||||
value: string,
|
||||
restartEnabled = false,
|
||||
) {
|
||||
async update(name: InfraConfigEnum, value: string, restartEnabled = false) {
|
||||
const isValidate = this.validateEnvValues([{ name, value }]);
|
||||
if (E.isLeft(isValidate)) return E.left(isValidate.left);
|
||||
|
||||
@@ -197,6 +138,11 @@ export class InfraConfigService implements OnModuleInit {
|
||||
* @returns InfraConfig model
|
||||
*/
|
||||
async updateMany(infraConfigs: InfraConfigArgs[]) {
|
||||
for (let i = 0; i < infraConfigs.length; i++) {
|
||||
if (this.EXCLUDE_FROM_UPDATE_CONFIGS.includes(infraConfigs[i].name))
|
||||
return E.left(INFRA_CONFIG_OPERATION_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
const isValidate = this.validateEnvValues(infraConfigs);
|
||||
if (E.isLeft(isValidate)) return E.left(isValidate.left);
|
||||
|
||||
@@ -230,12 +176,26 @@ export class InfraConfigService implements OnModuleInit {
|
||||
) {
|
||||
switch (service) {
|
||||
case AuthProvider.GOOGLE:
|
||||
return configMap.GOOGLE_CLIENT_ID && configMap.GOOGLE_CLIENT_SECRET;
|
||||
return (
|
||||
configMap.GOOGLE_CLIENT_ID &&
|
||||
configMap.GOOGLE_CLIENT_SECRET &&
|
||||
configMap.GOOGLE_CALLBACK_URL &&
|
||||
configMap.GOOGLE_SCOPE
|
||||
);
|
||||
case AuthProvider.GITHUB:
|
||||
return configMap.GITHUB_CLIENT_ID && configMap.GITHUB_CLIENT_SECRET;
|
||||
return (
|
||||
configMap.GITHUB_CLIENT_ID &&
|
||||
configMap.GITHUB_CLIENT_SECRET &&
|
||||
configMap.GITHUB_CALLBACK_URL &&
|
||||
configMap.GITHUB_SCOPE
|
||||
);
|
||||
case AuthProvider.MICROSOFT:
|
||||
return (
|
||||
configMap.MICROSOFT_CLIENT_ID && configMap.MICROSOFT_CLIENT_SECRET
|
||||
configMap.MICROSOFT_CLIENT_ID &&
|
||||
configMap.MICROSOFT_CLIENT_SECRET &&
|
||||
configMap.MICROSOFT_CALLBACK_URL &&
|
||||
configMap.MICROSOFT_SCOPE &&
|
||||
configMap.MICROSOFT_TENANT
|
||||
);
|
||||
case AuthProvider.EMAIL:
|
||||
return configMap.MAILER_SMTP_URL && configMap.MAILER_ADDRESS_FROM;
|
||||
@@ -310,7 +270,7 @@ export class InfraConfigService implements OnModuleInit {
|
||||
* @param name Name of the InfraConfig
|
||||
* @returns InfraConfig model
|
||||
*/
|
||||
async get(name: InfraConfigEnumForClient) {
|
||||
async get(name: InfraConfigEnum) {
|
||||
try {
|
||||
const infraConfig = await this.prisma.infraConfig.findUniqueOrThrow({
|
||||
where: { name },
|
||||
@@ -327,7 +287,15 @@ export class InfraConfigService implements OnModuleInit {
|
||||
* @param names Names of the InfraConfigs
|
||||
* @returns InfraConfig model
|
||||
*/
|
||||
async getMany(names: InfraConfigEnumForClient[]) {
|
||||
async getMany(names: InfraConfigEnum[], checkDisallowedKeys: boolean = true) {
|
||||
if (checkDisallowedKeys) {
|
||||
// Check if the names are allowed to fetch by client
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
if (this.EXCLUDE_FROM_FETCH_CONFIGS.includes(names[i]))
|
||||
return E.left(INFRA_CONFIG_OPERATION_NOT_ALLOWED);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const infraConfigs = await this.prisma.infraConfig.findMany({
|
||||
where: { name: { in: names } },
|
||||
@@ -354,7 +322,7 @@ export class InfraConfigService implements OnModuleInit {
|
||||
*/
|
||||
async reset() {
|
||||
try {
|
||||
const infraConfigDefaultObjs = await this.getDefaultInfraConfigs();
|
||||
const infraConfigDefaultObjs = await getDefaultInfraConfigs();
|
||||
|
||||
await this.prisma.infraConfig.deleteMany({
|
||||
where: { name: { in: infraConfigDefaultObjs.map((p) => p.name) } },
|
||||
@@ -387,36 +355,60 @@ export class InfraConfigService implements OnModuleInit {
|
||||
*/
|
||||
validateEnvValues(
|
||||
infraConfigs: {
|
||||
name: InfraConfigEnumForClient | InfraConfigEnum;
|
||||
name: InfraConfigEnum;
|
||||
value: string;
|
||||
}[],
|
||||
) {
|
||||
for (let i = 0; i < infraConfigs.length; i++) {
|
||||
switch (infraConfigs[i].name) {
|
||||
case InfraConfigEnumForClient.MAILER_SMTP_URL:
|
||||
case InfraConfigEnum.MAILER_SMTP_URL:
|
||||
const isValidUrl = validateSMTPUrl(infraConfigs[i].value);
|
||||
if (!isValidUrl) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnumForClient.MAILER_ADDRESS_FROM:
|
||||
case InfraConfigEnum.MAILER_ADDRESS_FROM:
|
||||
const isValidEmail = validateSMTPEmail(infraConfigs[i].value);
|
||||
if (!isValidEmail) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnumForClient.GOOGLE_CLIENT_ID:
|
||||
case InfraConfigEnum.GOOGLE_CLIENT_ID:
|
||||
if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnumForClient.GOOGLE_CLIENT_SECRET:
|
||||
case InfraConfigEnum.GOOGLE_CLIENT_SECRET:
|
||||
if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnumForClient.GITHUB_CLIENT_ID:
|
||||
case InfraConfigEnum.GOOGLE_CALLBACK_URL:
|
||||
if (!validateUrl(infraConfigs[i].value))
|
||||
return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnum.GOOGLE_SCOPE:
|
||||
if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnumForClient.GITHUB_CLIENT_SECRET:
|
||||
case InfraConfigEnum.GITHUB_CLIENT_ID:
|
||||
if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnumForClient.MICROSOFT_CLIENT_ID:
|
||||
case InfraConfigEnum.GITHUB_CLIENT_SECRET:
|
||||
if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnumForClient.MICROSOFT_CLIENT_SECRET:
|
||||
case InfraConfigEnum.GITHUB_CALLBACK_URL:
|
||||
if (!validateUrl(infraConfigs[i].value))
|
||||
return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnum.GITHUB_SCOPE:
|
||||
if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnum.MICROSOFT_CLIENT_ID:
|
||||
if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnum.MICROSOFT_CLIENT_SECRET:
|
||||
if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnum.MICROSOFT_CALLBACK_URL:
|
||||
if (!validateUrl(infraConfigs[i].value))
|
||||
return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnum.MICROSOFT_SCOPE:
|
||||
if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
case InfraConfigEnum.MICROSOFT_TENANT:
|
||||
if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
import { InfraConfigEnumForClient } from 'src/types/InfraConfig';
|
||||
import { InfraConfigEnum } from 'src/types/InfraConfig';
|
||||
import { ServiceStatus } from './helper';
|
||||
import { AuthProvider } from 'src/auth/helper';
|
||||
|
||||
@InputType()
|
||||
export class InfraConfigArgs {
|
||||
@Field(() => InfraConfigEnumForClient, {
|
||||
@Field(() => InfraConfigEnum, {
|
||||
description: 'Infra Config Name',
|
||||
})
|
||||
name: InfraConfigEnumForClient;
|
||||
name: InfraConfigEnum;
|
||||
|
||||
@Field({
|
||||
description: 'Infra Config Value',
|
||||
|
||||
@@ -4,12 +4,19 @@ export enum InfraConfigEnum {
|
||||
|
||||
GOOGLE_CLIENT_ID = 'GOOGLE_CLIENT_ID',
|
||||
GOOGLE_CLIENT_SECRET = 'GOOGLE_CLIENT_SECRET',
|
||||
GOOGLE_CALLBACK_URL = 'GOOGLE_CALLBACK_URL',
|
||||
GOOGLE_SCOPE = 'GOOGLE_SCOPE',
|
||||
|
||||
GITHUB_CLIENT_ID = 'GITHUB_CLIENT_ID',
|
||||
GITHUB_CLIENT_SECRET = 'GITHUB_CLIENT_SECRET',
|
||||
GITHUB_CALLBACK_URL = 'GITHUB_CALLBACK_URL',
|
||||
GITHUB_SCOPE = 'GITHUB_SCOPE',
|
||||
|
||||
MICROSOFT_CLIENT_ID = 'MICROSOFT_CLIENT_ID',
|
||||
MICROSOFT_CLIENT_SECRET = 'MICROSOFT_CLIENT_SECRET',
|
||||
MICROSOFT_CALLBACK_URL = 'MICROSOFT_CALLBACK_URL',
|
||||
MICROSOFT_SCOPE = 'MICROSOFT_SCOPE',
|
||||
MICROSOFT_TENANT = 'MICROSOFT_TENANT',
|
||||
|
||||
VITE_ALLOWED_AUTH_PROVIDERS = 'VITE_ALLOWED_AUTH_PROVIDERS',
|
||||
|
||||
@@ -17,20 +24,3 @@ export enum InfraConfigEnum {
|
||||
ANALYTICS_USER_ID = 'ANALYTICS_USER_ID',
|
||||
IS_FIRST_TIME_INFRA_SETUP = 'IS_FIRST_TIME_INFRA_SETUP',
|
||||
}
|
||||
|
||||
export enum InfraConfigEnumForClient {
|
||||
MAILER_SMTP_URL = 'MAILER_SMTP_URL',
|
||||
MAILER_ADDRESS_FROM = 'MAILER_ADDRESS_FROM',
|
||||
|
||||
GOOGLE_CLIENT_ID = 'GOOGLE_CLIENT_ID',
|
||||
GOOGLE_CLIENT_SECRET = 'GOOGLE_CLIENT_SECRET',
|
||||
|
||||
GITHUB_CLIENT_ID = 'GITHUB_CLIENT_ID',
|
||||
GITHUB_CLIENT_SECRET = 'GITHUB_CLIENT_SECRET',
|
||||
|
||||
MICROSOFT_CLIENT_ID = 'MICROSOFT_CLIENT_ID',
|
||||
MICROSOFT_CLIENT_SECRET = 'MICROSOFT_CLIENT_SECRET',
|
||||
|
||||
ALLOW_ANALYTICS_COLLECTION = 'ALLOW_ANALYTICS_COLLECTION',
|
||||
IS_FIRST_TIME_INFRA_SETUP = 'IS_FIRST_TIME_INFRA_SETUP',
|
||||
}
|
||||
|
||||
@@ -183,6 +183,16 @@ export const validateSMTPUrl = (url: string) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks to see if the URL is valid or not
|
||||
* @param url The URL to validate
|
||||
* @returns boolean
|
||||
*/
|
||||
export const validateUrl = (url: string) => {
|
||||
const urlRegex = /^(http|https):\/\/[^ "]+$/;
|
||||
return urlRegex.test(url);
|
||||
};
|
||||
|
||||
/**
|
||||
* String to JSON parser
|
||||
* @param {str} str The string to parse
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hoppscotch/cli",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
||||
"homepage": "https://hoppscotch.io",
|
||||
"type": "module",
|
||||
@@ -41,30 +41,28 @@
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"dependencies": {
|
||||
"axios": "^1.6.6",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^11.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"qs": "^6.11.2",
|
||||
"zod": "^3.22.4"
|
||||
"axios": "1.6.7",
|
||||
"chalk": "5.3.0",
|
||||
"commander": "11.1.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"qs": "6.11.2",
|
||||
"verzod": "0.2.2",
|
||||
"zod": "3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hoppscotch/data": "workspace:^",
|
||||
"@hoppscotch/js-sandbox": "workspace:^",
|
||||
"@relmify/jest-fp-ts": "^2.1.1",
|
||||
"@swc/core": "^1.3.105",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/qs": "^6.9.11",
|
||||
"fp-ts": "^2.16.2",
|
||||
"jest": "^29.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"prettier": "^3.2.4",
|
||||
"qs": "^6.11.2",
|
||||
"ts-jest": "^29.1.2",
|
||||
"tsup": "^8.0.1",
|
||||
"typescript": "^5.3.3",
|
||||
"verzod": "^0.2.2",
|
||||
"zod": "^3.22.4"
|
||||
"@relmify/jest-fp-ts": "2.1.1",
|
||||
"@swc/core": "1.4.2",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/qs": "6.9.12",
|
||||
"fp-ts": "2.16.2",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "3.2.5",
|
||||
"qs": "6.11.2",
|
||||
"ts-jest": "29.1.2",
|
||||
"tsup": "8.0.2",
|
||||
"typescript": "5.3.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestD",
|
||||
"params": [],
|
||||
@@ -40,7 +40,8 @@
|
||||
"body": {
|
||||
"contentType": null,
|
||||
"body": null
|
||||
}
|
||||
},
|
||||
"requestVariables": []
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
@@ -52,7 +53,7 @@
|
||||
],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestC",
|
||||
"params": [],
|
||||
@@ -67,7 +68,8 @@
|
||||
"body": {
|
||||
"contentType": null,
|
||||
"body": null
|
||||
}
|
||||
},
|
||||
"requestVariables": []
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
@@ -88,7 +90,7 @@
|
||||
],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestB",
|
||||
"params": [],
|
||||
@@ -104,6 +106,7 @@
|
||||
"contentType": null,
|
||||
"body": null
|
||||
},
|
||||
"requestVariables": [],
|
||||
"id": "clpttpdq00003qp16kut6doqv"
|
||||
}
|
||||
],
|
||||
@@ -116,7 +119,7 @@
|
||||
],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestA",
|
||||
"params": [],
|
||||
@@ -132,6 +135,7 @@
|
||||
"contentType": null,
|
||||
"body": null
|
||||
},
|
||||
"requestVariables": [],
|
||||
"id": "clpttpdq00003qp16kut6doqv"
|
||||
}
|
||||
],
|
||||
@@ -158,7 +162,7 @@
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestB",
|
||||
"params": [],
|
||||
@@ -174,6 +178,7 @@
|
||||
"contentType": null,
|
||||
"body": null
|
||||
},
|
||||
"requestVariables": [],
|
||||
"id": "clpttpdq00003qp16kut6doqv"
|
||||
}
|
||||
],
|
||||
@@ -186,7 +191,7 @@
|
||||
],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestA",
|
||||
"params": [],
|
||||
@@ -202,6 +207,7 @@
|
||||
"contentType": null,
|
||||
"body": null
|
||||
},
|
||||
"requestVariables": [],
|
||||
"id": "clpttpdq00003qp16kut6doqv"
|
||||
}
|
||||
],
|
||||
@@ -218,4 +224,4 @@
|
||||
"token": "BearerToken"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "<<URL>>",
|
||||
"name": "test1",
|
||||
"params": [],
|
||||
@@ -16,7 +16,8 @@
|
||||
"body": {
|
||||
"contentType": "application/json",
|
||||
"body": "{\n \"<<BODY_KEY>>\":\"<<BODY_VALUE>>\"\n}"
|
||||
}
|
||||
},
|
||||
"requestVariables": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
|
||||
"name": "",
|
||||
"params": [],
|
||||
@@ -23,10 +23,11 @@
|
||||
"body": {
|
||||
"contentType": "application/json",
|
||||
"body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}"
|
||||
}
|
||||
},
|
||||
"requestVariables": [],
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.dio/<<HEADERS_TYPE2>>",
|
||||
"name": "success",
|
||||
"params": [],
|
||||
@@ -44,7 +45,8 @@
|
||||
"body": {
|
||||
"contentType": "application/json",
|
||||
"body": "{\n\"test\": \"<<HEADERS_TYPE2>>\"\n}"
|
||||
}
|
||||
},
|
||||
"requestVariables": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
{
|
||||
"v": 1,
|
||||
"folders": [],
|
||||
"requests":
|
||||
"requests":
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
|
||||
"name": "fail",
|
||||
"params": [],
|
||||
@@ -22,10 +22,11 @@
|
||||
"body": {
|
||||
"contentType": "application/json",
|
||||
"body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}"
|
||||
}
|
||||
},
|
||||
"requestVariables": [],
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE2>>",
|
||||
"name": "success",
|
||||
"params": [],
|
||||
@@ -43,7 +44,8 @@
|
||||
"body": {
|
||||
"contentType": "application/json",
|
||||
"body": "{\n\"test\": \"<<HEADERS_TYPE2>>\"\n}"
|
||||
}
|
||||
},
|
||||
"requestVariables": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
{
|
||||
"v": 1,
|
||||
"folders": [],
|
||||
"requests":
|
||||
"requests":
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
|
||||
"name": "fail",
|
||||
"params": [],
|
||||
@@ -22,7 +22,8 @@
|
||||
"body": {
|
||||
"contentType": "application/json",
|
||||
"body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}"
|
||||
}
|
||||
},
|
||||
"requestVariables": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
|
||||
"name": "",
|
||||
"params": [],
|
||||
@@ -23,10 +23,11 @@
|
||||
"body": {
|
||||
"contentType": "application/json",
|
||||
"body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}"
|
||||
}
|
||||
},
|
||||
"requestVariables": []
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE2>>",
|
||||
"name": "success",
|
||||
"params": [],
|
||||
@@ -44,7 +45,8 @@
|
||||
"body": {
|
||||
"contentType": "application/json",
|
||||
"body": "{\n\"test\": \"<<HEADERS_TYPE2>>\"\n}"
|
||||
}
|
||||
},
|
||||
"requestVariables": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": { "authType": "none", "authActive": true },
|
||||
"body": { "body": null, "contentType": null },
|
||||
"name": "sample-req",
|
||||
@@ -13,7 +13,8 @@
|
||||
"headers": [],
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"testScript": "pw.expect(pw.env.get(\"variable\")).toBe(\"value\")",
|
||||
"preRequestScript": "pw.env.set(\"variable\", \"value\");"
|
||||
"preRequestScript": "pw.env.set(\"variable\", \"value\");",
|
||||
"requestVariables": []
|
||||
}
|
||||
],
|
||||
"auth": { "authType": "inherit", "authActive": true },
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"name": "test-request",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"method": "POST",
|
||||
@@ -19,7 +19,8 @@
|
||||
"body": "{\n \"firstName\": \"<<firstName>>\",\n \"lastName\": \"<<lastName>>\",\n \"greetText\": \"<<salutation>>, <<fullName>>\",\n \"fullName\": \"<<fullName>>\",\n \"id\": \"<<id>>\"\n}"
|
||||
},
|
||||
"preRequestScript": "",
|
||||
"testScript": "pw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully resolves environments recursively\", ()=> {\n pw.expect(pw.env.getResolve(\"recursiveVarX\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"recursiveVarY\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"salutation\")).toBe(\"Hello\")\n});\n\npw.test(\"Successfully resolves environments referenced in the request body\", () => {\n const expectedId = \"7\"\n const expectedFirstName = \"John\"\n const expectedLastName = \"Doe\"\n const expectedFullName = `${expectedFirstName} ${expectedLastName}`\n const expectedGreetText = `Hello, ${expectedFullName}`\n\n pw.expect(pw.env.getResolve(\"recursiveVarX\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"recursiveVarY\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"salutation\")).toBe(\"Hello\")\n\n const { id, firstName, lastName, fullName, greetText } = JSON.parse(pw.response.body.data)\n\n pw.expect(id).toBe(expectedId)\n pw.expect(expectedFirstName).toBe(firstName)\n pw.expect(expectedLastName).toBe(lastName)\n pw.expect(fullName).toBe(expectedFullName)\n pw.expect(greetText).toBe(expectedGreetText)\n});"
|
||||
"testScript": "pw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully resolves environments recursively\", ()=> {\n pw.expect(pw.env.getResolve(\"recursiveVarX\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"recursiveVarY\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"salutation\")).toBe(\"Hello\")\n});\n\npw.test(\"Successfully resolves environments referenced in the request body\", () => {\n const expectedId = \"7\"\n const expectedFirstName = \"John\"\n const expectedLastName = \"Doe\"\n const expectedFullName = `${expectedFirstName} ${expectedLastName}`\n const expectedGreetText = `Hello, ${expectedFullName}`\n\n pw.expect(pw.env.getResolve(\"recursiveVarX\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"recursiveVarY\")).toBe(\"Hello\")\n pw.expect(pw.env.getResolve(\"salutation\")).toBe(\"Hello\")\n\n const { id, firstName, lastName, fullName, greetText } = JSON.parse(pw.response.body.data)\n\n pw.expect(id).toBe(expectedId)\n pw.expect(expectedFirstName).toBe(firstName)\n pw.expect(expectedLastName).toBe(lastName)\n pw.expect(fullName).toBe(expectedFullName)\n pw.expect(greetText).toBe(expectedGreetText)\n});",
|
||||
"requestVariables": []
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": { "authType": "none", "authActive": true },
|
||||
"body": { "body": null, "contentType": null },
|
||||
"name": "test-secret-headers",
|
||||
@@ -17,12 +17,13 @@
|
||||
"active": true
|
||||
}
|
||||
],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>/headers",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.get(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.get(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
|
||||
"preRequestScript": "const secretHeaderValueFromPreReqScript = pw.env.get(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": { "authType": "none", "authActive": true },
|
||||
"body": {
|
||||
"body": "{\n \"secretBodyKey\": \"<<secretBodyValue>>\"\n}",
|
||||
@@ -32,12 +33,13 @@
|
||||
"method": "POST",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>/post",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
|
||||
"preRequestScript": "const secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": { "authType": "none", "authActive": true },
|
||||
"body": { "body": null, "contentType": null },
|
||||
"name": "test-secret-query-params",
|
||||
@@ -50,12 +52,13 @@
|
||||
}
|
||||
],
|
||||
"headers": [],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>/get",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})",
|
||||
"preRequestScript": "const secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": {
|
||||
"authType": "basic",
|
||||
"password": "<<secretBasicAuthPassword>>",
|
||||
@@ -67,12 +70,13 @@
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
|
||||
"testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});",
|
||||
"preRequestScript": ""
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": {
|
||||
"token": "<<secretBearerToken>>",
|
||||
"authType": "bearer",
|
||||
@@ -85,18 +89,20 @@
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>/bearer",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.get(\"secretBearerToken\")\n const preReqSecretBearerToken = pw.env.get(\"preReqSecretBearerToken\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});",
|
||||
"preRequestScript": "const secretBearerToken = pw.env.get(\"secretBearerToken\")\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)"
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": { "authType": "none", "authActive": true },
|
||||
"body": { "body": null, "contentType": null },
|
||||
"name": "test-secret-fallback",
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>",
|
||||
"testScript": "pw.test(\"Returns an empty string if the value for a secret environment variable is not found in the system environment\", () => {\n pw.expect(pw.env.get(\"nonExistentValueInSystemEnv\")).toBe(\"\")\n})",
|
||||
"preRequestScript": ""
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": {
|
||||
"authType": "none",
|
||||
"authActive": true
|
||||
@@ -16,6 +16,7 @@
|
||||
"name": "test-secret-headers",
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"requestVariables": [],
|
||||
"headers": [
|
||||
{
|
||||
"key": "Secret-Header-Key",
|
||||
@@ -28,7 +29,7 @@
|
||||
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": {
|
||||
"authType": "none",
|
||||
"authActive": true
|
||||
@@ -40,6 +41,7 @@
|
||||
"name": "test-secret-headers-overrides",
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"requestVariables": [],
|
||||
"headers": [
|
||||
{
|
||||
"key": "Secret-Header-Key",
|
||||
@@ -52,7 +54,7 @@
|
||||
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value-overriden\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": {
|
||||
"authType": "none",
|
||||
"authActive": true
|
||||
@@ -64,13 +66,14 @@
|
||||
"name": "test-secret-body",
|
||||
"method": "POST",
|
||||
"params": [],
|
||||
"requestVariables": [],
|
||||
"headers": [],
|
||||
"endpoint": "<<baseURL>>/post",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
|
||||
"preRequestScript": "const secretBodyValue = pw.env.get(\"secretBodyValue\")\n\nif (!secretBodyValue) { \n pw.env.set(\"secretBodyValue\", \"secret-body-value\")\n}\n\nconst secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": {
|
||||
"authType": "none",
|
||||
"authActive": true
|
||||
@@ -88,13 +91,14 @@
|
||||
"active": true
|
||||
}
|
||||
],
|
||||
"requestVariables": [],
|
||||
"headers": [],
|
||||
"endpoint": "<<baseURL>>/get",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})",
|
||||
"preRequestScript": "const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n\nif (!secretQueryParamValue) {\n pw.env.set(\"secretQueryParamValue\", \"secret-query-param-value\")\n}\n\nconst secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": {
|
||||
"authType": "basic",
|
||||
"password": "<<secretBasicAuthPassword>>",
|
||||
@@ -108,13 +112,14 @@
|
||||
"name": "test-secret-basic-auth",
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"requestVariables": [],
|
||||
"headers": [],
|
||||
"endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
|
||||
"testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});",
|
||||
"preRequestScript": "let secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n\nlet secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\nif (!secretBasicAuthUsername) {\n pw.env.set(\"secretBasicAuthUsername\", \"test-user\")\n}\n\nif (!secretBasicAuthPassword) {\n pw.env.set(\"secretBasicAuthPassword\", \"test-pass\")\n}"
|
||||
},
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"auth": {
|
||||
"token": "<<secretBearerToken>>",
|
||||
"authType": "bearer",
|
||||
@@ -129,6 +134,7 @@
|
||||
"name": "test-secret-bearer-auth",
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"requestVariables": [],
|
||||
"headers": [],
|
||||
"endpoint": "<<baseURL>>/bearer",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n const preReqSecretBearerToken = pw.env.resolve(\"<<preReqSecretBearerToken>>\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"v": "2",
|
||||
"endpoint": "https://httpbin.org/post",
|
||||
"name": "req",
|
||||
"params": [],
|
||||
@@ -22,7 +22,8 @@
|
||||
"body": {
|
||||
"contentType": "application/json",
|
||||
"body": "{\n \"key\": \"<<customBodyValue>>\"\n}"
|
||||
}
|
||||
},
|
||||
"requestVariables": []
|
||||
}
|
||||
],
|
||||
"auth": { "authType": "inherit", "authActive": false },
|
||||
|
||||
@@ -563,12 +563,22 @@ details[open] summary .indicator {
|
||||
.env-highlight {
|
||||
@apply text-accentContrast;
|
||||
|
||||
&.env-found {
|
||||
@apply bg-accentDark;
|
||||
@apply hover:bg-accent;
|
||||
&.request-variable-highlight {
|
||||
@apply bg-amber-500;
|
||||
@apply hover:bg-amber-600;
|
||||
}
|
||||
|
||||
&.env-not-found {
|
||||
&.environment-variable-highlight {
|
||||
@apply bg-green-500;
|
||||
@apply hover:bg-green-600;
|
||||
}
|
||||
|
||||
&.global-variable-highlight {
|
||||
@apply bg-blue-500;
|
||||
@apply hover:bg-blue-600;
|
||||
}
|
||||
|
||||
&.environment-not-found-highlight {
|
||||
@apply bg-red-500;
|
||||
@apply hover:bg-red-600;
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"invalid_name": "Please provide a name for the collection",
|
||||
"invalid_root_move": "Collection already in the root",
|
||||
"moved": "Moved Successfully",
|
||||
"my_collections": "My Collections",
|
||||
"my_collections": "Personal Collections",
|
||||
"name": "My New Collection",
|
||||
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
||||
"new": "New Collection",
|
||||
@@ -166,14 +166,12 @@
|
||||
"save_as": "Save as",
|
||||
"save_to_collection": "Save to Collection",
|
||||
"select": "Select a Collection",
|
||||
"select_location": "Select location",
|
||||
"select_team": "Select a team",
|
||||
"team_collections": "Team Collections"
|
||||
"select_location": "Select location"
|
||||
},
|
||||
"confirm": {
|
||||
"close_unsaved_tab": "Are you sure you want to close this tab?",
|
||||
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
|
||||
"exit_team": "Are you sure you want to leave this team?",
|
||||
"exit_team": "Are you sure you want to leave this workspace?",
|
||||
"logout": "Are you sure you want to logout?",
|
||||
"remove_collection": "Are you sure you want to permanently delete this collection?",
|
||||
"remove_environment": "Are you sure you want to permanently delete this environment?",
|
||||
@@ -181,7 +179,7 @@
|
||||
"remove_history": "Are you sure you want to permanently delete all history?",
|
||||
"remove_request": "Are you sure you want to permanently delete this request?",
|
||||
"remove_shared_request": "Are you sure you want to permanently delete this shared request?",
|
||||
"remove_team": "Are you sure you want to delete this team?",
|
||||
"remove_team": "Are you sure you want to delete this workspace?",
|
||||
"remove_telemetry": "Are you sure you want to opt-out of Telemetry?",
|
||||
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
||||
"save_unsaved_tab": "Do you want to save changes made in this tab?",
|
||||
@@ -234,18 +232,19 @@
|
||||
"headers": "This request does not have any headers",
|
||||
"history": "History is empty",
|
||||
"invites": "Invite list is empty",
|
||||
"members": "Team is empty",
|
||||
"members": "Workspace is empty",
|
||||
"parameters": "This request does not have any parameters",
|
||||
"pending_invites": "There are no pending invites for this team",
|
||||
"pending_invites": "There are no pending invites for this workspace",
|
||||
"profile": "Login to view your profile",
|
||||
"protocols": "Protocols are empty",
|
||||
"request_variables": "This request does not have any request variables",
|
||||
"schema": "Connect to a GraphQL endpoint to view schema",
|
||||
"secret_environments": "Secrets are not synced to Hoppscotch",
|
||||
"shared_requests": "Shared requests are empty",
|
||||
"shared_requests_logout": "Login to view your shared requests or create a new one",
|
||||
"subscription": "Subscriptions are empty",
|
||||
"team_name": "Team name empty",
|
||||
"teams": "You don't belong to any teams",
|
||||
"team_name": "Workspace name empty",
|
||||
"teams": "You don't belong to any workspaces",
|
||||
"tests": "There are no tests for this request"
|
||||
},
|
||||
"environment": {
|
||||
@@ -262,7 +261,7 @@
|
||||
"import_or_create": "Import or create a environment",
|
||||
"invalid_name": "Please provide a name for the environment",
|
||||
"list": "Environment variables",
|
||||
"my_environments": "My Environments",
|
||||
"my_environments": "Personal Environments",
|
||||
"name": "Name",
|
||||
"nested_overflow": "nested environment variables are limited to 10 levels",
|
||||
"new": "New Environment",
|
||||
@@ -277,7 +276,7 @@
|
||||
"select": "Select environment",
|
||||
"set": "Set environment",
|
||||
"set_as_environment": "Set as environment",
|
||||
"team_environments": "Team Environments",
|
||||
"team_environments": "Workspace Environments",
|
||||
"title": "Environments",
|
||||
"updated": "Environment updated",
|
||||
"value": "Value",
|
||||
@@ -292,8 +291,8 @@
|
||||
"check_how_to_add_origin": "Check how you can add an origin",
|
||||
"curl_invalid_format": "cURL is not formatted properly",
|
||||
"danger_zone": "Danger zone",
|
||||
"delete_account": "Your account is currently an owner in these teams:",
|
||||
"delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.",
|
||||
"delete_account": "Your account is currently an owner in these workspaces:",
|
||||
"delete_account_description": "You must either remove yourself, transfer ownership, or delete these workspaces before you can delete your account.",
|
||||
"empty_req_name": "Empty Request Name",
|
||||
"f12_details": "(F12 for details)",
|
||||
"gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again",
|
||||
@@ -395,8 +394,8 @@
|
||||
"from_insomnia_description": "Import from Insomnia collection",
|
||||
"from_json": "Import from Hoppscotch",
|
||||
"from_json_description": "Import from Hoppscotch collection file",
|
||||
"from_my_collections": "Import from My Collections",
|
||||
"from_my_collections_description": "Import from My Collections file",
|
||||
"from_my_collections": "Import from Personal Collections",
|
||||
"from_my_collections_description": "Import from Personal Collections file",
|
||||
"from_openapi": "Import from OpenAPI",
|
||||
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
|
||||
"from_postman": "Import from Postman",
|
||||
@@ -513,7 +512,7 @@
|
||||
"email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.",
|
||||
"no_permission": "You do not have permission to perform this action.",
|
||||
"owner": "Owner",
|
||||
"owner_description": "Owners can add, edit, and delete requests, collections and team members.",
|
||||
"owner_description": "Owners can add, edit, and delete requests, collections and workspace members.",
|
||||
"roles": "Roles",
|
||||
"roles_description": "Roles are used to control access to the shared collections.",
|
||||
"updated": "Profile updated",
|
||||
@@ -560,6 +559,7 @@
|
||||
"raw_body": "Raw Request Body",
|
||||
"rename": "Rename Request",
|
||||
"renamed": "Request renamed",
|
||||
"request_variables": "Request variables",
|
||||
"run": "Run",
|
||||
"save": "Save",
|
||||
"save_as": "Save as",
|
||||
@@ -817,12 +817,12 @@
|
||||
"title": "Tabs"
|
||||
},
|
||||
"workspace": {
|
||||
"delete": "Delete current team",
|
||||
"edit": "Edit current team",
|
||||
"invite": "Invite people to team",
|
||||
"new": "Create new team",
|
||||
"delete": "Delete current workspace",
|
||||
"edit": "Edit current workspace",
|
||||
"invite": "Invite people to workspace",
|
||||
"new": "Create new workspace",
|
||||
"switch_to_personal": "Switch to your personal workspace",
|
||||
"title": "Teams"
|
||||
"title": "Workspaces"
|
||||
}
|
||||
},
|
||||
"sse": {
|
||||
@@ -879,7 +879,6 @@
|
||||
"forum": "Ask questions and get answers",
|
||||
"github": "Follow us on Github",
|
||||
"shortcuts": "Browse app faster",
|
||||
"team": "Get in touch with the team",
|
||||
"title": "Support",
|
||||
"twitter": "Follow us on Twitter"
|
||||
},
|
||||
@@ -910,60 +909,59 @@
|
||||
"websocket": "WebSocket"
|
||||
},
|
||||
"team": {
|
||||
"already_member": "You are already a member of this team. Contact your team owner.",
|
||||
"create_new": "Create new team",
|
||||
"deleted": "Team deleted",
|
||||
"edit": "Edit Team",
|
||||
"already_member": "You are already a member of this workspace. Contact your workspace owner.",
|
||||
"create_new": "Create new workspace",
|
||||
"deleted": "Workspace deleted",
|
||||
"edit": "Edit Workspace",
|
||||
"email": "E-mail",
|
||||
"email_do_not_match": "Email doesn't match with your account details. Contact your team owner.",
|
||||
"exit": "Exit Team",
|
||||
"exit_disabled": "Only owner cannot exit the team",
|
||||
"email_do_not_match": "Email doesn't match with your account details. Contact your workspace owner.",
|
||||
"exit": "Exit Workspace",
|
||||
"exit_disabled": "Only owner cannot exit the workspace",
|
||||
"failed_invites": "Failed invites",
|
||||
"invalid_coll_id": "Invalid collection ID",
|
||||
"invalid_email_format": "Email format is invalid",
|
||||
"invalid_id": "Invalid team ID. Contact your team owner.",
|
||||
"invalid_id": "Invalid workspace ID. Contact your workspace owner.",
|
||||
"invalid_invite_link": "Invalid invite link",
|
||||
"invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.",
|
||||
"invalid_member_permission": "Please provide a valid permission to the team member",
|
||||
"invalid_invite_link_description": "The link you followed is invalid. Contact your workspace owner.",
|
||||
"invalid_member_permission": "Please provide a valid permission to the workspace member",
|
||||
"invite": "Invite",
|
||||
"invite_more": "Invite more",
|
||||
"invite_tooltip": "Invite people to this workspace",
|
||||
"invited_to_team": "{owner} invited you to join {team}",
|
||||
"invited_to_team": "{owner} invited you to join {workspace}",
|
||||
"join": "Invitation accepted",
|
||||
"join_beta": "Join the beta program to access teams.",
|
||||
"join_team": "Join {team}",
|
||||
"joined_team": "You have joined {team}",
|
||||
"joined_team_description": "You are now a member of this team",
|
||||
"left": "You left the team",
|
||||
"join_team": "Join {workspace}",
|
||||
"joined_team": "You have joined {workspace}",
|
||||
"joined_team_description": "You are now a member of this workspace",
|
||||
"left": "You left the workspace",
|
||||
"login_to_continue": "Login to continue",
|
||||
"login_to_continue_description": "You need to be logged in to join a team.",
|
||||
"login_to_continue_description": "You need to be logged in to join a workspace.",
|
||||
"logout_and_try_again": "Logout and sign in with another account",
|
||||
"member_has_invite": "This email ID already has an invite. Contact your team owner.",
|
||||
"member_not_found": "Member not found. Contact your team owner.",
|
||||
"member_has_invite": "This email ID already has an invite. Contact your workspace owner.",
|
||||
"member_not_found": "Member not found. Contact your workspace owner.",
|
||||
"member_removed": "User removed",
|
||||
"member_role_updated": "User roles updated",
|
||||
"members": "Members",
|
||||
"more_members": "+{count} more",
|
||||
"name_length_insufficient": "Team name should be at least 6 characters long",
|
||||
"name_updated": "Team name updated",
|
||||
"new": "New Team",
|
||||
"new_created": "New team created",
|
||||
"new_name": "My New Team",
|
||||
"no_access": "You do not have edit access to this team",
|
||||
"no_invite_found": "Invitation not found. Contact your team owner.",
|
||||
"name_length_insufficient": "Workspace name should be at least 6 characters long",
|
||||
"name_updated": "Workspace name updated",
|
||||
"new": "New Workspace",
|
||||
"new_created": "New workspace created",
|
||||
"new_name": "My New Workspace",
|
||||
"no_access": "You do not have edit access to this workspace",
|
||||
"no_invite_found": "Invitation not found. Contact your workspace owner.",
|
||||
"no_request_found": "Request not found.",
|
||||
"not_found": "Team not found. Contact your team owner.",
|
||||
"not_valid_viewer": "You are not a valid viewer. Contact your team owner.",
|
||||
"not_found": "Workspace not found. Contact your workspace owner.",
|
||||
"not_valid_viewer": "You are not a valid viewer. Contact your workspace owner.",
|
||||
"parent_coll_move": "Cannot move collection to a child collection",
|
||||
"pending_invites": "Pending invites",
|
||||
"permissions": "Permissions",
|
||||
"same_target_destination": "Same target and destination",
|
||||
"saved": "Team saved",
|
||||
"select_a_team": "Select a team",
|
||||
"saved": "Workspace saved",
|
||||
"select_a_team": "Select a workspace",
|
||||
"success_invites": "Success invites",
|
||||
"title": "Teams",
|
||||
"title": "Workspaces",
|
||||
"we_sent_invite_link": "We sent an invite link to all invitees!",
|
||||
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team."
|
||||
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the workspace."
|
||||
},
|
||||
"team_environment": {
|
||||
"deleted": "Environment Deleted",
|
||||
@@ -989,8 +987,14 @@
|
||||
},
|
||||
"workspace": {
|
||||
"change": "Change workspace",
|
||||
"personal": "My Workspace",
|
||||
"team": "Team Workspace",
|
||||
"personal": "Personal Workspace",
|
||||
"other_workspaces": "My Workspaces",
|
||||
"team": "Workspace",
|
||||
"title": "Workspaces"
|
||||
},
|
||||
"site_protection": {
|
||||
"login_to_continue": "Login to continue",
|
||||
"login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.",
|
||||
"error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hoppscotch/common",
|
||||
"private": true,
|
||||
"version": "2023.12.6",
|
||||
"version": "2024.3.0",
|
||||
"scripts": {
|
||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||
"test": "vitest --run",
|
||||
@@ -21,147 +21,147 @@
|
||||
"do-lintfix": "pnpm run lintfix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "^10.1.0",
|
||||
"@codemirror/autocomplete": "^6.11.1",
|
||||
"@codemirror/commands": "^6.3.2",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-xml": "^6.0.2",
|
||||
"@codemirror/language": "6.9.3",
|
||||
"@codemirror/legacy-modes": "^6.3.3",
|
||||
"@codemirror/lint": "^6.4.2",
|
||||
"@codemirror/search": "^6.5.5",
|
||||
"@codemirror/state": "^6.3.3",
|
||||
"@codemirror/view": "^6.22.3",
|
||||
"@apidevtools/swagger-parser": "10.1.0",
|
||||
"@codemirror/autocomplete": "6.13.0",
|
||||
"@codemirror/commands": "6.3.3",
|
||||
"@codemirror/lang-javascript": "6.2.2",
|
||||
"@codemirror/lang-json": "6.0.1",
|
||||
"@codemirror/lang-xml": "6.1.0",
|
||||
"@codemirror/language": "6.10.1",
|
||||
"@codemirror/legacy-modes": "6.3.3",
|
||||
"@codemirror/lint": "6.5.0",
|
||||
"@codemirror/search": "6.5.6",
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@codemirror/view": "6.25.1",
|
||||
"@hoppscotch/codemirror-lang-graphql": "workspace:^",
|
||||
"@hoppscotch/data": "workspace:^",
|
||||
"@hoppscotch/js-sandbox": "workspace:^",
|
||||
"@hoppscotch/ui": "^0.1.0",
|
||||
"@hoppscotch/vue-toasted": "^0.1.0",
|
||||
"@hoppscotch/ui": "0.1.0",
|
||||
"@hoppscotch/vue-toasted": "0.1.0",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@unhead/vue": "^1.8.8",
|
||||
"@urql/core": "^4.2.0",
|
||||
"@urql/devtools": "^2.0.3",
|
||||
"@urql/exchange-auth": "^2.1.6",
|
||||
"@urql/exchange-graphcache": "^6.3.3",
|
||||
"@vitejs/plugin-legacy": "^4.1.1",
|
||||
"@vueuse/core": "^10.6.1",
|
||||
"acorn-walk": "^8.3.0",
|
||||
"axios": "^1.6.2",
|
||||
"buffer": "^6.0.3",
|
||||
"cookie-es": "^1.0.0",
|
||||
"dioc": "^1.0.1",
|
||||
"esprima": "^4.0.1",
|
||||
"events": "^3.3.0",
|
||||
"fp-ts": "^2.16.1",
|
||||
"globalthis": "^1.0.3",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-language-service-interface": "^2.10.2",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"httpsnippet": "^3.0.1",
|
||||
"insomnia-importers": "^3.6.0",
|
||||
"io-ts": "^2.2.20",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonpath-plus": "^7.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lossless-json": "^3.0.2",
|
||||
"minisearch": "^6.3.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"paho-mqtt": "^1.1.0",
|
||||
"path": "^0.12.7",
|
||||
"postman-collection": "^4.3.0",
|
||||
"process": "^0.11.10",
|
||||
"qs": "^6.11.2",
|
||||
"quicktype-core": "^23.0.79",
|
||||
"rxjs": "^7.8.1",
|
||||
"set-cookie-parser": "^2.6.0",
|
||||
"set-cookie-parser-es": "^1.0.5",
|
||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||
"socket.io-client-v3": "npm:socket.io-client@^3.1.3",
|
||||
"socket.io-client-v4": "npm:socket.io-client@^4.4.1",
|
||||
"socketio-wildcard": "^2.0.0",
|
||||
"splitpanes": "^3.1.5",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"tern": "^0.24.3",
|
||||
"timers": "^0.1.1",
|
||||
"tippy.js": "^6.3.7",
|
||||
"url": "^0.11.3",
|
||||
"util": "^0.12.5",
|
||||
"uuid": "^9.0.1",
|
||||
"verzod": "^0.2.0",
|
||||
"vue": "^3.3.8",
|
||||
"vue-i18n": "^9.7.1",
|
||||
"vue-pdf-embed": "^1.2.1",
|
||||
"vue-router": "^4.2.5",
|
||||
"@unhead/vue": "1.8.8",
|
||||
"@urql/core": "4.2.0",
|
||||
"@urql/devtools": "2.0.3",
|
||||
"@urql/exchange-auth": "2.1.6",
|
||||
"@urql/exchange-graphcache": "6.4.0",
|
||||
"@vitejs/plugin-legacy": "4.1.1",
|
||||
"@vueuse/core": "10.7.0",
|
||||
"acorn-walk": "8.3.0",
|
||||
"axios": "1.6.2",
|
||||
"buffer": "6.0.3",
|
||||
"cookie-es": "1.0.0",
|
||||
"dioc": "1.0.1",
|
||||
"esprima": "4.0.1",
|
||||
"events": "3.3.0",
|
||||
"fp-ts": "2.16.1",
|
||||
"globalthis": "1.0.3",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-language-service-interface": "2.10.2",
|
||||
"graphql-tag": "2.12.6",
|
||||
"httpsnippet": "3.0.1",
|
||||
"insomnia-importers": "3.6.0",
|
||||
"io-ts": "2.2.20",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonpath-plus": "7.2.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"lossless-json": "3.0.2",
|
||||
"minisearch": "6.3.0",
|
||||
"nprogress": "0.2.0",
|
||||
"paho-mqtt": "1.1.0",
|
||||
"path": "0.12.7",
|
||||
"postman-collection": "4.3.0",
|
||||
"process": "0.11.10",
|
||||
"qs": "6.11.2",
|
||||
"quicktype-core": "23.0.79",
|
||||
"rxjs": "7.8.1",
|
||||
"set-cookie-parser": "2.6.0",
|
||||
"set-cookie-parser-es": "1.0.5",
|
||||
"socket.io-client-v2": "npm:socket.io-client@2.4.0",
|
||||
"socket.io-client-v3": "npm:socket.io-client@3.1.3",
|
||||
"socket.io-client-v4": "npm:socket.io-client@4.4.1",
|
||||
"socketio-wildcard": "2.0.0",
|
||||
"splitpanes": "3.1.5",
|
||||
"stream-browserify": "3.0.0",
|
||||
"subscriptions-transport-ws": "0.11.0",
|
||||
"tern": "0.24.3",
|
||||
"timers": "0.1.1",
|
||||
"tippy.js": "6.3.7",
|
||||
"url": "0.11.3",
|
||||
"util": "0.12.5",
|
||||
"uuid": "9.0.1",
|
||||
"verzod": "0.2.2",
|
||||
"vue": "3.3.9",
|
||||
"vue-i18n": "9.8.0",
|
||||
"vue-pdf-embed": "1.2.1",
|
||||
"vue-router": "4.2.5",
|
||||
"vue-tippy": "6.3.1",
|
||||
"vuedraggable-es": "^4.1.1",
|
||||
"wonka": "^6.3.4",
|
||||
"workbox-window": "^7.0.0",
|
||||
"xml-formatter": "^3.6.0",
|
||||
"yargs-parser": "^21.1.1",
|
||||
"zod": "^3.22.4"
|
||||
"vuedraggable-es": "4.1.1",
|
||||
"wonka": "6.3.4",
|
||||
"workbox-window": "7.0.0",
|
||||
"xml-formatter": "3.6.0",
|
||||
"yargs-parser": "21.1.1",
|
||||
"zod": "3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||
"@graphql-codegen/add": "^5.0.0",
|
||||
"@graphql-codegen/cli": "^5.0.0",
|
||||
"@graphql-codegen/typed-document-node": "^5.0.1",
|
||||
"@graphql-codegen/typescript": "^4.0.1",
|
||||
"@graphql-codegen/typescript-operations": "^4.0.1",
|
||||
"@graphql-codegen/typescript-urql-graphcache": "^3.0.0",
|
||||
"@graphql-codegen/urql-introspection": "^3.0.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@iconify-json/lucide": "^1.1.141",
|
||||
"@intlify/vite-plugin-vue-i18n": "^7.0.0",
|
||||
"@relmify/jest-fp-ts": "^2.1.1",
|
||||
"@rushstack/eslint-patch": "^1.6.0",
|
||||
"@types/har-format": "^1.2.15",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/lossless-json": "^1.0.4",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/paho-mqtt": "^1.0.10",
|
||||
"@types/postman-collection": "^3.5.10",
|
||||
"@types/splitpanes": "^2.2.6",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@types/yargs-parser": "^21.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"@vitejs/plugin-vue": "^4.5.0",
|
||||
"@vue/compiler-sfc": "^3.3.8",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"@vue/runtime-core": "^3.3.8",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-vue": "^9.18.1",
|
||||
"glob": "^10.3.10",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"openapi-types": "^12.1.3",
|
||||
"postcss": "^8.4.23",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-tailwindcss": "^0.5.7",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"sass": "^1.69.5",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^5.3.2",
|
||||
"unplugin-fonts": "^1.1.1",
|
||||
"unplugin-icons": "^0.17.4",
|
||||
"unplugin-vue-components": "^0.25.2",
|
||||
"vite": "^4.5.0",
|
||||
"vite-plugin-checker": "^0.6.2",
|
||||
"vite-plugin-fonts": "^0.7.0",
|
||||
"vite-plugin-html-config": "^1.0.11",
|
||||
"vite-plugin-inspect": "^0.7.42",
|
||||
"vite-plugin-pages": "^0.31.0",
|
||||
"vite-plugin-pages-sitemap": "^1.6.1",
|
||||
"vite-plugin-pwa": "^0.17.0",
|
||||
"vite-plugin-vue-layouts": "^0.8.0",
|
||||
"vitest": "^0.34.6",
|
||||
"vue-tsc": "^1.8.22"
|
||||
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
|
||||
"@esbuild-plugins/node-modules-polyfill": "0.2.2",
|
||||
"@graphql-codegen/add": "5.0.0",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||
"@graphql-codegen/typescript-urql-graphcache": "3.0.0",
|
||||
"@graphql-codegen/urql-introspection": "3.0.0",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@iconify-json/lucide": "1.1.144",
|
||||
"@intlify/vite-plugin-vue-i18n": "7.0.0",
|
||||
"@relmify/jest-fp-ts": "2.1.1",
|
||||
"@rushstack/eslint-patch": "1.6.0",
|
||||
"@types/har-format": "1.2.15",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/lossless-json": "1.0.4",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@types/paho-mqtt": "1.0.10",
|
||||
"@types/postman-collection": "3.5.10",
|
||||
"@types/splitpanes": "2.2.6",
|
||||
"@types/uuid": "9.0.7",
|
||||
"@types/yargs-parser": "21.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "6.13.2",
|
||||
"@typescript-eslint/parser": "6.13.2",
|
||||
"@vitejs/plugin-vue": "4.5.1",
|
||||
"@vue/compiler-sfc": "3.3.10",
|
||||
"@vue/eslint-config-typescript": "12.0.0",
|
||||
"@vue/runtime-core": "3.3.10",
|
||||
"autoprefixer": "10.4.16",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "16.3.1",
|
||||
"eslint": "8.55.0",
|
||||
"eslint-plugin-prettier": "5.0.1",
|
||||
"eslint-plugin-vue": "9.19.2",
|
||||
"glob": "10.3.10",
|
||||
"npm-run-all": "4.1.5",
|
||||
"openapi-types": "12.1.3",
|
||||
"postcss": "8.4.31",
|
||||
"prettier": "3.1.0",
|
||||
"prettier-plugin-tailwindcss": "0.5.7",
|
||||
"rollup-plugin-polyfill-node": "0.13.0",
|
||||
"sass": "1.69.5",
|
||||
"tailwindcss": "3.3.5",
|
||||
"typescript": "5.3.2",
|
||||
"unplugin-fonts": "1.1.1",
|
||||
"unplugin-icons": "0.17.4",
|
||||
"unplugin-vue-components": "0.25.2",
|
||||
"vite": "4.5.0",
|
||||
"vite-plugin-checker": "0.6.2",
|
||||
"vite-plugin-fonts": "0.7.0",
|
||||
"vite-plugin-html-config": "1.0.11",
|
||||
"vite-plugin-inspect": "0.7.42",
|
||||
"vite-plugin-pages": "0.31.0",
|
||||
"vite-plugin-pages-sitemap": "1.6.1",
|
||||
"vite-plugin-pwa": "0.17.3",
|
||||
"vite-plugin-vue-layouts": "0.8.0",
|
||||
"vitest": "0.34.6",
|
||||
"vue-tsc": "1.8.24"
|
||||
}
|
||||
}
|
||||
}
|
||||
409
packages/hoppscotch-common/src/components.d.ts
vendored
409
packages/hoppscotch-common/src/components.d.ts
vendored
@@ -1,213 +1,214 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
import "@vue/runtime-core"
|
||||
|
||||
export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
declare module "@vue/runtime-core" {
|
||||
export interface GlobalComponents {
|
||||
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
|
||||
AppBanner: typeof import('./components/app/Banner.vue')['default']
|
||||
AppContextMenu: typeof import('./components/app/ContextMenu.vue')['default']
|
||||
AppDeveloperOptions: typeof import('./components/app/DeveloperOptions.vue')['default']
|
||||
AppFooter: typeof import('./components/app/Footer.vue')['default']
|
||||
AppGitHubStarButton: typeof import('./components/app/GitHubStarButton.vue')['default']
|
||||
AppHeader: typeof import('./components/app/Header.vue')['default']
|
||||
AppInspection: typeof import('./components/app/Inspection.vue')['default']
|
||||
AppInterceptor: typeof import('./components/app/Interceptor.vue')['default']
|
||||
AppLogo: typeof import('./components/app/Logo.vue')['default']
|
||||
AppOptions: typeof import('./components/app/Options.vue')['default']
|
||||
AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default']
|
||||
AppShare: typeof import('./components/app/Share.vue')['default']
|
||||
AppShortcuts: typeof import('./components/app/Shortcuts.vue')['default']
|
||||
AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default']
|
||||
AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default']
|
||||
AppSidenav: typeof import('./components/app/Sidenav.vue')['default']
|
||||
AppSpotlight: typeof import('./components/app/spotlight/index.vue')['default']
|
||||
AppSpotlightEntry: typeof import('./components/app/spotlight/Entry.vue')['default']
|
||||
AppSpotlightEntryGQLHistory: typeof import('./components/app/spotlight/entry/GQLHistory.vue')['default']
|
||||
AppSpotlightEntryGQLRequest: typeof import('./components/app/spotlight/entry/GQLRequest.vue')['default']
|
||||
AppSpotlightEntryIconSelected: typeof import('./components/app/spotlight/entry/IconSelected.vue')['default']
|
||||
AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default']
|
||||
AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default']
|
||||
AppSupport: typeof import('./components/app/Support.vue')['default']
|
||||
Collections: typeof import('./components/collections/index.vue')['default']
|
||||
CollectionsAdd: typeof import('./components/collections/Add.vue')['default']
|
||||
CollectionsAddFolder: typeof import('./components/collections/AddFolder.vue')['default']
|
||||
CollectionsAddRequest: typeof import('./components/collections/AddRequest.vue')['default']
|
||||
CollectionsCollection: typeof import('./components/collections/Collection.vue')['default']
|
||||
CollectionsEdit: typeof import('./components/collections/Edit.vue')['default']
|
||||
CollectionsEditFolder: typeof import('./components/collections/EditFolder.vue')['default']
|
||||
CollectionsEditRequest: typeof import('./components/collections/EditRequest.vue')['default']
|
||||
CollectionsGraphql: typeof import('./components/collections/graphql/index.vue')['default']
|
||||
CollectionsGraphqlAdd: typeof import('./components/collections/graphql/Add.vue')['default']
|
||||
CollectionsGraphqlAddFolder: typeof import('./components/collections/graphql/AddFolder.vue')['default']
|
||||
CollectionsGraphqlAddRequest: typeof import('./components/collections/graphql/AddRequest.vue')['default']
|
||||
CollectionsGraphqlCollection: typeof import('./components/collections/graphql/Collection.vue')['default']
|
||||
CollectionsGraphqlEdit: typeof import('./components/collections/graphql/Edit.vue')['default']
|
||||
CollectionsGraphqlEditFolder: typeof import('./components/collections/graphql/EditFolder.vue')['default']
|
||||
CollectionsGraphqlEditRequest: typeof import('./components/collections/graphql/EditRequest.vue')['default']
|
||||
CollectionsGraphqlFolder: typeof import('./components/collections/graphql/Folder.vue')['default']
|
||||
CollectionsGraphqlImportExport: typeof import('./components/collections/graphql/ImportExport.vue')['default']
|
||||
CollectionsGraphqlRequest: typeof import('./components/collections/graphql/Request.vue')['default']
|
||||
CollectionsImportExport: typeof import('./components/collections/ImportExport.vue')['default']
|
||||
CollectionsMyCollections: typeof import('./components/collections/MyCollections.vue')['default']
|
||||
CollectionsProperties: typeof import('./components/collections/Properties.vue')['default']
|
||||
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
||||
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
||||
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
||||
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
|
||||
CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default']
|
||||
Embeds: typeof import('./components/embeds/index.vue')['default']
|
||||
Environments: typeof import('./components/environments/index.vue')['default']
|
||||
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
||||
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
||||
EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
|
||||
EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
|
||||
EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.vue')['default']
|
||||
EnvironmentsSelector: typeof import('./components/environments/Selector.vue')['default']
|
||||
EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default']
|
||||
EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default']
|
||||
EnvironmentsTeamsEnvironment: typeof import('./components/environments/teams/Environment.vue')['default']
|
||||
FirebaseLogin: typeof import('./components/firebase/Login.vue')['default']
|
||||
FirebaseLogout: typeof import('./components/firebase/Logout.vue')['default']
|
||||
GraphqlAuthorization: typeof import('./components/graphql/Authorization.vue')['default']
|
||||
GraphqlField: typeof import('./components/graphql/Field.vue')['default']
|
||||
GraphqlHeaders: typeof import('./components/graphql/Headers.vue')['default']
|
||||
GraphqlQuery: typeof import('./components/graphql/Query.vue')['default']
|
||||
GraphqlRequest: typeof import('./components/graphql/Request.vue')['default']
|
||||
GraphqlRequestOptions: typeof import('./components/graphql/RequestOptions.vue')['default']
|
||||
GraphqlRequestTab: typeof import('./components/graphql/RequestTab.vue')['default']
|
||||
GraphqlResponse: typeof import('./components/graphql/Response.vue')['default']
|
||||
GraphqlSidebar: typeof import('./components/graphql/Sidebar.vue')['default']
|
||||
GraphqlSubscriptionLog: typeof import('./components/graphql/SubscriptionLog.vue')['default']
|
||||
GraphqlTabHead: typeof import('./components/graphql/TabHead.vue')['default']
|
||||
GraphqlType: typeof import('./components/graphql/Type.vue')['default']
|
||||
GraphqlTypeLink: typeof import('./components/graphql/TypeLink.vue')['default']
|
||||
GraphqlVariable: typeof import('./components/graphql/Variable.vue')['default']
|
||||
History: typeof import('./components/history/index.vue')['default']
|
||||
HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
|
||||
HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
|
||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
||||
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
||||
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
||||
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
||||
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
||||
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
|
||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
||||
HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder']
|
||||
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
||||
HoppSmartRadio: typeof import('@hoppscotch/ui')['HoppSmartRadio']
|
||||
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
||||
HoppSmartSelectWrapper: typeof import('@hoppscotch/ui')['HoppSmartSelectWrapper']
|
||||
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
||||
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
||||
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
|
||||
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
||||
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
|
||||
HoppSmartTree: typeof import('@hoppscotch/ui')['HoppSmartTree']
|
||||
HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow']
|
||||
HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows']
|
||||
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
||||
HttpAuthorizationApiKey: typeof import('./components/http/authorization/ApiKey.vue')['default']
|
||||
HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default']
|
||||
HttpBody: typeof import('./components/http/Body.vue')['default']
|
||||
HttpBodyParameters: typeof import('./components/http/BodyParameters.vue')['default']
|
||||
HttpCodegenModal: typeof import('./components/http/CodegenModal.vue')['default']
|
||||
HttpHeaders: typeof import('./components/http/Headers.vue')['default']
|
||||
HttpImportCurl: typeof import('./components/http/ImportCurl.vue')['default']
|
||||
HttpOAuth2Authorization: typeof import('./components/http/OAuth2Authorization.vue')['default']
|
||||
HttpParameters: typeof import('./components/http/Parameters.vue')['default']
|
||||
HttpPreRequestScript: typeof import('./components/http/PreRequestScript.vue')['default']
|
||||
HttpRawBody: typeof import('./components/http/RawBody.vue')['default']
|
||||
HttpReqChangeConfirmModal: typeof import('./components/http/ReqChangeConfirmModal.vue')['default']
|
||||
HttpRequest: typeof import('./components/http/Request.vue')['default']
|
||||
HttpRequestOptions: typeof import('./components/http/RequestOptions.vue')['default']
|
||||
HttpRequestTab: typeof import('./components/http/RequestTab.vue')['default']
|
||||
HttpResponse: typeof import('./components/http/Response.vue')['default']
|
||||
HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default']
|
||||
HttpSidebar: typeof import('./components/http/Sidebar.vue')['default']
|
||||
HttpTabHead: typeof import('./components/http/TabHead.vue')['default']
|
||||
HttpTestResult: typeof import('./components/http/TestResult.vue')['default']
|
||||
HttpTestResultEntry: typeof import('./components/http/TestResultEntry.vue')['default']
|
||||
HttpTestResultEnv: typeof import('./components/http/TestResultEnv.vue')['default']
|
||||
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
|
||||
HttpTests: typeof import('./components/http/Tests.vue')['default']
|
||||
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
|
||||
IconLucideActivity: typeof import('~icons/lucide/activity')['default']
|
||||
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
||||
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
|
||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
||||
IconLucideInfo: typeof import('~icons/lucide/info')['default']
|
||||
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
||||
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||
IconLucideX: typeof import('~icons/lucide/x')['default']
|
||||
ImportExportBase: typeof import('./components/importExport/Base.vue')['default']
|
||||
ImportExportImportExportList: typeof import('./components/importExport/ImportExportList.vue')['default']
|
||||
ImportExportImportExportSourcesList: typeof import('./components/importExport/ImportExportSourcesList.vue')['default']
|
||||
ImportExportImportExportStepsFileImport: typeof import('./components/importExport/ImportExportSteps/FileImport.vue')['default']
|
||||
ImportExportImportExportStepsMyCollectionImport: typeof import('./components/importExport/ImportExportSteps/MyCollectionImport.vue')['default']
|
||||
ImportExportImportExportStepsUrlImport: typeof import('./components/importExport/ImportExportSteps/UrlImport.vue')['default']
|
||||
InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default']
|
||||
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
||||
LensesRenderersAudioLensRenderer: typeof import('./components/lenses/renderers/AudioLensRenderer.vue')['default']
|
||||
LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default']
|
||||
LensesRenderersImageLensRenderer: typeof import('./components/lenses/renderers/ImageLensRenderer.vue')['default']
|
||||
LensesRenderersJSONLensRenderer: typeof import('./components/lenses/renderers/JSONLensRenderer.vue')['default']
|
||||
LensesRenderersPDFLensRenderer: typeof import('./components/lenses/renderers/PDFLensRenderer.vue')['default']
|
||||
LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default']
|
||||
LensesRenderersVideoLensRenderer: typeof import('./components/lenses/renderers/VideoLensRenderer.vue')['default']
|
||||
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
|
||||
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
|
||||
ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default']
|
||||
RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default']
|
||||
RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default']
|
||||
RealtimeLog: typeof import('./components/realtime/Log.vue')['default']
|
||||
RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default']
|
||||
RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default']
|
||||
SettingsExtension: typeof import('./components/settings/Extension.vue')['default']
|
||||
SettingsProxy: typeof import('./components/settings/Proxy.vue')['default']
|
||||
Share: typeof import('./components/share/index.vue')['default']
|
||||
ShareCreateModal: typeof import('./components/share/CreateModal.vue')['default']
|
||||
ShareCustomizeModal: typeof import('./components/share/CustomizeModal.vue')['default']
|
||||
ShareModal: typeof import('./components/share/Modal.vue')['default']
|
||||
ShareRequest: typeof import('./components/share/Request.vue')['default']
|
||||
ShareTemplatesButton: typeof import('./components/share/templates/Button.vue')['default']
|
||||
ShareTemplatesEmbeds: typeof import('./components/share/templates/Embeds.vue')['default']
|
||||
ShareTemplatesLink: typeof import('./components/share/templates/Link.vue')['default']
|
||||
SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default']
|
||||
SmartChangeLanguage: typeof import('./components/smart/ChangeLanguage.vue')['default']
|
||||
SmartColorModePicker: typeof import('./components/smart/ColorModePicker.vue')['default']
|
||||
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
|
||||
TabPrimary: typeof import('./components/tab/Primary.vue')['default']
|
||||
TabSecondary: typeof import('./components/tab/Secondary.vue')['default']
|
||||
Teams: typeof import('./components/teams/index.vue')['default']
|
||||
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
|
||||
TeamsEdit: typeof import('./components/teams/Edit.vue')['default']
|
||||
TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
|
||||
TeamsMemberStack: typeof import('./components/teams/MemberStack.vue')['default']
|
||||
TeamsModal: typeof import('./components/teams/Modal.vue')['default']
|
||||
TeamsTeam: typeof import('./components/teams/Team.vue')['default']
|
||||
Tippy: typeof import('vue-tippy')['Tippy']
|
||||
WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default']
|
||||
WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default']
|
||||
AppActionHandler: (typeof import("./components/app/ActionHandler.vue"))["default"]
|
||||
AppBanner: (typeof import("./components/app/Banner.vue"))["default"]
|
||||
AppContextMenu: (typeof import("./components/app/ContextMenu.vue"))["default"]
|
||||
AppDeveloperOptions: (typeof import("./components/app/DeveloperOptions.vue"))["default"]
|
||||
AppFooter: (typeof import("./components/app/Footer.vue"))["default"]
|
||||
AppGitHubStarButton: (typeof import("./components/app/GitHubStarButton.vue"))["default"]
|
||||
AppHeader: (typeof import("./components/app/Header.vue"))["default"]
|
||||
AppInspection: (typeof import("./components/app/Inspection.vue"))["default"]
|
||||
AppInterceptor: (typeof import("./components/app/Interceptor.vue"))["default"]
|
||||
AppLogo: (typeof import("./components/app/Logo.vue"))["default"]
|
||||
AppOptions: (typeof import("./components/app/Options.vue"))["default"]
|
||||
AppPaneLayout: (typeof import("./components/app/PaneLayout.vue"))["default"]
|
||||
AppShare: (typeof import("./components/app/Share.vue"))["default"]
|
||||
AppShortcuts: (typeof import("./components/app/Shortcuts.vue"))["default"]
|
||||
AppShortcutsEntry: (typeof import("./components/app/ShortcutsEntry.vue"))["default"]
|
||||
AppShortcutsPrompt: (typeof import("./components/app/ShortcutsPrompt.vue"))["default"]
|
||||
AppSidenav: (typeof import("./components/app/Sidenav.vue"))["default"]
|
||||
AppSpotlight: (typeof import("./components/app/spotlight/index.vue"))["default"]
|
||||
AppSpotlightEntry: (typeof import("./components/app/spotlight/Entry.vue"))["default"]
|
||||
AppSpotlightEntryGQLHistory: (typeof import("./components/app/spotlight/entry/GQLHistory.vue"))["default"]
|
||||
AppSpotlightEntryGQLRequest: (typeof import("./components/app/spotlight/entry/GQLRequest.vue"))["default"]
|
||||
AppSpotlightEntryIconSelected: (typeof import("./components/app/spotlight/entry/IconSelected.vue"))["default"]
|
||||
AppSpotlightEntryRESTHistory: (typeof import("./components/app/spotlight/entry/RESTHistory.vue"))["default"]
|
||||
AppSpotlightEntryRESTRequest: (typeof import("./components/app/spotlight/entry/RESTRequest.vue"))["default"]
|
||||
AppSupport: (typeof import("./components/app/Support.vue"))["default"]
|
||||
Collections: (typeof import("./components/collections/index.vue"))["default"]
|
||||
CollectionsAdd: (typeof import("./components/collections/Add.vue"))["default"]
|
||||
CollectionsAddFolder: (typeof import("./components/collections/AddFolder.vue"))["default"]
|
||||
CollectionsAddRequest: (typeof import("./components/collections/AddRequest.vue"))["default"]
|
||||
CollectionsCollection: (typeof import("./components/collections/Collection.vue"))["default"]
|
||||
CollectionsEdit: (typeof import("./components/collections/Edit.vue"))["default"]
|
||||
CollectionsEditFolder: (typeof import("./components/collections/EditFolder.vue"))["default"]
|
||||
CollectionsEditRequest: (typeof import("./components/collections/EditRequest.vue"))["default"]
|
||||
CollectionsGraphql: (typeof import("./components/collections/graphql/index.vue"))["default"]
|
||||
CollectionsGraphqlAdd: (typeof import("./components/collections/graphql/Add.vue"))["default"]
|
||||
CollectionsGraphqlAddFolder: (typeof import("./components/collections/graphql/AddFolder.vue"))["default"]
|
||||
CollectionsGraphqlAddRequest: (typeof import("./components/collections/graphql/AddRequest.vue"))["default"]
|
||||
CollectionsGraphqlCollection: (typeof import("./components/collections/graphql/Collection.vue"))["default"]
|
||||
CollectionsGraphqlEdit: (typeof import("./components/collections/graphql/Edit.vue"))["default"]
|
||||
CollectionsGraphqlEditFolder: (typeof import("./components/collections/graphql/EditFolder.vue"))["default"]
|
||||
CollectionsGraphqlEditRequest: (typeof import("./components/collections/graphql/EditRequest.vue"))["default"]
|
||||
CollectionsGraphqlFolder: (typeof import("./components/collections/graphql/Folder.vue"))["default"]
|
||||
CollectionsGraphqlImportExport: (typeof import("./components/collections/graphql/ImportExport.vue"))["default"]
|
||||
CollectionsGraphqlRequest: (typeof import("./components/collections/graphql/Request.vue"))["default"]
|
||||
CollectionsImportExport: (typeof import("./components/collections/ImportExport.vue"))["default"]
|
||||
CollectionsMyCollections: (typeof import("./components/collections/MyCollections.vue"))["default"]
|
||||
CollectionsProperties: (typeof import("./components/collections/Properties.vue"))["default"]
|
||||
CollectionsRequest: (typeof import("./components/collections/Request.vue"))["default"]
|
||||
CollectionsSaveRequest: (typeof import("./components/collections/SaveRequest.vue"))["default"]
|
||||
CollectionsTeamCollections: (typeof import("./components/collections/TeamCollections.vue"))["default"]
|
||||
CookiesAllModal: (typeof import("./components/cookies/AllModal.vue"))["default"]
|
||||
CookiesEditCookie: (typeof import("./components/cookies/EditCookie.vue"))["default"]
|
||||
Embeds: (typeof import("./components/embeds/index.vue"))["default"]
|
||||
Environments: (typeof import("./components/environments/index.vue"))["default"]
|
||||
EnvironmentsAdd: (typeof import("./components/environments/Add.vue"))["default"]
|
||||
EnvironmentsImportExport: (typeof import("./components/environments/ImportExport.vue"))["default"]
|
||||
EnvironmentsMy: (typeof import("./components/environments/my/index.vue"))["default"]
|
||||
EnvironmentsMyDetails: (typeof import("./components/environments/my/Details.vue"))["default"]
|
||||
EnvironmentsMyEnvironment: (typeof import("./components/environments/my/Environment.vue"))["default"]
|
||||
EnvironmentsSelector: (typeof import("./components/environments/Selector.vue"))["default"]
|
||||
EnvironmentsTeams: (typeof import("./components/environments/teams/index.vue"))["default"]
|
||||
EnvironmentsTeamsDetails: (typeof import("./components/environments/teams/Details.vue"))["default"]
|
||||
EnvironmentsTeamsEnvironment: (typeof import("./components/environments/teams/Environment.vue"))["default"]
|
||||
FirebaseLogin: (typeof import("./components/firebase/Login.vue"))["default"]
|
||||
FirebaseLogout: (typeof import("./components/firebase/Logout.vue"))["default"]
|
||||
GraphqlAuthorization: (typeof import("./components/graphql/Authorization.vue"))["default"]
|
||||
GraphqlField: (typeof import("./components/graphql/Field.vue"))["default"]
|
||||
GraphqlHeaders: (typeof import("./components/graphql/Headers.vue"))["default"]
|
||||
GraphqlQuery: (typeof import("./components/graphql/Query.vue"))["default"]
|
||||
GraphqlRequest: (typeof import("./components/graphql/Request.vue"))["default"]
|
||||
GraphqlRequestOptions: (typeof import("./components/graphql/RequestOptions.vue"))["default"]
|
||||
GraphqlRequestTab: (typeof import("./components/graphql/RequestTab.vue"))["default"]
|
||||
GraphqlResponse: (typeof import("./components/graphql/Response.vue"))["default"]
|
||||
GraphqlSidebar: (typeof import("./components/graphql/Sidebar.vue"))["default"]
|
||||
GraphqlSubscriptionLog: (typeof import("./components/graphql/SubscriptionLog.vue"))["default"]
|
||||
GraphqlTabHead: (typeof import("./components/graphql/TabHead.vue"))["default"]
|
||||
GraphqlType: (typeof import("./components/graphql/Type.vue"))["default"]
|
||||
GraphqlTypeLink: (typeof import("./components/graphql/TypeLink.vue"))["default"]
|
||||
GraphqlVariable: (typeof import("./components/graphql/Variable.vue"))["default"]
|
||||
History: (typeof import("./components/history/index.vue"))["default"]
|
||||
HistoryGraphqlCard: (typeof import("./components/history/graphql/Card.vue"))["default"]
|
||||
HistoryRestCard: (typeof import("./components/history/rest/Card.vue"))["default"]
|
||||
HoppButtonPrimary: (typeof import("@hoppscotch/ui"))["HoppButtonPrimary"]
|
||||
HoppButtonSecondary: (typeof import("@hoppscotch/ui"))["HoppButtonSecondary"]
|
||||
HoppSmartAnchor: (typeof import("@hoppscotch/ui"))["HoppSmartAnchor"]
|
||||
HoppSmartCheckbox: (typeof import("@hoppscotch/ui"))["HoppSmartCheckbox"]
|
||||
HoppSmartConfirmModal: (typeof import("@hoppscotch/ui"))["HoppSmartConfirmModal"]
|
||||
HoppSmartExpand: (typeof import("@hoppscotch/ui"))["HoppSmartExpand"]
|
||||
HoppSmartFileChip: (typeof import("@hoppscotch/ui"))["HoppSmartFileChip"]
|
||||
HoppSmartInput: (typeof import("@hoppscotch/ui"))["HoppSmartInput"]
|
||||
HoppSmartIntersection: (typeof import("@hoppscotch/ui"))["HoppSmartIntersection"]
|
||||
HoppSmartItem: (typeof import("@hoppscotch/ui"))["HoppSmartItem"]
|
||||
HoppSmartLink: (typeof import("@hoppscotch/ui"))["HoppSmartLink"]
|
||||
HoppSmartModal: (typeof import("@hoppscotch/ui"))["HoppSmartModal"]
|
||||
HoppSmartPicture: (typeof import("@hoppscotch/ui"))["HoppSmartPicture"]
|
||||
HoppSmartPlaceholder: (typeof import("@hoppscotch/ui"))["HoppSmartPlaceholder"]
|
||||
HoppSmartProgressRing: (typeof import("@hoppscotch/ui"))["HoppSmartProgressRing"]
|
||||
HoppSmartRadio: (typeof import("@hoppscotch/ui"))["HoppSmartRadio"]
|
||||
HoppSmartRadioGroup: (typeof import("@hoppscotch/ui"))["HoppSmartRadioGroup"]
|
||||
HoppSmartSelectWrapper: (typeof import("@hoppscotch/ui"))["HoppSmartSelectWrapper"]
|
||||
HoppSmartSlideOver: (typeof import("@hoppscotch/ui"))["HoppSmartSlideOver"]
|
||||
HoppSmartSpinner: (typeof import("@hoppscotch/ui"))["HoppSmartSpinner"]
|
||||
HoppSmartTab: (typeof import("@hoppscotch/ui"))["HoppSmartTab"]
|
||||
HoppSmartTabs: (typeof import("@hoppscotch/ui"))["HoppSmartTabs"]
|
||||
HoppSmartToggle: (typeof import("@hoppscotch/ui"))["HoppSmartToggle"]
|
||||
HoppSmartTree: (typeof import("@hoppscotch/ui"))["HoppSmartTree"]
|
||||
HoppSmartWindow: (typeof import("@hoppscotch/ui"))["HoppSmartWindow"]
|
||||
HoppSmartWindows: (typeof import("@hoppscotch/ui"))["HoppSmartWindows"]
|
||||
HttpAuthorization: (typeof import("./components/http/Authorization.vue"))["default"]
|
||||
HttpAuthorizationApiKey: (typeof import("./components/http/authorization/ApiKey.vue"))["default"]
|
||||
HttpAuthorizationBasic: (typeof import("./components/http/authorization/Basic.vue"))["default"]
|
||||
HttpBody: (typeof import("./components/http/Body.vue"))["default"]
|
||||
HttpBodyParameters: (typeof import("./components/http/BodyParameters.vue"))["default"]
|
||||
HttpCodegenModal: (typeof import("./components/http/CodegenModal.vue"))["default"]
|
||||
HttpHeaders: (typeof import("./components/http/Headers.vue"))["default"]
|
||||
HttpImportCurl: (typeof import("./components/http/ImportCurl.vue"))["default"]
|
||||
HttpOAuth2Authorization: (typeof import("./components/http/OAuth2Authorization.vue"))["default"]
|
||||
HttpParameters: (typeof import("./components/http/Parameters.vue"))["default"]
|
||||
HttpPreRequestScript: (typeof import("./components/http/PreRequestScript.vue"))["default"]
|
||||
HttpRawBody: (typeof import("./components/http/RawBody.vue"))["default"]
|
||||
HttpReqChangeConfirmModal: (typeof import("./components/http/ReqChangeConfirmModal.vue"))["default"]
|
||||
HttpRequest: (typeof import("./components/http/Request.vue"))["default"]
|
||||
HttpRequestOptions: (typeof import("./components/http/RequestOptions.vue"))["default"]
|
||||
HttpRequestTab: (typeof import("./components/http/RequestTab.vue"))["default"]
|
||||
HttpResponse: (typeof import("./components/http/Response.vue"))["default"]
|
||||
HttpResponseMeta: (typeof import("./components/http/ResponseMeta.vue"))["default"]
|
||||
HttpSidebar: (typeof import("./components/http/Sidebar.vue"))["default"]
|
||||
HttpTabHead: (typeof import("./components/http/TabHead.vue"))["default"]
|
||||
HttpTestResult: (typeof import("./components/http/TestResult.vue"))["default"]
|
||||
HttpTestResultEntry: (typeof import("./components/http/TestResultEntry.vue"))["default"]
|
||||
HttpTestResultEnv: (typeof import("./components/http/TestResultEnv.vue"))["default"]
|
||||
HttpTestResultReport: (typeof import("./components/http/TestResultReport.vue"))["default"]
|
||||
HttpTests: (typeof import("./components/http/Tests.vue"))["default"]
|
||||
HttpURLEncodedParams: (typeof import("./components/http/URLEncodedParams.vue"))["default"]
|
||||
IconLucideActivity: (typeof import("~icons/lucide/activity"))["default"]
|
||||
IconLucideAlertTriangle: (typeof import("~icons/lucide/alert-triangle"))["default"]
|
||||
IconLucideArrowLeft: (typeof import("~icons/lucide/arrow-left"))["default"]
|
||||
IconLucideArrowUpRight: (typeof import("~icons/lucide/arrow-up-right"))["default"]
|
||||
IconLucideBrush: (typeof import("~icons/lucide/brush"))["default"]
|
||||
IconLucideCheckCircle: (typeof import("~icons/lucide/check-circle"))["default"]
|
||||
IconLucideChevronRight: (typeof import("~icons/lucide/chevron-right"))["default"]
|
||||
IconLucideGlobe: (typeof import("~icons/lucide/globe"))["default"]
|
||||
IconLucideHelpCircle: (typeof import("~icons/lucide/help-circle"))["default"]
|
||||
IconLucideInbox: (typeof import("~icons/lucide/inbox"))["default"]
|
||||
IconLucideInfo: (typeof import("~icons/lucide/info"))["default"]
|
||||
IconLucideLayers: (typeof import("~icons/lucide/layers"))["default"]
|
||||
IconLucideListEnd: (typeof import("~icons/lucide/list-end"))["default"]
|
||||
IconLucideMinus: (typeof import("~icons/lucide/minus"))["default"]
|
||||
IconLucideRss: (typeof import("~icons/lucide/rss"))["default"]
|
||||
IconLucideSearch: (typeof import("~icons/lucide/search"))["default"]
|
||||
IconLucideUsers: (typeof import("~icons/lucide/users"))["default"]
|
||||
IconLucideX: (typeof import("~icons/lucide/x"))["default"]
|
||||
ImportExportBase: (typeof import("./components/importExport/Base.vue"))["default"]
|
||||
ImportExportImportExportList: (typeof import("./components/importExport/ImportExportList.vue"))["default"]
|
||||
ImportExportImportExportSourcesList: (typeof import("./components/importExport/ImportExportSourcesList.vue"))["default"]
|
||||
ImportExportImportExportStepsFileImport: (typeof import("./components/importExport/ImportExportSteps/FileImport.vue"))["default"]
|
||||
ImportExportImportExportStepsMyCollectionImport: (typeof import("./components/importExport/ImportExportSteps/MyCollectionImport.vue"))["default"]
|
||||
ImportExportImportExportStepsUrlImport: (typeof import("./components/importExport/ImportExportSteps/UrlImport.vue"))["default"]
|
||||
InterceptorsErrorPlaceholder: (typeof import("./components/interceptors/ErrorPlaceholder.vue"))["default"]
|
||||
InterceptorsExtensionSubtitle: (typeof import("./components/interceptors/ExtensionSubtitle.vue"))["default"]
|
||||
LensesHeadersRenderer: (typeof import("./components/lenses/HeadersRenderer.vue"))["default"]
|
||||
LensesHeadersRendererEntry: (typeof import("./components/lenses/HeadersRendererEntry.vue"))["default"]
|
||||
LensesRenderersAudioLensRenderer: (typeof import("./components/lenses/renderers/AudioLensRenderer.vue"))["default"]
|
||||
LensesRenderersHTMLLensRenderer: (typeof import("./components/lenses/renderers/HTMLLensRenderer.vue"))["default"]
|
||||
LensesRenderersImageLensRenderer: (typeof import("./components/lenses/renderers/ImageLensRenderer.vue"))["default"]
|
||||
LensesRenderersJSONLensRenderer: (typeof import("./components/lenses/renderers/JSONLensRenderer.vue"))["default"]
|
||||
LensesRenderersPDFLensRenderer: (typeof import("./components/lenses/renderers/PDFLensRenderer.vue"))["default"]
|
||||
LensesRenderersRawLensRenderer: (typeof import("./components/lenses/renderers/RawLensRenderer.vue"))["default"]
|
||||
LensesRenderersVideoLensRenderer: (typeof import("./components/lenses/renderers/VideoLensRenderer.vue"))["default"]
|
||||
LensesRenderersXMLLensRenderer: (typeof import("./components/lenses/renderers/XMLLensRenderer.vue"))["default"]
|
||||
LensesResponseBodyRenderer: (typeof import("./components/lenses/ResponseBodyRenderer.vue"))["default"]
|
||||
ProfileUserDelete: (typeof import("./components/profile/UserDelete.vue"))["default"]
|
||||
RealtimeCommunication: (typeof import("./components/realtime/Communication.vue"))["default"]
|
||||
RealtimeConnectionConfig: (typeof import("./components/realtime/ConnectionConfig.vue"))["default"]
|
||||
RealtimeLog: (typeof import("./components/realtime/Log.vue"))["default"]
|
||||
RealtimeLogEntry: (typeof import("./components/realtime/LogEntry.vue"))["default"]
|
||||
RealtimeSubscription: (typeof import("./components/realtime/Subscription.vue"))["default"]
|
||||
SettingsExtension: (typeof import("./components/settings/Extension.vue"))["default"]
|
||||
SettingsProxy: (typeof import("./components/settings/Proxy.vue"))["default"]
|
||||
Share: (typeof import("./components/share/index.vue"))["default"]
|
||||
ShareCreateModal: (typeof import("./components/share/CreateModal.vue"))["default"]
|
||||
ShareCustomizeModal: (typeof import("./components/share/CustomizeModal.vue"))["default"]
|
||||
ShareModal: (typeof import("./components/share/Modal.vue"))["default"]
|
||||
ShareRequest: (typeof import("./components/share/Request.vue"))["default"]
|
||||
ShareTemplatesButton: (typeof import("./components/share/templates/Button.vue"))["default"]
|
||||
ShareTemplatesEmbeds: (typeof import("./components/share/templates/Embeds.vue"))["default"]
|
||||
ShareTemplatesLink: (typeof import("./components/share/templates/Link.vue"))["default"]
|
||||
SmartAccentModePicker: (typeof import("./components/smart/AccentModePicker.vue"))["default"]
|
||||
SmartChangeLanguage: (typeof import("./components/smart/ChangeLanguage.vue"))["default"]
|
||||
SmartColorModePicker: (typeof import("./components/smart/ColorModePicker.vue"))["default"]
|
||||
SmartEnvInput: (typeof import("./components/smart/EnvInput.vue"))["default"]
|
||||
TabPrimary: (typeof import("./components/tab/Primary.vue"))["default"]
|
||||
TabSecondary: (typeof import("./components/tab/Secondary.vue"))["default"]
|
||||
Teams: (typeof import("./components/teams/index.vue"))["default"]
|
||||
TeamsAdd: (typeof import("./components/teams/Add.vue"))["default"]
|
||||
TeamsEdit: (typeof import("./components/teams/Edit.vue"))["default"]
|
||||
TeamsInvite: (typeof import("./components/teams/Invite.vue"))["default"]
|
||||
TeamsMemberStack: (typeof import("./components/teams/MemberStack.vue"))["default"]
|
||||
TeamsModal: (typeof import("./components/teams/Modal.vue"))["default"]
|
||||
TeamsTeam: (typeof import("./components/teams/Team.vue"))["default"]
|
||||
Tippy: (typeof import("vue-tippy"))["Tippy"]
|
||||
WorkspaceCurrent: (typeof import("./components/workspace/Current.vue"))["default"]
|
||||
WorkspaceSelector: (typeof import("./components/workspace/Selector.vue"))["default"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div
|
||||
ref="contextMenuRef"
|
||||
class="fixed translate-y-8 transform rounded border border-dividerDark bg-popover p-2 shadow-lg"
|
||||
:style="`top: ${position.top}px; left: ${position.left}px; z-index: 1000;`"
|
||||
class="fixed transform -translate-x-10 -translate-y-8 rounded border border-dividerDark bg-popover p-2 shadow-lg"
|
||||
:style="`top: ${position.top}px; left: ${position.left}px; z-index: 100;`"
|
||||
>
|
||||
<div v-if="contextMenuOptions" class="flex flex-col">
|
||||
<div
|
||||
|
||||
@@ -330,11 +330,11 @@ const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||
|
||||
const workspace = workspaceService.currentWorkspace
|
||||
|
||||
const workspaceName = computed(() =>
|
||||
workspace.value.type === "personal"
|
||||
const workspaceName = computed(() => {
|
||||
return workspace.value.type === "personal"
|
||||
? t("workspace.personal")
|
||||
: workspace.value.teamName
|
||||
)
|
||||
})
|
||||
|
||||
const refetchTeams = () => {
|
||||
teamListAdapter.fetchList()
|
||||
|
||||
@@ -32,9 +32,13 @@
|
||||
{{ tab.document.request.method }}
|
||||
</span>
|
||||
<div
|
||||
class="flex items-center flex-1 flex-shrink-0 min-w-0 px-4 py-2 truncate rounded-r"
|
||||
class="flex items-center flex-1 flex-shrink-0 min-w-0 truncate rounded-r"
|
||||
>
|
||||
{{ tab.document.request.endpoint }}
|
||||
<SmartEnvInput
|
||||
v-model="tab.document.request.endpoint"
|
||||
:readonly="true"
|
||||
:envs="tabRequestVariables"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mt-2 space-x-2 sm:mt-0">
|
||||
@@ -69,6 +73,7 @@
|
||||
v-model="tab.document.request"
|
||||
v-model:option-tab="selectedOptionTab"
|
||||
:properties="properties"
|
||||
:envs="tabRequestVariables"
|
||||
/>
|
||||
<HttpResponse
|
||||
v-if="tab.document.response"
|
||||
@@ -117,6 +122,15 @@ const sharedRequestURL = computed(() => {
|
||||
return `${shortcodeBaseURL}/r/${props.sharedRequestID}`
|
||||
})
|
||||
|
||||
const tabRequestVariables = computed(() => {
|
||||
return tab.value.document.request.requestVariables.map(({ key, value }) => ({
|
||||
key,
|
||||
value,
|
||||
secret: false,
|
||||
sourceEnv: "RequestVariable",
|
||||
}))
|
||||
})
|
||||
|
||||
const { subscribeToStream } = useStreamSubscriber()
|
||||
|
||||
const newSendRequest = async () => {
|
||||
|
||||
@@ -165,7 +165,7 @@ import { environmentsStore } from "~/newstore/environments"
|
||||
import { platform } from "~/platform"
|
||||
import { useService } from "dioc/vue"
|
||||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
import { uniqueId } from "lodash-es"
|
||||
import { uniqueID } from "~/helpers/utils/uniqueID"
|
||||
|
||||
type EnvironmentVariable = {
|
||||
id: number
|
||||
@@ -277,7 +277,7 @@ const workingEnv = computed(() => {
|
||||
} as Environment
|
||||
} else if (props.action === "new") {
|
||||
return {
|
||||
id: uniqueId(),
|
||||
id: uniqueID(),
|
||||
name: "",
|
||||
variables: props.envVars(),
|
||||
}
|
||||
@@ -331,7 +331,7 @@ watch(
|
||||
: "variables"
|
||||
|
||||
if (props.editingEnvironmentIndex !== "Global") {
|
||||
editingID.value = workingEnv.value?.id ?? uniqueId()
|
||||
editingID.value = workingEnv.value?.id || uniqueID()
|
||||
}
|
||||
vars.value = pipe(
|
||||
workingEnv.value?.variables ?? [],
|
||||
@@ -416,14 +416,12 @@ const saveEnvironment = () => {
|
||||
|
||||
const variables = pipe(
|
||||
filteredVariables,
|
||||
A.map((e) =>
|
||||
e.secret ? { key: e.key, secret: e.secret, value: undefined } : e
|
||||
)
|
||||
A.map((e) => (e.secret ? { key: e.key, secret: e.secret } : e))
|
||||
)
|
||||
|
||||
const environmentUpdated: Environment = {
|
||||
v: 1,
|
||||
id: uniqueId(),
|
||||
id: uniqueID(),
|
||||
name: editingName.value,
|
||||
variables,
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ const saveEnvironment = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const filterdVariables = pipe(
|
||||
const filteredVariables = pipe(
|
||||
vars.value,
|
||||
A.filterMap(
|
||||
flow(
|
||||
@@ -371,17 +371,15 @@ const saveEnvironment = async () => {
|
||||
)
|
||||
|
||||
const secretVariables = pipe(
|
||||
filterdVariables,
|
||||
filteredVariables,
|
||||
A.filterMapWithIndex((i, e) =>
|
||||
e.secret ? O.some({ key: e.key, value: e.value, varIndex: i }) : O.none
|
||||
)
|
||||
)
|
||||
|
||||
const variables = pipe(
|
||||
filterdVariables,
|
||||
A.map((e) =>
|
||||
e.secret ? { key: e.key, secret: e.secret, value: undefined } : e
|
||||
)
|
||||
filteredVariables,
|
||||
A.map((e) => (e.secret ? { key: e.key, secret: e.secret } : e))
|
||||
)
|
||||
|
||||
const environmentUpdated: Environment = {
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
<div v-else class="flex flex-1 border-b border-dividerLight">
|
||||
<div class="w-2/3 border-r border-dividerLight">
|
||||
<div v-if="auth.authType === 'basic'">
|
||||
<HttpAuthorizationBasic v-model="auth" />
|
||||
<HttpAuthorizationBasic v-model="auth" :envs="envs" />
|
||||
</div>
|
||||
<div v-if="auth.authType === 'inherit'" class="p-4">
|
||||
<span v-if="inheritedProperties?.auth">
|
||||
@@ -169,17 +169,26 @@
|
||||
</div>
|
||||
<div v-if="auth.authType === 'bearer'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="auth.token" placeholder="Token" />
|
||||
<SmartEnvInput
|
||||
v-model="auth.token"
|
||||
placeholder="Token"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="auth.authType === 'oauth-2'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="auth.token" placeholder="Token" />
|
||||
<SmartEnvInput
|
||||
v-model="auth.token"
|
||||
placeholder="Token"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<HttpOAuth2Authorization v-model="auth" />
|
||||
<HttpOAuth2Authorization v-model="auth" :envs="envs" />
|
||||
</div>
|
||||
<div v-if="auth.authType === 'api-key'">
|
||||
<HttpAuthorizationApiKey v-model="auth" />
|
||||
<HttpAuthorizationApiKey v-model="auth" :envs="envs" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -215,6 +224,7 @@ import { useColorMode } from "@composables/theming"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { onMounted } from "vue"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -225,6 +235,7 @@ const props = defineProps<{
|
||||
isCollectionProperty?: boolean
|
||||
isRootCollection?: boolean
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -100,10 +100,12 @@
|
||||
<HttpBodyParameters
|
||||
v-if="body.contentType === 'multipart/form-data'"
|
||||
v-model="body"
|
||||
:envs="envs"
|
||||
/>
|
||||
<HttpURLEncodedParams
|
||||
v-else-if="body.contentType === 'application/x-www-form-urlencoded'"
|
||||
v-model="body"
|
||||
:envs="envs"
|
||||
/>
|
||||
<HttpRawBody v-else-if="body.contentType !== null" v-model="body" />
|
||||
<HoppSmartPlaceholder
|
||||
@@ -141,6 +143,7 @@ import IconExternalLink from "~icons/lucide/external-link"
|
||||
import IconInfo from "~icons/lucide/info"
|
||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||
import { RESTOptionTabs } from "./RequestOptions.vue"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const t = useI18n()
|
||||
@@ -148,6 +151,7 @@ const t = useI18n()
|
||||
const props = defineProps<{
|
||||
body: HoppRESTReqBody
|
||||
headers: HoppRESTHeader[]
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -65,6 +65,8 @@
|
||||
<SmartEnvInput
|
||||
v-model="entry.key"
|
||||
:placeholder="`${t('count.parameter', { count: index + 1 })}`"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateBodyParam(index, {
|
||||
key: $event,
|
||||
@@ -87,6 +89,8 @@
|
||||
<SmartEnvInput
|
||||
v-model="entry.value"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateBodyParam(index, {
|
||||
key: entry.key,
|
||||
@@ -190,11 +194,13 @@ import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
type Body = HoppRESTReqBody & { contentType: "multipart/form-data" }
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Body
|
||||
envs: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -188,11 +188,25 @@ const copyCodeIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||
|
||||
const requestCode = computed(() => {
|
||||
const aggregateEnvs = getAggregateEnvs()
|
||||
const requestVariables = request.value.requestVariables.map(
|
||||
(requestVariable) => {
|
||||
if (requestVariable.active)
|
||||
return {
|
||||
key: requestVariable.key,
|
||||
value: requestVariable.value,
|
||||
secret: false,
|
||||
}
|
||||
return {}
|
||||
}
|
||||
)
|
||||
const env: Environment = {
|
||||
v: 1,
|
||||
id: "env",
|
||||
name: "Env",
|
||||
variables: aggregateEnvs,
|
||||
variables: [
|
||||
...(requestVariables as Environment["variables"]),
|
||||
...aggregateEnvs,
|
||||
],
|
||||
}
|
||||
const effectiveRequest = getEffectiveRESTRequest(request.value, env)
|
||||
|
||||
@@ -212,6 +226,12 @@ const requestCode = computed(() => {
|
||||
active: true,
|
||||
})),
|
||||
endpoint: effectiveRequest.effectiveFinalURL,
|
||||
requestVariables: effectiveRequest.effectiveFinalRequestVariables.map(
|
||||
(requestVariable) => ({
|
||||
...requestVariable,
|
||||
active: true,
|
||||
})
|
||||
),
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
@click="clearContent()"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="bulkMode"
|
||||
v-if="bulkHeaders"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': WRAP_LINES }"
|
||||
@@ -92,6 +92,8 @@
|
||||
:auto-complete-source="commonHeaders"
|
||||
:env-index="index"
|
||||
:inspection-results="getInspectorResult(headerKeyResults, index)"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateHeader(index, {
|
||||
id: header.id,
|
||||
@@ -108,6 +110,8 @@
|
||||
getInspectorResult(headerValueResults, index)
|
||||
"
|
||||
:env-index="index"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateHeader(index, {
|
||||
id: header.id,
|
||||
@@ -329,7 +333,11 @@ import {
|
||||
getComputedHeaders,
|
||||
getComputedAuthHeaders,
|
||||
} from "~/helpers/utils/EffectiveURL"
|
||||
import { aggregateEnvs$, getAggregateEnvs } from "~/newstore/environments"
|
||||
import {
|
||||
AggregateEnvironment,
|
||||
aggregateEnvs$,
|
||||
getAggregateEnvs,
|
||||
} from "~/newstore/environments"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { useService } from "dioc/vue"
|
||||
import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||
@@ -359,6 +367,7 @@ const props = defineProps<{
|
||||
modelValue: HoppRESTRequest
|
||||
isCollectionProperty?: boolean
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -6,31 +6,52 @@
|
||||
:styles="
|
||||
hasAccessTokenOrAuthURL ? 'pointer-events-none opacity-70' : ''
|
||||
"
|
||||
:auto-complete-env="true"
|
||||
placeholder="OpenID Connect Discovery URL"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="authURL"
|
||||
placeholder="Authorization URL"
|
||||
:auto-complete-env="true"
|
||||
:styles="hasOIDCURL ? 'pointer-events-none opacity-70' : ''"
|
||||
></SmartEnvInput>
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="accessTokenURL"
|
||||
placeholder="Access Token URL"
|
||||
:auto-complete-env="true"
|
||||
:styles="hasOIDCURL ? 'pointer-events-none opacity-70' : ''"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="clientID" placeholder="Client ID" />
|
||||
<SmartEnvInput
|
||||
v-model="clientID"
|
||||
:auto-complete-env="true"
|
||||
placeholder="Client ID"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="clientSecret" placeholder="Client Secret" />
|
||||
<SmartEnvInput
|
||||
v-model="clientSecret"
|
||||
:auto-complete-env="true"
|
||||
placeholder="Client Secret"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="scope" placeholder="Scope" />
|
||||
<SmartEnvInput
|
||||
v-model="scope"
|
||||
:auto-complete-env="true"
|
||||
placeholder="Scope"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<HoppButtonSecondary
|
||||
@@ -62,6 +83,7 @@ const toast = useToast()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuthOAuth2 | HoppGQLAuthOAuth2
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -87,6 +87,8 @@
|
||||
:inspection-results="
|
||||
getInspectorResult(parameterKeyResults, index)
|
||||
"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateParam(index, {
|
||||
id: param.id,
|
||||
@@ -102,6 +104,8 @@
|
||||
:inspection-results="
|
||||
getInspectorResult(parameterValueResults, index)
|
||||
"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateParam(index, {
|
||||
id: param.id,
|
||||
@@ -209,6 +213,7 @@ import { InspectionService, InspectorResult } from "~/services/inspection"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { useNestedSetting } from "~/composables/settings"
|
||||
import { toggleNestedSetting } from "~/newstore/settings"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
@@ -242,6 +247,7 @@ useCodemirror(
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTParam[]
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
v-model="tab.document.request.endpoint"
|
||||
:placeholder="`${t('request.url')}`"
|
||||
:auto-complete-source="userHistories"
|
||||
:auto-complete-env="true"
|
||||
:inspection-results="tabResults"
|
||||
@paste="onPasteUrl($event)"
|
||||
@enter="newSendRequest"
|
||||
|
||||
@@ -5,48 +5,51 @@
|
||||
render-inactive-tabs
|
||||
>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('params') : true"
|
||||
v-if="properties?.includes('params') ?? true"
|
||||
:id="'params'"
|
||||
:label="`${t('tab.parameters')}`"
|
||||
:info="`${newActiveParamsCount$}`"
|
||||
:info="`${newActiveParamsCount}`"
|
||||
>
|
||||
<HttpParameters v-model="request.params" />
|
||||
<HttpParameters v-model="request.params" :envs="envs" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('bodyParams') : true"
|
||||
v-if="properties?.includes('bodyParams') ?? true"
|
||||
:id="'bodyParams'"
|
||||
:label="`${t('tab.body')}`"
|
||||
>
|
||||
<HttpBody
|
||||
v-model:headers="request.headers"
|
||||
v-model:body="request.body"
|
||||
:envs="envs"
|
||||
@change-tab="changeOptionTab"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('headers') : true"
|
||||
v-if="properties?.includes('headers') ?? true"
|
||||
:id="'headers'"
|
||||
:label="`${t('tab.headers')}`"
|
||||
:info="`${newActiveHeadersCount$}`"
|
||||
:info="`${newActiveHeadersCount}`"
|
||||
>
|
||||
<HttpHeaders
|
||||
v-model="request"
|
||||
:inherited-properties="inheritedProperties"
|
||||
:envs="envs"
|
||||
@change-tab="changeOptionTab"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('authorization') : true"
|
||||
v-if="properties?.includes('authorization') ?? true"
|
||||
:id="'authorization'"
|
||||
:label="`${t('tab.authorization')}`"
|
||||
>
|
||||
<HttpAuthorization
|
||||
v-model="request.auth"
|
||||
:inherited-properties="inheritedProperties"
|
||||
:envs="envs"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('preRequestScript') : true"
|
||||
v-if="properties?.includes('preRequestScript') ?? true"
|
||||
:id="'preRequestScript'"
|
||||
:label="`${t('tab.pre_request_script')}`"
|
||||
:indicator="
|
||||
@@ -58,7 +61,7 @@
|
||||
<HttpPreRequestScript v-model="request.preRequestScript" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('tests') : true"
|
||||
v-if="properties?.includes('tests') ?? true"
|
||||
:id="'tests'"
|
||||
:label="`${t('tab.tests')}`"
|
||||
:indicator="
|
||||
@@ -67,6 +70,15 @@
|
||||
>
|
||||
<HttpTests v-model="request.testScript" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties?.includes('requestVariables') ?? true"
|
||||
:id="'requestVariables'"
|
||||
:label="`${t('tab.variables')}`"
|
||||
:info="`${newActiveRequestVariablesCount}`"
|
||||
:align-last="true"
|
||||
>
|
||||
<HttpRequestVariables v-model="request.requestVariables" />
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
</template>
|
||||
|
||||
@@ -77,6 +89,7 @@ import { useVModel } from "@vueuse/core"
|
||||
import { computed } from "vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const VALID_OPTION_TABS = [
|
||||
"params",
|
||||
@@ -85,6 +98,7 @@ const VALID_OPTION_TABS = [
|
||||
"authorization",
|
||||
"preRequestScript",
|
||||
"tests",
|
||||
"requestVariables",
|
||||
] as const
|
||||
|
||||
export type RESTOptionTabs = (typeof VALID_OPTION_TABS)[number]
|
||||
@@ -98,6 +112,7 @@ const props = withDefaults(
|
||||
optionTab: RESTOptionTabs
|
||||
properties?: string[]
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
envs?: AggregateEnvironment[]
|
||||
}>(),
|
||||
{
|
||||
optionTab: "params",
|
||||
@@ -116,22 +131,27 @@ const changeOptionTab = (e: RESTOptionTabs) => {
|
||||
selectedOptionTab.value = e
|
||||
}
|
||||
|
||||
const newActiveParamsCount$ = computed(() => {
|
||||
const e = request.value.params.filter(
|
||||
(x) => x.active && (x.key !== "" || x.value !== "")
|
||||
const newActiveParamsCount = computed(() => {
|
||||
const count = request.value.params.filter(
|
||||
(x) => x.active && (x.key || x.value)
|
||||
).length
|
||||
|
||||
if (e === 0) return null
|
||||
return `${e}`
|
||||
return count ? count : null
|
||||
})
|
||||
|
||||
const newActiveHeadersCount$ = computed(() => {
|
||||
const e = request.value.headers.filter(
|
||||
(x) => x.active && (x.key !== "" || x.value !== "")
|
||||
const newActiveHeadersCount = computed(() => {
|
||||
const count = request.value.headers.filter(
|
||||
(x) => x.active && (x.key || x.value)
|
||||
).length
|
||||
|
||||
if (e === 0) return null
|
||||
return `${e}`
|
||||
return count ? count : null
|
||||
})
|
||||
|
||||
const newActiveRequestVariablesCount = computed(() => {
|
||||
const count = request.value.requestVariables.filter(
|
||||
(x) => x.active && (x.key || x.value)
|
||||
).length
|
||||
return count ? count : null
|
||||
})
|
||||
|
||||
defineActionHandler("request.open-tab", ({ tab }) => {
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
<template>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky top-upperMobileSecondaryStickyFold z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4 sm:top-upperSecondaryStickyFold"
|
||||
>
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("request.request_variables") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/documentation/features/rest-api-testing"
|
||||
blank
|
||||
:title="t('app.wiki')"
|
||||
:icon="IconHelpCircle"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.clear_all')"
|
||||
:icon="IconTrash2"
|
||||
@click="clearContent()"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="bulkVariables"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': WRAP_LINES }"
|
||||
:icon="IconWrapText"
|
||||
@click.prevent="
|
||||
toggleNestedSetting('WRAP_LINES', 'httpRequestVariables')
|
||||
"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.bulk_mode')"
|
||||
:icon="IconEdit"
|
||||
:class="{ '!text-accent': bulkMode }"
|
||||
@click="bulkMode = !bulkMode"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('add.new')"
|
||||
:icon="IconPlus"
|
||||
:disabled="bulkMode"
|
||||
@click="addVariable"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="bulkMode" class="h-full relative">
|
||||
<div ref="bulkEditor" class="absolute inset-0"></div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<draggable
|
||||
v-model="workingRequestVariables"
|
||||
item-key="id"
|
||||
animation="250"
|
||||
handle=".draggable-handle"
|
||||
draggable=".draggable-content"
|
||||
ghost-class="cursor-move"
|
||||
chosen-class="bg-primaryLight"
|
||||
drag-class="cursor-grabbing"
|
||||
>
|
||||
<template #item="{ element: variable, index }">
|
||||
<div
|
||||
class="draggable-content group flex divide-x divide-dividerLight border-b border-dividerLight"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{
|
||||
theme: 'tooltip',
|
||||
delay: [500, 20],
|
||||
content:
|
||||
index !== workingRequestVariables?.length - 1
|
||||
? t('action.drag_to_reorder')
|
||||
: null,
|
||||
}"
|
||||
:icon="IconGripVertical"
|
||||
class="opacity-0"
|
||||
:class="{
|
||||
'draggable-handle cursor-grab group-hover:opacity-100':
|
||||
index !== workingRequestVariables?.length - 1,
|
||||
}"
|
||||
tabindex="-1"
|
||||
/>
|
||||
<SmartEnvInput
|
||||
v-model="variable.key"
|
||||
:placeholder="`${t('count.variable', { count: index + 1 })}`"
|
||||
@change="
|
||||
updateVariable(index, {
|
||||
id: variable.id,
|
||||
key: $event,
|
||||
value: variable.value,
|
||||
active: variable.active,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<SmartEnvInput
|
||||
v-model="variable.value"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
@change="
|
||||
updateVariable(index, {
|
||||
id: variable.id,
|
||||
key: variable.key,
|
||||
value: $event,
|
||||
active: variable.active,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="
|
||||
variable.hasOwnProperty('active')
|
||||
? variable.active
|
||||
? t('action.turn_off')
|
||||
: t('action.turn_on')
|
||||
: t('action.turn_off')
|
||||
"
|
||||
:icon="
|
||||
variable.hasOwnProperty('active')
|
||||
? variable.active
|
||||
? IconCheckCircle
|
||||
: IconCircle
|
||||
: IconCheckCircle
|
||||
"
|
||||
color="green"
|
||||
@click="
|
||||
updateVariable(index, {
|
||||
id: variable.id,
|
||||
key: variable.key,
|
||||
value: variable.value,
|
||||
active: variable.hasOwnProperty('active')
|
||||
? !variable.active
|
||||
: false,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.remove')"
|
||||
:icon="IconTrash"
|
||||
color="red"
|
||||
@click="deleteVariable(index)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="workingRequestVariables.length === 0"
|
||||
:src="`/images/states/${colorMode.value}/add_files.svg`"
|
||||
:alt="`${t('empty.request_variables')}`"
|
||||
:text="t('empty.request_variables')"
|
||||
>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
:icon="IconPlus"
|
||||
filled
|
||||
@click="addVariable"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { throwError } from "@functional/error"
|
||||
import {
|
||||
HoppRESTRequestVariable,
|
||||
RawKeyValueEntry,
|
||||
parseRawKeyValueEntriesE,
|
||||
rawKeyValueEntriesToString,
|
||||
} from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as E from "fp-ts/Either"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as RA from "fp-ts/ReadonlyArray"
|
||||
import { flow, pipe } from "fp-ts/function"
|
||||
import { cloneDeep, isEqual } from "lodash-es"
|
||||
import { reactive, ref, watch } from "vue"
|
||||
import draggable from "vuedraggable-es"
|
||||
|
||||
import { useCodemirror } from "~/composables/codemirror"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { useNestedSetting } from "~/composables/settings"
|
||||
import { useColorMode } from "~/composables/theming"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import linter from "~/helpers/editor/linting/rawKeyValue"
|
||||
import { objRemoveKey } from "~/helpers/functional/object"
|
||||
import { toggleNestedSetting } from "~/newstore/settings"
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconGripVertical from "~icons/lucide/grip-vertical"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconTrash from "~icons/lucide/trash"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const bulkMode = ref(false)
|
||||
const bulkEditor = ref<any | null>(null)
|
||||
const bulkVariables = ref("")
|
||||
|
||||
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpRequestVariables")
|
||||
|
||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTRequestVariable[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: Array<HoppRESTRequestVariable>): void
|
||||
}>()
|
||||
|
||||
// The functional requestVariable list (the requestVariable actually applied to the session)
|
||||
const requestVariables = useVModel(props, "modelValue", emit)
|
||||
|
||||
useCodemirror(
|
||||
bulkEditor,
|
||||
bulkVariables,
|
||||
reactive({
|
||||
extendedEditorConfig: {
|
||||
mode: "text/x-yaml",
|
||||
placeholder: `${t("state.bulk_mode_placeholder")}`,
|
||||
lineWrapping: WRAP_LINES,
|
||||
},
|
||||
linter,
|
||||
completer: null,
|
||||
environmentHighlights: true,
|
||||
})
|
||||
)
|
||||
|
||||
const idTicker = ref(0)
|
||||
|
||||
const workingRequestVariables = ref<
|
||||
Array<HoppRESTRequestVariable & { id: number }>
|
||||
>([
|
||||
{
|
||||
id: idTicker.value++,
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
},
|
||||
])
|
||||
|
||||
// Sync logic between params and working/bulk params
|
||||
watch(
|
||||
requestVariables,
|
||||
(newRequestVariableList) => {
|
||||
// Sync should overwrite working params
|
||||
const filteredWorkingRequestVariables: HoppRESTRequestVariable[] = pipe(
|
||||
workingRequestVariables.value,
|
||||
A.filterMap(
|
||||
flow(
|
||||
O.fromPredicate((e) => e.key !== ""),
|
||||
O.map(objRemoveKey("id"))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const filteredBulkRequestVariables = pipe(
|
||||
parseRawKeyValueEntriesE(bulkVariables.value),
|
||||
E.map(
|
||||
flow(
|
||||
RA.filter((e) => e.key !== ""),
|
||||
RA.toArray
|
||||
)
|
||||
),
|
||||
E.getOrElse(() => [] as RawKeyValueEntry[])
|
||||
)
|
||||
|
||||
if (!isEqual(newRequestVariableList, filteredWorkingRequestVariables)) {
|
||||
workingRequestVariables.value = pipe(
|
||||
newRequestVariableList,
|
||||
A.map((x) => ({ id: idTicker.value++, ...x }))
|
||||
)
|
||||
}
|
||||
|
||||
if (!isEqual(newRequestVariableList, filteredBulkRequestVariables)) {
|
||||
bulkVariables.value = rawKeyValueEntriesToString(newRequestVariableList)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(workingRequestVariables, (newWorkingRequestVariables) => {
|
||||
const fixedRequestVariables = pipe(
|
||||
newWorkingRequestVariables,
|
||||
A.filterMap(
|
||||
flow(
|
||||
O.fromPredicate((e) => e.key !== ""),
|
||||
O.map(objRemoveKey("id"))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if (!isEqual(requestVariables.value, fixedRequestVariables)) {
|
||||
requestVariables.value = cloneDeep(fixedRequestVariables)
|
||||
}
|
||||
})
|
||||
|
||||
watch(bulkVariables, (newBulkParams) => {
|
||||
const filteredBulkRequestVariables = pipe(
|
||||
parseRawKeyValueEntriesE(newBulkParams),
|
||||
E.map(
|
||||
flow(
|
||||
RA.filter((e) => e.key !== ""),
|
||||
RA.toArray
|
||||
)
|
||||
),
|
||||
E.getOrElse(() => [] as RawKeyValueEntry[])
|
||||
)
|
||||
|
||||
if (!isEqual(requestVariables.value, filteredBulkRequestVariables)) {
|
||||
requestVariables.value = filteredBulkRequestVariables
|
||||
}
|
||||
})
|
||||
|
||||
// Rule: Working Request variable always have last element is always an empty param
|
||||
watch(workingRequestVariables, (variableList) => {
|
||||
if (
|
||||
variableList.length > 0 &&
|
||||
variableList[variableList.length - 1].key !== ""
|
||||
) {
|
||||
workingRequestVariables.value.push({
|
||||
id: idTicker.value++,
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const addVariable = () => {
|
||||
workingRequestVariables.value.push({
|
||||
id: idTicker.value++,
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
|
||||
const updateVariable = (index: number, variable: any & { id: number }) => {
|
||||
workingRequestVariables.value = workingRequestVariables.value.map((h, i) =>
|
||||
i === index ? variable : h
|
||||
)
|
||||
}
|
||||
|
||||
const deleteVariable = (index: number) => {
|
||||
const requestVariablesBeforeDeletion = cloneDeep(
|
||||
workingRequestVariables.value
|
||||
)
|
||||
|
||||
if (
|
||||
!(
|
||||
requestVariablesBeforeDeletion.length > 0 &&
|
||||
index === requestVariablesBeforeDeletion.length - 1
|
||||
)
|
||||
) {
|
||||
if (deletionToast.value) {
|
||||
deletionToast.value.goAway(0)
|
||||
deletionToast.value = null
|
||||
}
|
||||
|
||||
deletionToast.value = toast.success(`${t("state.deleted")}`, {
|
||||
action: [
|
||||
{
|
||||
text: `${t("action.undo")}`,
|
||||
onClick: (_, toastObject) => {
|
||||
workingRequestVariables.value = requestVariablesBeforeDeletion
|
||||
toastObject.goAway(0)
|
||||
deletionToast.value = null
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
onComplete: () => {
|
||||
deletionToast.value = null
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
workingRequestVariables.value = pipe(
|
||||
workingRequestVariables.value,
|
||||
A.deleteAt(index),
|
||||
O.getOrElseW(() =>
|
||||
throwError("Working Request Variable Deletion Out of Bounds")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
// set params list to the initial state
|
||||
workingRequestVariables.value = [
|
||||
{
|
||||
id: idTicker.value++,
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
},
|
||||
]
|
||||
|
||||
bulkVariables.value = ""
|
||||
}
|
||||
</script>
|
||||
@@ -21,7 +21,7 @@
|
||||
@click="clearContent()"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="bulkMode"
|
||||
v-if="bulkUrlEncodedParams"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': WRAP_LINES }"
|
||||
@@ -84,6 +84,8 @@
|
||||
<SmartEnvInput
|
||||
v-model="param.key"
|
||||
:placeholder="`${t('count.parameter', { count: index + 1 })}`"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateUrlEncodedParam(index, {
|
||||
id: param.id,
|
||||
@@ -96,6 +98,8 @@
|
||||
<SmartEnvInput
|
||||
v-model="param.value"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
@change="
|
||||
updateUrlEncodedParam(index, {
|
||||
id: param.id,
|
||||
@@ -200,6 +204,7 @@ import { throwError } from "~/helpers/functional/error"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { useNestedSetting } from "~/composables/settings"
|
||||
import { toggleNestedSetting } from "~/newstore/settings"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
type Body = HoppRESTReqBody & {
|
||||
contentType: "application/x-www-form-urlencoded"
|
||||
@@ -207,6 +212,7 @@ type Body = HoppRESTReqBody & {
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Body
|
||||
envs: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
<template>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="auth.key" placeholder="Key" />
|
||||
<SmartEnvInput
|
||||
v-model="auth.key"
|
||||
:auto-complete-env="true"
|
||||
placeholder="Key"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="auth.value" placeholder="Value" />
|
||||
<SmartEnvInput
|
||||
v-model="auth.value"
|
||||
:auto-complete-env="true"
|
||||
placeholder="Value"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center border-b border-dividerLight">
|
||||
<span class="flex items-center">
|
||||
@@ -65,11 +75,13 @@ import { useI18n } from "@composables/i18n"
|
||||
import { HoppRESTAuthAPIKey } from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { ref } from "vue"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuthAPIKey
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
<SmartEnvInput
|
||||
v-model="auth.username"
|
||||
:placeholder="t('authorization.username')"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="auth.password"
|
||||
:placeholder="t('authorization.password')"
|
||||
:auto-complete-env="true"
|
||||
:envs="envs"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -17,11 +21,13 @@
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { HoppRESTAuthBasic } from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuthBasic
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -21,28 +21,24 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-2 py-4">
|
||||
<span>
|
||||
<HoppSmartItem
|
||||
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
||||
blank
|
||||
:icon="IconChrome"
|
||||
label="Chrome"
|
||||
:info-icon="hasChromeExtInstalled ? IconCheckCircle : null"
|
||||
:active-info-icon="hasChromeExtInstalled"
|
||||
outline
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<HoppSmartItem
|
||||
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
||||
blank
|
||||
:icon="IconFirefox"
|
||||
label="Firefox"
|
||||
:info-icon="hasFirefoxExtInstalled ? IconCheckCircle : null"
|
||||
:active-info-icon="hasFirefoxExtInstalled"
|
||||
outline
|
||||
/>
|
||||
</span>
|
||||
<HoppSmartItem
|
||||
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
||||
blank
|
||||
:icon="IconChrome"
|
||||
label="Chrome"
|
||||
:info-icon="hasChromeExtInstalled ? IconCheckCircle : null"
|
||||
:active-info-icon="hasChromeExtInstalled"
|
||||
outline
|
||||
/>
|
||||
<HoppSmartItem
|
||||
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
||||
blank
|
||||
:icon="IconFirefox"
|
||||
label="Firefox"
|
||||
:info-icon="hasFirefoxExtInstalled ? IconCheckCircle : null"
|
||||
:active-info-icon="hasFirefoxExtInstalled"
|
||||
outline
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-4 py-4">
|
||||
<div class="flex items-center">
|
||||
|
||||
@@ -290,7 +290,12 @@ const widgets: Widget[] = [
|
||||
},
|
||||
]
|
||||
|
||||
type EmbedTabs = "params" | "bodyParams" | "headers" | "authorization"
|
||||
type EmbedTabs =
|
||||
| "params"
|
||||
| "bodyParams"
|
||||
| "headers"
|
||||
| "authorization"
|
||||
| "requestVariables"
|
||||
|
||||
type EmbedOption = {
|
||||
selectedTab: EmbedTabs
|
||||
|
||||
@@ -56,7 +56,12 @@ import { useI18n } from "~/composables/i18n"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
type EmbedTabs = "params" | "bodyParams" | "headers" | "authorization"
|
||||
type EmbedTabs =
|
||||
| "params"
|
||||
| "bodyParams"
|
||||
| "headers"
|
||||
| "authorization"
|
||||
| "requestVariables"
|
||||
|
||||
type EmbedOption = {
|
||||
selectedTab: EmbedTabs
|
||||
|
||||
@@ -173,6 +173,11 @@ const embedOptions = ref<EmbedOption>({
|
||||
label: t("tab.authorization"),
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
value: "requestVariables",
|
||||
label: t("tab.variables"),
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
theme: "system",
|
||||
})
|
||||
@@ -223,7 +228,12 @@ const currentUser = useReadonlyStream(
|
||||
|
||||
const step = ref(1)
|
||||
|
||||
type EmbedTabs = "params" | "bodyParams" | "headers" | "authorization"
|
||||
type EmbedTabs =
|
||||
| "params"
|
||||
| "bodyParams"
|
||||
| "headers"
|
||||
| "authorization"
|
||||
| "requestVariables"
|
||||
|
||||
type EmbedOption = {
|
||||
selectedTab: EmbedTabs
|
||||
@@ -369,6 +379,11 @@ const displayCustomizeRequestModal = (
|
||||
label: t("tab.authorization"),
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
value: "requestVariables",
|
||||
label: t("tab.variables"),
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
theme: "system",
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="flex"
|
||||
class="flex overflow-x-scroll"
|
||||
:class="[{ 'border-b pt-2 ': !noActiveTab }, embedColours]"
|
||||
>
|
||||
<span
|
||||
@@ -57,7 +57,12 @@ import { computed } from "vue"
|
||||
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
|
||||
type Tabs = "params" | "bodyParams" | "headers" | "authorization"
|
||||
type Tabs =
|
||||
| "params"
|
||||
| "bodyParams"
|
||||
| "headers"
|
||||
| "authorization"
|
||||
| "requestVariables"
|
||||
|
||||
type EmbedOption = {
|
||||
selectedTab: Tabs
|
||||
|
||||
@@ -79,29 +79,27 @@ import { history, historyKeymap } from "@codemirror/commands"
|
||||
import { inputTheme } from "~/helpers/editor/themes/baseTheme"
|
||||
import { HoppReactiveEnvPlugin } from "~/helpers/editor/extensions/HoppEnvironment"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import {
|
||||
AggregateEnvironment,
|
||||
aggregateEnvsWithSecrets$,
|
||||
} from "~/newstore/environments"
|
||||
import { AggregateEnvironment, aggregateEnvs$ } from "~/newstore/environments"
|
||||
import { platform } from "~/platform"
|
||||
import { onClickOutside, useDebounceFn } from "@vueuse/core"
|
||||
import { InspectorResult } from "~/services/inspection"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import IconEye from "~icons/lucide/eye"
|
||||
import IconEyeoff from "~icons/lucide/eye-off"
|
||||
import { CompletionContext, autocompletion } from "@codemirror/autocomplete"
|
||||
import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { syntaxTree } from "@codemirror/language"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
type Env = Environment["variables"][number] & { source: string }
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string
|
||||
placeholder?: string
|
||||
styles?: string
|
||||
envs?: Env[] | null
|
||||
envs?: AggregateEnvironment[] | null
|
||||
focus?: boolean
|
||||
selectTextOnMount?: boolean
|
||||
environmentHighlights?: boolean
|
||||
@@ -110,6 +108,7 @@ const props = withDefaults(
|
||||
inspectionResults?: InspectorResult[] | undefined
|
||||
contextMenuEnabled?: boolean
|
||||
secret?: boolean
|
||||
autoCompleteEnv?: boolean
|
||||
}>(),
|
||||
{
|
||||
modelValue: "",
|
||||
@@ -124,6 +123,7 @@ const props = withDefaults(
|
||||
inspectionResults: undefined,
|
||||
contextMenuEnabled: true,
|
||||
secret: false,
|
||||
autoCompleteEnvSource: false,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -353,29 +353,62 @@ watch(
|
||||
let clipboardEv: ClipboardEvent | null = null
|
||||
let pastedValue: string | null = null
|
||||
|
||||
const aggregateEnvs = useReadonlyStream(aggregateEnvsWithSecrets$, []) as Ref<
|
||||
const aggregateEnvs = useReadonlyStream(aggregateEnvs$, []) as Ref<
|
||||
AggregateEnvironment[]
|
||||
>
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
const envVars = computed(() => {
|
||||
return props.envs
|
||||
? props.envs.map((x) => {
|
||||
if (x.secret) {
|
||||
return {
|
||||
key: x.key,
|
||||
sourceEnv: "source" in x ? x.source : null,
|
||||
value: "********",
|
||||
}
|
||||
}
|
||||
return {
|
||||
key: x.key,
|
||||
value: x.value,
|
||||
sourceEnv: "source" in x ? x.source : null,
|
||||
}
|
||||
})
|
||||
: aggregateEnvs.value
|
||||
if (props.envs) {
|
||||
return props.envs.map((x) => {
|
||||
const { key, secret } = x
|
||||
const value = secret ? "********" : x.value
|
||||
const sourceEnv = "sourceEnv" in x ? x.sourceEnv : null
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
sourceEnv,
|
||||
secret,
|
||||
}
|
||||
})
|
||||
}
|
||||
return [
|
||||
...tabs.currentActiveTab.value.document.request.requestVariables.map(
|
||||
({ active, key, value }) =>
|
||||
active
|
||||
? {
|
||||
key,
|
||||
value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
}
|
||||
: ({} as AggregateEnvironment)
|
||||
),
|
||||
...aggregateEnvs.value,
|
||||
]
|
||||
})
|
||||
|
||||
function envAutoCompletion(context: CompletionContext) {
|
||||
const options = (envVars.value ?? [])
|
||||
.map((env) => ({
|
||||
label: env?.key ? `<<${env.key}>>` : "",
|
||||
info: env?.value ?? "",
|
||||
apply: env?.key ? `<<${env.key}>>` : "",
|
||||
}))
|
||||
.filter((x) => x)
|
||||
|
||||
const nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1)
|
||||
const textBefore = context.state.sliceDoc(nodeBefore.from, context.pos)
|
||||
const tagBefore = /<<\w*$/.exec(textBefore)
|
||||
if (!tagBefore && !context.explicit) return null
|
||||
return {
|
||||
from: tagBefore ? nodeBefore.from + tagBefore.index : context.pos,
|
||||
options: options,
|
||||
validFor: /^(<<\w*)?$/,
|
||||
}
|
||||
}
|
||||
|
||||
const envTooltipPlugin = new HoppReactiveEnvPlugin(envVars, view)
|
||||
|
||||
function handleTextSelection() {
|
||||
@@ -384,7 +417,9 @@ function handleTextSelection() {
|
||||
const { from, to } = selection
|
||||
if (from === to) return
|
||||
const text = view.value?.state.doc.sliceString(from, to)
|
||||
const { top, left } = view.value?.coordsAtPos(from)
|
||||
const coords = view.value?.coordsAtPos(from)
|
||||
const top = coords?.top ?? 0
|
||||
const left = coords?.left ?? 0
|
||||
if (text) {
|
||||
invokeAction("contextmenu.open", {
|
||||
position: {
|
||||
@@ -406,16 +441,17 @@ function handleTextSelection() {
|
||||
}
|
||||
}
|
||||
|
||||
const initView = (el: any) => {
|
||||
// Debounce to prevent double click from selecting the word
|
||||
const debounceFn = useDebounceFn(() => {
|
||||
// Debounce to prevent double click from selecting the word
|
||||
const debouncedTextSelection = (time: number) =>
|
||||
useDebounceFn(() => {
|
||||
handleTextSelection()
|
||||
}, 140)
|
||||
}, time)
|
||||
|
||||
const initView = (el: any) => {
|
||||
// Only add event listeners if context menu is enabled in the component
|
||||
if (props.contextMenuEnabled) {
|
||||
el.addEventListener("mouseup", debounceFn)
|
||||
el.addEventListener("keyup", debounceFn)
|
||||
el.addEventListener("mouseup", debouncedTextSelection(140))
|
||||
el.addEventListener("keyup", debouncedTextSelection(140))
|
||||
}
|
||||
|
||||
const extensions: Extension = getExtensions(props.readonly || isSecret.value)
|
||||
@@ -465,10 +501,17 @@ const getExtensions = (readonly: boolean): Extension => {
|
||||
},
|
||||
scroll(event) {
|
||||
if (event.target && props.contextMenuEnabled) {
|
||||
handleTextSelection()
|
||||
// Debounce to make the performance better
|
||||
debouncedTextSelection(30)()
|
||||
}
|
||||
},
|
||||
}),
|
||||
props.autoCompleteEnv
|
||||
? autocompletion({
|
||||
activateOnTyping: true,
|
||||
override: [envAutoCompletion],
|
||||
})
|
||||
: [],
|
||||
ViewPlugin.fromClass(
|
||||
class {
|
||||
update(update: ViewUpdate) {
|
||||
|
||||
@@ -27,13 +27,10 @@ const workspaceService = useService(WorkspaceService)
|
||||
const workspace = workspaceService.currentWorkspace
|
||||
|
||||
const currentWorkspace = computed(() => {
|
||||
if (props.isOnlyPersonal) {
|
||||
return `${t("workspace.personal")}`
|
||||
if (props.isOnlyPersonal || workspace.value.type === "personal") {
|
||||
return t("workspace.personal")
|
||||
}
|
||||
if (workspace.value.type === "team") {
|
||||
return teamWorkspaceName.value
|
||||
}
|
||||
return `${t("workspace.personal")}`
|
||||
return teamWorkspaceName.value
|
||||
})
|
||||
|
||||
const teamWorkspaceName = computed(() => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col">
|
||||
<HoppSmartItem
|
||||
label="My Workspace"
|
||||
:label="t('workspace.personal')"
|
||||
:icon="IconUser"
|
||||
:info-icon="workspace.type === 'personal' ? IconDone : undefined"
|
||||
:active-info-icon="workspace.type === 'personal'"
|
||||
@@ -36,7 +36,7 @@
|
||||
class="sticky top-0 z-10 mb-2 flex items-center justify-between bg-popover py-2 pl-2"
|
||||
>
|
||||
<div class="flex items-center px-2 font-semibold text-secondaryLight">
|
||||
{{ t("team.title") }}
|
||||
{{ t("workspace.other_workspaces") }}
|
||||
</div>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
|
||||
@@ -242,8 +242,10 @@ export function useCodemirror(
|
||||
const { from, to } = selection
|
||||
if (from === to) return
|
||||
const text = view.value?.state.doc.sliceString(from, to)
|
||||
const { top, left } = view.value?.coordsAtPos(from)
|
||||
if (text) {
|
||||
const coords = view.value?.coordsAtPos(from)
|
||||
const top = coords?.top ?? 0
|
||||
const left = coords?.left ?? 0
|
||||
if (text?.trim()) {
|
||||
invokeAction("contextmenu.open", {
|
||||
position: {
|
||||
top,
|
||||
@@ -263,6 +265,12 @@ export function useCodemirror(
|
||||
}
|
||||
}
|
||||
|
||||
// Debounce to prevent double click from selecting the word
|
||||
const debouncedTextSelection = (time: number) =>
|
||||
useDebounceFn(() => {
|
||||
handleTextSelection()
|
||||
}, time)
|
||||
|
||||
const initView = (el: any) => {
|
||||
if (el) platform.ui?.onCodemirrorInstanceMount?.(el)
|
||||
|
||||
@@ -274,15 +282,10 @@ export function useCodemirror(
|
||||
ViewPlugin.fromClass(
|
||||
class {
|
||||
update(update: ViewUpdate) {
|
||||
// Debounce to prevent double click from selecting the word
|
||||
const debounceFn = useDebounceFn(() => {
|
||||
handleTextSelection()
|
||||
}, 140)
|
||||
|
||||
// Only add event listeners if context menu is enabled in the editor
|
||||
if (options.contextMenuEnabled) {
|
||||
el.addEventListener("mouseup", debounceFn)
|
||||
el.addEventListener("keyup", debounceFn)
|
||||
el.addEventListener("mouseup", debouncedTextSelection(140))
|
||||
el.addEventListener("keyup", debouncedTextSelection(140))
|
||||
}
|
||||
|
||||
if (options.onUpdate) {
|
||||
@@ -324,7 +327,8 @@ export function useCodemirror(
|
||||
EditorView.domEventHandlers({
|
||||
scroll(event) {
|
||||
if (event.target && options.contextMenuEnabled) {
|
||||
handleTextSelection()
|
||||
// Debounce to make the performance better
|
||||
debouncedTextSelection(30)()
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -1,293 +0,0 @@
|
||||
import {
|
||||
FormDataKeyValue,
|
||||
HoppRESTAuth,
|
||||
HoppRESTHeader,
|
||||
HoppRESTParam,
|
||||
HoppRESTReqBody,
|
||||
HoppRESTRequest,
|
||||
RESTReqSchemaVersion,
|
||||
ValidContentTypes,
|
||||
} from "@hoppscotch/data"
|
||||
import { BehaviorSubject, combineLatest, map } from "rxjs"
|
||||
import { applyBodyTransition } from "~/helpers/rules/BodyTransition"
|
||||
import { HoppRESTResponse } from "./types/HoppRESTResponse"
|
||||
|
||||
export class RESTRequest {
|
||||
public v$ = new BehaviorSubject<typeof RESTReqSchemaVersion>(
|
||||
RESTReqSchemaVersion
|
||||
)
|
||||
public name$ = new BehaviorSubject("Untitled")
|
||||
public endpoint$ = new BehaviorSubject("https://echo.hoppscotch.io/")
|
||||
public params$ = new BehaviorSubject<HoppRESTParam[]>([])
|
||||
public headers$ = new BehaviorSubject<HoppRESTHeader[]>([])
|
||||
public method$ = new BehaviorSubject("GET")
|
||||
public auth$ = new BehaviorSubject<HoppRESTAuth>({
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
})
|
||||
public preRequestScript$ = new BehaviorSubject("")
|
||||
public testScript$ = new BehaviorSubject("")
|
||||
public body$ = new BehaviorSubject<HoppRESTReqBody>({
|
||||
contentType: null,
|
||||
body: null,
|
||||
})
|
||||
|
||||
public response$ = new BehaviorSubject<HoppRESTResponse | null>(null)
|
||||
|
||||
get request$() {
|
||||
// any of above changes construct requests
|
||||
return combineLatest([
|
||||
this.v$,
|
||||
this.name$,
|
||||
this.endpoint$,
|
||||
this.params$,
|
||||
this.headers$,
|
||||
this.method$,
|
||||
this.auth$,
|
||||
this.preRequestScript$,
|
||||
this.testScript$,
|
||||
this.body$,
|
||||
]).pipe(
|
||||
map(
|
||||
([
|
||||
v,
|
||||
name,
|
||||
endpoint,
|
||||
params,
|
||||
headers,
|
||||
method,
|
||||
auth,
|
||||
preRequestScript,
|
||||
testScript,
|
||||
body,
|
||||
]) => ({
|
||||
v,
|
||||
name,
|
||||
endpoint,
|
||||
params,
|
||||
headers,
|
||||
method,
|
||||
auth,
|
||||
preRequestScript,
|
||||
testScript,
|
||||
body,
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
get contentType$() {
|
||||
return this.body$.pipe(map((body) => body.contentType))
|
||||
}
|
||||
|
||||
get bodyContent$() {
|
||||
return this.body$.pipe(map((body) => body.body))
|
||||
}
|
||||
|
||||
get headersCount$() {
|
||||
return this.headers$.pipe(
|
||||
map(
|
||||
(params) =>
|
||||
params.filter((x) => x.active && (x.key !== "" || x.value !== ""))
|
||||
.length
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
get paramsCount$() {
|
||||
return this.params$.pipe(
|
||||
map(
|
||||
(params) =>
|
||||
params.filter((x) => x.active && (x.key !== "" || x.value !== ""))
|
||||
.length
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
setName(name: string) {
|
||||
this.name$.next(name)
|
||||
}
|
||||
|
||||
setEndpoint(newURL: string) {
|
||||
this.endpoint$.next(newURL)
|
||||
}
|
||||
|
||||
setMethod(newMethod: string) {
|
||||
this.method$.next(newMethod)
|
||||
}
|
||||
|
||||
setParams(entries: HoppRESTParam[]) {
|
||||
this.params$.next(entries)
|
||||
}
|
||||
|
||||
addParam(newParam: HoppRESTParam) {
|
||||
const newParams = this.params$.value.concat(newParam)
|
||||
this.params$.next(newParams)
|
||||
}
|
||||
|
||||
updateParam(index: number, updatedParam: HoppRESTParam) {
|
||||
const newParams = this.params$.value.map((param, i) =>
|
||||
i === index ? updatedParam : param
|
||||
)
|
||||
this.params$.next(newParams)
|
||||
}
|
||||
|
||||
deleteParam(index: number) {
|
||||
const newParams = this.params$.value.filter((_, i) => i !== index)
|
||||
this.params$.next(newParams)
|
||||
}
|
||||
|
||||
deleteAllParams() {
|
||||
this.params$.next([])
|
||||
}
|
||||
|
||||
setHeaders(entries: HoppRESTHeader[]) {
|
||||
this.headers$.next(entries)
|
||||
}
|
||||
|
||||
addHeader(newHeader: HoppRESTHeader) {
|
||||
const newHeaders = this.headers$.value.concat(newHeader)
|
||||
this.headers$.next(newHeaders)
|
||||
}
|
||||
|
||||
updateHeader(index: number, updatedHeader: HoppRESTHeader) {
|
||||
const newHeaders = this.headers$.value.map((header, i) =>
|
||||
i === index ? updatedHeader : header
|
||||
)
|
||||
this.headers$.next(newHeaders)
|
||||
}
|
||||
|
||||
deleteHeader(index: number) {
|
||||
const newHeaders = this.headers$.value.filter((_, i) => i !== index)
|
||||
this.headers$.next(newHeaders)
|
||||
}
|
||||
|
||||
deleteAllHeaders() {
|
||||
this.headers$.next([])
|
||||
}
|
||||
|
||||
setContentType(newContentType: ValidContentTypes | null) {
|
||||
// TODO: persist body evenafter switching content typees
|
||||
this.body$.next(applyBodyTransition(this.body$.value, newContentType))
|
||||
}
|
||||
|
||||
setBody(newBody: string | FormDataKeyValue[] | null) {
|
||||
const body = { ...this.body$.value }
|
||||
body.body = newBody
|
||||
this.body$.next({ ...body })
|
||||
}
|
||||
|
||||
addFormDataEntry(entry: FormDataKeyValue) {
|
||||
if (this.body$.value.contentType !== "multipart/form-data") return {}
|
||||
const body: HoppRESTReqBody = {
|
||||
contentType: "multipart/form-data",
|
||||
body: [...this.body$.value.body, entry],
|
||||
}
|
||||
this.body$.next(body)
|
||||
}
|
||||
|
||||
deleteFormDataEntry(index: number) {
|
||||
// Only perform update if the current content-type is formdata
|
||||
if (this.body$.value.contentType !== "multipart/form-data") return {}
|
||||
|
||||
const body: HoppRESTReqBody = {
|
||||
contentType: "multipart/form-data",
|
||||
body: [...this.body$.value.body.filter((_, i) => i !== index)],
|
||||
}
|
||||
|
||||
this.body$.next(body)
|
||||
}
|
||||
|
||||
updateFormDataEntry(index: number, entry: FormDataKeyValue) {
|
||||
// Only perform update if the current content-type is formdata
|
||||
if (this.body$.value.contentType !== "multipart/form-data") return {}
|
||||
|
||||
const body: HoppRESTReqBody = {
|
||||
contentType: "multipart/form-data",
|
||||
body: [
|
||||
...this.body$.value.body.map((oldEntry, i) =>
|
||||
i === index ? entry : oldEntry
|
||||
),
|
||||
],
|
||||
}
|
||||
this.body$.next(body)
|
||||
}
|
||||
|
||||
deleteAllFormDataEntries() {
|
||||
// Only perform update if the current content-type is formdata
|
||||
if (this.body$.value.contentType !== "multipart/form-data") return {}
|
||||
|
||||
const body: HoppRESTReqBody = {
|
||||
contentType: "multipart/form-data",
|
||||
body: [],
|
||||
}
|
||||
this.body$.next(body)
|
||||
}
|
||||
|
||||
setRequestBody(newBody: HoppRESTReqBody) {
|
||||
this.body$.next(newBody)
|
||||
}
|
||||
|
||||
setAuth(newAuth: HoppRESTAuth) {
|
||||
this.auth$.next(newAuth)
|
||||
}
|
||||
|
||||
setPreRequestScript(newScript: string) {
|
||||
this.preRequestScript$.next(newScript)
|
||||
}
|
||||
|
||||
setTestScript(newScript: string) {
|
||||
this.testScript$.next(newScript)
|
||||
}
|
||||
|
||||
updateResponse(response: HoppRESTResponse | null) {
|
||||
this.response$.next(response)
|
||||
}
|
||||
|
||||
setRequest(request: HoppRESTRequest) {
|
||||
this.v$.next(RESTReqSchemaVersion)
|
||||
this.name$.next(request.name)
|
||||
this.endpoint$.next(request.endpoint)
|
||||
this.params$.next(request.params)
|
||||
this.headers$.next(request.headers)
|
||||
this.method$.next(request.method)
|
||||
this.auth$.next(request.auth)
|
||||
this.preRequestScript$.next(request.preRequestScript)
|
||||
this.testScript$.next(request.testScript)
|
||||
this.body$.next(request.body)
|
||||
}
|
||||
|
||||
getRequest() {
|
||||
return {
|
||||
v: this.v$.value,
|
||||
name: this.name$.value,
|
||||
endpoint: this.endpoint$.value,
|
||||
params: this.params$.value,
|
||||
headers: this.headers$.value,
|
||||
method: this.method$.value,
|
||||
auth: this.auth$.value,
|
||||
preRequestScript: this.preRequestScript$.value,
|
||||
testScript: this.testScript$.value,
|
||||
body: this.body$.value,
|
||||
}
|
||||
}
|
||||
|
||||
resetRequest() {
|
||||
this.v$.next(RESTReqSchemaVersion)
|
||||
this.name$.next("")
|
||||
this.endpoint$.next("")
|
||||
this.params$.next([])
|
||||
this.headers$.next([])
|
||||
this.method$.next("GET")
|
||||
this.auth$.next({
|
||||
authType: "none",
|
||||
authActive: false,
|
||||
})
|
||||
this.preRequestScript$.next("")
|
||||
this.testScript$.next("")
|
||||
this.body$.next({
|
||||
contentType: null,
|
||||
body: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import {
|
||||
Environment,
|
||||
HoppRESTHeaders,
|
||||
HoppRESTRequestVariable,
|
||||
} from "@hoppscotch/data"
|
||||
import { SandboxTestResult, TestDescriptor } from "@hoppscotch/js-sandbox"
|
||||
import { runTestScript } from "@hoppscotch/js-sandbox/web"
|
||||
import * as A from "fp-ts/Array"
|
||||
@@ -65,10 +69,17 @@ const getTestableBody = (
|
||||
return x
|
||||
}
|
||||
|
||||
const combineEnvVariables = (envs: {
|
||||
global: Environment["variables"]
|
||||
selected: Environment["variables"]
|
||||
}) => [...envs.selected, ...envs.global]
|
||||
const combineEnvVariables = (variables: {
|
||||
environments: {
|
||||
selected: Environment["variables"]
|
||||
global: Environment["variables"]
|
||||
}
|
||||
requestVariables: Environment["variables"]
|
||||
}) => [
|
||||
...variables.requestVariables,
|
||||
...variables.environments.selected,
|
||||
...variables.environments.global,
|
||||
]
|
||||
|
||||
export const executedResponses$ = new Subject<
|
||||
HoppRESTResponse & { type: "success" | "fail " }
|
||||
@@ -122,6 +133,38 @@ const updateEnvironmentsWithSecret = (
|
||||
return updatedEnv
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the environment list to a list with unique keys with value
|
||||
* @param envs The environment list to be transformed
|
||||
* @returns The transformed environment list with keys with value
|
||||
*/
|
||||
const filterNonEmptyEnvironmentVariables = (
|
||||
envs: Environment["variables"]
|
||||
): Environment["variables"] => {
|
||||
const envsMap = new Map<string, Environment["variables"][number]>()
|
||||
|
||||
envs.forEach((env) => {
|
||||
if (env.secret) {
|
||||
envsMap.set(env.key, env)
|
||||
} else if (envsMap.has(env.key)) {
|
||||
const existingEnv = envsMap.get(env.key)
|
||||
|
||||
if (
|
||||
existingEnv &&
|
||||
"value" in existingEnv &&
|
||||
existingEnv.value === "" &&
|
||||
env.value !== ""
|
||||
) {
|
||||
envsMap.set(env.key, env)
|
||||
}
|
||||
} else {
|
||||
envsMap.set(env.key, env)
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(envsMap.values())
|
||||
}
|
||||
|
||||
export function runRESTRequest$(
|
||||
tab: Ref<HoppTab<HoppRESTDocument>>
|
||||
): [
|
||||
@@ -175,15 +218,40 @@ export function runRESTRequest$(
|
||||
requestHeaders = [...tab.value.document.request.headers]
|
||||
}
|
||||
|
||||
const finalRequestVariables =
|
||||
tab.value.document.request.requestVariables.map(
|
||||
(v: HoppRESTRequestVariable) => {
|
||||
if (v.active) {
|
||||
return {
|
||||
key: v.key,
|
||||
value: v.value,
|
||||
secret: false,
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
)
|
||||
|
||||
const finalRequest = {
|
||||
...tab.value.document.request,
|
||||
auth: requestAuth ?? { authType: "none", authActive: false },
|
||||
headers: requestHeaders,
|
||||
headers: requestHeaders as HoppRESTHeaders,
|
||||
}
|
||||
|
||||
const finalEnvs = {
|
||||
requestVariables: finalRequestVariables as Environment["variables"],
|
||||
environments: envs.right,
|
||||
}
|
||||
|
||||
const finalEnvsWithNonEmptyValues = filterNonEmptyEnvironmentVariables(
|
||||
combineEnvVariables(finalEnvs)
|
||||
)
|
||||
|
||||
const effectiveRequest = getEffectiveRESTRequest(finalRequest, {
|
||||
id: "env-id",
|
||||
v: 1,
|
||||
name: "Env",
|
||||
variables: combineEnvVariables(envs.right),
|
||||
variables: finalEnvsWithNonEmptyValues,
|
||||
})
|
||||
|
||||
const [stream, cancelRun] = createRESTNetworkRequestStream(effectiveRequest)
|
||||
|
||||
@@ -38,6 +38,7 @@ const samples = [
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -141,6 +142,7 @@ const samples = [
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -158,6 +160,7 @@ const samples = [
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -181,6 +184,7 @@ const samples = [
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -209,6 +213,7 @@ const samples = [
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -239,6 +244,7 @@ const samples = [
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -268,6 +274,7 @@ const samples = [
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -291,6 +298,7 @@ const samples = [
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -312,6 +320,7 @@ const samples = [
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -335,6 +344,7 @@ const samples = [
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -366,6 +376,7 @@ const samples = [
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -430,6 +441,7 @@ const samples = [
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -458,6 +470,7 @@ const samples = [
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -515,6 +528,7 @@ const samples = [
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -560,6 +574,7 @@ const samples = [
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -607,6 +622,7 @@ const samples = [
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -621,6 +637,7 @@ const samples = [
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -645,6 +662,7 @@ const samples = [
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -662,6 +680,7 @@ const samples = [
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -685,6 +704,7 @@ const samples = [
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -702,6 +722,7 @@ const samples = [
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -725,6 +746,7 @@ const samples = [
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -747,6 +769,7 @@ const samples = [
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -767,6 +790,7 @@ const samples = [
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -788,6 +812,7 @@ const samples = [
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -808,6 +833,7 @@ const samples = [
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -839,6 +865,7 @@ const samples = [
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -181,5 +181,6 @@ export const parseCurlCommand = (curlCommand: string) => {
|
||||
testScript: defaultRESTReq.testScript,
|
||||
auth,
|
||||
body: finalBody,
|
||||
requestVariables: defaultRESTReq.requestVariables,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { StreamSubscriberFunc } from "@composables/stream"
|
||||
import {
|
||||
AggregateEnvironment,
|
||||
aggregateEnvsWithSecrets$,
|
||||
getAggregateEnvsWithSecrets,
|
||||
getAggregateEnvs,
|
||||
getCurrentEnvironment,
|
||||
getSelectedEnvironmentType,
|
||||
} from "~/newstore/environments"
|
||||
@@ -23,15 +23,45 @@ import IconUsers from "~icons/lucide/users?raw"
|
||||
import IconEdit from "~icons/lucide/edit?raw"
|
||||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
import { getService } from "~/modules/dioc"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
|
||||
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
|
||||
|
||||
const HOPP_ENV_HIGHLIGHT =
|
||||
"cursor-help transition rounded px-1 focus:outline-none mx-0.5 env-highlight"
|
||||
const HOPP_ENV_HIGHLIGHT_FOUND = "env-found"
|
||||
const HOPP_ENV_HIGHLIGHT_NOT_FOUND = "env-not-found"
|
||||
|
||||
const HOPP_REQUEST_VARIABLE_HIGHLIGHT = "request-variable-highlight"
|
||||
const HOPP_ENVIRONMENT_HIGHLIGHT = "environment-variable-highlight"
|
||||
const HOPP_GLOBAL_ENVIRONMENT_HIGHLIGHT = "global-variable-highlight"
|
||||
const HOPP_ENV_HIGHLIGHT_NOT_FOUND = "environment-not-found-highlight"
|
||||
|
||||
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
const restTabs = getService(RESTTabService)
|
||||
|
||||
/**
|
||||
* Transforms the environment list to a list with unique keys with value
|
||||
* @param envs The environment list to be transformed
|
||||
* @returns The transformed environment list with keys with value
|
||||
*/
|
||||
const filterNonEmptyEnvironmentVariables = (
|
||||
envs: AggregateEnvironment[]
|
||||
): AggregateEnvironment[] => {
|
||||
const envsMap = new Map<string, AggregateEnvironment>()
|
||||
|
||||
envs.forEach((env) => {
|
||||
if (envsMap.has(env.key)) {
|
||||
const existingEnv = envsMap.get(env.key)
|
||||
|
||||
if (existingEnv?.value === "" && env.value !== "") {
|
||||
envsMap.set(env.key, env)
|
||||
}
|
||||
} else {
|
||||
envsMap.set(env.key, env)
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(envsMap.values())
|
||||
}
|
||||
|
||||
const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
||||
hoverTooltip(
|
||||
@@ -67,7 +97,12 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
||||
|
||||
const parsedEnvKey = text.slice(start - from, end - from)
|
||||
|
||||
const tooltipEnv = aggregateEnvs.find((env) => env.key === parsedEnvKey)
|
||||
const envsWithNoEmptyValues =
|
||||
filterNonEmptyEnvironmentVariables(aggregateEnvs)
|
||||
|
||||
const tooltipEnv = envsWithNoEmptyValues.find(
|
||||
(env) => env.key === parsedEnvKey
|
||||
)
|
||||
|
||||
const envName = tooltipEnv?.sourceEnv ?? "Choose an Environment"
|
||||
|
||||
@@ -95,7 +130,12 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
||||
|
||||
const result = parseTemplateStringE(envValue, aggregateEnvs)
|
||||
|
||||
const finalEnv = E.isLeft(result) ? "error" : result.right
|
||||
let finalEnv = E.isLeft(result) ? "error" : result.right
|
||||
|
||||
// If the request variable has an secret variable
|
||||
// parseTemplateStringE is passed the secret value which has value undefined
|
||||
// So, we need to check if the result is undefined and then set the finalEnv to ******
|
||||
if (finalEnv === "undefined") finalEnv = "******"
|
||||
|
||||
const selectedEnvType = getSelectedEnvironmentType()
|
||||
|
||||
@@ -123,11 +163,16 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
||||
invokeActionType = "modals.my.environment.edit"
|
||||
}
|
||||
|
||||
invokeAction(invokeActionType, {
|
||||
envName: tooltipEnv?.sourceEnv !== "Global" ? envName : "Global",
|
||||
variableName: parsedEnvKey,
|
||||
isSecret: tooltipEnv?.secret,
|
||||
})
|
||||
if (tooltipEnv?.sourceEnv === "RequestVariable") {
|
||||
restTabs.currentActiveTab.value.document.optionTabPreference =
|
||||
"requestVariables"
|
||||
} else {
|
||||
invokeAction(invokeActionType, {
|
||||
envName: tooltipEnv?.sourceEnv === "Global" ? "Global" : envName,
|
||||
variableName: parsedEnvKey,
|
||||
isSecret: tooltipEnv?.secret,
|
||||
})
|
||||
}
|
||||
})
|
||||
editIcon.innerHTML = `<span class="inline-flex items-center justify-center my-1">${IconEdit}</span>`
|
||||
tooltip.appendChild(editIcon)
|
||||
@@ -165,11 +210,16 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
||||
)
|
||||
|
||||
function checkEnv(env: string, aggregateEnvs: AggregateEnvironment[]) {
|
||||
const className = aggregateEnvs.find(
|
||||
let className = HOPP_ENV_HIGHLIGHT_NOT_FOUND
|
||||
|
||||
const envSource = aggregateEnvs.find(
|
||||
(k: { key: string }) => k.key === env.slice(2, -2)
|
||||
)
|
||||
? HOPP_ENV_HIGHLIGHT_FOUND
|
||||
: HOPP_ENV_HIGHLIGHT_NOT_FOUND
|
||||
)?.sourceEnv
|
||||
|
||||
if (envSource === "RequestVariable")
|
||||
className = HOPP_REQUEST_VARIABLE_HIGHLIGHT
|
||||
else if (envSource === "Global") className = HOPP_GLOBAL_ENVIRONMENT_HIGHLIGHT
|
||||
else if (envSource !== undefined) className = HOPP_ENVIRONMENT_HIGHLIGHT
|
||||
|
||||
return Decoration.mark({
|
||||
class: `${HOPP_ENV_HIGHLIGHT} ${className}`,
|
||||
@@ -185,7 +235,10 @@ const getMatchDecorator = (aggregateEnvs: AggregateEnvironment[]) =>
|
||||
export const environmentHighlightStyle = (
|
||||
aggregateEnvs: AggregateEnvironment[]
|
||||
) => {
|
||||
const decorator = getMatchDecorator(aggregateEnvs)
|
||||
const envsWithNoEmptyValues =
|
||||
filterNonEmptyEnvironmentVariables(aggregateEnvs)
|
||||
|
||||
const decorator = getMatchDecorator(envsWithNoEmptyValues)
|
||||
|
||||
return ViewPlugin.define(
|
||||
(view) => ({
|
||||
@@ -209,10 +262,45 @@ export class HoppEnvironmentPlugin {
|
||||
subscribeToStream: StreamSubscriberFunc,
|
||||
private editorView: Ref<EditorView | undefined>
|
||||
) {
|
||||
this.envs = getAggregateEnvsWithSecrets()
|
||||
const aggregateEnvs = getAggregateEnvs()
|
||||
const currentTab = restTabs.currentActiveTab.value
|
||||
|
||||
watch(
|
||||
currentTab.document.request,
|
||||
(reqVariables) => {
|
||||
this.envs = [
|
||||
...reqVariables.requestVariables.map(({ key, value }) => ({
|
||||
key,
|
||||
value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
})),
|
||||
...aggregateEnvs,
|
||||
]
|
||||
|
||||
this.editorView.value?.dispatch({
|
||||
effects: this.compartment.reconfigure([
|
||||
cursorTooltipField(this.envs),
|
||||
environmentHighlightStyle(this.envs),
|
||||
]),
|
||||
})
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
subscribeToStream(aggregateEnvsWithSecrets$, (envs) => {
|
||||
this.envs = envs
|
||||
this.envs = [
|
||||
...currentTab.document.request.requestVariables.map(
|
||||
({ key, value }) => ({
|
||||
key,
|
||||
value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
})
|
||||
),
|
||||
...envs,
|
||||
]
|
||||
|
||||
this.editorView.value?.dispatch({
|
||||
effects: this.compartment.reconfigure([
|
||||
cursorTooltipField(this.envs),
|
||||
@@ -251,7 +339,7 @@ export class HoppReactiveEnvPlugin {
|
||||
]),
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@ import * as O from "fp-ts/Option"
|
||||
import * as RA from "fp-ts/ReadonlyArray"
|
||||
import * as A from "fp-ts/Array"
|
||||
import { translateToNewRESTCollection, HoppCollection } from "@hoppscotch/data"
|
||||
import { isPlainObject as _isPlainObject } from "lodash-es"
|
||||
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { safeParseJSON } from "~/helpers/functional/json"
|
||||
import { translateToNewGQLCollection } from "@hoppscotch/data"
|
||||
import { entityReference } from "verzod"
|
||||
import { z } from "zod"
|
||||
|
||||
export const hoppRESTImporter = (content: string[]) =>
|
||||
pipe(
|
||||
@@ -26,26 +27,14 @@ export const hoppRESTImporter = (content: string[]) =>
|
||||
TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT)
|
||||
)
|
||||
|
||||
/**
|
||||
* checks if a value is a plain object
|
||||
*/
|
||||
const isPlainObject = (value: any): value is object => _isPlainObject(value)
|
||||
|
||||
/**
|
||||
* checks if a collection matches the schema for a hoppscotch collection.
|
||||
* here 2 is the latest version of the schema.
|
||||
*/
|
||||
const isValidCollection = (collection: unknown): collection is HoppCollection =>
|
||||
isPlainObject(collection) && "v" in collection && collection.v === 2
|
||||
|
||||
/**
|
||||
* checks if a collection is a valid hoppscotch collection.
|
||||
* else translate it into one.
|
||||
*/
|
||||
const validateCollection = (collection: unknown) => {
|
||||
if (isValidCollection(collection)) {
|
||||
return O.some(collection)
|
||||
}
|
||||
const result = entityReference(HoppCollection).safeParse(collection)
|
||||
if (result.success) return O.some(result.data)
|
||||
|
||||
return O.some(translateToNewRESTCollection(collection))
|
||||
}
|
||||
|
||||
@@ -75,8 +64,9 @@ export const hoppGQLImporter = (content: string) =>
|
||||
* @returns the collection if it is valid, else a translated version of the collection
|
||||
*/
|
||||
export const validateGQLCollection = (collection: unknown) => {
|
||||
if (isValidCollection(collection)) {
|
||||
return O.some(collection)
|
||||
}
|
||||
const result = z.array(entityReference(HoppCollection)).safeParse(collection)
|
||||
|
||||
if (result.success) return O.some(result.data)
|
||||
|
||||
return O.some(translateToNewGQLCollection(collection))
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
knownContentTypes,
|
||||
makeCollection,
|
||||
makeRESTRequest,
|
||||
HoppRESTRequestVariable,
|
||||
} from "@hoppscotch/data"
|
||||
|
||||
import * as A from "fp-ts/Array"
|
||||
@@ -28,14 +29,27 @@ type UnwrapPromise<T extends Promise<any>> = T extends Promise<infer Y>
|
||||
type InsomniaDoc = UnwrapPromise<ReturnType<typeof convert>>
|
||||
type InsomniaResource = ImportRequest
|
||||
|
||||
// insomnia-importers v3.6.0 doesn't provide a type for path parameters and they have deprecated the library
|
||||
type InsomniaPathParameter = {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type InsomniaFolderResource = ImportRequest & { _type: "request_group" }
|
||||
type InsomniaRequestResource = ImportRequest & { _type: "request" }
|
||||
type InsomniaRequestResource = ImportRequest & {
|
||||
_type: "request"
|
||||
} & {
|
||||
pathParameters?: InsomniaPathParameter[]
|
||||
}
|
||||
|
||||
const parseInsomniaDoc = (content: string) =>
|
||||
TO.tryCatch(() => convert(content))
|
||||
|
||||
const replacePathVarTemplating = (expression: string) =>
|
||||
expression.replaceAll(/:([^/]+)/g, "<<$1>>")
|
||||
|
||||
const replaceVarTemplating = (expression: string) =>
|
||||
replaceInsomniaTemplating(expression)
|
||||
pipe(expression, replacePathVarTemplating, replaceInsomniaTemplating)
|
||||
|
||||
const getFoldersIn = (
|
||||
folder: InsomniaFolderResource | null,
|
||||
@@ -177,6 +191,15 @@ const getHoppReqParams = (req: InsomniaRequestResource): HoppRESTParam[] =>
|
||||
active: !(param.disabled ?? false),
|
||||
})) ?? []
|
||||
|
||||
const getHoppReqVariables = (
|
||||
req: InsomniaRequestResource
|
||||
): HoppRESTRequestVariable[] =>
|
||||
req.pathParameters?.map((variable) => ({
|
||||
key: replaceVarTemplating(variable.name),
|
||||
value: replaceVarTemplating(variable.value ?? ""),
|
||||
active: true,
|
||||
})) ?? []
|
||||
|
||||
const getHoppRequest = (req: InsomniaRequestResource): HoppRESTRequest =>
|
||||
makeRESTRequest({
|
||||
name: req.name ?? "Untitled Request",
|
||||
@@ -189,6 +212,8 @@ const getHoppRequest = (req: InsomniaRequestResource): HoppRESTRequest =>
|
||||
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
|
||||
requestVariables: getHoppReqVariables(req),
|
||||
})
|
||||
|
||||
const getHoppFolder = (
|
||||
|
||||
@@ -6,7 +6,7 @@ import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { z } from "zod"
|
||||
import { NonSecretEnvironment } from "@hoppscotch/data"
|
||||
import { safeParseJSONOrYAML } from "~/helpers/functional/yaml"
|
||||
import { uniqueId } from "lodash-es"
|
||||
import { uniqueID } from "~/helpers/utils/uniqueID"
|
||||
|
||||
const insomniaResourcesSchema = z.object({
|
||||
resources: z.array(
|
||||
@@ -67,7 +67,7 @@ export const insomniaEnvImporter = (contents: string[]) => {
|
||||
|
||||
if (parsedInsomniaEnv.success) {
|
||||
const environment: NonSecretEnvironment = {
|
||||
id: uniqueId(),
|
||||
id: uniqueID(),
|
||||
v: 1,
|
||||
name: parsedInsomniaEnv.data.name,
|
||||
variables: Object.entries(parsedInsomniaEnv.data.data).map(
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
makeRESTRequest,
|
||||
HoppCollection,
|
||||
makeCollection,
|
||||
HoppRESTRequestVariable,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import * as A from "fp-ts/Array"
|
||||
@@ -24,6 +26,7 @@ import * as O from "fp-ts/Option"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import * as RA from "fp-ts/ReadonlyArray"
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { cloneDeep } from "lodash-es"
|
||||
|
||||
export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const
|
||||
|
||||
@@ -82,6 +85,27 @@ const parseOpenAPIParams = (params: OpenAPIParamsType[]): HoppRESTParam[] =>
|
||||
)
|
||||
)
|
||||
|
||||
const parseOpenAPIVariables = (
|
||||
variables: OpenAPIParamsType[]
|
||||
): HoppRESTRequestVariable[] =>
|
||||
pipe(
|
||||
variables,
|
||||
|
||||
A.filterMap(
|
||||
flow(
|
||||
O.fromPredicate((param) => param.in === "path"),
|
||||
O.map(
|
||||
(param) =>
|
||||
<HoppRESTRequestVariable>{
|
||||
key: param.name,
|
||||
value: "", // TODO: Can we do anything more ? (parse default values maybe)
|
||||
active: true,
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const parseOpenAPIHeaders = (params: OpenAPIParamsType[]): HoppRESTHeader[] =>
|
||||
pipe(
|
||||
params,
|
||||
@@ -558,26 +582,42 @@ const convertPathToHoppReqs = (
|
||||
? openAPIUrl + openAPIPath.slice(1)
|
||||
: openAPIUrl + openAPIPath
|
||||
|
||||
return makeRESTRequest({
|
||||
name: info.operationId ?? info.summary ?? "Untitled Request",
|
||||
method: method.toUpperCase(),
|
||||
endpoint,
|
||||
const res: {
|
||||
request: HoppRESTRequest
|
||||
metadata: {
|
||||
tags: string[]
|
||||
}
|
||||
} = {
|
||||
request: makeRESTRequest({
|
||||
name: info.operationId ?? info.summary ?? "Untitled Request",
|
||||
method: method.toUpperCase(),
|
||||
endpoint,
|
||||
|
||||
// We don't need to worry about reference types as the Dereferencing pass should remove them
|
||||
params: parseOpenAPIParams(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
headers: parseOpenAPIHeaders(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
// We don't need to worry about reference types as the Dereferencing pass should remove them
|
||||
params: parseOpenAPIParams(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
headers: parseOpenAPIHeaders(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
|
||||
auth: parseOpenAPIAuth(doc, info),
|
||||
auth: parseOpenAPIAuth(doc, info),
|
||||
|
||||
body: parseOpenAPIBody(doc, info),
|
||||
body: parseOpenAPIBody(doc, info),
|
||||
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
})
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
|
||||
requestVariables: parseOpenAPIVariables(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
}),
|
||||
metadata: {
|
||||
tags: info.tags ?? [],
|
||||
},
|
||||
}
|
||||
|
||||
return res
|
||||
}),
|
||||
|
||||
// Disable Readonly
|
||||
@@ -596,10 +636,38 @@ const convertOpenApiDocsToHopp = (
|
||||
)
|
||||
.flat()
|
||||
|
||||
const requestsByTags: Record<string, Array<HoppRESTRequest>> = {}
|
||||
const requestsWithoutTags: Array<HoppRESTRequest> = []
|
||||
|
||||
paths.forEach(({ metadata, request }) => {
|
||||
const tags = metadata.tags
|
||||
|
||||
if (tags.length === 0) {
|
||||
requestsWithoutTags.push(request)
|
||||
return
|
||||
}
|
||||
|
||||
for (const tag of tags) {
|
||||
if (!requestsByTags[tag]) {
|
||||
requestsByTags[tag] = []
|
||||
}
|
||||
|
||||
requestsByTags[tag].push(cloneDeep(request))
|
||||
}
|
||||
})
|
||||
|
||||
return makeCollection({
|
||||
name,
|
||||
folders: [],
|
||||
requests: paths,
|
||||
folders: Object.entries(requestsByTags).map(([name, paths]) =>
|
||||
makeCollection({
|
||||
name,
|
||||
requests: paths,
|
||||
folders: [],
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
})
|
||||
),
|
||||
requests: requestsWithoutTags,
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
QueryParam,
|
||||
RequestAuthDefinition,
|
||||
VariableDefinition,
|
||||
Variable,
|
||||
} from "postman-collection"
|
||||
import {
|
||||
HoppRESTAuth,
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
ValidContentTypes,
|
||||
knownContentTypes,
|
||||
FormDataKeyValue,
|
||||
HoppRESTRequestVariable,
|
||||
} from "@hoppscotch/data"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import * as S from "fp-ts/string"
|
||||
@@ -91,6 +93,25 @@ const getHoppReqParams = (item: Item): HoppRESTParam[] => {
|
||||
)
|
||||
}
|
||||
|
||||
const getHoppReqVariables = (item: Item) => {
|
||||
return pipe(
|
||||
item.request.url.variables.all(),
|
||||
A.filter(
|
||||
(variable): variable is Variable =>
|
||||
variable.key !== undefined &&
|
||||
variable.key !== null &&
|
||||
variable.key.length > 0
|
||||
),
|
||||
A.map((variable) => {
|
||||
return <HoppRESTRequestVariable>{
|
||||
key: replacePMVarTemplating(variable.key ?? ""),
|
||||
value: replacePMVarTemplating(variable.value ?? ""),
|
||||
active: !variable.disabled,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
type PMRequestAuthDef<
|
||||
AuthType extends
|
||||
RequestAuthDefinition["type"] = RequestAuthDefinition["type"],
|
||||
@@ -280,6 +301,7 @@ const getHoppRequest = (item: Item): HoppRESTRequest => {
|
||||
params: getHoppReqParams(item),
|
||||
auth: getHoppReqAuth(item),
|
||||
body: getHoppReqBody(item),
|
||||
requestVariables: getHoppReqVariables(item),
|
||||
|
||||
// TODO: Decide about this
|
||||
preRequestScript: "",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { uniqueId } from "lodash-es"
|
||||
import { z } from "zod"
|
||||
|
||||
import { safeParseJSON } from "~/helpers/functional/json"
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { uniqueID } from "~/helpers/utils/uniqueID"
|
||||
|
||||
const postmanEnvSchema = z.object({
|
||||
name: z.string(),
|
||||
@@ -49,7 +49,7 @@ export const postmanEnvImporter = (contents: string[]) => {
|
||||
// Convert `values` to `variables` to match the format expected by the system
|
||||
const environments: Environment[] = validationResult.data.map(
|
||||
({ name, values }) => ({
|
||||
id: uniqueId(),
|
||||
id: uniqueID(),
|
||||
v: 1,
|
||||
name,
|
||||
variables: values.map((entires) => ({ ...entires, secret: false })),
|
||||
|
||||
@@ -14,8 +14,8 @@ import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
const secretEnvironmentService = getService(SecretEnvironmentService)
|
||||
|
||||
const unsecretEnvironments = (
|
||||
global: Environment["variables"],
|
||||
selected: Environment
|
||||
selected: Environment,
|
||||
global: Environment["variables"]
|
||||
) => {
|
||||
const resolvedGlobalWithSecrets = global.map((globalVar, index) => {
|
||||
const secretVar = secretEnvironmentService.getSecretEnvironmentVariable(
|
||||
@@ -65,8 +65,8 @@ const unsecretEnvironments = (
|
||||
|
||||
export const getCombinedEnvVariables = () => {
|
||||
const reformedVars = unsecretEnvironments(
|
||||
getGlobalVariables(),
|
||||
getCurrentEnvironment()
|
||||
getCurrentEnvironment(),
|
||||
getGlobalVariables()
|
||||
)
|
||||
return {
|
||||
global: cloneDeep(reformedVars.global),
|
||||
|
||||
@@ -17,4 +17,5 @@ export const getDefaultRESTRequest = (): HoppRESTRequest => ({
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
requestVariables: [],
|
||||
})
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
import { arrayFlatMap, arraySort } from "../functional/array"
|
||||
import { toFormData } from "../functional/formData"
|
||||
import { tupleWithSameKeysToRecord } from "../functional/record"
|
||||
import { getGlobalVariables } from "~/newstore/environments"
|
||||
|
||||
export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
||||
/**
|
||||
@@ -34,6 +33,7 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
||||
effectiveFinalHeaders: { key: string; value: string }[]
|
||||
effectiveFinalParams: { key: string; value: string }[]
|
||||
effectiveFinalBody: FormData | string | null
|
||||
effectiveFinalRequestVariables: { key: string; value: string }[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,38 +313,53 @@ export function getEffectiveRESTRequest(
|
||||
request: HoppRESTRequest,
|
||||
environment: Environment
|
||||
): EffectiveHoppRESTRequest {
|
||||
const envVariables = [...environment.variables, ...getGlobalVariables()]
|
||||
|
||||
const effectiveFinalHeaders = pipe(
|
||||
getComputedHeaders(request, envVariables).map((h) => h.header),
|
||||
getComputedHeaders(request, environment.variables).map((h) => h.header),
|
||||
A.concat(request.headers),
|
||||
A.filter((x) => x.active && x.key !== ""),
|
||||
A.map((x) => ({
|
||||
active: true,
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: parseTemplateString(x.value, envVariables),
|
||||
key: parseTemplateString(x.key, environment.variables),
|
||||
value: parseTemplateString(x.value, environment.variables),
|
||||
}))
|
||||
)
|
||||
|
||||
const effectiveFinalParams = pipe(
|
||||
getComputedParams(request, envVariables).map((p) => p.param),
|
||||
getComputedParams(request, environment.variables).map((p) => p.param),
|
||||
A.concat(request.params),
|
||||
A.filter((x) => x.active && x.key !== ""),
|
||||
A.map((x) => ({
|
||||
active: true,
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: parseTemplateString(x.value, envVariables),
|
||||
key: parseTemplateString(x.key, environment.variables),
|
||||
value: parseTemplateString(x.value, environment.variables),
|
||||
}))
|
||||
)
|
||||
|
||||
const effectiveFinalBody = getFinalBodyFromRequest(request, envVariables)
|
||||
const effectiveFinalRequestVariables = pipe(
|
||||
request.requestVariables,
|
||||
A.filter((x) => x.active && x.key !== ""),
|
||||
A.map((x) => ({
|
||||
active: true,
|
||||
key: parseTemplateString(x.key, environment.variables),
|
||||
value: parseTemplateString(x.value, environment.variables),
|
||||
}))
|
||||
)
|
||||
|
||||
const effectiveFinalBody = getFinalBodyFromRequest(
|
||||
request,
|
||||
environment.variables
|
||||
)
|
||||
|
||||
return {
|
||||
...request,
|
||||
effectiveFinalURL: parseTemplateString(request.endpoint, envVariables),
|
||||
effectiveFinalURL: parseTemplateString(
|
||||
request.endpoint,
|
||||
environment.variables
|
||||
),
|
||||
effectiveFinalHeaders,
|
||||
effectiveFinalParams,
|
||||
effectiveFinalBody,
|
||||
effectiveFinalRequestVariables,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
packages/hoppscotch-common/src/helpers/utils/uniqueID.ts
Normal file
3
packages/hoppscotch-common/src/helpers/utils/uniqueID.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const uniqueID = (length = 16) => {
|
||||
return Math.random().toString(36).substring(2, length)
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export type HoppModule = {
|
||||
to: RouteLocationNormalized,
|
||||
from: RouteLocationNormalized,
|
||||
router: Router
|
||||
) => void
|
||||
) => void | Promise<void>
|
||||
|
||||
/**
|
||||
* Called by the router to tell all the modules that a route navigation has completed
|
||||
|
||||
@@ -47,15 +47,21 @@ export default <HoppModule>{
|
||||
routes,
|
||||
})
|
||||
|
||||
router.beforeEach((to, from) => {
|
||||
router.beforeEach(async (to, from) => {
|
||||
_isLoadingInitialRoute.value = isInitialRoute(from)
|
||||
|
||||
const onBeforeRouteChangePromises: Promise<any>[] = []
|
||||
|
||||
HOPP_MODULES.forEach((mod) => {
|
||||
mod.onBeforeRouteChange?.(to, from, router)
|
||||
const res = mod.onBeforeRouteChange?.(to, from, router)
|
||||
if (res) onBeforeRouteChangePromises.push(res)
|
||||
})
|
||||
platform.addedHoppModules?.forEach((mod) => {
|
||||
mod.onBeforeRouteChange?.(to, from, router)
|
||||
const res = mod.onBeforeRouteChange?.(to, from, router)
|
||||
if (res) onBeforeRouteChangePromises.push(res)
|
||||
})
|
||||
|
||||
await Promise.all(onBeforeRouteChangePromises)
|
||||
})
|
||||
|
||||
// Instead of this a better architecture is for the router
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import { cloneDeep, isEqual, uniqueId } from "lodash-es"
|
||||
import { cloneDeep, isEqual } from "lodash-es"
|
||||
import { combineLatest, Observable } from "rxjs"
|
||||
import { distinctUntilChanged, map, pluck } from "rxjs/operators"
|
||||
import { uniqueID } from "~/helpers/utils/uniqueID"
|
||||
import { getService } from "~/modules/dioc"
|
||||
import DispatchingStore, {
|
||||
defineDispatchers,
|
||||
@@ -22,7 +23,7 @@ const defaultEnvironmentsState = {
|
||||
environments: [
|
||||
{
|
||||
v: 1,
|
||||
id: uniqueId(),
|
||||
id: uniqueID(),
|
||||
name: "My Environment Variables",
|
||||
variables: [],
|
||||
},
|
||||
@@ -100,7 +101,7 @@ const dispatchers = defineDispatchers({
|
||||
}
|
||||
: {
|
||||
v: 1,
|
||||
id: "",
|
||||
id: uniqueID(),
|
||||
name,
|
||||
variables,
|
||||
},
|
||||
@@ -123,7 +124,7 @@ const dispatchers = defineDispatchers({
|
||||
...environments,
|
||||
{
|
||||
...cloneDeep(newEnvironment),
|
||||
id: uniqueId(),
|
||||
id: uniqueID(),
|
||||
name: `${newEnvironment.name} - Duplicate`,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -353,6 +353,7 @@ executedResponses$.subscribe((res) => {
|
||||
params: res.req.params,
|
||||
preRequestScript: res.req.preRequestScript,
|
||||
testScript: res.req.testScript,
|
||||
requestVariables: res.req.requestVariables,
|
||||
v: res.req.v,
|
||||
},
|
||||
responseMeta: {
|
||||
|
||||
@@ -38,6 +38,7 @@ export type SettingsDef = {
|
||||
httpUrlEncoded: boolean
|
||||
httpPreRequest: boolean
|
||||
httpTest: boolean
|
||||
httpRequestVariables: boolean
|
||||
graphqlQuery: boolean
|
||||
graphqlResponseBody: boolean
|
||||
graphqlHeaders: boolean
|
||||
@@ -79,6 +80,7 @@ export const getDefaultSettings = (): SettingsDef => ({
|
||||
httpUrlEncoded: true,
|
||||
httpPreRequest: true,
|
||||
httpTest: true,
|
||||
httpRequestVariables: true,
|
||||
graphqlQuery: true,
|
||||
graphqlResponseBody: true,
|
||||
graphqlHeaders: false,
|
||||
|
||||
@@ -44,4 +44,5 @@ export default defineComponent({
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: empty
|
||||
onlyGuest: true
|
||||
</route>
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
<h1 class="heading">
|
||||
{{
|
||||
t("team.join_team", {
|
||||
team: inviteDetails.data.right.teamInvitation.team.name,
|
||||
workspace: inviteDetails.data.right.teamInvitation.team.name,
|
||||
})
|
||||
}}
|
||||
</h1>
|
||||
@@ -81,7 +81,7 @@
|
||||
owner:
|
||||
inviteDetails.data.right.teamInvitation.creator.displayName ??
|
||||
inviteDetails.data.right.teamInvitation.creator.email,
|
||||
team: inviteDetails.data.right.teamInvitation.team.name,
|
||||
workspace: inviteDetails.data.right.teamInvitation.team.name,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
@@ -89,7 +89,7 @@
|
||||
<HoppButtonPrimary
|
||||
:label="
|
||||
t('team.join_team', {
|
||||
team: inviteDetails.data.right.teamInvitation.team.name,
|
||||
workspace: inviteDetails.data.right.teamInvitation.team.name,
|
||||
})
|
||||
"
|
||||
:loading="loading"
|
||||
@@ -109,14 +109,14 @@
|
||||
<h1 class="heading">
|
||||
{{
|
||||
t("team.joined_team", {
|
||||
team: inviteDetails.data.right.teamInvitation.team.name,
|
||||
workspace: inviteDetails.data.right.teamInvitation.team.name,
|
||||
})
|
||||
}}
|
||||
</h1>
|
||||
<p class="mt-2 text-secondaryLight">
|
||||
{{
|
||||
t("team.joined_team_description", {
|
||||
team: inviteDetails.data.right.teamInvitation.team.name,
|
||||
workspace: inviteDetails.data.right.teamInvitation.team.name,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Ref, markRaw } from "vue"
|
||||
import IconPlusCircle from "~icons/lucide/plus-circle"
|
||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import {
|
||||
AggregateEnvironment,
|
||||
aggregateEnvsWithSecrets$,
|
||||
getCurrentEnvironment,
|
||||
getSelectedEnvironmentType,
|
||||
@@ -18,6 +19,7 @@ import { invokeAction } from "~/helpers/actions"
|
||||
import { computed } from "vue"
|
||||
import { useStreamStatic } from "~/composables/stream"
|
||||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
|
||||
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
|
||||
|
||||
@@ -41,6 +43,7 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
||||
|
||||
private readonly inspection = this.bind(InspectionService)
|
||||
private readonly secretEnvs = this.bind(SecretEnvironmentService)
|
||||
private readonly restTabs = this.bind(RESTTabService)
|
||||
|
||||
private aggregateEnvsWithSecrets = useStreamStatic(
|
||||
aggregateEnvsWithSecrets$,
|
||||
@@ -68,7 +71,14 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
||||
) => {
|
||||
const newErrors: InspectorResult[] = []
|
||||
|
||||
const envKeys = this.aggregateEnvsWithSecrets.value.map((e) => e.key)
|
||||
const currentTab = this.restTabs.currentActiveTab.value
|
||||
|
||||
const environmentVariables = [
|
||||
...currentTab.document.request.requestVariables,
|
||||
...this.aggregateEnvsWithSecrets.value,
|
||||
]
|
||||
|
||||
const envKeys = environmentVariables.map((e) => e.key)
|
||||
|
||||
target.forEach((element, index) => {
|
||||
if (isENVInString(element)) {
|
||||
@@ -124,6 +134,31 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
||||
return newErrors
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the environment list to a list with unique keys with value
|
||||
* @param envs The environment list to be transformed
|
||||
* @returns The transformed environment list with keys with value
|
||||
*/
|
||||
private filterNonEmptyEnvironmentVariables = (
|
||||
envs: AggregateEnvironment[]
|
||||
): AggregateEnvironment[] => {
|
||||
const envsMap = new Map<string, AggregateEnvironment>()
|
||||
|
||||
envs.forEach((env) => {
|
||||
if (envsMap.has(env.key)) {
|
||||
const existingEnv = envsMap.get(env.key)
|
||||
|
||||
if (existingEnv?.value === "" && env.value !== "") {
|
||||
envsMap.set(env.key, env)
|
||||
}
|
||||
} else {
|
||||
envsMap.set(env.key, env)
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(envsMap.values())
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the environment variables in the target array are empty
|
||||
* @param target The target array to validate
|
||||
@@ -145,7 +180,19 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
||||
const formattedExEnv = exEnv.slice(2, -2)
|
||||
const currentSelectedEnvironment = getCurrentEnvironment()
|
||||
|
||||
this.aggregateEnvsWithSecrets.value.forEach((env) => {
|
||||
const currentTab = this.restTabs.currentActiveTab.value
|
||||
|
||||
const environmentVariables =
|
||||
this.filterNonEmptyEnvironmentVariables([
|
||||
...currentTab.document.request.requestVariables.map((env) => ({
|
||||
...env,
|
||||
secret: false,
|
||||
sourceEnv: "RequestVariable",
|
||||
})),
|
||||
...this.aggregateEnvsWithSecrets.value,
|
||||
])
|
||||
|
||||
environmentVariables.forEach((env) => {
|
||||
const hasSecretEnv = this.secretEnvs.hasSecretValue(
|
||||
env.sourceEnv !== "Global"
|
||||
? currentSelectedEnvironment.id
|
||||
@@ -199,14 +246,19 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
||||
"inspections.environment.add_environment_value"
|
||||
),
|
||||
apply: () => {
|
||||
invokeAction(invokeActionType, {
|
||||
envName:
|
||||
env.sourceEnv !== "Global"
|
||||
? currentSelectedEnvironment.name
|
||||
: "Global",
|
||||
variableName: formattedExEnv,
|
||||
isSecret: env.secret,
|
||||
})
|
||||
if (env.sourceEnv === "RequestVariable") {
|
||||
currentTab.document.optionTabPreference =
|
||||
"requestVariables"
|
||||
} else {
|
||||
invokeAction(invokeActionType, {
|
||||
envName:
|
||||
env.sourceEnv === "Global"
|
||||
? "Global"
|
||||
: currentSelectedEnvironment.name,
|
||||
variableName: formattedExEnv,
|
||||
isSecret: env.secret,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
severity: 2,
|
||||
|
||||
@@ -25,7 +25,7 @@ export const REST_COLLECTIONS_MOCK: HoppCollection[] = [
|
||||
folders: [],
|
||||
requests: [
|
||||
{
|
||||
v: "1",
|
||||
v: "2",
|
||||
endpoint: "https://echo.hoppscotch.io",
|
||||
name: "Echo test",
|
||||
params: [],
|
||||
@@ -35,6 +35,7 @@ export const REST_COLLECTIONS_MOCK: HoppCollection[] = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
body: { contentType: null, body: null },
|
||||
requestVariables: [],
|
||||
},
|
||||
],
|
||||
auth: { authType: "none", authActive: true },
|
||||
@@ -136,7 +137,8 @@ export const REST_HISTORY_MOCK: RESTHistoryEntry[] = [
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
v: "1",
|
||||
requestVariables: [],
|
||||
v: "2",
|
||||
},
|
||||
responseMeta: { duration: 807, statusCode: 200 },
|
||||
star: false,
|
||||
@@ -192,7 +194,7 @@ export const REST_TAB_STATE_MOCK: PersistableTabState<HoppRESTDocument> = {
|
||||
tabID: "e6e8d800-caa8-44a2-a6a6-b4765a3167aa",
|
||||
doc: {
|
||||
request: {
|
||||
v: "1",
|
||||
v: "2",
|
||||
endpoint: "https://echo.hoppscotch.io",
|
||||
name: "Echo test",
|
||||
params: [],
|
||||
@@ -202,6 +204,7 @@ export const REST_TAB_STATE_MOCK: PersistableTabState<HoppRESTDocument> = {
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
body: { contentType: null, body: null },
|
||||
requestVariables: [],
|
||||
},
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
|
||||
@@ -48,21 +48,22 @@ const SettingsDefSchema = z.object({
|
||||
|
||||
WRAP_LINES: z.optional(
|
||||
z.object({
|
||||
httpRequestBody: z.boolean(),
|
||||
httpResponseBody: z.boolean(),
|
||||
httpHeaders: z.boolean(),
|
||||
httpParams: z.boolean(),
|
||||
httpUrlEncoded: z.boolean(),
|
||||
httpPreRequest: z.boolean(),
|
||||
httpTest: z.boolean(),
|
||||
graphqlQuery: z.boolean(),
|
||||
graphqlResponseBody: z.boolean(),
|
||||
graphqlHeaders: z.boolean(),
|
||||
graphqlVariables: z.boolean(),
|
||||
graphqlSchema: z.boolean(),
|
||||
importCurl: z.boolean(),
|
||||
codeGen: z.boolean(),
|
||||
cookie: z.boolean(),
|
||||
httpRequestBody: z.boolean().catch(true),
|
||||
httpResponseBody: z.boolean().catch(true),
|
||||
httpHeaders: z.boolean().catch(true),
|
||||
httpParams: z.boolean().catch(true),
|
||||
httpUrlEncoded: z.boolean().catch(true),
|
||||
httpPreRequest: z.boolean().catch(true),
|
||||
httpTest: z.boolean().catch(true),
|
||||
httpRequestVariables: z.boolean().catch(true),
|
||||
graphqlQuery: z.boolean().catch(true),
|
||||
graphqlResponseBody: z.boolean().catch(true),
|
||||
graphqlHeaders: z.boolean().catch(false),
|
||||
graphqlVariables: z.boolean().catch(false),
|
||||
graphqlSchema: z.boolean().catch(true),
|
||||
importCurl: z.boolean().catch(true),
|
||||
codeGen: z.boolean().catch(true),
|
||||
cookie: z.boolean().catch(true),
|
||||
})
|
||||
),
|
||||
})
|
||||
@@ -514,6 +515,7 @@ const validRestOperations = [
|
||||
"authorization",
|
||||
"preRequestScript",
|
||||
"tests",
|
||||
"requestVariables",
|
||||
] as const
|
||||
|
||||
export const REST_TAB_STATE_SCHEMA = z
|
||||
|
||||
@@ -34,16 +34,16 @@
|
||||
},
|
||||
"homepage": "https://github.com/hoppscotch/hoppscotch#readme",
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.200",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.5.0"
|
||||
"@types/lodash": "4.14.200",
|
||||
"typescript": "5.2.2",
|
||||
"vite": "4.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fp-ts": "^2.16.1",
|
||||
"io-ts": "^2.2.20",
|
||||
"lodash": "^4.17.21",
|
||||
"parser-ts": "^0.7.0",
|
||||
"verzod": "^0.2.2",
|
||||
"zod": "^3.22.4"
|
||||
"fp-ts": "2.16.1",
|
||||
"io-ts": "2.2.20",
|
||||
"lodash": "4.17.21",
|
||||
"parser-ts": "0.7.0",
|
||||
"verzod": "0.2.2",
|
||||
"zod": "3.22.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,8 +52,6 @@ export function makeCollection(x: Omit<HoppCollection, "v">): HoppCollection {
|
||||
* @returns The proper new collection format
|
||||
*/
|
||||
export function translateToNewRESTCollection(x: any): HoppCollection {
|
||||
if (x.v && x.v === CollectionSchemaVersion) return x
|
||||
|
||||
// Legacy
|
||||
const name = x.name ?? "Untitled"
|
||||
const folders = (x.folders ?? []).map(translateToNewRESTCollection)
|
||||
@@ -81,8 +79,6 @@ export function translateToNewRESTCollection(x: any): HoppCollection {
|
||||
* @returns The proper new collection format
|
||||
*/
|
||||
export function translateToNewGQLCollection(x: any): HoppCollection {
|
||||
if (x.v && x.v === CollectionSchemaVersion) return x
|
||||
|
||||
// Legacy
|
||||
const name = x.name ?? "Untitled"
|
||||
const folders = (x.folders ?? []).map(translateToNewGQLCollection)
|
||||
|
||||
@@ -5,7 +5,7 @@ import { InferredEntity, createVersionedEntity } from "verzod"
|
||||
import { z } from "zod"
|
||||
|
||||
import V0_VERSION from "./v/0"
|
||||
import V1_VERSION from "./v/1"
|
||||
import V1_VERSION, { uniqueID } from "./v/1"
|
||||
|
||||
const versionedObject = z.object({
|
||||
v: z.number(),
|
||||
@@ -165,7 +165,7 @@ export const translateToNewEnvironment = (x: any): Environment => {
|
||||
if (x.v && x.v === EnvironmentSchemaVersion) return x
|
||||
|
||||
// Legacy
|
||||
const id = x.id ?? ""
|
||||
const id = x.id || uniqueID()
|
||||
const name = x.name ?? "Untitled"
|
||||
const variables = (x.variables ?? []).map(translateToNewEnvironmentVariables)
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import { z } from "zod"
|
||||
import { defineVersion } from "verzod"
|
||||
import { V0_SCHEMA } from "./0"
|
||||
|
||||
export const uniqueID = () => Math.random().toString(36).substring(2, 16)
|
||||
|
||||
export const V1_SCHEMA = z.object({
|
||||
v: z.literal(1),
|
||||
id: z.string(),
|
||||
@@ -28,7 +30,7 @@ export default defineVersion({
|
||||
const result: z.infer<typeof V1_SCHEMA> = {
|
||||
...old,
|
||||
v: 1,
|
||||
id: old.id ?? "",
|
||||
id: old.id || uniqueID(),
|
||||
variables: old.variables.map((variable) => {
|
||||
return {
|
||||
...variable,
|
||||
|
||||
@@ -3,6 +3,7 @@ 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 V2_VERSION from "./v/2"
|
||||
import { createVersionedEntity, InferredEntity } from "verzod"
|
||||
import { lodashIsEqualEq, mapThenEq, undefinedEq } from "../utils/eq"
|
||||
import {
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
HoppRESTHeaders,
|
||||
HoppRESTParams,
|
||||
} from "./v/1"
|
||||
import { HoppRESTRequestVariables } from "./v/2"
|
||||
import { z } from "zod"
|
||||
|
||||
export * from "./content-types"
|
||||
@@ -28,16 +30,19 @@ export {
|
||||
HoppRESTHeaders,
|
||||
} from "./v/1"
|
||||
|
||||
export { HoppRESTRequestVariables } from "./v/2"
|
||||
|
||||
const versionedObject = z.object({
|
||||
// v is a stringified number
|
||||
v: z.string().regex(/^\d+$/).transform(Number),
|
||||
})
|
||||
|
||||
export const HoppRESTRequest = createVersionedEntity({
|
||||
latestVersion: 1,
|
||||
latestVersion: 2,
|
||||
versionMap: {
|
||||
0: V0_VERSION,
|
||||
1: V1_VERSION,
|
||||
2: V2_VERSION,
|
||||
},
|
||||
getVersion(data) {
|
||||
// For V1 onwards we have the v string storing the number
|
||||
@@ -73,12 +78,18 @@ const HoppRESTRequestEq = Eq.struct<HoppRESTRequest>({
|
||||
name: S.Eq,
|
||||
preRequestScript: S.Eq,
|
||||
testScript: S.Eq,
|
||||
requestVariables: mapThenEq(
|
||||
(arr) => arr.filter((v: any) => v.key !== "" && v.value !== ""),
|
||||
lodashIsEqualEq
|
||||
),
|
||||
})
|
||||
|
||||
export const RESTReqSchemaVersion = "1"
|
||||
export const RESTReqSchemaVersion = "2"
|
||||
|
||||
export type HoppRESTParam = HoppRESTRequest["params"][number]
|
||||
export type HoppRESTHeader = HoppRESTRequest["headers"][number]
|
||||
export type HoppRESTRequestVariable =
|
||||
HoppRESTRequest["requestVariables"][number]
|
||||
|
||||
export const isEqualHoppRESTRequest = HoppRESTRequestEq.equals
|
||||
|
||||
@@ -144,6 +155,14 @@ export function safelyExtractRESTRequest(
|
||||
req.headers = result.data
|
||||
}
|
||||
}
|
||||
|
||||
if ("requestVariables" in x) {
|
||||
const result = HoppRESTRequestVariables.safeParse(x.requestVariables)
|
||||
|
||||
if (result.success) {
|
||||
req.requestVariables = result.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return req
|
||||
@@ -160,7 +179,7 @@ export function makeRESTRequest(
|
||||
|
||||
export function getDefaultRESTRequest(): HoppRESTRequest {
|
||||
return {
|
||||
v: "1",
|
||||
v: "2",
|
||||
endpoint: "https://echo.hoppscotch.io",
|
||||
name: "Untitled",
|
||||
params: [],
|
||||
@@ -176,6 +195,7 @@ export function getDefaultRESTRequest(): HoppRESTRequest {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
requestVariables: [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ export const HoppRESTHeaders = z.array(
|
||||
|
||||
export type HoppRESTHeaders = z.infer<typeof HoppRESTHeaders>
|
||||
|
||||
const V1_SCHEMA = z.object({
|
||||
export const V1_SCHEMA = z.object({
|
||||
v: z.literal("1"),
|
||||
id: z.optional(z.string()), // Firebase Firestore ID
|
||||
|
||||
@@ -158,7 +158,7 @@ const V1_SCHEMA = z.object({
|
||||
body: HoppRESTReqBody,
|
||||
})
|
||||
|
||||
function parseRequestBody(
|
||||
export function parseRequestBody(
|
||||
x: z.infer<typeof V0_SCHEMA>
|
||||
): z.infer<typeof V1_SCHEMA>["body"] {
|
||||
return {
|
||||
|
||||
36
packages/hoppscotch-data/src/rest/v/2.ts
Normal file
36
packages/hoppscotch-data/src/rest/v/2.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { defineVersion } from "verzod"
|
||||
import { z } from "zod"
|
||||
import {
|
||||
HoppRESTAuth,
|
||||
HoppRESTHeaders,
|
||||
HoppRESTParams,
|
||||
HoppRESTReqBody,
|
||||
} from "./1"
|
||||
import { V1_SCHEMA } from "./1"
|
||||
|
||||
export const HoppRESTRequestVariables = z.array(
|
||||
z.object({
|
||||
key: z.string().catch(""),
|
||||
value: z.string().catch(""),
|
||||
active: z.boolean().catch(true),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTRequestVariables = z.infer<typeof HoppRESTRequestVariables>
|
||||
|
||||
const V2_SCHEMA = V1_SCHEMA.extend({
|
||||
v: z.literal("2"),
|
||||
requestVariables: HoppRESTRequestVariables,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
initial: false,
|
||||
schema: V2_SCHEMA,
|
||||
up(old: z.infer<typeof V1_SCHEMA>) {
|
||||
return {
|
||||
...old,
|
||||
v: "2",
|
||||
requestVariables: [],
|
||||
} as z.infer<typeof V2_SCHEMA>
|
||||
},
|
||||
})
|
||||
@@ -52,27 +52,27 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@hoppscotch/data": "workspace:^",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"fp-ts": "^2.11.10",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21"
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"fp-ts": "2.12.1",
|
||||
"lodash": "4.17.21",
|
||||
"lodash-es": "4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@digitak/esrun": "^3.1.2",
|
||||
"@relmify/jest-fp-ts": "^2.0.1",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/lodash": "^4.14.181",
|
||||
"@types/node": "^17.0.24",
|
||||
"@typescript-eslint/eslint-plugin": "^5.19.0",
|
||||
"@typescript-eslint/parser": "^5.19.0",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"io-ts": "^2.2.16",
|
||||
"jest": "^27.5.1",
|
||||
"prettier": "^2.8.4",
|
||||
"ts-jest": "^27.1.4",
|
||||
"typescript": "^4.6.3",
|
||||
"vite": "^5.0.4"
|
||||
"@digitak/esrun": "3.2.8",
|
||||
"@relmify/jest-fp-ts": "2.0.2",
|
||||
"@types/jest": "27.5.2",
|
||||
"@types/lodash": "4.14.182",
|
||||
"@types/node": "17.0.45",
|
||||
"@typescript-eslint/eslint-plugin": "5.30.6",
|
||||
"@typescript-eslint/parser": "5.30.6",
|
||||
"eslint": "8.19.0",
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"io-ts": "2.2.16",
|
||||
"jest": "27.5.1",
|
||||
"prettier": "2.8.4",
|
||||
"ts-jest": "27.1.5",
|
||||
"typescript": "4.9.5",
|
||||
"vite": "5.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hoppscotch/selfhost-desktop",
|
||||
"private": true,
|
||||
"version": "2023.12.6",
|
||||
"version": "2024.3.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:vite": "vite",
|
||||
@@ -13,69 +13,69 @@
|
||||
"gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml dotenv_config_path=\"../../.env\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.0.5",
|
||||
"@fontsource-variable/material-symbols-rounded": "^5.0.5",
|
||||
"@fontsource-variable/roboto-mono": "^5.0.6",
|
||||
"@fontsource-variable/inter": "5.0.15",
|
||||
"@fontsource-variable/material-symbols-rounded": "5.0.16",
|
||||
"@fontsource-variable/roboto-mono": "5.0.16",
|
||||
"@hoppscotch/common": "workspace:^",
|
||||
"@platform/auth": "^0.1.106",
|
||||
"@tauri-apps/api": "^1.3.0",
|
||||
"@tauri-apps/cli": "^1.3.0",
|
||||
"@vueuse/core": "^10.4.1",
|
||||
"axios": "^0.21.4",
|
||||
"buffer": "^6.0.3",
|
||||
"dioc": "^1.0.1",
|
||||
"@platform/auth": "0.1.106",
|
||||
"@tauri-apps/api": "1.5.1",
|
||||
"@tauri-apps/cli": "1.5.6",
|
||||
"@vueuse/core": "10.5.0",
|
||||
"axios": "0.21.4",
|
||||
"buffer": "6.0.3",
|
||||
"dioc": "1.0.1",
|
||||
"environments.api": "link:@platform/environments/environments.api",
|
||||
"event": "link:@tauri-apps/api/event",
|
||||
"fp-ts": "^2.16.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"process": "^0.11.10",
|
||||
"rxjs": "^7.8.1",
|
||||
"fp-ts": "2.16.1",
|
||||
"lodash-es": "4.17.21",
|
||||
"process": "0.11.10",
|
||||
"rxjs": "7.8.1",
|
||||
"shell": "link:@tauri-apps/api/shell",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"stream-browserify": "3.0.0",
|
||||
"tauri": "link:@tauri-apps/api/tauri",
|
||||
"tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1",
|
||||
"util": "^0.12.4",
|
||||
"vue": "^3.2.45",
|
||||
"workbox-window": "^6.5.4"
|
||||
"tauri-plugin-store-api": "0.0.0",
|
||||
"util": "0.12.5",
|
||||
"vue": "3.3.9",
|
||||
"workbox-window": "6.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^5.0.0",
|
||||
"@graphql-codegen/cli": "^5.0.0",
|
||||
"@graphql-codegen/typed-document-node": "^5.0.1",
|
||||
"@graphql-codegen/typescript": "^4.0.1",
|
||||
"@graphql-codegen/typescript-operations": "^4.0.1",
|
||||
"@graphql-codegen/typescript-urql-graphcache": "^2.4.5",
|
||||
"@graphql-codegen/urql-introspection": "^2.2.1",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
"@types/node": "^18.7.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.19.0",
|
||||
"@typescript-eslint/parser": "^5.19.0",
|
||||
"@vitejs/plugin-legacy": "^2.3.0",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.5.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.23",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"typescript": "^4.9.5",
|
||||
"unplugin-fonts": "^1.1.1",
|
||||
"unplugin-icons": "^0.14.9",
|
||||
"unplugin-vue-components": "^0.21.0",
|
||||
"vite": "^4.2.1",
|
||||
"vite-plugin-html-config": "^1.0.10",
|
||||
"vite-plugin-inspect": "^0.7.4",
|
||||
"vite-plugin-pages": "^0.26.0",
|
||||
"vite-plugin-pages-sitemap": "^1.4.0",
|
||||
"vite-plugin-pwa": "^0.13.1",
|
||||
"vite-plugin-static-copy": "^0.12.0",
|
||||
"vite-plugin-vue-layouts": "^0.7.0",
|
||||
"vue-tsc": "^1.0.11"
|
||||
"@graphql-codegen/add": "5.0.0",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||
"@graphql-codegen/typescript-urql-graphcache": "2.4.5",
|
||||
"@graphql-codegen/urql-introspection": "2.2.1",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@intlify/vite-plugin-vue-i18n": "6.0.1",
|
||||
"@rushstack/eslint-patch": "1.3.3",
|
||||
"@types/lodash-es": "4.17.10",
|
||||
"@types/node": "18.18.8",
|
||||
"@typescript-eslint/eslint-plugin": "5.62.0",
|
||||
"@typescript-eslint/parser": "5.62.0",
|
||||
"@vitejs/plugin-legacy": "2.3.0",
|
||||
"@vitejs/plugin-vue": "4.3.1",
|
||||
"@vue/eslint-config-typescript": "11.0.3",
|
||||
"autoprefixer": "10.4.16",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.47.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"eslint-plugin-vue": "9.17.0",
|
||||
"npm-run-all": "4.1.5",
|
||||
"postcss": "8.4.32",
|
||||
"tailwindcss": "3.3.6",
|
||||
"typescript": "4.9.5",
|
||||
"unplugin-fonts": "1.1.1",
|
||||
"unplugin-icons": "0.14.9",
|
||||
"unplugin-vue-components": "0.21.0",
|
||||
"vite": "4.5.0",
|
||||
"vite-plugin-html-config": "1.0.11",
|
||||
"vite-plugin-inspect": "0.7.38",
|
||||
"vite-plugin-pages": "0.26.0",
|
||||
"vite-plugin-pages-sitemap": "1.6.1",
|
||||
"vite-plugin-pwa": "0.13.1",
|
||||
"vite-plugin-static-copy": "0.12.0",
|
||||
"vite-plugin-vue-layouts": "0.7.0",
|
||||
"vue-tsc": "1.8.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1260,7 +1260,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hoppscotch-desktop"
|
||||
version = "23.12.6"
|
||||
version = "24.3.0"
|
||||
dependencies = [
|
||||
"cocoa 0.25.0",
|
||||
"hex_color",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hoppscotch-desktop"
|
||||
version = "23.12.6"
|
||||
version = "24.3.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "Hoppscotch",
|
||||
"version": "23.12.6"
|
||||
"version": "24.3.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
||||
@@ -112,10 +112,15 @@ function exportedCollectionToHoppCollection(
|
||||
folders: restCollection.folders.map((folder) =>
|
||||
exportedCollectionToHoppCollection(folder, collectionType)
|
||||
),
|
||||
requests: restCollection.requests.map(
|
||||
({
|
||||
id,
|
||||
requests: restCollection.requests.map((request) => {
|
||||
const requestParsedResult = HoppRESTRequest.safeParse(request)
|
||||
if (requestParsedResult.type === "ok") {
|
||||
return requestParsedResult.value
|
||||
}
|
||||
|
||||
const {
|
||||
v,
|
||||
id,
|
||||
auth,
|
||||
body,
|
||||
endpoint,
|
||||
@@ -125,20 +130,23 @@ function exportedCollectionToHoppCollection(
|
||||
params,
|
||||
preRequestScript,
|
||||
testScript,
|
||||
}) => ({
|
||||
id,
|
||||
requestVariables,
|
||||
} = request
|
||||
return {
|
||||
v,
|
||||
auth,
|
||||
body,
|
||||
endpoint,
|
||||
headers,
|
||||
method,
|
||||
id,
|
||||
name,
|
||||
endpoint,
|
||||
method,
|
||||
params,
|
||||
requestVariables: requestVariables,
|
||||
auth,
|
||||
headers,
|
||||
body,
|
||||
preRequestScript,
|
||||
testScript,
|
||||
})
|
||||
),
|
||||
}
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
const gqlCollection = collection as ExportedUserCollectionGQL
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hoppscotch/selfhost-web",
|
||||
"private": true,
|
||||
"version": "2023.12.6",
|
||||
"version": "2024.3.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:vite": "vite",
|
||||
@@ -23,62 +23,62 @@
|
||||
"postinstall": "pnpm run gql-codegen"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.0.15",
|
||||
"@fontsource-variable/material-symbols-rounded": "^5.0.16",
|
||||
"@fontsource-variable/roboto-mono": "^5.0.16",
|
||||
"@fontsource-variable/inter": "5.0.15",
|
||||
"@fontsource-variable/material-symbols-rounded": "5.0.16",
|
||||
"@fontsource-variable/roboto-mono": "5.0.16",
|
||||
"@hoppscotch/common": "workspace:^",
|
||||
"@hoppscotch/data": "workspace:^",
|
||||
"@hoppscotch/ui": "^0.1.0",
|
||||
"@import-meta-env/unplugin": "^0.4.10",
|
||||
"axios": "^1.6.2",
|
||||
"buffer": "^6.0.3",
|
||||
"fp-ts": "^2.16.1",
|
||||
"process": "^0.11.10",
|
||||
"rxjs": "^7.8.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"util": "^0.12.5",
|
||||
"vue": "^3.3.8",
|
||||
"workbox-window": "^7.0.0",
|
||||
"zod": "^3.22.4"
|
||||
"@hoppscotch/ui": "0.1.0",
|
||||
"@import-meta-env/unplugin": "0.4.10",
|
||||
"axios": "1.6.2",
|
||||
"buffer": "6.0.3",
|
||||
"fp-ts": "2.16.1",
|
||||
"process": "0.11.10",
|
||||
"rxjs": "7.8.1",
|
||||
"stream-browserify": "3.0.0",
|
||||
"util": "0.12.5",
|
||||
"vue": "3.3.9",
|
||||
"workbox-window": "7.0.0",
|
||||
"zod": "3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^5.0.0",
|
||||
"@graphql-codegen/cli": "^5.0.0",
|
||||
"@graphql-codegen/typed-document-node": "^5.0.1",
|
||||
"@graphql-codegen/typescript": "^4.0.1",
|
||||
"@graphql-codegen/typescript-operations": "^4.0.1",
|
||||
"@graphql-codegen/typescript-urql-graphcache": "^3.0.0",
|
||||
"@graphql-codegen/urql-introspection": "^3.0.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@intlify/vite-plugin-vue-i18n": "^7.0.0",
|
||||
"@rushstack/eslint-patch": "^1.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"@vitejs/plugin-legacy": "^4.1.1",
|
||||
"@vitejs/plugin-vue": "^4.5.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-vue": "^9.18.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.23",
|
||||
"prettier-plugin-tailwindcss": "^0.5.7",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^5.3.2",
|
||||
"unplugin-fonts": "^1.1.1",
|
||||
"unplugin-icons": "^0.17.4",
|
||||
"unplugin-vue-components": "^0.25.2",
|
||||
"vite": "^4.5.0",
|
||||
"vite-plugin-fonts": "^0.7.0",
|
||||
"vite-plugin-html-config": "^1.0.11",
|
||||
"vite-plugin-inspect": "^0.7.42",
|
||||
"vite-plugin-pages": "^0.31.0",
|
||||
"vite-plugin-pages-sitemap": "^1.6.1",
|
||||
"vite-plugin-pwa": "^0.17.0",
|
||||
"vite-plugin-static-copy": "^0.17.1",
|
||||
"vite-plugin-vue-layouts": "^0.8.0",
|
||||
"vue-tsc": "^1.8.22"
|
||||
"@graphql-codegen/add": "5.0.0",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||
"@graphql-codegen/typescript-urql-graphcache": "3.0.0",
|
||||
"@graphql-codegen/urql-introspection": "3.0.0",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@intlify/vite-plugin-vue-i18n": "7.0.0",
|
||||
"@rushstack/eslint-patch": "1.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.13.2",
|
||||
"@typescript-eslint/parser": "6.13.2",
|
||||
"@vitejs/plugin-legacy": "4.1.1",
|
||||
"@vitejs/plugin-vue": "4.5.1",
|
||||
"@vue/eslint-config-typescript": "12.0.0",
|
||||
"autoprefixer": "10.4.16",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.55.0",
|
||||
"eslint-plugin-prettier": "5.0.1",
|
||||
"eslint-plugin-vue": "9.19.2",
|
||||
"npm-run-all": "4.1.5",
|
||||
"postcss": "8.4.31",
|
||||
"prettier-plugin-tailwindcss": "0.5.7",
|
||||
"tailwindcss": "3.3.5",
|
||||
"typescript": "5.3.2",
|
||||
"unplugin-fonts": "1.1.1",
|
||||
"unplugin-icons": "0.17.4",
|
||||
"unplugin-vue-components": "0.25.2",
|
||||
"vite": "4.5.0",
|
||||
"vite-plugin-fonts": "0.7.0",
|
||||
"vite-plugin-html-config": "1.0.11",
|
||||
"vite-plugin-inspect": "0.7.42",
|
||||
"vite-plugin-pages": "0.31.0",
|
||||
"vite-plugin-pages-sitemap": "1.6.1",
|
||||
"vite-plugin-pwa": "0.17.3",
|
||||
"vite-plugin-static-copy": "0.17.1",
|
||||
"vite-plugin-vue-layouts": "0.8.0",
|
||||
"vue-tsc": "1.8.24"
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user