Compare commits
47 Commits
feat/reque
...
2024.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
787aab650f | ||
|
|
1f7a8edb14 | ||
|
|
81f1e05a6c | ||
|
|
0a71783eaa | ||
|
|
c326f54f7e | ||
|
|
1113c79e20 | ||
|
|
6fd30f9aca | ||
|
|
2c5b0dcd1b | ||
|
|
6f4455ba03 | ||
|
|
ba8c4480d9 | ||
|
|
380397cc55 | ||
|
|
d19807b212 | ||
|
|
c89c2a5f5c | ||
|
|
256553b9bb | ||
|
|
89d9951f3b | ||
|
|
dd65ad3103 | ||
|
|
018ed3db26 | ||
|
|
a9cd6c0c01 | ||
|
|
e53382666a | ||
|
|
7621ff2961 | ||
|
|
fc20b76080 | ||
|
|
146c73d7b6 | ||
|
|
6b58915caa | ||
|
|
457857a711 | ||
|
|
a3f3e3e62d | ||
|
|
66f20d10e1 | ||
|
|
32e9366609 | ||
|
|
e41e956273 | ||
|
|
a14870f3f0 | ||
|
|
0e96665254 | ||
|
|
efdc1c2f5d | ||
|
|
c5334d4c06 | ||
|
|
4f549974ed | ||
|
|
41d617b507 | ||
|
|
be7387ed19 | ||
|
|
acfb0189df | ||
|
|
8fdba760a2 | ||
|
|
bf98009abb | ||
|
|
dce396c164 | ||
|
|
07e8af7947 | ||
|
|
e69d5a6253 | ||
|
|
6d66d12a9e | ||
|
|
439cd82c88 | ||
|
|
6dbaf524ce | ||
|
|
68e439d1a4 | ||
|
|
8deba7a28e | ||
|
|
7ec8659381 |
48
docker-compose.deploy.yml
Normal file
48
docker-compose.deploy.yml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# THIS IS NOT TO BE USED FOR PERSONAL DEPLOYMENTS!
|
||||||
|
# Internal Docker Compose Image used for internal testing deployments
|
||||||
|
|
||||||
|
version: "3.7"
|
||||||
|
|
||||||
|
services:
|
||||||
|
hoppscotch-db:
|
||||||
|
image: postgres:15
|
||||||
|
user: postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: testpass
|
||||||
|
POSTGRES_DB: hoppscotch
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD-SHELL",
|
||||||
|
"sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'"
|
||||||
|
]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
hoppscotch-aio:
|
||||||
|
container_name: hoppscotch-aio
|
||||||
|
build:
|
||||||
|
dockerfile: prod.Dockerfile
|
||||||
|
context: .
|
||||||
|
target: aio
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch
|
||||||
|
- ENABLE_SUBPATH_BASED_ACCESS=true
|
||||||
|
env_file:
|
||||||
|
- ./.env
|
||||||
|
depends_on:
|
||||||
|
hoppscotch-db:
|
||||||
|
condition: service_healthy
|
||||||
|
command: ["sh", "-c", "pnpm exec prisma migrate deploy && node /usr/src/app/aio_run.mjs"]
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- curl
|
||||||
|
- '-f'
|
||||||
|
- 'http://localhost:80'
|
||||||
|
interval: 2s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 30
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
dockerfile: packages/hoppscotch-backend/Dockerfile
|
dockerfile: packages/hoppscotch-backend/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
target: dev
|
target: prod
|
||||||
env_file:
|
env_file:
|
||||||
- ./.env
|
- ./.env
|
||||||
restart: always
|
restart: always
|
||||||
@@ -122,7 +122,7 @@ services:
|
|||||||
- PORT=3000
|
- PORT=3000
|
||||||
volumes:
|
volumes:
|
||||||
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
|
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
|
||||||
- ./packages/hoppscotch-backend/:/usr/src/app
|
# - ./packages/hoppscotch-backend/:/usr/src/app
|
||||||
- /usr/src/app/node_modules/
|
- /usr/src/app/node_modules/
|
||||||
depends_on:
|
depends_on:
|
||||||
hoppscotch-db:
|
hoppscotch-db:
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ curlCheck() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
curlCheck "http://localhost:3000"
|
if [ "$ENABLE_SUBPATH_BASED_ACCESS" = "true" ]; then
|
||||||
curlCheck "http://localhost:3100"
|
curlCheck "http://localhost:80/backend/ping"
|
||||||
curlCheck "http://localhost:3170/ping"
|
else
|
||||||
|
curlCheck "http://localhost:3000"
|
||||||
|
curlCheck "http://localhost:3100"
|
||||||
|
curlCheck "http://localhost:3170/ping"
|
||||||
|
fi
|
||||||
|
|||||||
14
package.json
14
package.json
@@ -23,13 +23,13 @@
|
|||||||
"./packages/*"
|
"./packages/*"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^16.2.3",
|
"@commitlint/cli": "16.3.0",
|
||||||
"@commitlint/config-conventional": "^16.2.1",
|
"@commitlint/config-conventional": "16.2.4",
|
||||||
"@hoppscotch/ui": "^0.1.0",
|
"@hoppscotch/ui": "0.1.0",
|
||||||
"@types/node": "17.0.27",
|
"@types/node": "17.0.27",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "14.1.1",
|
||||||
"husky": "^7.0.4",
|
"husky": "7.0.4",
|
||||||
"lint-staged": "12.4.0"
|
"lint-staged": "12.4.0"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"vue": "3.3.9"
|
"vue": "3.3.9"
|
||||||
},
|
},
|
||||||
"packageExtensions": {
|
"packageExtensions": {
|
||||||
"httpsnippet@^3.0.1": {
|
"httpsnippet@3.0.1": {
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ajv": "6.12.3"
|
"ajv": "6.12.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,16 @@
|
|||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/language": "6.9.3",
|
"@codemirror/language": "6.10.1",
|
||||||
"@lezer/highlight": "1.2.0",
|
"@lezer/highlight": "1.2.0",
|
||||||
"@lezer/lr": "^1.3.14"
|
"@lezer/lr": "1.3.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lezer/generator": "^1.5.1",
|
"@lezer/generator": "1.5.1",
|
||||||
"mocha": "^9.2.2",
|
"mocha": "9.2.2",
|
||||||
"rollup": "^3.29.3",
|
"rollup": "3.29.4",
|
||||||
"rollup-plugin-dts": "^6.0.2",
|
"rollup-plugin-dts": "6.0.2",
|
||||||
"rollup-plugin-ts": "^3.4.5",
|
"rollup-plugin-ts": "3.4.5",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-backend",
|
"name": "hoppscotch-backend",
|
||||||
"version": "2023.12.6",
|
"version": "2024.3.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -24,83 +24,83 @@
|
|||||||
"do-test": "pnpm run test"
|
"do-test": "pnpm run test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/server": "^4.9.4",
|
"@apollo/server": "4.9.5",
|
||||||
"@nestjs-modules/mailer": "^1.9.1",
|
"@nestjs-modules/mailer": "1.9.1",
|
||||||
"@nestjs/apollo": "^12.0.9",
|
"@nestjs/apollo": "12.0.9",
|
||||||
"@nestjs/common": "^10.2.6",
|
"@nestjs/common": "10.2.7",
|
||||||
"@nestjs/config": "^3.1.1",
|
"@nestjs/config": "3.1.1",
|
||||||
"@nestjs/core": "^10.2.6",
|
"@nestjs/core": "10.2.7",
|
||||||
"@nestjs/graphql": "^12.0.9",
|
"@nestjs/graphql": "12.0.9",
|
||||||
"@nestjs/jwt": "^10.1.1",
|
"@nestjs/jwt": "10.1.1",
|
||||||
"@nestjs/passport": "^10.0.2",
|
"@nestjs/passport": "10.0.2",
|
||||||
"@nestjs/platform-express": "^10.2.6",
|
"@nestjs/platform-express": "10.2.7",
|
||||||
"@nestjs/schedule": "^4.0.1",
|
"@nestjs/schedule": "4.0.1",
|
||||||
"@nestjs/throttler": "^5.0.0",
|
"@nestjs/throttler": "5.0.1",
|
||||||
"@prisma/client": "^5.8.0",
|
"@prisma/client": "5.8.1",
|
||||||
"argon2": "^0.30.3",
|
"argon2": "0.30.3",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "5.1.0",
|
||||||
"cookie": "^0.5.0",
|
"cookie": "0.5.0",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "1.4.6",
|
||||||
"cron": "^3.1.6",
|
"cron": "3.1.6",
|
||||||
"express": "^4.17.1",
|
"express": "4.18.2",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "1.17.3",
|
||||||
"fp-ts": "^2.13.1",
|
"fp-ts": "2.13.1",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "16.8.1",
|
||||||
"graphql-query-complexity": "^0.12.0",
|
"graphql-query-complexity": "0.12.0",
|
||||||
"graphql-redis-subscriptions": "^2.6.0",
|
"graphql-redis-subscriptions": "2.6.0",
|
||||||
"graphql-subscriptions": "^2.0.0",
|
"graphql-subscriptions": "2.0.0",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "4.7.7",
|
||||||
"io-ts": "^2.2.16",
|
"io-ts": "2.2.16",
|
||||||
"luxon": "^3.2.1",
|
"luxon": "3.2.1",
|
||||||
"nodemailer": "^6.9.1",
|
"nodemailer": "6.9.1",
|
||||||
"passport": "^0.6.0",
|
"passport": "0.6.0",
|
||||||
"passport-github2": "^0.1.12",
|
"passport-github2": "0.1.12",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "2.0.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "4.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "1.0.0",
|
||||||
"passport-microsoft": "^1.0.0",
|
"passport-microsoft": "1.0.0",
|
||||||
"posthog-node": "^3.6.3",
|
"posthog-node": "3.6.3",
|
||||||
"prisma": "^5.8.0",
|
"prisma": "5.8.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"rxjs": "^7.6.0"
|
"rxjs": "7.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.1.18",
|
"@nestjs/cli": "10.2.1",
|
||||||
"@nestjs/schematics": "^10.0.2",
|
"@nestjs/schematics": "10.0.3",
|
||||||
"@nestjs/testing": "^10.2.6",
|
"@nestjs/testing": "10.2.7",
|
||||||
"@relmify/jest-fp-ts": "^2.0.2",
|
"@relmify/jest-fp-ts": "2.0.2",
|
||||||
"@types/argon2": "^0.15.0",
|
"@types/argon2": "0.15.0",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "5.0.0",
|
||||||
"@types/cookie": "^0.5.1",
|
"@types/cookie": "0.5.1",
|
||||||
"@types/cookie-parser": "^1.4.3",
|
"@types/cookie-parser": "1.4.3",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "4.17.14",
|
||||||
"@types/jest": "^29.4.0",
|
"@types/jest": "29.4.0",
|
||||||
"@types/luxon": "^3.2.0",
|
"@types/luxon": "3.2.0",
|
||||||
"@types/node": "^18.11.10",
|
"@types/node": "18.11.10",
|
||||||
"@types/nodemailer": "^6.4.7",
|
"@types/nodemailer": "6.4.7",
|
||||||
"@types/passport-github2": "^1.2.5",
|
"@types/passport-github2": "1.2.5",
|
||||||
"@types/passport-google-oauth20": "^2.0.11",
|
"@types/passport-google-oauth20": "2.0.11",
|
||||||
"@types/passport-jwt": "^3.0.8",
|
"@types/passport-jwt": "3.0.8",
|
||||||
"@types/passport-microsoft": "^0.0.0",
|
"@types/passport-microsoft": "0.0.0",
|
||||||
"@types/supertest": "^2.0.12",
|
"@types/supertest": "2.0.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
"@typescript-eslint/eslint-plugin": "5.45.0",
|
||||||
"@typescript-eslint/parser": "^5.45.0",
|
"@typescript-eslint/parser": "5.45.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "^8.29.0",
|
"eslint": "8.29.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"jest": "^29.4.1",
|
"jest": "29.4.1",
|
||||||
"jest-mock-extended": "^3.0.1",
|
"jest-mock-extended": "3.0.1",
|
||||||
"jwt": "link:@types/nestjs/jwt",
|
"jwt": "link:@types/nestjs/jwt",
|
||||||
"prettier": "^2.8.4",
|
"prettier": "2.8.4",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "0.5.21",
|
||||||
"supertest": "^6.3.2",
|
"supertest": "6.3.2",
|
||||||
"ts-jest": "29.0.5",
|
"ts-jest": "29.0.5",
|
||||||
"ts-loader": "^9.4.2",
|
"ts-loader": "9.4.2",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "10.9.1",
|
||||||
"tsconfig-paths": "4.1.1",
|
"tsconfig-paths": "4.1.1",
|
||||||
"typescript": "^4.9.3"
|
"typescript": "4.9.3"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
@@ -121,4 +121,4 @@
|
|||||||
"^src/(.*)$": "<rootDir>/$1"
|
"^src/(.*)$": "<rootDir>/$1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,22 @@
|
|||||||
-- AlterTable
|
-- This is a custom migration file which is not generated by Prisma.
|
||||||
ALTER TABLE
|
-- The aim of this migration is to add text search indices to the TeamCollection and TeamRequest tables.
|
||||||
|
|
||||||
|
-- Create Extension
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||||
|
|
||||||
|
-- Create GIN Trigram Index for Team Collection title
|
||||||
|
CREATE INDEX
|
||||||
|
"TeamCollection_title_trgm_idx"
|
||||||
|
ON
|
||||||
"TeamCollection"
|
"TeamCollection"
|
||||||
ADD
|
USING
|
||||||
titleSearch tsvector GENERATED ALWAYS AS (to_tsvector('english', title)) STORED;
|
GIN (title gin_trgm_ops);
|
||||||
|
|
||||||
-- AlterTable
|
-- Create GIN Trigram Index for Team Collection title
|
||||||
ALTER TABLE
|
CREATE INDEX
|
||||||
|
"TeamRequest_title_trgm_idx"
|
||||||
|
ON
|
||||||
"TeamRequest"
|
"TeamRequest"
|
||||||
ADD
|
USING
|
||||||
titleSearch tsvector GENERATED ALWAYS AS (to_tsvector('english', title)) STORED;
|
GIN (title gin_trgm_ops);
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "TeamCollection_textSearch_idx" ON "TeamCollection" USING GIN (titleSearch);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "TeamRequest_textSearch_idx" ON "TeamRequest" USING GIN (titleSearch);
|
|
||||||
|
|||||||
@@ -84,6 +84,12 @@ export const USER_ALREADY_INVITED = 'admin/user_already_invited' as const;
|
|||||||
*/
|
*/
|
||||||
export const USER_UPDATE_FAILED = 'user/update_failed' as const;
|
export const USER_UPDATE_FAILED = 'user/update_failed' as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User display name validation failure
|
||||||
|
* (UserService)
|
||||||
|
*/
|
||||||
|
export const USER_SHORT_DISPLAY_NAME = 'user/short_display_name' as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User deletion failure
|
* User deletion failure
|
||||||
* (UserService)
|
* (UserService)
|
||||||
@@ -750,3 +756,8 @@ export const DATABASE_TABLE_NOT_EXIST =
|
|||||||
* (InfraConfigService)
|
* (InfraConfigService)
|
||||||
*/
|
*/
|
||||||
export const POSTHOG_CLIENT_NOT_INITIALIZED = 'posthog/client_not_initialized';
|
export const POSTHOG_CLIENT_NOT_INITIALIZED = 'posthog/client_not_initialized';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inputs supplied are invalid
|
||||||
|
*/
|
||||||
|
export const INVALID_PARAMS = 'invalid_parameters' as const;
|
||||||
|
|||||||
@@ -321,25 +321,28 @@ export class InfraConfigService implements OnModuleInit {
|
|||||||
* Reset all the InfraConfigs to their default values (from .env)
|
* Reset all the InfraConfigs to their default values (from .env)
|
||||||
*/
|
*/
|
||||||
async reset() {
|
async reset() {
|
||||||
|
// These are all the infra-configs that should not be reset
|
||||||
|
const RESET_EXCLUSION_LIST = [
|
||||||
|
InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
||||||
|
InfraConfigEnum.ANALYTICS_USER_ID,
|
||||||
|
InfraConfigEnum.ALLOW_ANALYTICS_COLLECTION,
|
||||||
|
];
|
||||||
try {
|
try {
|
||||||
const infraConfigDefaultObjs = await getDefaultInfraConfigs();
|
const infraConfigDefaultObjs = await getDefaultInfraConfigs();
|
||||||
|
const updatedInfraConfigDefaultObjs = infraConfigDefaultObjs.filter(
|
||||||
|
(p) => RESET_EXCLUSION_LIST.includes(p.name) === false,
|
||||||
|
);
|
||||||
|
|
||||||
await this.prisma.infraConfig.deleteMany({
|
await this.prisma.infraConfig.deleteMany({
|
||||||
where: { name: { in: infraConfigDefaultObjs.map((p) => p.name) } },
|
where: {
|
||||||
|
name: {
|
||||||
|
in: updatedInfraConfigDefaultObjs.map((p) => p.name),
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hardcode t
|
|
||||||
const updatedInfraConfigDefaultObjs = infraConfigDefaultObjs.filter(
|
|
||||||
(obj) => obj.name !== InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
|
||||||
);
|
|
||||||
await this.prisma.infraConfig.createMany({
|
await this.prisma.infraConfig.createMany({
|
||||||
data: [
|
data: updatedInfraConfigDefaultObjs,
|
||||||
...updatedInfraConfigDefaultObjs,
|
|
||||||
{
|
|
||||||
name: InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
|
|
||||||
value: 'true',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
stopApp();
|
stopApp();
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { TeamCollectionService } from './team-collection.service';
|
import { TeamCollectionService } from './team-collection.service';
|
||||||
import * as E from 'fp-ts/Either';
|
import * as E from 'fp-ts/Either';
|
||||||
import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard';
|
import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard';
|
||||||
@@ -7,13 +14,15 @@ import { RequiresTeamRole } from 'src/team/decorators/requires-team-role.decorat
|
|||||||
import { TeamMemberRole } from '@prisma/client';
|
import { TeamMemberRole } from '@prisma/client';
|
||||||
import { RESTTeamMemberGuard } from 'src/team/guards/rest-team-member.guard';
|
import { RESTTeamMemberGuard } from 'src/team/guards/rest-team-member.guard';
|
||||||
import { throwHTTPErr } from 'src/utils';
|
import { throwHTTPErr } from 'src/utils';
|
||||||
|
import { RESTError } from 'src/types/RESTError';
|
||||||
|
import { INVALID_PARAMS } from 'src/errors';
|
||||||
|
|
||||||
@UseGuards(ThrottlerBehindProxyGuard)
|
@UseGuards(ThrottlerBehindProxyGuard)
|
||||||
@Controller({ path: 'team-collection', version: '1' })
|
@Controller({ path: 'team-collection', version: '1' })
|
||||||
export class TeamCollectionController {
|
export class TeamCollectionController {
|
||||||
constructor(private readonly teamCollectionService: TeamCollectionService) {}
|
constructor(private readonly teamCollectionService: TeamCollectionService) {}
|
||||||
|
|
||||||
@Get('search/:teamID/:searchQuery')
|
@Get('search/:teamID')
|
||||||
@RequiresTeamRole(
|
@RequiresTeamRole(
|
||||||
TeamMemberRole.VIEWER,
|
TeamMemberRole.VIEWER,
|
||||||
TeamMemberRole.EDITOR,
|
TeamMemberRole.EDITOR,
|
||||||
@@ -21,13 +30,20 @@ export class TeamCollectionController {
|
|||||||
)
|
)
|
||||||
@UseGuards(JwtAuthGuard, RESTTeamMemberGuard)
|
@UseGuards(JwtAuthGuard, RESTTeamMemberGuard)
|
||||||
async searchByTitle(
|
async searchByTitle(
|
||||||
@Param('searchQuery') searchQuery: string,
|
@Query('searchQuery') searchQuery: string,
|
||||||
@Param('teamID') teamID: string,
|
@Param('teamID') teamID: string,
|
||||||
@Query('take') take: string,
|
@Query('take') take: string,
|
||||||
@Query('skip') skip: string,
|
@Query('skip') skip: string,
|
||||||
) {
|
) {
|
||||||
|
if (!teamID || !searchQuery) {
|
||||||
|
return <RESTError>{
|
||||||
|
message: INVALID_PARAMS,
|
||||||
|
statusCode: HttpStatus.BAD_REQUEST,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const res = await this.teamCollectionService.searchByTitle(
|
const res = await this.teamCollectionService.searchByTitle(
|
||||||
searchQuery,
|
searchQuery.trim(),
|
||||||
teamID,
|
teamID,
|
||||||
parseInt(take),
|
parseInt(take),
|
||||||
parseInt(skip),
|
parseInt(skip),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
TEAM_COLL_PARENT_TREE_GEN_FAILED,
|
TEAM_COLL_PARENT_TREE_GEN_FAILED,
|
||||||
} from '../errors';
|
} from '../errors';
|
||||||
import { PubSubService } from '../pubsub/pubsub.service';
|
import { PubSubService } from '../pubsub/pubsub.service';
|
||||||
import { isValidLength } from 'src/utils';
|
import { escapeSqlLikeString, isValidLength } from 'src/utils';
|
||||||
import * as E from 'fp-ts/Either';
|
import * as E from 'fp-ts/Either';
|
||||||
import * as O from 'fp-ts/Option';
|
import * as O from 'fp-ts/Option';
|
||||||
import { Prisma, TeamCollection as DBTeamCollection } from '@prisma/client';
|
import { Prisma, TeamCollection as DBTeamCollection } from '@prisma/client';
|
||||||
@@ -1125,7 +1125,7 @@ export class TeamCollectionService {
|
|||||||
id: searchResults[i].id,
|
id: searchResults[i].id,
|
||||||
path: !fetchedParentTree
|
path: !fetchedParentTree
|
||||||
? []
|
? []
|
||||||
: ([fetchedParentTree.right] as CollectionSearchNode[]),
|
: (fetchedParentTree.right as CollectionSearchNode[]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1148,14 +1148,20 @@ export class TeamCollectionService {
|
|||||||
skip: number,
|
skip: number,
|
||||||
) {
|
) {
|
||||||
const query = Prisma.sql`
|
const query = Prisma.sql`
|
||||||
select id,title,'collection' AS type
|
SELECT
|
||||||
from "TeamCollection"
|
id,title,'collection' AS type
|
||||||
where "TeamCollection"."teamID"=${teamID}
|
FROM
|
||||||
and titlesearch @@ to_tsquery(${searchQuery})
|
"TeamCollection"
|
||||||
order by ts_rank(titlesearch,to_tsquery(${searchQuery}))
|
WHERE
|
||||||
limit ${take}
|
"TeamCollection"."teamID"=${teamID}
|
||||||
|
AND
|
||||||
|
title ILIKE ${`%${escapeSqlLikeString(searchQuery)}%`}
|
||||||
|
ORDER BY
|
||||||
|
similarity(title, ${searchQuery})
|
||||||
|
LIMIT ${take}
|
||||||
OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
|
OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await this.prisma.$queryRaw<SearchQueryReturnType[]>(query);
|
const res = await this.prisma.$queryRaw<SearchQueryReturnType[]>(query);
|
||||||
return E.right(res);
|
return E.right(res);
|
||||||
@@ -1180,12 +1186,17 @@ export class TeamCollectionService {
|
|||||||
skip: number,
|
skip: number,
|
||||||
) {
|
) {
|
||||||
const query = Prisma.sql`
|
const query = Prisma.sql`
|
||||||
select id,title,request->>'method' as method,'request' AS type
|
SELECT
|
||||||
from "TeamRequest"
|
id,title,request->>'method' as method,'request' AS type
|
||||||
where "TeamRequest"."teamID"=${teamID}
|
FROM
|
||||||
and titlesearch @@ to_tsquery(${searchQuery})
|
"TeamRequest"
|
||||||
order by ts_rank(titlesearch,to_tsquery(${searchQuery}))
|
WHERE
|
||||||
limit ${take}
|
"TeamRequest"."teamID"=${teamID}
|
||||||
|
AND
|
||||||
|
title ILIKE ${`%${escapeSqlLikeString(searchQuery)}%`}
|
||||||
|
ORDER BY
|
||||||
|
similarity(title, ${searchQuery})
|
||||||
|
LIMIT ${take}
|
||||||
OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
|
OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1250,45 +1261,53 @@ export class TeamCollectionService {
|
|||||||
* @returns The parent tree of the parent collections
|
* @returns The parent tree of the parent collections
|
||||||
*/
|
*/
|
||||||
private generateParentTree(parentCollections: ParentTreeQueryReturnType[]) {
|
private generateParentTree(parentCollections: ParentTreeQueryReturnType[]) {
|
||||||
function findChildren(id) {
|
function findChildren(id: string): CollectionSearchNode[] {
|
||||||
const collection = parentCollections.filter((item) => item.id === id)[0];
|
const collection = parentCollections.filter((item) => item.id === id)[0];
|
||||||
if (collection.parentID == null) {
|
if (collection.parentID == null) {
|
||||||
return {
|
return <CollectionSearchNode[]>[
|
||||||
id: collection.id,
|
{
|
||||||
title: collection.title,
|
id: collection.id,
|
||||||
type: 'collection',
|
title: collection.title,
|
||||||
path: [],
|
type: 'collection' as const,
|
||||||
};
|
path: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = {
|
const res = <CollectionSearchNode[]>[
|
||||||
id: collection.id,
|
{
|
||||||
title: collection.title,
|
id: collection.id,
|
||||||
type: 'collection',
|
title: collection.title,
|
||||||
path: findChildren(collection.parentID),
|
type: 'collection' as const,
|
||||||
};
|
path: findChildren(collection.parentID),
|
||||||
|
},
|
||||||
|
];
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentCollections.length > 0) {
|
if (parentCollections.length > 0) {
|
||||||
if (parentCollections[0].parentID == null) {
|
if (parentCollections[0].parentID == null) {
|
||||||
return {
|
return <CollectionSearchNode[]>[
|
||||||
|
{
|
||||||
|
id: parentCollections[0].id,
|
||||||
|
title: parentCollections[0].title,
|
||||||
|
type: 'collection',
|
||||||
|
path: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return <CollectionSearchNode[]>[
|
||||||
|
{
|
||||||
id: parentCollections[0].id,
|
id: parentCollections[0].id,
|
||||||
title: parentCollections[0].title,
|
title: parentCollections[0].title,
|
||||||
type: 'collection',
|
type: 'collection',
|
||||||
path: [],
|
path: findChildren(parentCollections[0].parentID),
|
||||||
};
|
},
|
||||||
}
|
];
|
||||||
|
|
||||||
return {
|
|
||||||
id: parentCollections[0].id,
|
|
||||||
title: parentCollections[0].title,
|
|
||||||
type: 'collection',
|
|
||||||
path: findChildren(parentCollections[0].parentID),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return <CollectionSearchNode[]>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -58,6 +58,29 @@ export class UserResolver {
|
|||||||
if (E.isLeft(updatedUser)) throwErr(updatedUser.left);
|
if (E.isLeft(updatedUser)) throwErr(updatedUser.left);
|
||||||
return updatedUser.right;
|
return updatedUser.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => User, {
|
||||||
|
description: 'Update a users display name',
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard)
|
||||||
|
async updateDisplayName(
|
||||||
|
@GqlUser() user: AuthUser,
|
||||||
|
@Args({
|
||||||
|
name: 'updatedDisplayName',
|
||||||
|
description: 'New name of user',
|
||||||
|
type: () => String,
|
||||||
|
})
|
||||||
|
updatedDisplayName: string,
|
||||||
|
) {
|
||||||
|
const updatedUser = await this.userService.updateUserDisplayName(
|
||||||
|
user.uid,
|
||||||
|
updatedDisplayName,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (E.isLeft(updatedUser)) throwErr(updatedUser.left);
|
||||||
|
return updatedUser.right;
|
||||||
|
}
|
||||||
|
|
||||||
@Mutation(() => Boolean, {
|
@Mutation(() => Boolean, {
|
||||||
description: 'Delete an user account',
|
description: 'Delete an user account',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { JSON_INVALID, USERS_NOT_FOUND, USER_NOT_FOUND } from 'src/errors';
|
import {
|
||||||
|
JSON_INVALID,
|
||||||
|
USERS_NOT_FOUND,
|
||||||
|
USER_NOT_FOUND,
|
||||||
|
USER_SHORT_DISPLAY_NAME,
|
||||||
|
} from 'src/errors';
|
||||||
import { mockDeep, mockReset } from 'jest-mock-extended';
|
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
import { AuthUser } from 'src/types/AuthUser';
|
import { AuthUser } from 'src/types/AuthUser';
|
||||||
@@ -480,6 +485,14 @@ describe('UserService', () => {
|
|||||||
);
|
);
|
||||||
expect(result).toEqualLeft(USER_NOT_FOUND);
|
expect(result).toEqualLeft(USER_NOT_FOUND);
|
||||||
});
|
});
|
||||||
|
test('should resolve left and error when short display name is passed', async () => {
|
||||||
|
const newDisplayName = '';
|
||||||
|
const result = await userService.updateUserDisplayName(
|
||||||
|
user.uid,
|
||||||
|
newDisplayName,
|
||||||
|
);
|
||||||
|
expect(result).toEqualLeft(USER_SHORT_DISPLAY_NAME);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('fetchAllUsers', () => {
|
describe('fetchAllUsers', () => {
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import * as T from 'fp-ts/Task';
|
|||||||
import * as A from 'fp-ts/Array';
|
import * as A from 'fp-ts/Array';
|
||||||
import { pipe, constVoid } from 'fp-ts/function';
|
import { pipe, constVoid } from 'fp-ts/function';
|
||||||
import { AuthUser } from 'src/types/AuthUser';
|
import { AuthUser } from 'src/types/AuthUser';
|
||||||
import { USERS_NOT_FOUND, USER_NOT_FOUND } from 'src/errors';
|
import {
|
||||||
|
USERS_NOT_FOUND,
|
||||||
|
USER_NOT_FOUND,
|
||||||
|
USER_SHORT_DISPLAY_NAME,
|
||||||
|
} from 'src/errors';
|
||||||
import { SessionType, User } from './user.model';
|
import { SessionType, User } from './user.model';
|
||||||
import { USER_UPDATE_FAILED } from 'src/errors';
|
import { USER_UPDATE_FAILED } from 'src/errors';
|
||||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||||
@@ -291,6 +295,10 @@ export class UserService {
|
|||||||
* @returns a Either of User or error
|
* @returns a Either of User or error
|
||||||
*/
|
*/
|
||||||
async updateUserDisplayName(userUID: string, displayName: string) {
|
async updateUserDisplayName(userUID: string, displayName: string) {
|
||||||
|
if (!displayName || displayName.length === 0) {
|
||||||
|
return E.left(USER_SHORT_DISPLAY_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dbUpdatedUser = await this.prisma.user.update({
|
const dbUpdatedUser = await this.prisma.user.update({
|
||||||
where: { uid: userUID },
|
where: { uid: userUID },
|
||||||
|
|||||||
@@ -250,3 +250,39 @@ export function checkEnvironmentAuthProvider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds escape backslashes to the input so that it can be used inside
|
||||||
|
* SQL LIKE/ILIKE queries. Inspired by PHP's `mysql_real_escape_string`
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* Eg. "100%" -> "100\\%"
|
||||||
|
*
|
||||||
|
* Source: https://stackoverflow.com/a/32648526
|
||||||
|
*/
|
||||||
|
export function escapeSqlLikeString(str: string) {
|
||||||
|
if (typeof str != 'string') return str;
|
||||||
|
|
||||||
|
return str.replace(/[\0\x08\x09\x1a\n\r"'\\\%]/g, function (char) {
|
||||||
|
switch (char) {
|
||||||
|
case '\0':
|
||||||
|
return '\\0';
|
||||||
|
case '\x08':
|
||||||
|
return '\\b';
|
||||||
|
case '\x09':
|
||||||
|
return '\\t';
|
||||||
|
case '\x1a':
|
||||||
|
return '\\z';
|
||||||
|
case '\n':
|
||||||
|
return '\\n';
|
||||||
|
case '\r':
|
||||||
|
return '\\r';
|
||||||
|
case '"':
|
||||||
|
case "'":
|
||||||
|
case '\\':
|
||||||
|
case '%':
|
||||||
|
return '\\' + char; // prepends a backslash to backslash, percent,
|
||||||
|
// and double/single quotes
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/cli",
|
"name": "@hoppscotch/cli",
|
||||||
"version": "0.6.0",
|
"version": "0.7.0",
|
||||||
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
||||||
"homepage": "https://hoppscotch.io",
|
"homepage": "https://hoppscotch.io",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -41,30 +41,28 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": false,
|
"private": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.6",
|
"axios": "1.6.7",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "5.3.0",
|
||||||
"commander": "^11.1.0",
|
"commander": "11.1.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"qs": "^6.11.2",
|
"qs": "6.11.2",
|
||||||
"zod": "^3.22.4"
|
"verzod": "0.2.2",
|
||||||
|
"zod": "3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hoppscotch/data": "workspace:^",
|
"@hoppscotch/data": "workspace:^",
|
||||||
"@hoppscotch/js-sandbox": "workspace:^",
|
"@hoppscotch/js-sandbox": "workspace:^",
|
||||||
"@relmify/jest-fp-ts": "^2.1.1",
|
"@relmify/jest-fp-ts": "2.1.1",
|
||||||
"@swc/core": "^1.3.105",
|
"@swc/core": "1.4.2",
|
||||||
"@types/jest": "^29.5.11",
|
"@types/jest": "29.5.12",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "4.17.12",
|
||||||
"@types/qs": "^6.9.11",
|
"@types/qs": "6.9.12",
|
||||||
"fp-ts": "^2.16.2",
|
"fp-ts": "2.16.2",
|
||||||
"jest": "^29.7.0",
|
"jest": "29.7.0",
|
||||||
"lodash": "^4.17.21",
|
"prettier": "3.2.5",
|
||||||
"prettier": "^3.2.4",
|
"qs": "6.11.2",
|
||||||
"qs": "^6.11.2",
|
"ts-jest": "29.1.2",
|
||||||
"ts-jest": "^29.1.2",
|
"tsup": "8.0.2",
|
||||||
"tsup": "^8.0.1",
|
"typescript": "5.3.3"
|
||||||
"typescript": "^5.3.3",
|
|
||||||
"verzod": "^0.2.2",
|
|
||||||
"zod": "^3.22.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ describe("Test `hopp test <file>` command:", () => {
|
|||||||
const out = getErrorCode(stderr);
|
const out = getErrorCode(stderr);
|
||||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("Supplied collection export file validations", () => {
|
describe("Supplied collection export file validations", () => {
|
||||||
test("Errors with the code `FILE_NOT_FOUND` if the supplied collection export file doesn't exist", async () => {
|
test("Errors with the code `FILE_NOT_FOUND` if the supplied collection export file doesn't exist", async () => {
|
||||||
@@ -66,6 +66,43 @@ describe("Test `hopp test <file>` command:", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Versioned entities", () => {
|
||||||
|
describe("Collections & Requests", () => {
|
||||||
|
const testFixtures = [
|
||||||
|
{ fileName: "coll-v1-req-v0.json", collVersion: 1, reqVersion: 0 },
|
||||||
|
{ fileName: "coll-v1-req-v1.json", collVersion: 1, reqVersion: 1 },
|
||||||
|
{ fileName: "coll-v2-req-v2.json", collVersion: 2, reqVersion: 2 },
|
||||||
|
{ fileName: "coll-v2-req-v3.json", collVersion: 2, reqVersion: 3 },
|
||||||
|
];
|
||||||
|
|
||||||
|
testFixtures.forEach(({ collVersion, fileName, reqVersion }) => {
|
||||||
|
test(`Successfully processes a supplied collection export file where the collection is based on the "v${collVersion}" schema and the request following the "v${reqVersion}" schema`, async () => {
|
||||||
|
const args = `test ${getTestJsonFilePath(fileName, "collection")}`;
|
||||||
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
|
expect(error).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Environments", () => {
|
||||||
|
const testFixtures = [
|
||||||
|
{ fileName: "env-v0.json", version: 0 },
|
||||||
|
{ fileName: "env-v1.json", version: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
testFixtures.forEach(({ fileName, version }) => {
|
||||||
|
test(`Successfully processes the supplied collection and environment export files where the environment is based on the "v${version}" schema`, async () => {
|
||||||
|
const ENV_PATH = getTestJsonFilePath(fileName, "environment");
|
||||||
|
const args = `test ${getTestJsonFilePath("sample-coll.json", "collection")} --env ${ENV_PATH}`;
|
||||||
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
|
expect(error).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("Successfully processes a supplied collection export file of the expected format", async () => {
|
test("Successfully processes a supplied collection export file of the expected format", async () => {
|
||||||
const args = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`;
|
const args = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`;
|
||||||
const { error } = await runCLI(args);
|
const { error } = await runCLI(args);
|
||||||
@@ -75,7 +112,8 @@ describe("Test `hopp test <file>` command:", () => {
|
|||||||
|
|
||||||
test("Successfully inherits headers and authorization set at the root collection", async () => {
|
test("Successfully inherits headers and authorization set at the root collection", async () => {
|
||||||
const args = `test ${getTestJsonFilePath(
|
const args = `test ${getTestJsonFilePath(
|
||||||
"collection-level-headers-auth-coll.json", "collection"
|
"collection-level-headers-auth-coll.json",
|
||||||
|
"collection"
|
||||||
)}`;
|
)}`;
|
||||||
const { error } = await runCLI(args);
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
@@ -84,7 +122,8 @@ describe("Test `hopp test <file>` command:", () => {
|
|||||||
|
|
||||||
test("Persists environment variables set in the pre-request script for consumption in the test script", async () => {
|
test("Persists environment variables set in the pre-request script for consumption in the test script", async () => {
|
||||||
const args = `test ${getTestJsonFilePath(
|
const args = `test ${getTestJsonFilePath(
|
||||||
"pre-req-script-env-var-persistence-coll.json", "collection"
|
"pre-req-script-env-var-persistence-coll.json",
|
||||||
|
"collection"
|
||||||
)}`;
|
)}`;
|
||||||
const { error } = await runCLI(args);
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
@@ -106,7 +145,8 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
|
|||||||
|
|
||||||
test("Errors with the code `INVALID_FILE_TYPE` if the supplied environment export file doesn't end with the `.json` extension", async () => {
|
test("Errors with the code `INVALID_FILE_TYPE` if the supplied environment export file doesn't end with the `.json` extension", async () => {
|
||||||
const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath(
|
const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath(
|
||||||
"notjson-coll.txt", "collection"
|
"notjson-coll.txt",
|
||||||
|
"collection"
|
||||||
)}`;
|
)}`;
|
||||||
const { stderr } = await runCLI(args);
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
@@ -123,7 +163,10 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Errors with the code `MALFORMED_ENV_FILE` on supplying a malformed environment export file", async () => {
|
test("Errors with the code `MALFORMED_ENV_FILE` on supplying a malformed environment export file", async () => {
|
||||||
const ENV_PATH = getTestJsonFilePath("malformed-envs.json", "environment");
|
const ENV_PATH = getTestJsonFilePath(
|
||||||
|
"malformed-envs.json",
|
||||||
|
"environment"
|
||||||
|
);
|
||||||
const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`;
|
const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`;
|
||||||
const { stderr } = await runCLI(args);
|
const { stderr } = await runCLI(args);
|
||||||
|
|
||||||
@@ -142,7 +185,10 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Successfully resolves values from the supplied environment export file", async () => {
|
test("Successfully resolves values from the supplied environment export file", async () => {
|
||||||
const TESTS_PATH = getTestJsonFilePath("env-flag-tests-coll.json", "collection");
|
const TESTS_PATH = getTestJsonFilePath(
|
||||||
|
"env-flag-tests-coll.json",
|
||||||
|
"collection"
|
||||||
|
);
|
||||||
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
|
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
|
||||||
const args = `test ${TESTS_PATH} --env ${ENV_PATH}`;
|
const args = `test ${TESTS_PATH} --env ${ENV_PATH}`;
|
||||||
|
|
||||||
@@ -151,8 +197,14 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Successfully resolves environment variables referenced in the request body", async () => {
|
test("Successfully resolves environment variables referenced in the request body", async () => {
|
||||||
const COLL_PATH = getTestJsonFilePath("req-body-env-vars-coll.json", "collection");
|
const COLL_PATH = getTestJsonFilePath(
|
||||||
const ENVS_PATH = getTestJsonFilePath("req-body-env-vars-envs.json", "environment");
|
"req-body-env-vars-coll.json",
|
||||||
|
"collection"
|
||||||
|
);
|
||||||
|
const ENVS_PATH = getTestJsonFilePath(
|
||||||
|
"req-body-env-vars-envs.json",
|
||||||
|
"environment"
|
||||||
|
);
|
||||||
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
||||||
|
|
||||||
const { error } = await runCLI(args);
|
const { error } = await runCLI(args);
|
||||||
@@ -160,7 +212,10 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Works with shorth `-e` flag", async () => {
|
test("Works with shorth `-e` flag", async () => {
|
||||||
const TESTS_PATH = getTestJsonFilePath("env-flag-tests-coll.json", "collection");
|
const TESTS_PATH = getTestJsonFilePath(
|
||||||
|
"env-flag-tests-coll.json",
|
||||||
|
"collection"
|
||||||
|
);
|
||||||
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
|
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
|
||||||
const args = `test ${TESTS_PATH} -e ${ENV_PATH}`;
|
const args = `test ${TESTS_PATH} -e ${ENV_PATH}`;
|
||||||
|
|
||||||
@@ -183,7 +238,10 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
|
|||||||
secretHeaderValue: "secret-header-value",
|
secretHeaderValue: "secret-header-value",
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLL_PATH = getTestJsonFilePath("secret-envs-coll.json", "collection");
|
const COLL_PATH = getTestJsonFilePath(
|
||||||
|
"secret-envs-coll.json",
|
||||||
|
"collection"
|
||||||
|
);
|
||||||
const ENVS_PATH = getTestJsonFilePath("secret-envs.json", "environment");
|
const ENVS_PATH = getTestJsonFilePath("secret-envs.json", "environment");
|
||||||
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
||||||
|
|
||||||
@@ -197,8 +255,14 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
|
|||||||
|
|
||||||
// Prefers values specified in the environment export file over values set in the system environment
|
// Prefers values specified in the environment export file over values set in the system environment
|
||||||
test("Successfully picks the values for secret environment variables set directly in the environment export file and persists the environment variables set from the pre-request script", async () => {
|
test("Successfully picks the values for secret environment variables set directly in the environment export file and persists the environment variables set from the pre-request script", async () => {
|
||||||
const COLL_PATH = getTestJsonFilePath("secret-envs-coll.json", "collection");
|
const COLL_PATH = getTestJsonFilePath(
|
||||||
const ENVS_PATH = getTestJsonFilePath("secret-supplied-values-envs.json", "environment");
|
"secret-envs-coll.json",
|
||||||
|
"collection"
|
||||||
|
);
|
||||||
|
const ENVS_PATH = getTestJsonFilePath(
|
||||||
|
"secret-supplied-values-envs.json",
|
||||||
|
"environment"
|
||||||
|
);
|
||||||
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
||||||
|
|
||||||
const { error, stdout } = await runCLI(args);
|
const { error, stdout } = await runCLI(args);
|
||||||
@@ -212,9 +276,13 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
|
|||||||
// Values set from the scripting context takes the highest precedence
|
// Values set from the scripting context takes the highest precedence
|
||||||
test("Setting values for secret environment variables from the pre-request script overrides values set at the supplied environment export file", async () => {
|
test("Setting values for secret environment variables from the pre-request script overrides values set at the supplied environment export file", async () => {
|
||||||
const COLL_PATH = getTestJsonFilePath(
|
const COLL_PATH = getTestJsonFilePath(
|
||||||
"secret-envs-persistence-coll.json", "collection"
|
"secret-envs-persistence-coll.json",
|
||||||
|
"collection"
|
||||||
|
);
|
||||||
|
const ENVS_PATH = getTestJsonFilePath(
|
||||||
|
"secret-supplied-values-envs.json",
|
||||||
|
"environment"
|
||||||
);
|
);
|
||||||
const ENVS_PATH = getTestJsonFilePath("secret-supplied-values-envs.json", "environment");
|
|
||||||
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
||||||
|
|
||||||
const { error, stdout } = await runCLI(args);
|
const { error, stdout } = await runCLI(args);
|
||||||
@@ -227,10 +295,12 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
|
|||||||
|
|
||||||
test("Persists secret environment variable values set from the pre-request script for consumption in the request and post-request script context", async () => {
|
test("Persists secret environment variable values set from the pre-request script for consumption in the request and post-request script context", async () => {
|
||||||
const COLL_PATH = getTestJsonFilePath(
|
const COLL_PATH = getTestJsonFilePath(
|
||||||
"secret-envs-persistence-scripting-coll.json", "collection"
|
"secret-envs-persistence-scripting-coll.json",
|
||||||
|
"collection"
|
||||||
);
|
);
|
||||||
const ENVS_PATH = getTestJsonFilePath(
|
const ENVS_PATH = getTestJsonFilePath(
|
||||||
"secret-envs-persistence-scripting-envs.json", "environment"
|
"secret-envs-persistence-scripting-envs.json",
|
||||||
|
"environment"
|
||||||
);
|
);
|
||||||
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
import { isRESTCollection } from "../../../utils/checks";
|
|
||||||
|
|
||||||
describe("isRESTCollection", () => {
|
|
||||||
test("Undefined collection value.", () => {
|
|
||||||
expect(isRESTCollection(undefined)).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Invalid id value.", () => {
|
|
||||||
expect(
|
|
||||||
isRESTCollection({
|
|
||||||
v: 1,
|
|
||||||
name: "test",
|
|
||||||
id: 1,
|
|
||||||
})
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Invalid requests value.", () => {
|
|
||||||
expect(
|
|
||||||
isRESTCollection({
|
|
||||||
v: 1,
|
|
||||||
name: "test",
|
|
||||||
id: "1",
|
|
||||||
requests: null,
|
|
||||||
})
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Invalid folders value.", () => {
|
|
||||||
expect(
|
|
||||||
isRESTCollection({
|
|
||||||
v: 1,
|
|
||||||
name: "test",
|
|
||||||
id: "1",
|
|
||||||
requests: [],
|
|
||||||
folders: undefined,
|
|
||||||
})
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Invalid RESTCollection(s) in folders.", () => {
|
|
||||||
expect(
|
|
||||||
isRESTCollection({
|
|
||||||
v: 1,
|
|
||||||
name: "test",
|
|
||||||
id: "1",
|
|
||||||
requests: [],
|
|
||||||
folders: [
|
|
||||||
{
|
|
||||||
v: 1,
|
|
||||||
name: "test1",
|
|
||||||
id: "2",
|
|
||||||
requests: undefined,
|
|
||||||
folders: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Invalid HoppRESTRequest(s) in requests.", () => {
|
|
||||||
expect(
|
|
||||||
isRESTCollection({
|
|
||||||
v: 1,
|
|
||||||
name: "test",
|
|
||||||
id: "1",
|
|
||||||
requests: [{}],
|
|
||||||
folders: [],
|
|
||||||
})
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Valid RESTCollection.", () => {
|
|
||||||
expect(
|
|
||||||
isRESTCollection({
|
|
||||||
v: 1,
|
|
||||||
name: "test",
|
|
||||||
id: "1",
|
|
||||||
requests: [],
|
|
||||||
folders: [],
|
|
||||||
})
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"name": "coll-v1",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"url": "https://httpbin.org",
|
||||||
|
"path": "/get",
|
||||||
|
"headers": [
|
||||||
|
{ "key": "Inactive-Header", "value": "Inactive Header", "active": false },
|
||||||
|
{ "key": "Authorization", "value": "Bearer token123", "active": true }
|
||||||
|
],
|
||||||
|
"params": [
|
||||||
|
{ "key": "key", "value": "value", "active": true },
|
||||||
|
{ "key": "inactive-key", "value": "inactive-param", "active": false }
|
||||||
|
],
|
||||||
|
"name": "req-v0",
|
||||||
|
"method": "GET",
|
||||||
|
"preRequestScript": "",
|
||||||
|
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
|
||||||
|
"contentType": "application/json",
|
||||||
|
"body": "",
|
||||||
|
"auth": "Bearer Token",
|
||||||
|
"bearerToken": "token123"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"name": "coll-v1",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"endpoint": "https://httpbin.org/get",
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Inactive-Header",
|
||||||
|
"value": "Inactive Header",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer token123",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"key": "key",
|
||||||
|
"value": "value",
|
||||||
|
"active": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "inactive-key",
|
||||||
|
"value": "inactive-param",
|
||||||
|
"active": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "req-v1",
|
||||||
|
"method": "GET",
|
||||||
|
"preRequestScript": "",
|
||||||
|
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
|
||||||
|
"body": {
|
||||||
|
"contentType": null,
|
||||||
|
"body": null
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"authType": "bearer",
|
||||||
|
"authActive": true,
|
||||||
|
"token": "token123"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"v": 2,
|
||||||
|
"name": "coll-v2",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"v": "2",
|
||||||
|
"endpoint": "https://httpbin.org/get",
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Inactive-Header",
|
||||||
|
"value": "Inactive Header",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer token123",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"key": "key",
|
||||||
|
"value": "value",
|
||||||
|
"active": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "inactive-key",
|
||||||
|
"value": "inactive-param",
|
||||||
|
"active": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "req-v2",
|
||||||
|
"method": "GET",
|
||||||
|
"preRequestScript": "",
|
||||||
|
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
|
||||||
|
"body": {
|
||||||
|
"contentType": null,
|
||||||
|
"body": null
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"authType": "bearer",
|
||||||
|
"authActive": true,
|
||||||
|
"token": "token123"
|
||||||
|
},
|
||||||
|
"requestVariables": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auth": {
|
||||||
|
"authType": "inherit",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"headers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"v": 2,
|
||||||
|
"name": "coll-v2",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"v": "3",
|
||||||
|
"endpoint": "https://httpbin.org/get",
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Inactive-Header",
|
||||||
|
"value": "Inactive Header",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer token123",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"key": "key",
|
||||||
|
"value": "value",
|
||||||
|
"active": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "inactive-key",
|
||||||
|
"value": "inactive-param",
|
||||||
|
"active": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "req-v3",
|
||||||
|
"method": "GET",
|
||||||
|
"preRequestScript": "",
|
||||||
|
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
|
||||||
|
"body": {
|
||||||
|
"contentType": null,
|
||||||
|
"body": null
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"authType": "bearer",
|
||||||
|
"authActive": true,
|
||||||
|
"token": "token123"
|
||||||
|
},
|
||||||
|
"requestVariables": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auth": {
|
||||||
|
"authType": "inherit",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"headers": []
|
||||||
|
}
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"v": 1,
|
"v": 2,
|
||||||
"name": "CollectionA",
|
"name": "CollectionA",
|
||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
"v": 1,
|
"v": 2,
|
||||||
"name": "FolderA",
|
"name": "FolderA",
|
||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
"v": 1,
|
"v": 2,
|
||||||
"name": "FolderB",
|
"name": "FolderB",
|
||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
"v": 1,
|
"v": 2,
|
||||||
"name": "FolderC",
|
"name": "FolderC",
|
||||||
"folders": [],
|
"folders": [],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.io",
|
"endpoint": "https://echo.hoppscotch.io",
|
||||||
"name": "RequestD",
|
"name": "RequestD",
|
||||||
"params": [],
|
"params": [],
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
],
|
],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.io",
|
"endpoint": "https://echo.hoppscotch.io",
|
||||||
"name": "RequestC",
|
"name": "RequestC",
|
||||||
"params": [],
|
"params": [],
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
],
|
],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.io",
|
"endpoint": "https://echo.hoppscotch.io",
|
||||||
"name": "RequestB",
|
"name": "RequestB",
|
||||||
"params": [],
|
"params": [],
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
],
|
],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.io",
|
"endpoint": "https://echo.hoppscotch.io",
|
||||||
"name": "RequestA",
|
"name": "RequestA",
|
||||||
"params": [],
|
"params": [],
|
||||||
@@ -153,16 +153,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"v": 1,
|
"v": 2,
|
||||||
"name": "CollectionB",
|
"name": "CollectionB",
|
||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
"v": 1,
|
"v": 2,
|
||||||
"name": "FolderA",
|
"name": "FolderA",
|
||||||
"folders": [],
|
"folders": [],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.io",
|
"endpoint": "https://echo.hoppscotch.io",
|
||||||
"name": "RequestB",
|
"name": "RequestB",
|
||||||
"params": [],
|
"params": [],
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
],
|
],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.io",
|
"endpoint": "https://echo.hoppscotch.io",
|
||||||
"name": "RequestA",
|
"name": "RequestA",
|
||||||
"params": [],
|
"params": [],
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"folders": [],
|
"folders": [],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "<<URL>>",
|
"endpoint": "<<URL>>",
|
||||||
"name": "test1",
|
"name": "test1",
|
||||||
"params": [],
|
"params": [],
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"folders": [],
|
"folders": [],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
|
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
|
||||||
"name": "",
|
"name": "",
|
||||||
"params": [],
|
"params": [],
|
||||||
@@ -13,10 +13,7 @@
|
|||||||
"method": "GET",
|
"method": "GET",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "none",
|
"authType": "none",
|
||||||
"authActive": true,
|
"authActive": true
|
||||||
"addTo": "Headers",
|
|
||||||
"key": "",
|
|
||||||
"value": ""
|
|
||||||
},
|
},
|
||||||
"preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");",
|
"preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");",
|
||||||
"testScript": "// Check status code is 200\npwd.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"string\");\n});",
|
"testScript": "// Check status code is 200\npwd.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"string\");\n});",
|
||||||
@@ -24,10 +21,10 @@
|
|||||||
"contentType": "application/json",
|
"contentType": "application/json",
|
||||||
"body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}"
|
"body": "{\n\"test\": \"<<HEADERS_TYPE1>>\"\n}"
|
||||||
},
|
},
|
||||||
"requestVariables": [],
|
"requestVariables": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.dio/<<HEADERS_TYPE2>>",
|
"endpoint": "https://echo.hoppscotch.dio/<<HEADERS_TYPE2>>",
|
||||||
"name": "success",
|
"name": "success",
|
||||||
"params": [],
|
"params": [],
|
||||||
@@ -35,10 +32,7 @@
|
|||||||
"method": "GET",
|
"method": "GET",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "none",
|
"authType": "none",
|
||||||
"authActive": true,
|
"authActive": true
|
||||||
"addTo": "Headers",
|
|
||||||
"key": "",
|
|
||||||
"value": ""
|
|
||||||
},
|
},
|
||||||
"preRequestScript": "pw.env.setd(\"HEADERS_TYPE2\", \"devblin_local2\");",
|
"preRequestScript": "pw.env.setd(\"HEADERS_TYPE2\", \"devblin_local2\");",
|
||||||
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(300);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
|
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(300);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"folders": [],
|
"folders": [],
|
||||||
"requests":
|
"requests":
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
|
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
|
||||||
"name": "fail",
|
"name": "fail",
|
||||||
"params": [],
|
"params": [],
|
||||||
@@ -12,10 +12,7 @@
|
|||||||
"method": "GET",
|
"method": "GET",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "none",
|
"authType": "none",
|
||||||
"authActive": true,
|
"authActive": true
|
||||||
"addTo": "Headers",
|
|
||||||
"key": "",
|
|
||||||
"value": ""
|
|
||||||
},
|
},
|
||||||
"preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");",
|
"preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");",
|
||||||
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"string\");\n});",
|
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"string\");\n});",
|
||||||
@@ -26,7 +23,7 @@
|
|||||||
"requestVariables": [],
|
"requestVariables": [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE2>>",
|
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE2>>",
|
||||||
"name": "success",
|
"name": "success",
|
||||||
"params": [],
|
"params": [],
|
||||||
@@ -34,10 +31,7 @@
|
|||||||
"method": "GET",
|
"method": "GET",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "none",
|
"authType": "none",
|
||||||
"authActive": true,
|
"authActive": true
|
||||||
"addTo": "Headers",
|
|
||||||
"key": "",
|
|
||||||
"value": ""
|
|
||||||
},
|
},
|
||||||
"preRequestScript": "pw.env.set(\"HEADERS_TYPE2\", \"devblin_local2\");",
|
"preRequestScript": "pw.env.set(\"HEADERS_TYPE2\", \"devblin_local2\");",
|
||||||
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(300);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
|
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(300);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"folders": [],
|
"folders": [],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
|
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE1>>",
|
||||||
"name": "",
|
"name": "",
|
||||||
"params": [],
|
"params": [],
|
||||||
@@ -13,10 +13,7 @@
|
|||||||
"method": "GET",
|
"method": "GET",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "none",
|
"authType": "none",
|
||||||
"authActive": true,
|
"authActive": true
|
||||||
"addTo": "Headers",
|
|
||||||
"key": "",
|
|
||||||
"value": ""
|
|
||||||
},
|
},
|
||||||
"preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");",
|
"preRequestScript": "pw.env.set(\"HEADERS_TYPE1\", \"devblin_local1\");",
|
||||||
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
|
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
|
||||||
@@ -27,7 +24,7 @@
|
|||||||
"requestVariables": []
|
"requestVariables": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE2>>",
|
"endpoint": "https://echo.hoppscotch.io/<<HEADERS_TYPE2>>",
|
||||||
"name": "success",
|
"name": "success",
|
||||||
"params": [],
|
"params": [],
|
||||||
@@ -35,10 +32,7 @@
|
|||||||
"method": "GET",
|
"method": "GET",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "none",
|
"authType": "none",
|
||||||
"authActive": true,
|
"authActive": true
|
||||||
"addTo": "Headers",
|
|
||||||
"key": "",
|
|
||||||
"value": ""
|
|
||||||
},
|
},
|
||||||
"preRequestScript": "pw.env.set(\"HEADERS_TYPE2\", \"devblin_local2\");",
|
"preRequestScript": "pw.env.set(\"HEADERS_TYPE2\", \"devblin_local2\");",
|
||||||
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
|
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"folders": [],
|
"folders": [],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"auth": { "authType": "none", "authActive": true },
|
"auth": { "authType": "none", "authActive": true },
|
||||||
"body": { "body": null, "contentType": null },
|
"body": { "body": null, "contentType": null },
|
||||||
"name": "sample-req",
|
"name": "sample-req",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"folders": [],
|
"folders": [],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"name": "test-request",
|
"name": "test-request",
|
||||||
"endpoint": "https://echo.hoppscotch.io",
|
"endpoint": "https://echo.hoppscotch.io",
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"name": "tests",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"v": "2",
|
||||||
|
"endpoint": "<<baseURL>>",
|
||||||
|
"name": "",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"method": "GET",
|
||||||
|
"auth": {
|
||||||
|
"authType": "none",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"preRequestScript": "",
|
||||||
|
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
|
||||||
|
"body": {
|
||||||
|
"contentType": null,
|
||||||
|
"body": null
|
||||||
|
},
|
||||||
|
"requestVariables": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"folders": [],
|
"folders": [],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"auth": { "authType": "none", "authActive": true },
|
"auth": { "authType": "none", "authActive": true },
|
||||||
"body": { "body": null, "contentType": null },
|
"body": { "body": null, "contentType": null },
|
||||||
"name": "test-secret-headers",
|
"name": "test-secret-headers",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"preRequestScript": "const secretHeaderValueFromPreReqScript = pw.env.get(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
"preRequestScript": "const secretHeaderValueFromPreReqScript = pw.env.get(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"auth": { "authType": "none", "authActive": true },
|
"auth": { "authType": "none", "authActive": true },
|
||||||
"body": {
|
"body": {
|
||||||
"body": "{\n \"secretBodyKey\": \"<<secretBodyValue>>\"\n}",
|
"body": "{\n \"secretBodyKey\": \"<<secretBodyValue>>\"\n}",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"preRequestScript": "const secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
|
"preRequestScript": "const secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"auth": { "authType": "none", "authActive": true },
|
"auth": { "authType": "none", "authActive": true },
|
||||||
"body": { "body": null, "contentType": null },
|
"body": { "body": null, "contentType": null },
|
||||||
"name": "test-secret-query-params",
|
"name": "test-secret-query-params",
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"preRequestScript": "const secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
|
"preRequestScript": "const secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "basic",
|
"authType": "basic",
|
||||||
"password": "<<secretBasicAuthPassword>>",
|
"password": "<<secretBasicAuthPassword>>",
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"preRequestScript": ""
|
"preRequestScript": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"auth": {
|
"auth": {
|
||||||
"token": "<<secretBearerToken>>",
|
"token": "<<secretBearerToken>>",
|
||||||
"authType": "bearer",
|
"authType": "bearer",
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
"preRequestScript": "const secretBearerToken = pw.env.get(\"secretBearerToken\")\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)"
|
"preRequestScript": "const secretBearerToken = pw.env.get(\"secretBearerToken\")\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"auth": { "authType": "none", "authActive": true },
|
"auth": { "authType": "none", "authActive": true },
|
||||||
"body": { "body": null, "contentType": null },
|
"body": { "body": null, "contentType": null },
|
||||||
"name": "test-secret-fallback",
|
"name": "test-secret-fallback",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"folders": [],
|
"folders": [],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "none",
|
"authType": "none",
|
||||||
"authActive": true
|
"authActive": true
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "none",
|
"authType": "none",
|
||||||
"authActive": true
|
"authActive": true
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value-overriden\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value-overriden\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "none",
|
"authType": "none",
|
||||||
"authActive": true
|
"authActive": true
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
"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)"
|
"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": "2",
|
"v": "3",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "none",
|
"authType": "none",
|
||||||
"authActive": true
|
"authActive": true
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
"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)"
|
"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": "2",
|
"v": "3",
|
||||||
"auth": {
|
"auth": {
|
||||||
"authType": "basic",
|
"authType": "basic",
|
||||||
"password": "<<secretBasicAuthPassword>>",
|
"password": "<<secretBasicAuthPassword>>",
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"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}"
|
"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": "2",
|
"v": "3",
|
||||||
"auth": {
|
"auth": {
|
||||||
"token": "<<secretBearerToken>>",
|
"token": "<<secretBearerToken>>",
|
||||||
"authType": "bearer",
|
"authType": "bearer",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"folders": [],
|
"folders": [],
|
||||||
"requests": [
|
"requests": [
|
||||||
{
|
{
|
||||||
"v": "2",
|
"v": "3",
|
||||||
"endpoint": "https://httpbin.org/post",
|
"endpoint": "https://httpbin.org/post",
|
||||||
"name": "req",
|
"name": "req",
|
||||||
"params": [],
|
"params": [],
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "env-v0",
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"key": "baseURL",
|
||||||
|
"value": "https://echo.hoppscotch.io"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "env-v0",
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"key": "baseURL",
|
||||||
|
"value": "https://echo.hoppscotch.io",
|
||||||
|
"secret": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import { error } from "../../types/errors";
|
|||||||
import {
|
import {
|
||||||
HoppEnvKeyPairObject,
|
HoppEnvKeyPairObject,
|
||||||
HoppEnvPair,
|
HoppEnvPair,
|
||||||
HoppEnvs
|
HoppEnvs,
|
||||||
} from "../../types/request";
|
} from "../../types/request";
|
||||||
import { readJsonFile } from "../../utils/mutators";
|
import { readJsonFile } from "../../utils/mutators";
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ import { readJsonFile } from "../../utils/mutators";
|
|||||||
*/
|
*/
|
||||||
export async function parseEnvsData(path: string) {
|
export async function parseEnvsData(path: string) {
|
||||||
const contents = await readJsonFile(path);
|
const contents = await readJsonFile(path);
|
||||||
const envPairs: Array<Environment["variables"][number] | HoppEnvPair> = [];
|
const envPairs: Array<HoppEnvPair | Record<string, string>> = [];
|
||||||
|
|
||||||
// The legacy key-value pair format that is still supported
|
// The legacy key-value pair format that is still supported
|
||||||
const HoppEnvKeyPairResult = HoppEnvKeyPairObject.safeParse(contents);
|
const HoppEnvKeyPairResult = HoppEnvKeyPairObject.safeParse(contents);
|
||||||
@@ -26,7 +26,9 @@ export async function parseEnvsData(path: string) {
|
|||||||
const HoppEnvExportObjectResult = Environment.safeParse(contents);
|
const HoppEnvExportObjectResult = Environment.safeParse(contents);
|
||||||
|
|
||||||
// Shape of the bulk environment export object that is exported from the app
|
// Shape of the bulk environment export object that is exported from the app
|
||||||
const HoppBulkEnvExportObjectResult = z.array(entityReference(Environment)).safeParse(contents)
|
const HoppBulkEnvExportObjectResult = z
|
||||||
|
.array(entityReference(Environment))
|
||||||
|
.safeParse(contents);
|
||||||
|
|
||||||
// CLI doesnt support bulk environments export
|
// CLI doesnt support bulk environments export
|
||||||
// Hence we check for this case and throw an error if it matches the format
|
// Hence we check for this case and throw an error if it matches the format
|
||||||
@@ -36,13 +38,16 @@ export async function parseEnvsData(path: string) {
|
|||||||
|
|
||||||
// Checks if the environment file is of the correct format
|
// Checks if the environment file is of the correct format
|
||||||
// If it doesnt match either of them, we throw an error
|
// If it doesnt match either of them, we throw an error
|
||||||
if (!HoppEnvKeyPairResult.success && HoppEnvExportObjectResult.type === "err") {
|
if (
|
||||||
|
!HoppEnvKeyPairResult.success &&
|
||||||
|
HoppEnvExportObjectResult.type === "err"
|
||||||
|
) {
|
||||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: error });
|
throw error({ code: "MALFORMED_ENV_FILE", path, data: error });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HoppEnvKeyPairResult.success) {
|
if (HoppEnvKeyPairResult.success) {
|
||||||
for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) {
|
for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) {
|
||||||
envPairs.push({ key, value });
|
envPairs.push({ key, value, secret: false });
|
||||||
}
|
}
|
||||||
} else if (HoppEnvExportObjectResult.type === "ok") {
|
} else if (HoppEnvExportObjectResult.type === "ok") {
|
||||||
envPairs.push(...HoppEnvExportObjectResult.value.variables);
|
envPairs.push(...HoppEnvExportObjectResult.value.variables);
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { HoppCollection, isHoppRESTRequest } from "@hoppscotch/data";
|
|
||||||
import * as A from "fp-ts/Array";
|
|
||||||
import { CommanderError } from "commander";
|
import { CommanderError } from "commander";
|
||||||
import { HoppCLIError, HoppErrnoException } from "../types/errors";
|
import { HoppCLIError, HoppErrnoException } from "../types/errors";
|
||||||
|
|
||||||
@@ -14,48 +12,6 @@ export const hasProperty = <P extends PropertyKey>(
|
|||||||
prop: P
|
prop: P
|
||||||
): target is Record<P, unknown> => prop in target;
|
): target is Record<P, unknown> => prop in target;
|
||||||
|
|
||||||
/**
|
|
||||||
* Typeguard to check valid Hoppscotch REST Collection.
|
|
||||||
* @param param The object to be checked.
|
|
||||||
* @returns True, if unknown parameter is valid Hoppscotch REST Collection;
|
|
||||||
* False, otherwise.
|
|
||||||
*/
|
|
||||||
export const isRESTCollection = (param: unknown): param is HoppCollection => {
|
|
||||||
if (!!param && typeof param === "object") {
|
|
||||||
if (!hasProperty(param, "v") || typeof param.v !== "number") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!hasProperty(param, "name") || typeof param.name !== "string") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasProperty(param, "id") && typeof param.id !== "string") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!hasProperty(param, "requests") || !Array.isArray(param.requests)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// Checks each requests array to be valid HoppRESTRequest.
|
|
||||||
const checkRequests = A.every(isHoppRESTRequest)(param.requests);
|
|
||||||
if (!checkRequests) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasProperty(param, "folders") || !Array.isArray(param.folders)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// Checks each folder to be valid REST collection.
|
|
||||||
const checkFolders = A.every(isRESTCollection)(param.folders);
|
|
||||||
if (!checkFolders) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if given error data is of type HoppCLIError, based on existence
|
* Checks if given error data is of type HoppCLIError, based on existence
|
||||||
* of code property.
|
* of code property.
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ const getCollectionStack = (collections: HoppCollection[]): CollectionStack[] =>
|
|||||||
* path of each request within collection-json file, failed-tests-report, errors,
|
* path of each request within collection-json file, failed-tests-report, errors,
|
||||||
* total execution duration for requests, pre-request-scripts, test-scripts.
|
* total execution duration for requests, pre-request-scripts, test-scripts.
|
||||||
* @returns True, if collection runner executed without any errors or failed test-cases.
|
* @returns True, if collection runner executed without any errors or failed test-cases.
|
||||||
* False, if errors occured or test-cases failed.
|
* False, if errors occurred or test-cases failed.
|
||||||
*/
|
*/
|
||||||
export const collectionsRunnerResult = (
|
export const collectionsRunnerResult = (
|
||||||
requestsReport: RequestReport[]
|
requestsReport: RequestReport[]
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export const printTestsMetrics = (testsMetrics: TestMetrics) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prints details of each reported error for a request with error code.
|
* Prints details of each reported error for a request with error code.
|
||||||
* @param path Request's path in collection for which errors occured.
|
* @param path Request's path in collection for which errors occurred.
|
||||||
* @param errorsReport List of errors reported.
|
* @param errorsReport List of errors reported.
|
||||||
*/
|
*/
|
||||||
export const printErrorsReport = (
|
export const printErrorsReport = (
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import { FormDataEntry } from "../types/request";
|
import { entityReference } from "verzod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { error } from "../types/errors";
|
import { error } from "../types/errors";
|
||||||
import { isRESTCollection, isHoppErrnoException } from "./checks";
|
import { FormDataEntry } from "../types/request";
|
||||||
import { HoppCollection } from "@hoppscotch/data";
|
import { isHoppErrnoException } from "./checks";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses array of FormDataEntry to FormData.
|
* Parses array of FormDataEntry to FormData.
|
||||||
@@ -67,7 +70,11 @@ export async function parseCollectionData(
|
|||||||
? contents
|
? contents
|
||||||
: [contents];
|
: [contents];
|
||||||
|
|
||||||
if (maybeArrayOfCollections.some((x) => !isRESTCollection(x))) {
|
const collectionSchemaParsedResult = z
|
||||||
|
.array(entityReference(HoppCollection))
|
||||||
|
.safeParse(maybeArrayOfCollections);
|
||||||
|
|
||||||
|
if (!collectionSchemaParsedResult.success) {
|
||||||
throw error({
|
throw error({
|
||||||
code: "MALFORMED_COLLECTION",
|
code: "MALFORMED_COLLECTION",
|
||||||
path,
|
path,
|
||||||
@@ -75,5 +82,22 @@ export async function parseCollectionData(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return maybeArrayOfCollections as HoppCollection[];
|
return collectionSchemaParsedResult.data.map((collection) => {
|
||||||
|
const requestSchemaParsedResult = z
|
||||||
|
.array(entityReference(HoppRESTRequest))
|
||||||
|
.safeParse(collection.requests);
|
||||||
|
|
||||||
|
if (!requestSchemaParsedResult.success) {
|
||||||
|
throw error({
|
||||||
|
code: "MALFORMED_COLLECTION",
|
||||||
|
path,
|
||||||
|
data: "Please check the collection data.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...collection,
|
||||||
|
requests: requestSchemaParsedResult.data,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,18 +109,31 @@ export function getEffectiveRESTRequest(
|
|||||||
key: "Authorization",
|
key: "Authorization",
|
||||||
value: `Basic ${btoa(`${username}:${password}`)}`,
|
value: `Basic ${btoa(`${username}:${password}`)}`,
|
||||||
});
|
});
|
||||||
} else if (
|
} else if (request.auth.authType === "bearer") {
|
||||||
request.auth.authType === "bearer" ||
|
|
||||||
request.auth.authType === "oauth-2"
|
|
||||||
) {
|
|
||||||
effectiveFinalHeaders.push({
|
effectiveFinalHeaders.push({
|
||||||
active: true,
|
active: true,
|
||||||
key: "Authorization",
|
key: "Authorization",
|
||||||
value: `Bearer ${parseTemplateString(
|
value: `Bearer ${parseTemplateString(request.auth.token, envVariables)}`,
|
||||||
request.auth.token,
|
|
||||||
envVariables
|
|
||||||
)}`,
|
|
||||||
});
|
});
|
||||||
|
} else if (request.auth.authType === "oauth-2") {
|
||||||
|
const { addTo } = request.auth;
|
||||||
|
|
||||||
|
if (addTo === "HEADERS") {
|
||||||
|
effectiveFinalHeaders.push({
|
||||||
|
active: true,
|
||||||
|
key: "Authorization",
|
||||||
|
value: `Bearer ${parseTemplateString(request.auth.grantTypeInfo.token, envVariables)}`,
|
||||||
|
});
|
||||||
|
} else if (addTo === "QUERY_PARAMS") {
|
||||||
|
effectiveFinalParams.push({
|
||||||
|
active: true,
|
||||||
|
key: "access_token",
|
||||||
|
value: parseTemplateString(
|
||||||
|
request.auth.grantTypeInfo.token,
|
||||||
|
envVariables
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (request.auth.authType === "api-key") {
|
} else if (request.auth.authType === "api-key") {
|
||||||
const { key, value, addTo } = request.auth;
|
const { key, value, addTo } = request.auth;
|
||||||
if (addTo === "Headers") {
|
if (addTo === "Headers") {
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ const processVariables = (variable: Environment["variables"][number]) => {
|
|||||||
...variable,
|
...variable,
|
||||||
value:
|
value:
|
||||||
"value" in variable ? variable.value : process.env[variable.key] || "",
|
"value" in variable ? variable.value : process.env[variable.key] || "",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return variable
|
return variable;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes given envs, which includes processing each variable in global
|
* Processes given envs, which includes processing each variable in global
|
||||||
@@ -56,10 +56,10 @@ const processEnvs = (envs: HoppEnvs) => {
|
|||||||
const processedEnvs = {
|
const processedEnvs = {
|
||||||
global: envs.global.map(processVariables),
|
global: envs.global.map(processVariables),
|
||||||
selected: envs.selected.map(processVariables),
|
selected: envs.selected.map(processVariables),
|
||||||
}
|
};
|
||||||
|
|
||||||
return processedEnvs
|
return processedEnvs;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms given request data to request-config used by request-runner to
|
* Transforms given request data to request-config used by request-runner to
|
||||||
@@ -70,7 +70,7 @@ const processEnvs = (envs: HoppEnvs) => {
|
|||||||
export const createRequest = (req: EffectiveHoppRESTRequest): RequestConfig => {
|
export const createRequest = (req: EffectiveHoppRESTRequest): RequestConfig => {
|
||||||
const config: RequestConfig = {
|
const config: RequestConfig = {
|
||||||
supported: true,
|
supported: true,
|
||||||
displayUrl: req.effectiveFinalDisplayURL
|
displayUrl: req.effectiveFinalDisplayURL,
|
||||||
};
|
};
|
||||||
const { finalBody, finalEndpoint, finalHeaders, finalParams } = getRequest;
|
const { finalBody, finalEndpoint, finalHeaders, finalParams } = getRequest;
|
||||||
const reqParams = finalParams(req);
|
const reqParams = finalParams(req);
|
||||||
@@ -131,6 +131,7 @@ export const requestRunner =
|
|||||||
let status: number;
|
let status: number;
|
||||||
const baseResponse = await axios(requestConfig);
|
const baseResponse = await axios(requestConfig);
|
||||||
const { config } = baseResponse;
|
const { config } = baseResponse;
|
||||||
|
// PR-COMMENT: type error
|
||||||
const runnerResponse: RequestRunnerResponse = {
|
const runnerResponse: RequestRunnerResponse = {
|
||||||
...baseResponse,
|
...baseResponse,
|
||||||
endpoint: getRequest.endpoint(config.url),
|
endpoint: getRequest.endpoint(config.url),
|
||||||
@@ -257,10 +258,13 @@ export const processRequest =
|
|||||||
let updatedEnvs = <HoppEnvs>{};
|
let updatedEnvs = <HoppEnvs>{};
|
||||||
|
|
||||||
// Fetch values for secret environment variables from system environment
|
// Fetch values for secret environment variables from system environment
|
||||||
const processedEnvs = processEnvs(envs)
|
const processedEnvs = processEnvs(envs);
|
||||||
|
|
||||||
// Executing pre-request-script
|
// Executing pre-request-script
|
||||||
const preRequestRes = await preRequestScriptRunner(request, processedEnvs)();
|
const preRequestRes = await preRequestScriptRunner(
|
||||||
|
request,
|
||||||
|
processedEnvs
|
||||||
|
)();
|
||||||
if (E.isLeft(preRequestRes)) {
|
if (E.isLeft(preRequestRes)) {
|
||||||
printPreRequestRunner.fail();
|
printPreRequestRunner.fail();
|
||||||
|
|
||||||
@@ -347,7 +351,7 @@ export const processRequest =
|
|||||||
*/
|
*/
|
||||||
export const preProcessRequest = (
|
export const preProcessRequest = (
|
||||||
request: HoppRESTRequest,
|
request: HoppRESTRequest,
|
||||||
collection: HoppCollection,
|
collection: HoppCollection
|
||||||
): HoppRESTRequest => {
|
): HoppRESTRequest => {
|
||||||
const tempRequest = Object.assign({}, request);
|
const tempRequest = Object.assign({}, request);
|
||||||
const { headers: parentHeaders, auth: parentAuth } = collection;
|
const { headers: parentHeaders, auth: parentAuth } = collection;
|
||||||
@@ -372,8 +376,10 @@ export const preProcessRequest = (
|
|||||||
// Filter out header entries present in the parent (folder/collection) under the same name
|
// Filter out header entries present in the parent (folder/collection) under the same name
|
||||||
// This ensures the child headers take precedence over the parent headers
|
// This ensures the child headers take precedence over the parent headers
|
||||||
const filteredEntries = parentHeaders.filter((parentHeaderEntries) => {
|
const filteredEntries = parentHeaders.filter((parentHeaderEntries) => {
|
||||||
return !tempRequest.headers.some((reqHeaderEntries) => reqHeaderEntries.key === parentHeaderEntries.key)
|
return !tempRequest.headers.some(
|
||||||
})
|
(reqHeaderEntries) => reqHeaderEntries.key === parentHeaderEntries.key
|
||||||
|
);
|
||||||
|
});
|
||||||
tempRequest.headers.push(...filteredEntries);
|
tempRequest.headers.push(...filteredEntries);
|
||||||
} else if (!tempRequest.headers) {
|
} else if (!tempRequest.headers) {
|
||||||
tempRequest.headers = [];
|
tempRequest.headers = [];
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ module.exports = {
|
|||||||
parserOptions: {
|
parserOptions: {
|
||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
requireConfigFile: false,
|
requireConfigFile: false,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
"@vue/typescript/recommended",
|
"@vue/typescript/recommended",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"hide_secret": "Hide secret",
|
"hide_secret": "Hide secret",
|
||||||
"label": "Label",
|
"label": "Label",
|
||||||
"learn_more": "Learn more",
|
"learn_more": "Learn more",
|
||||||
|
"download_here": "Download here",
|
||||||
"less": "Less",
|
"less": "Less",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
"new": "New",
|
"new": "New",
|
||||||
@@ -103,8 +104,10 @@
|
|||||||
"auth": {
|
"auth": {
|
||||||
"account_exists": "Account exists with different credential - Login to link both accounts",
|
"account_exists": "Account exists with different credential - Login to link both accounts",
|
||||||
"all_sign_in_options": "All sign in options",
|
"all_sign_in_options": "All sign in options",
|
||||||
|
"continue_with_auth_provider": "Continue with {provider}",
|
||||||
"continue_with_email": "Continue with Email",
|
"continue_with_email": "Continue with Email",
|
||||||
"continue_with_github": "Continue with GitHub",
|
"continue_with_github": "Continue with GitHub",
|
||||||
|
"continue_with_github_enterprise": "Continue with GitHub Enterprise",
|
||||||
"continue_with_google": "Continue with Google",
|
"continue_with_google": "Continue with Google",
|
||||||
"continue_with_microsoft": "Continue with Microsoft",
|
"continue_with_microsoft": "Continue with Microsoft",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
@@ -137,7 +140,26 @@
|
|||||||
"redirect_no_token_endpoint": "No Token Endpoint Defined",
|
"redirect_no_token_endpoint": "No Token Endpoint Defined",
|
||||||
"something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect",
|
"something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect",
|
||||||
"something_went_wrong_on_token_generation": "Something went wrong on token generation",
|
"something_went_wrong_on_token_generation": "Something went wrong on token generation",
|
||||||
"token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed"
|
"token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed",
|
||||||
|
"grant_type": "Grant Type",
|
||||||
|
"grant_type_auth_code": "Authorization Code",
|
||||||
|
"token_fetched_successfully": "Token fetched successfully",
|
||||||
|
"token_fetch_failed": "Failed to fetch token",
|
||||||
|
"validation_failed": "Validation Failed, please check the form fields",
|
||||||
|
"label_authorization_endpoint": "Authorization Endpoint",
|
||||||
|
"label_client_id": "Client ID",
|
||||||
|
"label_client_secret": "Client Secret",
|
||||||
|
"label_code_challenge": "Code Challenge",
|
||||||
|
"label_code_challenge_method": "Code Challenge Method",
|
||||||
|
"label_code_verifier": "Code Verifier",
|
||||||
|
"label_scopes": "Scopes",
|
||||||
|
"label_token_endpoint": "Token Endpoint",
|
||||||
|
"label_use_pkce": "Use PKCE",
|
||||||
|
"label_implicit": "Implicit",
|
||||||
|
"label_password": "Password",
|
||||||
|
"label_username": "Username",
|
||||||
|
"label_auth_code": "Authorization Code",
|
||||||
|
"label_client_credentials": "Client Credentials"
|
||||||
},
|
},
|
||||||
"pass_key_by": "Pass by",
|
"pass_key_by": "Pass by",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
@@ -154,7 +176,7 @@
|
|||||||
"invalid_name": "Please provide a name for the collection",
|
"invalid_name": "Please provide a name for the collection",
|
||||||
"invalid_root_move": "Collection already in the root",
|
"invalid_root_move": "Collection already in the root",
|
||||||
"moved": "Moved Successfully",
|
"moved": "Moved Successfully",
|
||||||
"my_collections": "My Collections",
|
"my_collections": "Personal Collections",
|
||||||
"name": "My New Collection",
|
"name": "My New Collection",
|
||||||
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
||||||
"new": "New Collection",
|
"new": "New Collection",
|
||||||
@@ -166,14 +188,12 @@
|
|||||||
"save_as": "Save as",
|
"save_as": "Save as",
|
||||||
"save_to_collection": "Save to Collection",
|
"save_to_collection": "Save to Collection",
|
||||||
"select": "Select a Collection",
|
"select": "Select a Collection",
|
||||||
"select_location": "Select location",
|
"select_location": "Select location"
|
||||||
"select_team": "Select a team",
|
|
||||||
"team_collections": "Team Collections"
|
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"close_unsaved_tab": "Are you sure you want to close this tab?",
|
"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.",
|
"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?",
|
"logout": "Are you sure you want to logout?",
|
||||||
"remove_collection": "Are you sure you want to permanently delete this collection?",
|
"remove_collection": "Are you sure you want to permanently delete this collection?",
|
||||||
"remove_environment": "Are you sure you want to permanently delete this environment?",
|
"remove_environment": "Are you sure you want to permanently delete this environment?",
|
||||||
@@ -181,7 +201,7 @@
|
|||||||
"remove_history": "Are you sure you want to permanently delete all history?",
|
"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_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_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?",
|
"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.",
|
"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?",
|
"save_unsaved_tab": "Do you want to save changes made in this tab?",
|
||||||
@@ -234,9 +254,9 @@
|
|||||||
"headers": "This request does not have any headers",
|
"headers": "This request does not have any headers",
|
||||||
"history": "History is empty",
|
"history": "History is empty",
|
||||||
"invites": "Invite list is empty",
|
"invites": "Invite list is empty",
|
||||||
"members": "Team is empty",
|
"members": "Workspace is empty",
|
||||||
"parameters": "This request does not have any parameters",
|
"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",
|
"profile": "Login to view your profile",
|
||||||
"protocols": "Protocols are empty",
|
"protocols": "Protocols are empty",
|
||||||
"request_variables": "This request does not have any request variables",
|
"request_variables": "This request does not have any request variables",
|
||||||
@@ -245,8 +265,8 @@
|
|||||||
"shared_requests": "Shared requests are empty",
|
"shared_requests": "Shared requests are empty",
|
||||||
"shared_requests_logout": "Login to view your shared requests or create a new one",
|
"shared_requests_logout": "Login to view your shared requests or create a new one",
|
||||||
"subscription": "Subscriptions are empty",
|
"subscription": "Subscriptions are empty",
|
||||||
"team_name": "Team name empty",
|
"team_name": "Workspace name empty",
|
||||||
"teams": "You don't belong to any teams",
|
"teams": "You don't belong to any workspaces",
|
||||||
"tests": "There are no tests for this request"
|
"tests": "There are no tests for this request"
|
||||||
},
|
},
|
||||||
"environment": {
|
"environment": {
|
||||||
@@ -263,7 +283,7 @@
|
|||||||
"import_or_create": "Import or create a environment",
|
"import_or_create": "Import or create a environment",
|
||||||
"invalid_name": "Please provide a name for the environment",
|
"invalid_name": "Please provide a name for the environment",
|
||||||
"list": "Environment variables",
|
"list": "Environment variables",
|
||||||
"my_environments": "My Environments",
|
"my_environments": "Personal Environments",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"nested_overflow": "nested environment variables are limited to 10 levels",
|
"nested_overflow": "nested environment variables are limited to 10 levels",
|
||||||
"new": "New Environment",
|
"new": "New Environment",
|
||||||
@@ -278,12 +298,12 @@
|
|||||||
"select": "Select environment",
|
"select": "Select environment",
|
||||||
"set": "Set environment",
|
"set": "Set environment",
|
||||||
"set_as_environment": "Set as environment",
|
"set_as_environment": "Set as environment",
|
||||||
"team_environments": "Team Environments",
|
"team_environments": "Workspace Environments",
|
||||||
"title": "Environments",
|
"title": "Environments",
|
||||||
"updated": "Environment updated",
|
"updated": "Environment updated",
|
||||||
"value": "Value",
|
"value": "Value",
|
||||||
"variable": "Variable",
|
"variable": "Variable",
|
||||||
"variables":"Variables",
|
"variables": "Variables",
|
||||||
"variable_list": "Variable List"
|
"variable_list": "Variable List"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -293,8 +313,9 @@
|
|||||||
"check_how_to_add_origin": "Check how you can add an origin",
|
"check_how_to_add_origin": "Check how you can add an origin",
|
||||||
"curl_invalid_format": "cURL is not formatted properly",
|
"curl_invalid_format": "cURL is not formatted properly",
|
||||||
"danger_zone": "Danger zone",
|
"danger_zone": "Danger zone",
|
||||||
"delete_account": "Your account is currently an owner in these teams:",
|
"delete_account": "Your account is currently an owner in these workspaces:",
|
||||||
"delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.",
|
"delete_account_description": "You must either remove yourself, transfer ownership, or delete these workspaces before you can delete your account.",
|
||||||
|
"empty_profile_name": "Profile name cannot be empty",
|
||||||
"empty_req_name": "Empty Request Name",
|
"empty_req_name": "Empty Request Name",
|
||||||
"f12_details": "(F12 for details)",
|
"f12_details": "(F12 for details)",
|
||||||
"gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again",
|
"gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again",
|
||||||
@@ -314,6 +335,7 @@
|
|||||||
"page_not_found": "This page could not be found",
|
"page_not_found": "This page could not be found",
|
||||||
"please_install_extension": "Please install the extension and add origin to the extension.",
|
"please_install_extension": "Please install the extension and add origin to the extension.",
|
||||||
"proxy_error": "Proxy error",
|
"proxy_error": "Proxy error",
|
||||||
|
"same_profile_name": "Updated profile name is same as the current profile name",
|
||||||
"script_fail": "Could not execute pre-request script",
|
"script_fail": "Could not execute pre-request script",
|
||||||
"something_went_wrong": "Something went wrong",
|
"something_went_wrong": "Something went wrong",
|
||||||
"test_script_fail": "Could not execute post-request script",
|
"test_script_fail": "Could not execute post-request script",
|
||||||
@@ -396,8 +418,8 @@
|
|||||||
"from_insomnia_description": "Import from Insomnia collection",
|
"from_insomnia_description": "Import from Insomnia collection",
|
||||||
"from_json": "Import from Hoppscotch",
|
"from_json": "Import from Hoppscotch",
|
||||||
"from_json_description": "Import from Hoppscotch collection file",
|
"from_json_description": "Import from Hoppscotch collection file",
|
||||||
"from_my_collections": "Import from My Collections",
|
"from_my_collections": "Import from Personal Collections",
|
||||||
"from_my_collections_description": "Import from My Collections file",
|
"from_my_collections_description": "Import from Personal Collections file",
|
||||||
"from_openapi": "Import from OpenAPI",
|
"from_openapi": "Import from OpenAPI",
|
||||||
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
|
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
|
||||||
"from_postman": "Import from Postman",
|
"from_postman": "Import from Postman",
|
||||||
@@ -429,7 +451,7 @@
|
|||||||
"not_found": "Environment variable “{environment}” not found."
|
"not_found": "Environment variable “{environment}” not found."
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
|
"cookie": "The browser doesn't allow Hoppscotch to set Cookie Headers. Please use Authorization Headers instead. However, our Hoppscotch Desktop App is live now and supports Cookies."
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"401_error": "Please check your authentication credentials.",
|
"401_error": "Please check your authentication credentials.",
|
||||||
@@ -514,7 +536,7 @@
|
|||||||
"email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.",
|
"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.",
|
"no_permission": "You do not have permission to perform this action.",
|
||||||
"owner": "Owner",
|
"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": "Roles",
|
||||||
"roles_description": "Roles are used to control access to the shared collections.",
|
"roles_description": "Roles are used to control access to the shared collections.",
|
||||||
"updated": "Profile updated",
|
"updated": "Profile updated",
|
||||||
@@ -819,12 +841,12 @@
|
|||||||
"title": "Tabs"
|
"title": "Tabs"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"delete": "Delete current team",
|
"delete": "Delete current workspace",
|
||||||
"edit": "Edit current team",
|
"edit": "Edit current workspace",
|
||||||
"invite": "Invite people to team",
|
"invite": "Invite people to workspace",
|
||||||
"new": "Create new team",
|
"new": "Create new workspace",
|
||||||
"switch_to_personal": "Switch to your personal workspace",
|
"switch_to_personal": "Switch to your personal workspace",
|
||||||
"title": "Teams"
|
"title": "Workspaces"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sse": {
|
"sse": {
|
||||||
@@ -881,7 +903,6 @@
|
|||||||
"forum": "Ask questions and get answers",
|
"forum": "Ask questions and get answers",
|
||||||
"github": "Follow us on Github",
|
"github": "Follow us on Github",
|
||||||
"shortcuts": "Browse app faster",
|
"shortcuts": "Browse app faster",
|
||||||
"team": "Get in touch with the team",
|
|
||||||
"title": "Support",
|
"title": "Support",
|
||||||
"twitter": "Follow us on Twitter"
|
"twitter": "Follow us on Twitter"
|
||||||
},
|
},
|
||||||
@@ -912,60 +933,60 @@
|
|||||||
"websocket": "WebSocket"
|
"websocket": "WebSocket"
|
||||||
},
|
},
|
||||||
"team": {
|
"team": {
|
||||||
"already_member": "You are already a member of this team. Contact your team owner.",
|
"already_member": "You are already a member of this workspace. Contact your workspace owner.",
|
||||||
"create_new": "Create new team",
|
"create_new": "Create new workspace",
|
||||||
"deleted": "Team deleted",
|
"deleted": "Workspace deleted",
|
||||||
"edit": "Edit Team",
|
"edit": "Edit Workspace",
|
||||||
"email": "E-mail",
|
"email": "E-mail",
|
||||||
"email_do_not_match": "Email doesn't match with your account details. Contact your team owner.",
|
"email_do_not_match": "Email doesn't match with your account details. Contact your workspace owner.",
|
||||||
"exit": "Exit Team",
|
"exit": "Exit Workspace",
|
||||||
"exit_disabled": "Only owner cannot exit the team",
|
"exit_disabled": "Only owner cannot exit the workspace",
|
||||||
"failed_invites": "Failed invites",
|
"failed_invites": "Failed invites",
|
||||||
"invalid_coll_id": "Invalid collection ID",
|
"invalid_coll_id": "Invalid collection ID",
|
||||||
"invalid_email_format": "Email format is invalid",
|
"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": "Invalid invite link",
|
||||||
"invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.",
|
"invalid_invite_link_description": "The link you followed is invalid. Contact your workspace owner.",
|
||||||
"invalid_member_permission": "Please provide a valid permission to the team member",
|
"invalid_member_permission": "Please provide a valid permission to the workspace member",
|
||||||
"invite": "Invite",
|
"invite": "Invite",
|
||||||
"invite_more": "Invite more",
|
"invite_more": "Invite more",
|
||||||
"invite_tooltip": "Invite people to this workspace",
|
"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": "Invitation accepted",
|
||||||
"join_beta": "Join the beta program to access teams.",
|
"join_team": "Join {workspace}",
|
||||||
"join_team": "Join {team}",
|
"joined_team": "You have joined {workspace}",
|
||||||
"joined_team": "You have joined {team}",
|
"joined_team_description": "You are now a member of this workspace",
|
||||||
"joined_team_description": "You are now a member of this team",
|
"left": "You left the workspace",
|
||||||
"left": "You left the team",
|
|
||||||
"login_to_continue": "Login to continue",
|
"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",
|
"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_has_invite": "This email ID already has an invite. Contact your workspace owner.",
|
||||||
"member_not_found": "Member not found. Contact your team owner.",
|
"member_not_found": "Member not found. Contact your workspace owner.",
|
||||||
"member_removed": "User removed",
|
"member_removed": "User removed",
|
||||||
"member_role_updated": "User roles updated",
|
"member_role_updated": "User roles updated",
|
||||||
"members": "Members",
|
"members": "Members",
|
||||||
"more_members": "+{count} more",
|
"more_members": "+{count} more",
|
||||||
"name_length_insufficient": "Team name should be at least 6 characters long",
|
"name_length_insufficient": "Workspace name should be at least 6 characters long",
|
||||||
"name_updated": "Team name updated",
|
"name_updated": "Workspace name updated",
|
||||||
"new": "New Team",
|
"new": "New Workspace",
|
||||||
"new_created": "New team created",
|
"new_created": "New workspace created",
|
||||||
"new_name": "My New Team",
|
"new_name": "My New Workspace",
|
||||||
"no_access": "You do not have edit access to this team",
|
"no_access": "You do not have edit access to this workspace",
|
||||||
"no_invite_found": "Invitation not found. Contact your team owner.",
|
"no_invite_found": "Invitation not found. Contact your workspace owner.",
|
||||||
"no_request_found": "Request not found.",
|
"no_request_found": "Request not found.",
|
||||||
"not_found": "Team not found. Contact your team owner.",
|
"not_found": "Workspace not found. Contact your workspace owner.",
|
||||||
"not_valid_viewer": "You are not a valid viewer. Contact your team owner.",
|
"not_valid_viewer": "You are not a valid viewer. Contact your workspace owner.",
|
||||||
"parent_coll_move": "Cannot move collection to a child collection",
|
"parent_coll_move": "Cannot move collection to a child collection",
|
||||||
"pending_invites": "Pending invites",
|
"pending_invites": "Pending invites",
|
||||||
"permissions": "Permissions",
|
"permissions": "Permissions",
|
||||||
"same_target_destination": "Same target and destination",
|
"same_target_destination": "Same target and destination",
|
||||||
"saved": "Team saved",
|
"saved": "Workspace saved",
|
||||||
"select_a_team": "Select a team",
|
"select_a_team": "Select a workspace",
|
||||||
"success_invites": "Success invites",
|
"success_invites": "Success invites",
|
||||||
"title": "Teams",
|
"title": "Workspaces",
|
||||||
"we_sent_invite_link": "We sent an invite link to all invitees!",
|
"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.",
|
||||||
|
"search_title": "Team Requests"
|
||||||
},
|
},
|
||||||
"team_environment": {
|
"team_environment": {
|
||||||
"deleted": "Environment Deleted",
|
"deleted": "Environment Deleted",
|
||||||
@@ -991,8 +1012,14 @@
|
|||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"change": "Change workspace",
|
"change": "Change workspace",
|
||||||
"personal": "My Workspace",
|
"personal": "Personal Workspace",
|
||||||
"team": "Team Workspace",
|
"other_workspaces": "My Workspaces",
|
||||||
|
"team": "Workspace",
|
||||||
"title": "Workspaces"
|
"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",
|
"name": "@hoppscotch/common",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.12.6",
|
"version": "2024.3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
"test": "vitest --run",
|
"test": "vitest --run",
|
||||||
@@ -21,147 +21,147 @@
|
|||||||
"do-lintfix": "pnpm run lintfix"
|
"do-lintfix": "pnpm run lintfix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "^10.1.0",
|
"@apidevtools/swagger-parser": "10.1.0",
|
||||||
"@codemirror/autocomplete": "^6.11.1",
|
"@codemirror/autocomplete": "6.13.0",
|
||||||
"@codemirror/commands": "^6.3.2",
|
"@codemirror/commands": "6.3.3",
|
||||||
"@codemirror/lang-javascript": "^6.2.1",
|
"@codemirror/lang-javascript": "6.2.2",
|
||||||
"@codemirror/lang-json": "^6.0.1",
|
"@codemirror/lang-json": "6.0.1",
|
||||||
"@codemirror/lang-xml": "^6.0.2",
|
"@codemirror/lang-xml": "6.1.0",
|
||||||
"@codemirror/language": "6.9.3",
|
"@codemirror/language": "6.10.1",
|
||||||
"@codemirror/legacy-modes": "^6.3.3",
|
"@codemirror/legacy-modes": "6.3.3",
|
||||||
"@codemirror/lint": "^6.4.2",
|
"@codemirror/lint": "6.5.0",
|
||||||
"@codemirror/search": "^6.5.5",
|
"@codemirror/search": "6.5.6",
|
||||||
"@codemirror/state": "^6.3.3",
|
"@codemirror/state": "6.4.1",
|
||||||
"@codemirror/view": "^6.22.3",
|
"@codemirror/view": "6.25.1",
|
||||||
"@hoppscotch/codemirror-lang-graphql": "workspace:^",
|
"@hoppscotch/codemirror-lang-graphql": "workspace:^",
|
||||||
"@hoppscotch/data": "workspace:^",
|
"@hoppscotch/data": "workspace:^",
|
||||||
"@hoppscotch/js-sandbox": "workspace:^",
|
"@hoppscotch/js-sandbox": "workspace:^",
|
||||||
"@hoppscotch/ui": "^0.1.0",
|
"@hoppscotch/ui": "0.1.0",
|
||||||
"@hoppscotch/vue-toasted": "^0.1.0",
|
"@hoppscotch/vue-toasted": "0.1.0",
|
||||||
"@lezer/highlight": "1.2.0",
|
"@lezer/highlight": "1.2.0",
|
||||||
"@unhead/vue": "^1.8.8",
|
"@unhead/vue": "1.8.8",
|
||||||
"@urql/core": "^4.2.0",
|
"@urql/core": "4.2.0",
|
||||||
"@urql/devtools": "^2.0.3",
|
"@urql/devtools": "2.0.3",
|
||||||
"@urql/exchange-auth": "^2.1.6",
|
"@urql/exchange-auth": "2.1.6",
|
||||||
"@urql/exchange-graphcache": "^6.3.3",
|
"@urql/exchange-graphcache": "6.4.0",
|
||||||
"@vitejs/plugin-legacy": "^4.1.1",
|
"@vitejs/plugin-legacy": "4.1.1",
|
||||||
"@vueuse/core": "^10.6.1",
|
"@vueuse/core": "10.7.0",
|
||||||
"acorn-walk": "^8.3.0",
|
"acorn-walk": "8.3.0",
|
||||||
"axios": "^1.6.2",
|
"axios": "1.6.2",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "6.0.3",
|
||||||
"cookie-es": "^1.0.0",
|
"cookie-es": "1.0.0",
|
||||||
"dioc": "^1.0.1",
|
"dioc": "1.0.1",
|
||||||
"esprima": "^4.0.1",
|
"esprima": "4.0.1",
|
||||||
"events": "^3.3.0",
|
"events": "3.3.0",
|
||||||
"fp-ts": "^2.16.1",
|
"fp-ts": "2.16.1",
|
||||||
"globalthis": "^1.0.3",
|
"globalthis": "1.0.3",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "16.8.1",
|
||||||
"graphql-language-service-interface": "^2.10.2",
|
"graphql-language-service-interface": "2.10.2",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "2.12.6",
|
||||||
"httpsnippet": "^3.0.1",
|
"httpsnippet": "3.0.1",
|
||||||
"insomnia-importers": "^3.6.0",
|
"insomnia-importers": "3.6.0",
|
||||||
"io-ts": "^2.2.20",
|
"io-ts": "2.2.20",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsonpath-plus": "^7.2.0",
|
"jsonpath-plus": "7.2.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"lossless-json": "^3.0.2",
|
"lossless-json": "3.0.2",
|
||||||
"minisearch": "^6.3.0",
|
"minisearch": "6.3.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"paho-mqtt": "^1.1.0",
|
"paho-mqtt": "1.1.0",
|
||||||
"path": "^0.12.7",
|
"path": "0.12.7",
|
||||||
"postman-collection": "^4.3.0",
|
"postman-collection": "4.3.0",
|
||||||
"process": "^0.11.10",
|
"process": "0.11.10",
|
||||||
"qs": "^6.11.2",
|
"qs": "6.11.2",
|
||||||
"quicktype-core": "^23.0.79",
|
"quicktype-core": "23.0.79",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "7.8.1",
|
||||||
"set-cookie-parser": "^2.6.0",
|
"set-cookie-parser": "2.6.0",
|
||||||
"set-cookie-parser-es": "^1.0.5",
|
"set-cookie-parser-es": "1.0.5",
|
||||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
"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-v3": "npm:socket.io-client@3.1.3",
|
||||||
"socket.io-client-v4": "npm:socket.io-client@^4.4.1",
|
"socket.io-client-v4": "npm:socket.io-client@4.4.1",
|
||||||
"socketio-wildcard": "^2.0.0",
|
"socketio-wildcard": "2.0.0",
|
||||||
"splitpanes": "^3.1.5",
|
"splitpanes": "3.1.5",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "3.0.0",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "0.11.0",
|
||||||
"tern": "^0.24.3",
|
"tern": "0.24.3",
|
||||||
"timers": "^0.1.1",
|
"timers": "0.1.1",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "6.3.7",
|
||||||
"url": "^0.11.3",
|
"url": "0.11.3",
|
||||||
"util": "^0.12.5",
|
"util": "0.12.5",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "9.0.1",
|
||||||
"verzod": "^0.2.0",
|
"verzod": "0.2.2",
|
||||||
"vue": "^3.3.8",
|
"vue": "3.3.9",
|
||||||
"vue-i18n": "^9.7.1",
|
"vue-i18n": "9.8.0",
|
||||||
"vue-pdf-embed": "^1.2.1",
|
"vue-pdf-embed": "1.2.1",
|
||||||
"vue-router": "^4.2.5",
|
"vue-router": "4.2.5",
|
||||||
"vue-tippy": "6.3.1",
|
"vue-tippy": "6.3.1",
|
||||||
"vuedraggable-es": "^4.1.1",
|
"vuedraggable-es": "4.1.1",
|
||||||
"wonka": "^6.3.4",
|
"wonka": "6.3.4",
|
||||||
"workbox-window": "^7.0.0",
|
"workbox-window": "7.0.0",
|
||||||
"xml-formatter": "^3.6.0",
|
"xml-formatter": "3.6.0",
|
||||||
"yargs-parser": "^21.1.1",
|
"yargs-parser": "21.1.1",
|
||||||
"zod": "^3.22.4"
|
"zod": "3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
|
||||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
"@esbuild-plugins/node-modules-polyfill": "0.2.2",
|
||||||
"@graphql-codegen/add": "^5.0.0",
|
"@graphql-codegen/add": "5.0.0",
|
||||||
"@graphql-codegen/cli": "^5.0.0",
|
"@graphql-codegen/cli": "5.0.0",
|
||||||
"@graphql-codegen/typed-document-node": "^5.0.1",
|
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||||
"@graphql-codegen/typescript": "^4.0.1",
|
"@graphql-codegen/typescript": "4.0.1",
|
||||||
"@graphql-codegen/typescript-operations": "^4.0.1",
|
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||||
"@graphql-codegen/typescript-urql-graphcache": "^3.0.0",
|
"@graphql-codegen/typescript-urql-graphcache": "3.0.0",
|
||||||
"@graphql-codegen/urql-introspection": "^3.0.0",
|
"@graphql-codegen/urql-introspection": "3.0.0",
|
||||||
"@graphql-typed-document-node/core": "^3.2.0",
|
"@graphql-typed-document-node/core": "3.2.0",
|
||||||
"@iconify-json/lucide": "^1.1.141",
|
"@iconify-json/lucide": "1.1.144",
|
||||||
"@intlify/vite-plugin-vue-i18n": "^7.0.0",
|
"@intlify/vite-plugin-vue-i18n": "7.0.0",
|
||||||
"@relmify/jest-fp-ts": "^2.1.1",
|
"@relmify/jest-fp-ts": "2.1.1",
|
||||||
"@rushstack/eslint-patch": "^1.6.0",
|
"@rushstack/eslint-patch": "1.6.0",
|
||||||
"@types/har-format": "^1.2.15",
|
"@types/har-format": "1.2.15",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "4.17.12",
|
||||||
"@types/lossless-json": "^1.0.4",
|
"@types/lossless-json": "1.0.4",
|
||||||
"@types/nprogress": "^0.2.3",
|
"@types/nprogress": "0.2.3",
|
||||||
"@types/paho-mqtt": "^1.0.10",
|
"@types/paho-mqtt": "1.0.10",
|
||||||
"@types/postman-collection": "^3.5.10",
|
"@types/postman-collection": "3.5.10",
|
||||||
"@types/splitpanes": "^2.2.6",
|
"@types/splitpanes": "2.2.6",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "9.0.7",
|
||||||
"@types/yargs-parser": "^21.0.3",
|
"@types/yargs-parser": "21.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
"@typescript-eslint/eslint-plugin": "7.3.1",
|
||||||
"@typescript-eslint/parser": "^6.12.0",
|
"@typescript-eslint/parser": "7.3.1",
|
||||||
"@vitejs/plugin-vue": "^4.5.0",
|
"@vitejs/plugin-vue": "4.5.1",
|
||||||
"@vue/compiler-sfc": "^3.3.8",
|
"@vue/compiler-sfc": "3.3.10",
|
||||||
"@vue/eslint-config-typescript": "^12.0.0",
|
"@vue/eslint-config-typescript": "12.0.0",
|
||||||
"@vue/runtime-core": "^3.3.8",
|
"@vue/runtime-core": "3.3.10",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "10.4.16",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "16.3.1",
|
||||||
"eslint": "^8.54.0",
|
"eslint": "8.57.0",
|
||||||
"eslint-plugin-prettier": "^5.0.1",
|
"eslint-plugin-prettier": "5.1.3",
|
||||||
"eslint-plugin-vue": "^9.18.1",
|
"eslint-plugin-vue": "9.24.0",
|
||||||
"glob": "^10.3.10",
|
"glob": "10.3.10",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "4.1.5",
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "12.1.3",
|
||||||
"postcss": "^8.4.23",
|
"postcss": "8.4.31",
|
||||||
"prettier": "^3.1.0",
|
"prettier": "3.1.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.7",
|
"prettier-plugin-tailwindcss": "0.5.7",
|
||||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
"rollup-plugin-polyfill-node": "0.13.0",
|
||||||
"sass": "^1.69.5",
|
"sass": "1.69.5",
|
||||||
"tailwindcss": "^3.3.2",
|
"tailwindcss": "3.3.5",
|
||||||
"typescript": "^5.3.2",
|
"typescript": "5.3.2",
|
||||||
"unplugin-fonts": "^1.1.1",
|
"unplugin-fonts": "1.1.1",
|
||||||
"unplugin-icons": "^0.17.4",
|
"unplugin-icons": "0.17.4",
|
||||||
"unplugin-vue-components": "^0.25.2",
|
"unplugin-vue-components": "0.25.2",
|
||||||
"vite": "^4.5.0",
|
"vite": "4.5.0",
|
||||||
"vite-plugin-checker": "^0.6.2",
|
"vite-plugin-checker": "0.6.2",
|
||||||
"vite-plugin-fonts": "^0.7.0",
|
"vite-plugin-fonts": "0.7.0",
|
||||||
"vite-plugin-html-config": "^1.0.11",
|
"vite-plugin-html-config": "1.0.11",
|
||||||
"vite-plugin-inspect": "^0.7.42",
|
"vite-plugin-inspect": "0.7.42",
|
||||||
"vite-plugin-pages": "^0.31.0",
|
"vite-plugin-pages": "0.31.0",
|
||||||
"vite-plugin-pages-sitemap": "^1.6.1",
|
"vite-plugin-pages-sitemap": "1.6.1",
|
||||||
"vite-plugin-pwa": "^0.17.0",
|
"vite-plugin-pwa": "0.17.3",
|
||||||
"vite-plugin-vue-layouts": "^0.8.0",
|
"vite-plugin-vue-layouts": "0.8.0",
|
||||||
"vitest": "^0.34.6",
|
"vitest": "0.34.6",
|
||||||
"vue-tsc": "^1.8.22"
|
"vue-tsc": "1.8.24"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
415
packages/hoppscotch-common/src/components.d.ts
vendored
415
packages/hoppscotch-common/src/components.d.ts
vendored
@@ -1,213 +1,216 @@
|
|||||||
// generated by unplugin-vue-components
|
/* eslint-disable */
|
||||||
// We suggest you to commit this file into source control
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// Generated by unplugin-vue-components
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
import '@vue/runtime-core'
|
import "@vue/runtime-core"
|
||||||
|
|
||||||
export {}
|
export {}
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module "@vue/runtime-core" {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
|
AppActionHandler: (typeof import("./components/app/ActionHandler.vue"))["default"]
|
||||||
AppBanner: typeof import('./components/app/Banner.vue')['default']
|
AppBanner: (typeof import("./components/app/Banner.vue"))["default"]
|
||||||
AppContextMenu: typeof import('./components/app/ContextMenu.vue')['default']
|
AppContextMenu: (typeof import("./components/app/ContextMenu.vue"))["default"]
|
||||||
AppDeveloperOptions: typeof import('./components/app/DeveloperOptions.vue')['default']
|
AppDeveloperOptions: (typeof import("./components/app/DeveloperOptions.vue"))["default"]
|
||||||
AppFooter: typeof import('./components/app/Footer.vue')['default']
|
AppFooter: (typeof import("./components/app/Footer.vue"))["default"]
|
||||||
AppGitHubStarButton: typeof import('./components/app/GitHubStarButton.vue')['default']
|
AppGitHubStarButton: (typeof import("./components/app/GitHubStarButton.vue"))["default"]
|
||||||
AppHeader: typeof import('./components/app/Header.vue')['default']
|
AppHeader: (typeof import("./components/app/Header.vue"))["default"]
|
||||||
AppInspection: typeof import('./components/app/Inspection.vue')['default']
|
AppInspection: (typeof import("./components/app/Inspection.vue"))["default"]
|
||||||
AppInterceptor: typeof import('./components/app/Interceptor.vue')['default']
|
AppInterceptor: (typeof import("./components/app/Interceptor.vue"))["default"]
|
||||||
AppLogo: typeof import('./components/app/Logo.vue')['default']
|
AppLogo: (typeof import("./components/app/Logo.vue"))["default"]
|
||||||
AppOptions: typeof import('./components/app/Options.vue')['default']
|
AppOptions: (typeof import("./components/app/Options.vue"))["default"]
|
||||||
AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default']
|
AppPaneLayout: (typeof import("./components/app/PaneLayout.vue"))["default"]
|
||||||
AppShare: typeof import('./components/app/Share.vue')['default']
|
AppShare: (typeof import("./components/app/Share.vue"))["default"]
|
||||||
AppShortcuts: typeof import('./components/app/Shortcuts.vue')['default']
|
AppShortcuts: (typeof import("./components/app/Shortcuts.vue"))["default"]
|
||||||
AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default']
|
AppShortcutsEntry: (typeof import("./components/app/ShortcutsEntry.vue"))["default"]
|
||||||
AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default']
|
AppShortcutsPrompt: (typeof import("./components/app/ShortcutsPrompt.vue"))["default"]
|
||||||
AppSidenav: typeof import('./components/app/Sidenav.vue')['default']
|
AppSidenav: (typeof import("./components/app/Sidenav.vue"))["default"]
|
||||||
AppSpotlight: typeof import('./components/app/spotlight/index.vue')['default']
|
AppSpotlight: (typeof import("./components/app/spotlight/index.vue"))["default"]
|
||||||
AppSpotlightEntry: typeof import('./components/app/spotlight/Entry.vue')['default']
|
AppSpotlightEntry: (typeof import("./components/app/spotlight/Entry.vue"))["default"]
|
||||||
AppSpotlightEntryGQLHistory: typeof import('./components/app/spotlight/entry/GQLHistory.vue')['default']
|
AppSpotlightEntryGQLHistory: (typeof import("./components/app/spotlight/entry/GQLHistory.vue"))["default"]
|
||||||
AppSpotlightEntryGQLRequest: typeof import('./components/app/spotlight/entry/GQLRequest.vue')['default']
|
AppSpotlightEntryGQLRequest: (typeof import("./components/app/spotlight/entry/GQLRequest.vue"))["default"]
|
||||||
AppSpotlightEntryIconSelected: typeof import('./components/app/spotlight/entry/IconSelected.vue')['default']
|
AppSpotlightEntryIconSelected: (typeof import("./components/app/spotlight/entry/IconSelected.vue"))["default"]
|
||||||
AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default']
|
AppSpotlightEntryRESTHistory: (typeof import("./components/app/spotlight/entry/RESTHistory.vue"))["default"]
|
||||||
AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default']
|
AppSpotlightEntryRESTRequest: (typeof import("./components/app/spotlight/entry/RESTRequest.vue"))["default"]
|
||||||
AppSupport: typeof import('./components/app/Support.vue')['default']
|
AppSupport: (typeof import("./components/app/Support.vue"))["default"]
|
||||||
Collections: typeof import('./components/collections/index.vue')['default']
|
Collections: (typeof import("./components/collections/index.vue"))["default"]
|
||||||
CollectionsAdd: typeof import('./components/collections/Add.vue')['default']
|
CollectionsAdd: (typeof import("./components/collections/Add.vue"))["default"]
|
||||||
CollectionsAddFolder: typeof import('./components/collections/AddFolder.vue')['default']
|
CollectionsAddFolder: (typeof import("./components/collections/AddFolder.vue"))["default"]
|
||||||
CollectionsAddRequest: typeof import('./components/collections/AddRequest.vue')['default']
|
CollectionsAddRequest: (typeof import("./components/collections/AddRequest.vue"))["default"]
|
||||||
CollectionsCollection: typeof import('./components/collections/Collection.vue')['default']
|
CollectionsCollection: (typeof import("./components/collections/Collection.vue"))["default"]
|
||||||
CollectionsEdit: typeof import('./components/collections/Edit.vue')['default']
|
CollectionsEdit: (typeof import("./components/collections/Edit.vue"))["default"]
|
||||||
CollectionsEditFolder: typeof import('./components/collections/EditFolder.vue')['default']
|
CollectionsEditFolder: (typeof import("./components/collections/EditFolder.vue"))["default"]
|
||||||
CollectionsEditRequest: typeof import('./components/collections/EditRequest.vue')['default']
|
CollectionsEditRequest: (typeof import("./components/collections/EditRequest.vue"))["default"]
|
||||||
CollectionsGraphql: typeof import('./components/collections/graphql/index.vue')['default']
|
CollectionsGraphql: (typeof import("./components/collections/graphql/index.vue"))["default"]
|
||||||
CollectionsGraphqlAdd: typeof import('./components/collections/graphql/Add.vue')['default']
|
CollectionsGraphqlAdd: (typeof import("./components/collections/graphql/Add.vue"))["default"]
|
||||||
CollectionsGraphqlAddFolder: typeof import('./components/collections/graphql/AddFolder.vue')['default']
|
CollectionsGraphqlAddFolder: (typeof import("./components/collections/graphql/AddFolder.vue"))["default"]
|
||||||
CollectionsGraphqlAddRequest: typeof import('./components/collections/graphql/AddRequest.vue')['default']
|
CollectionsGraphqlAddRequest: (typeof import("./components/collections/graphql/AddRequest.vue"))["default"]
|
||||||
CollectionsGraphqlCollection: typeof import('./components/collections/graphql/Collection.vue')['default']
|
CollectionsGraphqlCollection: (typeof import("./components/collections/graphql/Collection.vue"))["default"]
|
||||||
CollectionsGraphqlEdit: typeof import('./components/collections/graphql/Edit.vue')['default']
|
CollectionsGraphqlEdit: (typeof import("./components/collections/graphql/Edit.vue"))["default"]
|
||||||
CollectionsGraphqlEditFolder: typeof import('./components/collections/graphql/EditFolder.vue')['default']
|
CollectionsGraphqlEditFolder: (typeof import("./components/collections/graphql/EditFolder.vue"))["default"]
|
||||||
CollectionsGraphqlEditRequest: typeof import('./components/collections/graphql/EditRequest.vue')['default']
|
CollectionsGraphqlEditRequest: (typeof import("./components/collections/graphql/EditRequest.vue"))["default"]
|
||||||
CollectionsGraphqlFolder: typeof import('./components/collections/graphql/Folder.vue')['default']
|
CollectionsGraphqlFolder: (typeof import("./components/collections/graphql/Folder.vue"))["default"]
|
||||||
CollectionsGraphqlImportExport: typeof import('./components/collections/graphql/ImportExport.vue')['default']
|
CollectionsGraphqlImportExport: (typeof import("./components/collections/graphql/ImportExport.vue"))["default"]
|
||||||
CollectionsGraphqlRequest: typeof import('./components/collections/graphql/Request.vue')['default']
|
CollectionsGraphqlRequest: (typeof import("./components/collections/graphql/Request.vue"))["default"]
|
||||||
CollectionsImportExport: typeof import('./components/collections/ImportExport.vue')['default']
|
CollectionsImportExport: (typeof import("./components/collections/ImportExport.vue"))["default"]
|
||||||
CollectionsMyCollections: typeof import('./components/collections/MyCollections.vue')['default']
|
CollectionsMyCollections: (typeof import("./components/collections/MyCollections.vue"))["default"]
|
||||||
CollectionsProperties: typeof import('./components/collections/Properties.vue')['default']
|
CollectionsProperties: (typeof import("./components/collections/Properties.vue"))["default"]
|
||||||
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
CollectionsRequest: (typeof import("./components/collections/Request.vue"))["default"]
|
||||||
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
CollectionsSaveRequest: (typeof import("./components/collections/SaveRequest.vue"))["default"]
|
||||||
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
CollectionsTeamCollections: (typeof import("./components/collections/TeamCollections.vue"))["default"]
|
||||||
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
|
CookiesAllModal: (typeof import("./components/cookies/AllModal.vue"))["default"]
|
||||||
CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default']
|
CookiesEditCookie: (typeof import("./components/cookies/EditCookie.vue"))["default"]
|
||||||
Embeds: typeof import('./components/embeds/index.vue')['default']
|
Embeds: (typeof import("./components/embeds/index.vue"))["default"]
|
||||||
Environments: typeof import('./components/environments/index.vue')['default']
|
Environments: (typeof import("./components/environments/index.vue"))["default"]
|
||||||
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
EnvironmentsAdd: (typeof import("./components/environments/Add.vue"))["default"]
|
||||||
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
EnvironmentsImportExport: (typeof import("./components/environments/ImportExport.vue"))["default"]
|
||||||
EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
|
EnvironmentsMy: (typeof import("./components/environments/my/index.vue"))["default"]
|
||||||
EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
|
EnvironmentsMyDetails: (typeof import("./components/environments/my/Details.vue"))["default"]
|
||||||
EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.vue')['default']
|
EnvironmentsMyEnvironment: (typeof import("./components/environments/my/Environment.vue"))["default"]
|
||||||
EnvironmentsSelector: typeof import('./components/environments/Selector.vue')['default']
|
EnvironmentsSelector: (typeof import("./components/environments/Selector.vue"))["default"]
|
||||||
EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default']
|
EnvironmentsTeams: (typeof import("./components/environments/teams/index.vue"))["default"]
|
||||||
EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default']
|
EnvironmentsTeamsDetails: (typeof import("./components/environments/teams/Details.vue"))["default"]
|
||||||
EnvironmentsTeamsEnvironment: typeof import('./components/environments/teams/Environment.vue')['default']
|
EnvironmentsTeamsEnvironment: (typeof import("./components/environments/teams/Environment.vue"))["default"]
|
||||||
FirebaseLogin: typeof import('./components/firebase/Login.vue')['default']
|
FirebaseLogin: (typeof import("./components/firebase/Login.vue"))["default"]
|
||||||
FirebaseLogout: typeof import('./components/firebase/Logout.vue')['default']
|
FirebaseLogout: (typeof import("./components/firebase/Logout.vue"))["default"]
|
||||||
GraphqlAuthorization: typeof import('./components/graphql/Authorization.vue')['default']
|
GraphqlAuthorization: (typeof import("./components/graphql/Authorization.vue"))["default"]
|
||||||
GraphqlField: typeof import('./components/graphql/Field.vue')['default']
|
GraphqlField: (typeof import("./components/graphql/Field.vue"))["default"]
|
||||||
GraphqlHeaders: typeof import('./components/graphql/Headers.vue')['default']
|
GraphqlHeaders: (typeof import("./components/graphql/Headers.vue"))["default"]
|
||||||
GraphqlQuery: typeof import('./components/graphql/Query.vue')['default']
|
GraphqlQuery: (typeof import("./components/graphql/Query.vue"))["default"]
|
||||||
GraphqlRequest: typeof import('./components/graphql/Request.vue')['default']
|
GraphqlRequest: (typeof import("./components/graphql/Request.vue"))["default"]
|
||||||
GraphqlRequestOptions: typeof import('./components/graphql/RequestOptions.vue')['default']
|
GraphqlRequestOptions: (typeof import("./components/graphql/RequestOptions.vue"))["default"]
|
||||||
GraphqlRequestTab: typeof import('./components/graphql/RequestTab.vue')['default']
|
GraphqlRequestTab: (typeof import("./components/graphql/RequestTab.vue"))["default"]
|
||||||
GraphqlResponse: typeof import('./components/graphql/Response.vue')['default']
|
GraphqlResponse: (typeof import("./components/graphql/Response.vue"))["default"]
|
||||||
GraphqlSidebar: typeof import('./components/graphql/Sidebar.vue')['default']
|
GraphqlSidebar: (typeof import("./components/graphql/Sidebar.vue"))["default"]
|
||||||
GraphqlSubscriptionLog: typeof import('./components/graphql/SubscriptionLog.vue')['default']
|
GraphqlSubscriptionLog: (typeof import("./components/graphql/SubscriptionLog.vue"))["default"]
|
||||||
GraphqlTabHead: typeof import('./components/graphql/TabHead.vue')['default']
|
GraphqlTabHead: (typeof import("./components/graphql/TabHead.vue"))["default"]
|
||||||
GraphqlType: typeof import('./components/graphql/Type.vue')['default']
|
GraphqlType: (typeof import("./components/graphql/Type.vue"))["default"]
|
||||||
GraphqlTypeLink: typeof import('./components/graphql/TypeLink.vue')['default']
|
GraphqlTypeLink: (typeof import("./components/graphql/TypeLink.vue"))["default"]
|
||||||
GraphqlVariable: typeof import('./components/graphql/Variable.vue')['default']
|
GraphqlVariable: (typeof import("./components/graphql/Variable.vue"))["default"]
|
||||||
History: typeof import('./components/history/index.vue')['default']
|
History: (typeof import("./components/history/index.vue"))["default"]
|
||||||
HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
|
HistoryGraphqlCard: (typeof import("./components/history/graphql/Card.vue"))["default"]
|
||||||
HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
|
HistoryRestCard: (typeof import("./components/history/rest/Card.vue"))["default"]
|
||||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
HoppButtonPrimary: (typeof import("@hoppscotch/ui"))["HoppButtonPrimary"]
|
||||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
HoppButtonSecondary: (typeof import("@hoppscotch/ui"))["HoppButtonSecondary"]
|
||||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
HoppSmartAnchor: (typeof import("@hoppscotch/ui"))["HoppSmartAnchor"]
|
||||||
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
HoppSmartCheckbox: (typeof import("@hoppscotch/ui"))["HoppSmartCheckbox"]
|
||||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
HoppSmartConfirmModal: (typeof import("@hoppscotch/ui"))["HoppSmartConfirmModal"]
|
||||||
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
HoppSmartExpand: (typeof import("@hoppscotch/ui"))["HoppSmartExpand"]
|
||||||
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
HoppSmartFileChip: (typeof import("@hoppscotch/ui"))["HoppSmartFileChip"]
|
||||||
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
HoppSmartInput: (typeof import("@hoppscotch/ui"))["HoppSmartInput"]
|
||||||
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
|
HoppSmartIntersection: (typeof import("@hoppscotch/ui"))["HoppSmartIntersection"]
|
||||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
HoppSmartItem: (typeof import("@hoppscotch/ui"))["HoppSmartItem"]
|
||||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
HoppSmartLink: (typeof import("@hoppscotch/ui"))["HoppSmartLink"]
|
||||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
HoppSmartModal: (typeof import("@hoppscotch/ui"))["HoppSmartModal"]
|
||||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
HoppSmartPicture: (typeof import("@hoppscotch/ui"))["HoppSmartPicture"]
|
||||||
HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder']
|
HoppSmartPlaceholder: (typeof import("@hoppscotch/ui"))["HoppSmartPlaceholder"]
|
||||||
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
HoppSmartProgressRing: (typeof import("@hoppscotch/ui"))["HoppSmartProgressRing"]
|
||||||
HoppSmartRadio: typeof import('@hoppscotch/ui')['HoppSmartRadio']
|
HoppSmartRadio: (typeof import("@hoppscotch/ui"))["HoppSmartRadio"]
|
||||||
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
HoppSmartRadioGroup: (typeof import("@hoppscotch/ui"))["HoppSmartRadioGroup"]
|
||||||
HoppSmartSelectWrapper: typeof import('@hoppscotch/ui')['HoppSmartSelectWrapper']
|
HoppSmartSelectWrapper: (typeof import("@hoppscotch/ui"))["HoppSmartSelectWrapper"]
|
||||||
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
HoppSmartSlideOver: (typeof import("@hoppscotch/ui"))["HoppSmartSlideOver"]
|
||||||
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
HoppSmartSpinner: (typeof import("@hoppscotch/ui"))["HoppSmartSpinner"]
|
||||||
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
|
HoppSmartTab: (typeof import("@hoppscotch/ui"))["HoppSmartTab"]
|
||||||
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
HoppSmartTabs: (typeof import("@hoppscotch/ui"))["HoppSmartTabs"]
|
||||||
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
|
HoppSmartToggle: (typeof import("@hoppscotch/ui"))["HoppSmartToggle"]
|
||||||
HoppSmartTree: typeof import('@hoppscotch/ui')['HoppSmartTree']
|
HoppSmartTree: (typeof import("@hoppscotch/ui"))["HoppSmartTree"]
|
||||||
HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow']
|
HoppSmartWindow: (typeof import("@hoppscotch/ui"))["HoppSmartWindow"]
|
||||||
HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows']
|
HoppSmartWindows: (typeof import("@hoppscotch/ui"))["HoppSmartWindows"]
|
||||||
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
HttpAuthorization: (typeof import("./components/http/Authorization.vue"))["default"]
|
||||||
HttpAuthorizationApiKey: typeof import('./components/http/authorization/ApiKey.vue')['default']
|
HttpAuthorizationApiKey: (typeof import("./components/http/authorization/ApiKey.vue"))["default"]
|
||||||
HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default']
|
HttpAuthorizationBasic: (typeof import("./components/http/authorization/Basic.vue"))["default"]
|
||||||
HttpBody: typeof import('./components/http/Body.vue')['default']
|
HttpBody: (typeof import("./components/http/Body.vue"))["default"]
|
||||||
HttpBodyParameters: typeof import('./components/http/BodyParameters.vue')['default']
|
HttpBodyParameters: (typeof import("./components/http/BodyParameters.vue"))["default"]
|
||||||
HttpCodegenModal: typeof import('./components/http/CodegenModal.vue')['default']
|
HttpCodegenModal: (typeof import("./components/http/CodegenModal.vue"))["default"]
|
||||||
HttpHeaders: typeof import('./components/http/Headers.vue')['default']
|
HttpHeaders: (typeof import("./components/http/Headers.vue"))["default"]
|
||||||
HttpImportCurl: typeof import('./components/http/ImportCurl.vue')['default']
|
HttpImportCurl: (typeof import("./components/http/ImportCurl.vue"))["default"]
|
||||||
HttpOAuth2Authorization: typeof import('./components/http/OAuth2Authorization.vue')['default']
|
HttpOAuth2Authorization: (typeof import("./components/http/OAuth2Authorization.vue"))["default"]
|
||||||
HttpParameters: typeof import('./components/http/Parameters.vue')['default']
|
HttpParameters: (typeof import("./components/http/Parameters.vue"))["default"]
|
||||||
HttpPreRequestScript: typeof import('./components/http/PreRequestScript.vue')['default']
|
HttpPreRequestScript: (typeof import("./components/http/PreRequestScript.vue"))["default"]
|
||||||
HttpRawBody: typeof import('./components/http/RawBody.vue')['default']
|
HttpRawBody: (typeof import("./components/http/RawBody.vue"))["default"]
|
||||||
HttpReqChangeConfirmModal: typeof import('./components/http/ReqChangeConfirmModal.vue')['default']
|
HttpReqChangeConfirmModal: (typeof import("./components/http/ReqChangeConfirmModal.vue"))["default"]
|
||||||
HttpRequest: typeof import('./components/http/Request.vue')['default']
|
HttpRequest: (typeof import("./components/http/Request.vue"))["default"]
|
||||||
HttpRequestOptions: typeof import('./components/http/RequestOptions.vue')['default']
|
HttpRequestOptions: (typeof import("./components/http/RequestOptions.vue"))["default"]
|
||||||
HttpRequestTab: typeof import('./components/http/RequestTab.vue')['default']
|
HttpRequestTab: (typeof import("./components/http/RequestTab.vue"))["default"]
|
||||||
HttpResponse: typeof import('./components/http/Response.vue')['default']
|
HttpResponse: (typeof import("./components/http/Response.vue"))["default"]
|
||||||
HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default']
|
HttpResponseMeta: (typeof import("./components/http/ResponseMeta.vue"))["default"]
|
||||||
HttpSidebar: typeof import('./components/http/Sidebar.vue')['default']
|
HttpSidebar: (typeof import("./components/http/Sidebar.vue"))["default"]
|
||||||
HttpTabHead: typeof import('./components/http/TabHead.vue')['default']
|
HttpTabHead: (typeof import("./components/http/TabHead.vue"))["default"]
|
||||||
HttpTestResult: typeof import('./components/http/TestResult.vue')['default']
|
HttpTestResult: (typeof import("./components/http/TestResult.vue"))["default"]
|
||||||
HttpTestResultEntry: typeof import('./components/http/TestResultEntry.vue')['default']
|
HttpTestResultEntry: (typeof import("./components/http/TestResultEntry.vue"))["default"]
|
||||||
HttpTestResultEnv: typeof import('./components/http/TestResultEnv.vue')['default']
|
HttpTestResultEnv: (typeof import("./components/http/TestResultEnv.vue"))["default"]
|
||||||
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
|
HttpTestResultReport: (typeof import("./components/http/TestResultReport.vue"))["default"]
|
||||||
HttpTests: typeof import('./components/http/Tests.vue')['default']
|
HttpTests: (typeof import("./components/http/Tests.vue"))["default"]
|
||||||
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
|
HttpURLEncodedParams: (typeof import("./components/http/URLEncodedParams.vue"))["default"]
|
||||||
IconLucideActivity: typeof import('~icons/lucide/activity')['default']
|
IconLucideActivity: (typeof import("~icons/lucide/activity"))["default"]
|
||||||
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
IconLucideAlertTriangle: (typeof import("~icons/lucide/alert-triangle"))["default"]
|
||||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
IconLucideArrowLeft: (typeof import("~icons/lucide/arrow-left"))["default"]
|
||||||
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
IconLucideArrowUpRight: (typeof import("~icons/lucide/arrow-up-right"))["default"]
|
||||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
IconLucideBrush: (typeof import("~icons/lucide/brush"))["default"]
|
||||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
IconLucideCheckCircle: (typeof import("~icons/lucide/check-circle"))["default"]
|
||||||
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
IconLucideChevronRight: (typeof import("~icons/lucide/chevron-right"))["default"]
|
||||||
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
|
IconLucideGlobe: (typeof import("~icons/lucide/globe"))["default"]
|
||||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
IconLucideHelpCircle: (typeof import("~icons/lucide/help-circle"))["default"]
|
||||||
IconLucideInfo: typeof import('~icons/lucide/info')['default']
|
IconLucideInbox: (typeof import("~icons/lucide/inbox"))["default"]
|
||||||
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
IconLucideInfo: (typeof import("~icons/lucide/info"))["default"]
|
||||||
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
IconLucideLayers: (typeof import("~icons/lucide/layers"))["default"]
|
||||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
IconLucideListEnd: (typeof import("~icons/lucide/list-end"))["default"]
|
||||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
IconLucideMinus: (typeof import("~icons/lucide/minus"))["default"]
|
||||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
IconLucideRss: (typeof import("~icons/lucide/rss"))["default"]
|
||||||
IconLucideX: typeof import('~icons/lucide/x')['default']
|
IconLucideSearch: (typeof import("~icons/lucide/search"))["default"]
|
||||||
ImportExportBase: typeof import('./components/importExport/Base.vue')['default']
|
IconLucideUsers: (typeof import("~icons/lucide/users"))["default"]
|
||||||
ImportExportImportExportList: typeof import('./components/importExport/ImportExportList.vue')['default']
|
IconLucideX: (typeof import("~icons/lucide/x"))["default"]
|
||||||
ImportExportImportExportSourcesList: typeof import('./components/importExport/ImportExportSourcesList.vue')['default']
|
ImportExportBase: (typeof import("./components/importExport/Base.vue"))["default"]
|
||||||
ImportExportImportExportStepsFileImport: typeof import('./components/importExport/ImportExportSteps/FileImport.vue')['default']
|
ImportExportImportExportList: (typeof import("./components/importExport/ImportExportList.vue"))["default"]
|
||||||
ImportExportImportExportStepsMyCollectionImport: typeof import('./components/importExport/ImportExportSteps/MyCollectionImport.vue')['default']
|
ImportExportImportExportSourcesList: (typeof import("./components/importExport/ImportExportSourcesList.vue"))["default"]
|
||||||
ImportExportImportExportStepsUrlImport: typeof import('./components/importExport/ImportExportSteps/UrlImport.vue')['default']
|
ImportExportImportExportStepsFileImport: (typeof import("./components/importExport/ImportExportSteps/FileImport.vue"))["default"]
|
||||||
InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default']
|
ImportExportImportExportStepsMyCollectionImport: (typeof import("./components/importExport/ImportExportSteps/MyCollectionImport.vue"))["default"]
|
||||||
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
ImportExportImportExportStepsUrlImport: (typeof import("./components/importExport/ImportExportSteps/UrlImport.vue"))["default"]
|
||||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
InterceptorsErrorPlaceholder: (typeof import("./components/interceptors/ErrorPlaceholder.vue"))["default"]
|
||||||
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
InterceptorsExtensionSubtitle: (typeof import("./components/interceptors/ExtensionSubtitle.vue"))["default"]
|
||||||
LensesRenderersAudioLensRenderer: typeof import('./components/lenses/renderers/AudioLensRenderer.vue')['default']
|
LensesHeadersRenderer: (typeof import("./components/lenses/HeadersRenderer.vue"))["default"]
|
||||||
LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default']
|
LensesHeadersRendererEntry: (typeof import("./components/lenses/HeadersRendererEntry.vue"))["default"]
|
||||||
LensesRenderersImageLensRenderer: typeof import('./components/lenses/renderers/ImageLensRenderer.vue')['default']
|
LensesRenderersAudioLensRenderer: (typeof import("./components/lenses/renderers/AudioLensRenderer.vue"))["default"]
|
||||||
LensesRenderersJSONLensRenderer: typeof import('./components/lenses/renderers/JSONLensRenderer.vue')['default']
|
LensesRenderersHTMLLensRenderer: (typeof import("./components/lenses/renderers/HTMLLensRenderer.vue"))["default"]
|
||||||
LensesRenderersPDFLensRenderer: typeof import('./components/lenses/renderers/PDFLensRenderer.vue')['default']
|
LensesRenderersImageLensRenderer: (typeof import("./components/lenses/renderers/ImageLensRenderer.vue"))["default"]
|
||||||
LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default']
|
LensesRenderersJSONLensRenderer: (typeof import("./components/lenses/renderers/JSONLensRenderer.vue"))["default"]
|
||||||
LensesRenderersVideoLensRenderer: typeof import('./components/lenses/renderers/VideoLensRenderer.vue')['default']
|
LensesRenderersPDFLensRenderer: (typeof import("./components/lenses/renderers/PDFLensRenderer.vue"))["default"]
|
||||||
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
|
LensesRenderersRawLensRenderer: (typeof import("./components/lenses/renderers/RawLensRenderer.vue"))["default"]
|
||||||
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
|
LensesRenderersVideoLensRenderer: (typeof import("./components/lenses/renderers/VideoLensRenderer.vue"))["default"]
|
||||||
ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default']
|
LensesRenderersXMLLensRenderer: (typeof import("./components/lenses/renderers/XMLLensRenderer.vue"))["default"]
|
||||||
RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default']
|
LensesResponseBodyRenderer: (typeof import("./components/lenses/ResponseBodyRenderer.vue"))["default"]
|
||||||
RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default']
|
ProfileUserDelete: (typeof import("./components/profile/UserDelete.vue"))["default"]
|
||||||
RealtimeLog: typeof import('./components/realtime/Log.vue')['default']
|
RealtimeCommunication: (typeof import("./components/realtime/Communication.vue"))["default"]
|
||||||
RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default']
|
RealtimeConnectionConfig: (typeof import("./components/realtime/ConnectionConfig.vue"))["default"]
|
||||||
RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default']
|
RealtimeLog: (typeof import("./components/realtime/Log.vue"))["default"]
|
||||||
SettingsExtension: typeof import('./components/settings/Extension.vue')['default']
|
RealtimeLogEntry: (typeof import("./components/realtime/LogEntry.vue"))["default"]
|
||||||
SettingsProxy: typeof import('./components/settings/Proxy.vue')['default']
|
RealtimeSubscription: (typeof import("./components/realtime/Subscription.vue"))["default"]
|
||||||
Share: typeof import('./components/share/index.vue')['default']
|
SettingsExtension: (typeof import("./components/settings/Extension.vue"))["default"]
|
||||||
ShareCreateModal: typeof import('./components/share/CreateModal.vue')['default']
|
SettingsProxy: (typeof import("./components/settings/Proxy.vue"))["default"]
|
||||||
ShareCustomizeModal: typeof import('./components/share/CustomizeModal.vue')['default']
|
Share: (typeof import("./components/share/index.vue"))["default"]
|
||||||
ShareModal: typeof import('./components/share/Modal.vue')['default']
|
ShareCreateModal: (typeof import("./components/share/CreateModal.vue"))["default"]
|
||||||
ShareRequest: typeof import('./components/share/Request.vue')['default']
|
ShareCustomizeModal: (typeof import("./components/share/CustomizeModal.vue"))["default"]
|
||||||
ShareTemplatesButton: typeof import('./components/share/templates/Button.vue')['default']
|
ShareModal: (typeof import("./components/share/Modal.vue"))["default"]
|
||||||
ShareTemplatesEmbeds: typeof import('./components/share/templates/Embeds.vue')['default']
|
ShareRequest: (typeof import("./components/share/Request.vue"))["default"]
|
||||||
ShareTemplatesLink: typeof import('./components/share/templates/Link.vue')['default']
|
ShareTemplatesButton: (typeof import("./components/share/templates/Button.vue"))["default"]
|
||||||
SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default']
|
ShareTemplatesEmbeds: (typeof import("./components/share/templates/Embeds.vue"))["default"]
|
||||||
SmartChangeLanguage: typeof import('./components/smart/ChangeLanguage.vue')['default']
|
ShareTemplatesLink: (typeof import("./components/share/templates/Link.vue"))["default"]
|
||||||
SmartColorModePicker: typeof import('./components/smart/ColorModePicker.vue')['default']
|
SmartAccentModePicker: (typeof import("./components/smart/AccentModePicker.vue"))["default"]
|
||||||
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
|
SmartChangeLanguage: (typeof import("./components/smart/ChangeLanguage.vue"))["default"]
|
||||||
TabPrimary: typeof import('./components/tab/Primary.vue')['default']
|
SmartColorModePicker: (typeof import("./components/smart/ColorModePicker.vue"))["default"]
|
||||||
TabSecondary: typeof import('./components/tab/Secondary.vue')['default']
|
SmartEnvInput: (typeof import("./components/smart/EnvInput.vue"))["default"]
|
||||||
Teams: typeof import('./components/teams/index.vue')['default']
|
TabPrimary: (typeof import("./components/tab/Primary.vue"))["default"]
|
||||||
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
|
TabSecondary: (typeof import("./components/tab/Secondary.vue"))["default"]
|
||||||
TeamsEdit: typeof import('./components/teams/Edit.vue')['default']
|
Teams: (typeof import("./components/teams/index.vue"))["default"]
|
||||||
TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
|
TeamsAdd: (typeof import("./components/teams/Add.vue"))["default"]
|
||||||
TeamsMemberStack: typeof import('./components/teams/MemberStack.vue')['default']
|
TeamsEdit: (typeof import("./components/teams/Edit.vue"))["default"]
|
||||||
TeamsModal: typeof import('./components/teams/Modal.vue')['default']
|
TeamsInvite: (typeof import("./components/teams/Invite.vue"))["default"]
|
||||||
TeamsTeam: typeof import('./components/teams/Team.vue')['default']
|
TeamsMemberStack: (typeof import("./components/teams/MemberStack.vue"))["default"]
|
||||||
Tippy: typeof import('vue-tippy')['Tippy']
|
TeamsModal: (typeof import("./components/teams/Modal.vue"))["default"]
|
||||||
WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default']
|
TeamsTeam: (typeof import("./components/teams/Team.vue"))["default"]
|
||||||
WorkspaceSelector: typeof import('./components/workspace/Selector.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>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
class="fixed translate-y-8 transform rounded border border-dividerDark bg-popover p-2 shadow-lg"
|
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: 1000;`"
|
:style="`top: ${position.top}px; left: ${position.left}px; z-index: 100;`"
|
||||||
>
|
>
|
||||||
<div v-if="contextMenuOptions" class="flex flex-col">
|
<div v-if="contextMenuOptions" class="flex flex-col">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -330,11 +330,11 @@ const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
|||||||
|
|
||||||
const workspace = workspaceService.currentWorkspace
|
const workspace = workspaceService.currentWorkspace
|
||||||
|
|
||||||
const workspaceName = computed(() =>
|
const workspaceName = computed(() => {
|
||||||
workspace.value.type === "personal"
|
return workspace.value.type === "personal"
|
||||||
? t("workspace.personal")
|
? t("workspace.personal")
|
||||||
: workspace.value.teamName
|
: workspace.value.teamName
|
||||||
)
|
})
|
||||||
|
|
||||||
const refetchTeams = () => {
|
const refetchTeams = () => {
|
||||||
teamListAdapter.fetchList()
|
teamListAdapter.fetchList()
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<span class="flex flex-1 items-center space-x-2">
|
||||||
|
<template v-for="(title, index) in collectionTitles" :key="index">
|
||||||
|
<span class="block" :class="{ truncate: index !== 0 }">
|
||||||
|
{{ title }}
|
||||||
|
</span>
|
||||||
|
<icon-lucide-chevron-right class="flex flex-shrink-0" />
|
||||||
|
</template>
|
||||||
|
<span
|
||||||
|
v-if="request"
|
||||||
|
class="flex flex-shrink-0 truncate rounded-md border border-dividerDark px-1 text-tiny font-semibold"
|
||||||
|
:style="{ color: getMethodLabelColor(request.method) }"
|
||||||
|
>
|
||||||
|
{{ request.method.toUpperCase() }}
|
||||||
|
</span>
|
||||||
|
<span v-if="request" class="block">
|
||||||
|
{{ request.name }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getMethodLabelColor } from "~/helpers/rest/labelColoring"
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
collectionTitles: string[]
|
||||||
|
request: {
|
||||||
|
name: string
|
||||||
|
method: string
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
@@ -111,6 +111,7 @@ import { RequestSpotlightSearcherService } from "~/services/spotlight/searchers/
|
|||||||
import { ResponseSpotlightSearcherService } from "~/services/spotlight/searchers/response.searcher"
|
import { ResponseSpotlightSearcherService } from "~/services/spotlight/searchers/response.searcher"
|
||||||
import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.searcher"
|
import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.searcher"
|
||||||
import { TabSpotlightSearcherService } from "~/services/spotlight/searchers/tab.searcher"
|
import { TabSpotlightSearcherService } from "~/services/spotlight/searchers/tab.searcher"
|
||||||
|
import { TeamsSpotlightSearcherService } from "~/services/spotlight/searchers/teamRequest.searcher"
|
||||||
import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/user.searcher"
|
import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/user.searcher"
|
||||||
import {
|
import {
|
||||||
SwitchWorkspaceSpotlightSearcherService,
|
SwitchWorkspaceSpotlightSearcherService,
|
||||||
@@ -144,6 +145,7 @@ useService(SwitchEnvSpotlightSearcherService)
|
|||||||
useService(WorkspaceSpotlightSearcherService)
|
useService(WorkspaceSpotlightSearcherService)
|
||||||
useService(SwitchWorkspaceSpotlightSearcherService)
|
useService(SwitchWorkspaceSpotlightSearcherService)
|
||||||
useService(InterceptorSpotlightSearcherService)
|
useService(InterceptorSpotlightSearcherService)
|
||||||
|
useService(TeamsSpotlightSearcherService)
|
||||||
|
|
||||||
platform.spotlight?.additionalSearchers?.forEach((searcher) =>
|
platform.spotlight?.additionalSearchers?.forEach((searcher) =>
|
||||||
useService(searcher)
|
useService(searcher)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<HoppSmartTabs
|
<HoppSmartTabs
|
||||||
v-model="selectedOptionTab"
|
v-model="activeTab"
|
||||||
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-0 z-10 !-py-4"
|
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-0 z-10 !-py-4"
|
||||||
render-inactive-tabs
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
<HttpHeaders
|
<HttpHeaders
|
||||||
v-model="editableCollection"
|
v-model="editableCollection"
|
||||||
:is-collection-property="true"
|
:is-collection-property="true"
|
||||||
@change-tab="changeOptionTab"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
|
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
|
||||||
@@ -34,6 +33,7 @@
|
|||||||
:is-collection-property="true"
|
:is-collection-property="true"
|
||||||
:is-root-collection="editingProperties?.isRootCollection"
|
:is-root-collection="editingProperties?.isRootCollection"
|
||||||
:inherited-properties="editingProperties?.inheritedProperties"
|
:inherited-properties="editingProperties?.inheritedProperties"
|
||||||
|
:source="source"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
|
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
|
||||||
@@ -64,28 +64,42 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { watch, ref } from "vue"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { HoppCollection } from "@hoppscotch/data"
|
import {
|
||||||
import { RESTOptionTabs } from "../http/RequestOptions.vue"
|
GQLHeader,
|
||||||
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
HoppCollection,
|
||||||
|
HoppGQLAuth,
|
||||||
|
HoppRESTAuth,
|
||||||
|
HoppRESTHeaders,
|
||||||
|
} from "@hoppscotch/data"
|
||||||
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
import { clone } from "lodash-es"
|
import { clone } from "lodash-es"
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { ref, watch } from "vue"
|
||||||
|
|
||||||
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
|
import { PersistenceService } from "~/services/persistence"
|
||||||
|
|
||||||
|
const persistenceService = useService(PersistenceService)
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
type EditingProperties = {
|
export type EditingProperties = {
|
||||||
collection: HoppCollection | TeamCollection | null
|
collection: Partial<HoppCollection> | null
|
||||||
isRootCollection: boolean
|
isRootCollection: boolean
|
||||||
path: string
|
path: string
|
||||||
inheritedProperties: HoppInheritedProperty | undefined
|
inheritedProperties?: HoppInheritedProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HoppCollectionAuth = HoppRESTAuth | HoppGQLAuth
|
||||||
|
type HoppCollectionHeaders = HoppRESTHeaders | GQLHeader[]
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
loadingState: boolean
|
loadingState: boolean
|
||||||
editingProperties: EditingProperties | null
|
editingProperties: EditingProperties | null
|
||||||
|
source: "REST" | "GraphQL"
|
||||||
|
modelValue: string
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
show: false,
|
show: false,
|
||||||
@@ -95,50 +109,68 @@ const props = withDefaults(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "set-collection-properties", newCollection: any): void
|
(
|
||||||
|
e: "set-collection-properties",
|
||||||
|
newCollection: Omit<EditingProperties, "inheritedProperties">
|
||||||
|
): void
|
||||||
(e: "hide-modal"): void
|
(e: "hide-modal"): void
|
||||||
|
(e: "update:modelValue"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const editableCollection = ref({
|
const editableCollection = ref<{
|
||||||
body: {
|
headers: HoppCollectionHeaders
|
||||||
contentType: null,
|
auth: HoppCollectionAuth
|
||||||
body: null,
|
}>({
|
||||||
},
|
|
||||||
headers: [],
|
headers: [],
|
||||||
auth: {
|
auth: {
|
||||||
authType: "inherit",
|
authType: "inherit",
|
||||||
authActive: false,
|
authActive: false,
|
||||||
},
|
},
|
||||||
}) as any
|
})
|
||||||
|
|
||||||
const selectedOptionTab = ref("headers")
|
watch(
|
||||||
|
editableCollection,
|
||||||
|
(updatedEditableCollection) => {
|
||||||
|
if (props.show && props.editingProperties) {
|
||||||
|
const unsavedCollectionProperties: EditingProperties = {
|
||||||
|
collection: updatedEditableCollection,
|
||||||
|
isRootCollection: props.editingProperties?.isRootCollection ?? false,
|
||||||
|
path: props.editingProperties?.path,
|
||||||
|
inheritedProperties: props.editingProperties?.inheritedProperties,
|
||||||
|
}
|
||||||
|
persistenceService.setLocalConfig(
|
||||||
|
"unsaved_collection_properties",
|
||||||
|
JSON.stringify(unsavedCollectionProperties)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const changeOptionTab = (tab: RESTOptionTabs) => {
|
const activeTab = useVModel(props, "modelValue", emit)
|
||||||
selectedOptionTab.value = tab
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
if (show && props.editingProperties?.collection) {
|
if (show && props.editingProperties?.collection) {
|
||||||
editableCollection.value.auth = clone(
|
editableCollection.value.auth = clone(
|
||||||
props.editingProperties.collection.auth
|
props.editingProperties.collection.auth as HoppCollectionAuth
|
||||||
)
|
)
|
||||||
editableCollection.value.headers = clone(
|
editableCollection.value.headers = clone(
|
||||||
props.editingProperties.collection.headers
|
props.editingProperties.collection.headers as HoppCollectionHeaders
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
editableCollection.value = {
|
editableCollection.value = {
|
||||||
body: {
|
|
||||||
contentType: null,
|
|
||||||
body: null,
|
|
||||||
},
|
|
||||||
headers: [],
|
headers: [],
|
||||||
auth: {
|
auth: {
|
||||||
authType: "inherit",
|
authType: "inherit",
|
||||||
authActive: false,
|
authActive: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
persistenceService.removeLocalConfig("unsaved_collection_properties")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -146,7 +178,6 @@ watch(
|
|||||||
const saveEditedCollection = () => {
|
const saveEditedCollection = () => {
|
||||||
if (!props.editingProperties) return
|
if (!props.editingProperties) return
|
||||||
const finalCollection = clone(editableCollection.value)
|
const finalCollection = clone(editableCollection.value)
|
||||||
delete finalCollection.body
|
|
||||||
const collection = {
|
const collection = {
|
||||||
path: props.editingProperties.path,
|
path: props.editingProperties.path,
|
||||||
collection: {
|
collection: {
|
||||||
@@ -155,10 +186,12 @@ const saveEditedCollection = () => {
|
|||||||
},
|
},
|
||||||
isRootCollection: props.editingProperties.isRootCollection,
|
isRootCollection: props.editingProperties.isRootCollection,
|
||||||
}
|
}
|
||||||
emit("set-collection-properties", collection)
|
emit("set-collection-properties", collection as EditingProperties)
|
||||||
|
persistenceService.removeLocalConfig("unsaved_collection_properties")
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
|
persistenceService.removeLocalConfig("unsaved_collection_properties")
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="hasNoTeamAccess"
|
v-if="hasNoTeamAccess || isShowingSearchResults"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
disabled
|
disabled
|
||||||
class="!rounded-none"
|
class="!rounded-none"
|
||||||
@@ -36,8 +36,9 @@
|
|||||||
v-if="!saveRequest"
|
v-if="!saveRequest"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:disabled="
|
:disabled="
|
||||||
collectionsType.type === 'team-collections' &&
|
(collectionsType.type === 'team-collections' &&
|
||||||
collectionsType.selectedTeam === undefined
|
collectionsType.selectedTeam === undefined) ||
|
||||||
|
isShowingSearchResults
|
||||||
"
|
"
|
||||||
:icon="IconImport"
|
:icon="IconImport"
|
||||||
:title="t('modal.import_export')"
|
:title="t('modal.import_export')"
|
||||||
@@ -58,7 +59,7 @@
|
|||||||
:collections-type="collectionsType.type"
|
:collections-type="collectionsType.type"
|
||||||
:is-open="isOpen"
|
:is-open="isOpen"
|
||||||
:export-loading="exportLoading"
|
:export-loading="exportLoading"
|
||||||
:has-no-team-access="hasNoTeamAccess"
|
:has-no-team-access="hasNoTeamAccess || isShowingSearchResults"
|
||||||
:collection-move-loading="collectionMoveLoading"
|
:collection-move-loading="collectionMoveLoading"
|
||||||
:is-last-item="node.data.isLastItem"
|
:is-last-item="node.data.isLastItem"
|
||||||
:is-selected="
|
:is-selected="
|
||||||
@@ -128,6 +129,14 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
handleCollectionClick({
|
||||||
|
collectionID: node.id,
|
||||||
|
isOpen,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<CollectionsCollection
|
<CollectionsCollection
|
||||||
v-if="node.data.type === 'folders'"
|
v-if="node.data.type === 'folders'"
|
||||||
@@ -137,7 +146,7 @@
|
|||||||
:collections-type="collectionsType.type"
|
:collections-type="collectionsType.type"
|
||||||
:is-open="isOpen"
|
:is-open="isOpen"
|
||||||
:export-loading="exportLoading"
|
:export-loading="exportLoading"
|
||||||
:has-no-team-access="hasNoTeamAccess"
|
:has-no-team-access="hasNoTeamAccess || isShowingSearchResults"
|
||||||
:collection-move-loading="collectionMoveLoading"
|
:collection-move-loading="collectionMoveLoading"
|
||||||
:is-last-item="node.data.isLastItem"
|
:is-last-item="node.data.isLastItem"
|
||||||
:is-selected="
|
:is-selected="
|
||||||
@@ -209,6 +218,15 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
handleCollectionClick({
|
||||||
|
// for the folders, we get a path, so we need to get the last part of the path which is the folder id
|
||||||
|
collectionID: node.id.split('/').pop() as string,
|
||||||
|
isOpen,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<CollectionsRequest
|
<CollectionsRequest
|
||||||
v-if="node.data.type === 'requests'"
|
v-if="node.data.type === 'requests'"
|
||||||
@@ -218,7 +236,7 @@
|
|||||||
:collections-type="collectionsType.type"
|
:collections-type="collectionsType.type"
|
||||||
:duplicate-loading="duplicateLoading"
|
:duplicate-loading="duplicateLoading"
|
||||||
:is-active="isActiveRequest(node.data.data.data.id)"
|
:is-active="isActiveRequest(node.data.data.data.id)"
|
||||||
:has-no-team-access="hasNoTeamAccess"
|
:has-no-team-access="hasNoTeamAccess || isShowingSearchResults"
|
||||||
:request-move-loading="requestMoveLoading"
|
:request-move-loading="requestMoveLoading"
|
||||||
:is-last-item="node.data.isLastItem"
|
:is-last-item="node.data.isLastItem"
|
||||||
:is-selected="
|
:is-selected="
|
||||||
@@ -283,7 +301,15 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #emptyNode="{ node }">
|
<template #emptyNode="{ node }">
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="node === null"
|
v-if="filterText.length !== 0 && teamCollectionList.length === 0"
|
||||||
|
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<icon-lucide-search class="svg-icons opacity-75" />
|
||||||
|
</template>
|
||||||
|
</HoppSmartPlaceholder>
|
||||||
|
<HoppSmartPlaceholder
|
||||||
|
v-else-if="node === null"
|
||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
:alt="`${t('empty.collections')}`"
|
:alt="`${t('empty.collections')}`"
|
||||||
:text="t('empty.collections')"
|
:text="t('empty.collections')"
|
||||||
@@ -394,6 +420,11 @@ const props = defineProps({
|
|||||||
default: () => ({ type: "my-collections", selectedTeam: undefined }),
|
default: () => ({ type: "my-collections", selectedTeam: undefined }),
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
filterText: {
|
||||||
|
type: String as PropType<string>,
|
||||||
|
default: "",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
teamCollectionList: {
|
teamCollectionList: {
|
||||||
type: Array as PropType<TeamCollection[]>,
|
type: Array as PropType<TeamCollection[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
@@ -436,6 +467,8 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isShowingSearchResults = computed(() => props.filterText.length > 0)
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(
|
(
|
||||||
event: "add-request",
|
event: "add-request",
|
||||||
@@ -543,6 +576,14 @@ const emit = defineEmits<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
): void
|
): void
|
||||||
|
(
|
||||||
|
event: "collection-click",
|
||||||
|
payload: {
|
||||||
|
// if the collection is open or not in the tree
|
||||||
|
isOpen: boolean
|
||||||
|
collectionID: string
|
||||||
|
}
|
||||||
|
): void
|
||||||
(event: "select", payload: Picked | null): void
|
(event: "select", payload: Picked | null): void
|
||||||
(event: "expand-team-collection", payload: string): void
|
(event: "expand-team-collection", payload: string): void
|
||||||
(event: "display-modal-add"): void
|
(event: "display-modal-add"): void
|
||||||
@@ -555,6 +596,18 @@ const getPath = (path: string) => {
|
|||||||
return pathArray.join("/")
|
return pathArray.join("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCollectionClick = (payload: {
|
||||||
|
collectionID: string
|
||||||
|
isOpen: boolean
|
||||||
|
}) => {
|
||||||
|
const { collectionID, isOpen } = payload
|
||||||
|
|
||||||
|
emit("collection-click", {
|
||||||
|
collectionID,
|
||||||
|
isOpen,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const teamCollectionsList = toRef(props, "teamCollectionList")
|
const teamCollectionsList = toRef(props, "teamCollectionList")
|
||||||
|
|
||||||
const hasNoTeamAccess = computed(
|
const hasNoTeamAccess = computed(
|
||||||
|
|||||||
@@ -146,8 +146,10 @@
|
|||||||
@hide-modal="displayModalImportExport(false)"
|
@hide-modal="displayModalImportExport(false)"
|
||||||
/>
|
/>
|
||||||
<CollectionsProperties
|
<CollectionsProperties
|
||||||
|
v-model="collectionPropertiesModalActiveTab"
|
||||||
:show="showModalEditProperties"
|
:show="showModalEditProperties"
|
||||||
:editing-properties="editingProperties"
|
:editing-properties="editingProperties"
|
||||||
|
source="GraphQL"
|
||||||
@hide-modal="displayModalEditProperties(false)"
|
@hide-modal="displayModalEditProperties(false)"
|
||||||
@set-collection-properties="setCollectionProperties"
|
@set-collection-properties="setCollectionProperties"
|
||||||
/>
|
/>
|
||||||
@@ -155,7 +157,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { nextTick, ref } from "vue"
|
import { nextTick, onMounted, ref } from "vue"
|
||||||
import { clone, cloneDeep } from "lodash-es"
|
import { clone, cloneDeep } from "lodash-es"
|
||||||
import {
|
import {
|
||||||
graphqlCollections$,
|
graphqlCollections$,
|
||||||
@@ -186,6 +188,10 @@ import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
|||||||
import { updateInheritedPropertiesForAffectedRequests } from "~/helpers/collection/collection"
|
import { updateInheritedPropertiesForAffectedRequests } from "~/helpers/collection/collection"
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { getRequestsByPath } from "~/helpers/collection/request"
|
import { getRequestsByPath } from "~/helpers/collection/request"
|
||||||
|
import { PersistenceService } from "~/services/persistence"
|
||||||
|
import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
|
||||||
|
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
||||||
|
import { EditingProperties } from "../Properties.vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -219,7 +225,7 @@ const editingRequest = ref<HoppGQLRequest | null>(null)
|
|||||||
const editingRequestIndex = ref<number | null>(null)
|
const editingRequestIndex = ref<number | null>(null)
|
||||||
|
|
||||||
const editingProperties = ref<{
|
const editingProperties = ref<{
|
||||||
collection: HoppCollection | null
|
collection: Partial<HoppCollection> | null
|
||||||
isRootCollection: boolean
|
isRootCollection: boolean
|
||||||
path: string
|
path: string
|
||||||
inheritedProperties?: HoppInheritedProperty
|
inheritedProperties?: HoppInheritedProperty
|
||||||
@@ -232,6 +238,53 @@ const editingProperties = ref<{
|
|||||||
|
|
||||||
const filterText = ref("")
|
const filterText = ref("")
|
||||||
|
|
||||||
|
const persistenceService = useService(PersistenceService)
|
||||||
|
|
||||||
|
const collectionPropertiesModalActiveTab = ref<GQLOptionTabs>("headers")
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const localOAuthTempConfig =
|
||||||
|
persistenceService.getLocalConfig("oauth_temp_config")
|
||||||
|
|
||||||
|
if (!localOAuthTempConfig) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { context, source, token }: PersistedOAuthConfig =
|
||||||
|
JSON.parse(localOAuthTempConfig)
|
||||||
|
|
||||||
|
if (source === "REST") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context?.type === "collection-properties") {
|
||||||
|
// load the unsaved editing properties
|
||||||
|
const unsavedCollectionPropertiesString = persistenceService.getLocalConfig(
|
||||||
|
"unsaved_collection_properties"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (unsavedCollectionPropertiesString) {
|
||||||
|
const unsavedCollectionProperties: EditingProperties = JSON.parse(
|
||||||
|
unsavedCollectionPropertiesString
|
||||||
|
)
|
||||||
|
|
||||||
|
const auth = unsavedCollectionProperties.collection?.auth
|
||||||
|
|
||||||
|
if (auth?.authType === "oauth-2") {
|
||||||
|
const grantTypeInfo = auth.grantTypeInfo
|
||||||
|
|
||||||
|
grantTypeInfo && (grantTypeInfo.token = token ?? "")
|
||||||
|
}
|
||||||
|
|
||||||
|
editingProperties.value = unsavedCollectionProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
persistenceService.removeLocalConfig("oauth_temp_config")
|
||||||
|
collectionPropertiesModalActiveTab.value = "authorization"
|
||||||
|
showModalEditProperties.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const filteredCollections = computed(() => {
|
const filteredCollections = computed(() => {
|
||||||
const collectionsClone = clone(collections.value)
|
const collectionsClone = clone(collections.value)
|
||||||
|
|
||||||
@@ -557,7 +610,7 @@ const editProperties = ({
|
|||||||
if (collectionIndex === null || collection === null) return
|
if (collectionIndex === null || collection === null) return
|
||||||
|
|
||||||
const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder
|
const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder
|
||||||
let inheritedProperties = {}
|
let inheritedProperties = undefined
|
||||||
|
|
||||||
if (parentIndex) {
|
if (parentIndex) {
|
||||||
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||||
@@ -568,7 +621,7 @@ const editProperties = ({
|
|||||||
inheritedProperties = {
|
inheritedProperties = {
|
||||||
auth,
|
auth,
|
||||||
headers,
|
headers,
|
||||||
} as HoppInheritedProperty
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editingProperties.value = {
|
editingProperties.value = {
|
||||||
@@ -582,11 +635,15 @@ const editProperties = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setCollectionProperties = (newCollection: {
|
const setCollectionProperties = (newCollection: {
|
||||||
collection: HoppCollection
|
collection: Partial<HoppCollection> | null
|
||||||
path: string
|
path: string
|
||||||
isRootCollection: boolean
|
isRootCollection: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const { collection, path, isRootCollection } = newCollection
|
const { collection, path, isRootCollection } = newCollection
|
||||||
|
if (!collection) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (isRootCollection) {
|
if (isRootCollection) {
|
||||||
editGraphqlCollection(parseInt(path), collection)
|
editGraphqlCollection(parseInt(path), collection)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
class="flex w-full bg-transparent px-4 py-2 h-8"
|
class="flex w-full bg-transparent px-4 py-2 h-8"
|
||||||
:placeholder="t('action.search')"
|
:placeholder="t('action.search')"
|
||||||
:disabled="collectionsType.type === 'team-collections'"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<CollectionsMyCollections
|
<CollectionsMyCollections
|
||||||
@@ -58,8 +57,15 @@
|
|||||||
<CollectionsTeamCollections
|
<CollectionsTeamCollections
|
||||||
v-else
|
v-else
|
||||||
:collections-type="collectionsType"
|
:collections-type="collectionsType"
|
||||||
:team-collection-list="teamCollectionList"
|
:team-collection-list="
|
||||||
:team-loading-collections="teamLoadingCollections"
|
filterTexts.length > 0 ? teamsSearchResults : teamCollectionList
|
||||||
|
"
|
||||||
|
:team-loading-collections="
|
||||||
|
filterTexts.length > 0
|
||||||
|
? collectionsBeingLoadedFromSearch
|
||||||
|
: teamLoadingCollections
|
||||||
|
"
|
||||||
|
:filter-text="filterTexts"
|
||||||
:export-loading="exportLoading"
|
:export-loading="exportLoading"
|
||||||
:duplicate-loading="duplicateLoading"
|
:duplicate-loading="duplicateLoading"
|
||||||
:save-request="saveRequest"
|
:save-request="saveRequest"
|
||||||
@@ -87,6 +93,7 @@
|
|||||||
@expand-team-collection="expandTeamCollection"
|
@expand-team-collection="expandTeamCollection"
|
||||||
@display-modal-add="displayModalAdd(true)"
|
@display-modal-add="displayModalAdd(true)"
|
||||||
@display-modal-import-export="displayModalImportExport(true)"
|
@display-modal-import-export="displayModalImportExport(true)"
|
||||||
|
@collection-click="handleCollectionClick"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="py-15 hidden flex-1 flex-col items-center justify-center bg-primaryDark px-4 text-secondaryLight"
|
class="py-15 hidden flex-1 flex-col items-center justify-center bg-primaryDark px-4 text-secondaryLight"
|
||||||
@@ -154,8 +161,10 @@
|
|||||||
@hide-modal="displayTeamModalAdd(false)"
|
@hide-modal="displayTeamModalAdd(false)"
|
||||||
/>
|
/>
|
||||||
<CollectionsProperties
|
<CollectionsProperties
|
||||||
|
v-model="collectionPropertiesModalActiveTab"
|
||||||
:show="showModalEditProperties"
|
:show="showModalEditProperties"
|
||||||
:editing-properties="editingProperties"
|
:editing-properties="editingProperties"
|
||||||
|
source="REST"
|
||||||
@hide-modal="displayModalEditProperties(false)"
|
@hide-modal="displayModalEditProperties(false)"
|
||||||
@set-collection-properties="setCollectionProperties"
|
@set-collection-properties="setCollectionProperties"
|
||||||
/>
|
/>
|
||||||
@@ -163,7 +172,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, PropType, ref, watch } from "vue"
|
import { computed, nextTick, onMounted, PropType, ref, watch } from "vue"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked"
|
import { Picked } from "~/helpers/types/HoppPicked"
|
||||||
@@ -199,7 +208,7 @@ import {
|
|||||||
HoppRESTRequest,
|
HoppRESTRequest,
|
||||||
makeCollection,
|
makeCollection,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { cloneDeep, isEqual } from "lodash-es"
|
import { cloneDeep, debounce, isEqual } from "lodash-es"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import {
|
import {
|
||||||
createNewRootCollection,
|
createNewRootCollection,
|
||||||
@@ -240,6 +249,11 @@ import { WorkspaceService } from "~/services/workspace.service"
|
|||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
|
import { TeamSearchService } from "~/helpers/teams/TeamsSearch.service"
|
||||||
|
import { PersistenceService } from "~/services/persistence"
|
||||||
|
import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
|
||||||
|
import { RESTOptionTabs } from "../http/RequestOptions.vue"
|
||||||
|
import { EditingProperties } from "./Properties.vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -291,12 +305,7 @@ const editingRequestName = ref("")
|
|||||||
const editingRequestIndex = ref<number | null>(null)
|
const editingRequestIndex = ref<number | null>(null)
|
||||||
const editingRequestID = ref<string | null>(null)
|
const editingRequestID = ref<string | null>(null)
|
||||||
|
|
||||||
const editingProperties = ref<{
|
const editingProperties = ref<EditingProperties>({
|
||||||
collection: Omit<HoppCollection, "v"> | TeamCollection | null
|
|
||||||
isRootCollection: boolean
|
|
||||||
path: string
|
|
||||||
inheritedProperties?: HoppInheritedProperty
|
|
||||||
}>({
|
|
||||||
collection: null,
|
collection: null,
|
||||||
isRootCollection: false,
|
isRootCollection: false,
|
||||||
path: "",
|
path: "",
|
||||||
@@ -336,6 +345,96 @@ const teamLoadingCollections = useReadonlyStream(
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const {
|
||||||
|
cascadeParentCollectionForHeaderAuthForSearchResults,
|
||||||
|
searchTeams,
|
||||||
|
teamsSearchResults,
|
||||||
|
teamsSearchResultsLoading,
|
||||||
|
expandCollection,
|
||||||
|
expandingCollections,
|
||||||
|
} = useService(TeamSearchService)
|
||||||
|
|
||||||
|
watch(teamsSearchResults, (newSearchResults) => {
|
||||||
|
if (newSearchResults.length === 1 && filterTexts.value.length > 0) {
|
||||||
|
expandCollection(newSearchResults[0].id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const debouncedSearch = debounce(searchTeams, 400)
|
||||||
|
|
||||||
|
const collectionsBeingLoadedFromSearch = computed(() => {
|
||||||
|
const collections = []
|
||||||
|
|
||||||
|
if (teamsSearchResultsLoading.value) {
|
||||||
|
collections.push("root")
|
||||||
|
}
|
||||||
|
|
||||||
|
collections.push(...expandingCollections.value)
|
||||||
|
|
||||||
|
return collections
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
filterTexts,
|
||||||
|
(newFilterText) => {
|
||||||
|
if (collectionsType.value.type === "team-collections") {
|
||||||
|
const selectedTeamID = collectionsType.value.selectedTeam?.id
|
||||||
|
|
||||||
|
selectedTeamID &&
|
||||||
|
debouncedSearch(newFilterText, selectedTeamID)?.catch(() => {})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const persistenceService = useService(PersistenceService)
|
||||||
|
|
||||||
|
const collectionPropertiesModalActiveTab = ref<RESTOptionTabs>("headers")
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const localOAuthTempConfig =
|
||||||
|
persistenceService.getLocalConfig("oauth_temp_config")
|
||||||
|
|
||||||
|
if (!localOAuthTempConfig) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { context, source, token }: PersistedOAuthConfig =
|
||||||
|
JSON.parse(localOAuthTempConfig)
|
||||||
|
|
||||||
|
if (source === "GraphQL") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context?.type === "collection-properties") {
|
||||||
|
// load the unsaved editing properties
|
||||||
|
const unsavedCollectionPropertiesString = persistenceService.getLocalConfig(
|
||||||
|
"unsaved_collection_properties"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (unsavedCollectionPropertiesString) {
|
||||||
|
const unsavedCollectionProperties: EditingProperties = JSON.parse(
|
||||||
|
unsavedCollectionPropertiesString
|
||||||
|
)
|
||||||
|
|
||||||
|
const auth = unsavedCollectionProperties.collection?.auth
|
||||||
|
|
||||||
|
if (auth?.authType === "oauth-2") {
|
||||||
|
const grantTypeInfo = auth.grantTypeInfo
|
||||||
|
|
||||||
|
grantTypeInfo && (grantTypeInfo.token = token ?? "")
|
||||||
|
}
|
||||||
|
|
||||||
|
editingProperties.value = unsavedCollectionProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
persistenceService.removeLocalConfig("oauth_temp_config")
|
||||||
|
collectionPropertiesModalActiveTab.value = "authorization"
|
||||||
|
showModalEditProperties.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => myTeams.value,
|
() => myTeams.value,
|
||||||
(newTeams) => {
|
(newTeams) => {
|
||||||
@@ -364,7 +463,28 @@ const switchToMyCollections = () => {
|
|||||||
teamCollectionAdapter.changeTeamID(null)
|
teamCollectionAdapter.changeTeamID(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* right now, for search results, we rely on collection click + isOpen to expand the collection
|
||||||
|
*/
|
||||||
|
const handleCollectionClick = (payload: {
|
||||||
|
collectionID: string
|
||||||
|
isOpen: boolean
|
||||||
|
}) => {
|
||||||
|
if (
|
||||||
|
filterTexts.value.length > 0 &&
|
||||||
|
teamsSearchResults.value.length &&
|
||||||
|
payload.isOpen
|
||||||
|
) {
|
||||||
|
expandCollection(payload.collectionID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const expandTeamCollection = (collectionID: string) => {
|
const expandTeamCollection = (collectionID: string) => {
|
||||||
|
if (filterTexts.value.length > 0 && teamsSearchResults.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
teamCollectionAdapter.expandCollection(collectionID)
|
teamCollectionAdapter.expandCollection(collectionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,7 +859,7 @@ const onAddRequest = (requestName: string) => {
|
|||||||
saveContext: {
|
saveContext: {
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID: createRequestInCollection.id,
|
requestID: createRequestInCollection.id,
|
||||||
collectionID: createRequestInCollection.collection.id,
|
collectionID: path,
|
||||||
teamID: createRequestInCollection.collection.team.id,
|
teamID: createRequestInCollection.collection.team.id,
|
||||||
},
|
},
|
||||||
inheritedProperties: {
|
inheritedProperties: {
|
||||||
@@ -1330,13 +1450,25 @@ const selectRequest = (selectedRequest: {
|
|||||||
let possibleTab = null
|
let possibleTab = null
|
||||||
|
|
||||||
if (collectionsType.value.type === "team-collections") {
|
if (collectionsType.value.type === "team-collections") {
|
||||||
const { auth, headers } =
|
let inheritedProperties: HoppInheritedProperty | undefined = undefined
|
||||||
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(folderPath)
|
|
||||||
|
|
||||||
possibleTab = tabs.getTabRefWithSaveContext({
|
if (filterTexts.value.length > 0) {
|
||||||
|
const collectionID = folderPath.split("/").at(-1)
|
||||||
|
|
||||||
|
if (!collectionID) return
|
||||||
|
|
||||||
|
inheritedProperties =
|
||||||
|
cascadeParentCollectionForHeaderAuthForSearchResults(collectionID)
|
||||||
|
} else {
|
||||||
|
inheritedProperties =
|
||||||
|
teamCollectionAdapter.cascadeParentCollectionForHeaderAuth(folderPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const possibleTab = tabs.getTabRefWithSaveContext({
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
requestID: requestIndex,
|
requestID: requestIndex,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (possibleTab) {
|
if (possibleTab) {
|
||||||
tabs.setActiveTab(possibleTab.value.id)
|
tabs.setActiveTab(possibleTab.value.id)
|
||||||
} else {
|
} else {
|
||||||
@@ -1348,10 +1480,7 @@ const selectRequest = (selectedRequest: {
|
|||||||
requestID: requestIndex,
|
requestID: requestIndex,
|
||||||
collectionID: folderPath,
|
collectionID: folderPath,
|
||||||
},
|
},
|
||||||
inheritedProperties: {
|
inheritedProperties: inheritedProperties,
|
||||||
auth,
|
|
||||||
headers,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -2021,7 +2150,7 @@ const editProperties = (payload: {
|
|||||||
{
|
{
|
||||||
parentID: "",
|
parentID: "",
|
||||||
parentName: "",
|
parentName: "",
|
||||||
inheritedHeaders: [],
|
inheritedHeader: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as HoppInheritedProperty
|
} as HoppInheritedProperty
|
||||||
@@ -2039,7 +2168,7 @@ const editProperties = (payload: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editingProperties.value = {
|
editingProperties.value = {
|
||||||
collection,
|
collection: collection as Partial<HoppCollection>,
|
||||||
isRootCollection: isAlreadyInRoot(collectionIndex),
|
isRootCollection: isAlreadyInRoot(collectionIndex),
|
||||||
path: collectionIndex,
|
path: collectionIndex,
|
||||||
inheritedProperties,
|
inheritedProperties,
|
||||||
@@ -2083,7 +2212,7 @@ const editProperties = (payload: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editingProperties.value = {
|
editingProperties.value = {
|
||||||
collection: coll,
|
collection: coll as unknown as Partial<HoppCollection>,
|
||||||
isRootCollection: isAlreadyInRoot(collectionIndex),
|
isRootCollection: isAlreadyInRoot(collectionIndex),
|
||||||
path: collectionIndex,
|
path: collectionIndex,
|
||||||
inheritedProperties,
|
inheritedProperties,
|
||||||
@@ -2094,11 +2223,12 @@ const editProperties = (payload: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setCollectionProperties = (newCollection: {
|
const setCollectionProperties = (newCollection: {
|
||||||
collection: HoppCollection
|
collection: Partial<HoppCollection> | null
|
||||||
path: string
|
|
||||||
isRootCollection: boolean
|
isRootCollection: boolean
|
||||||
|
path: string
|
||||||
}) => {
|
}) => {
|
||||||
const { collection, path, isRootCollection } = newCollection
|
const { collection, path, isRootCollection } = newCollection
|
||||||
|
if (!collection) return
|
||||||
|
|
||||||
if (collectionsType.value.type === "my-collections") {
|
if (collectionsType.value.type === "my-collections") {
|
||||||
if (isRootCollection) {
|
if (isRootCollection) {
|
||||||
@@ -2148,8 +2278,7 @@ const setCollectionProperties = (newCollection: {
|
|||||||
auth,
|
auth,
|
||||||
headers,
|
headers,
|
||||||
},
|
},
|
||||||
"rest",
|
"rest"
|
||||||
"team"
|
|
||||||
)
|
)
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ import { environmentsStore } from "~/newstore/environments"
|
|||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
import { SecretEnvironmentService } from "~/services/secret-environment.service"
|
||||||
import { uniqueId } from "lodash-es"
|
import { uniqueID } from "~/helpers/utils/uniqueID"
|
||||||
|
|
||||||
type EnvironmentVariable = {
|
type EnvironmentVariable = {
|
||||||
id: number
|
id: number
|
||||||
@@ -277,7 +277,7 @@ const workingEnv = computed(() => {
|
|||||||
} as Environment
|
} as Environment
|
||||||
} else if (props.action === "new") {
|
} else if (props.action === "new") {
|
||||||
return {
|
return {
|
||||||
id: uniqueId(),
|
id: uniqueID(),
|
||||||
name: "",
|
name: "",
|
||||||
variables: props.envVars(),
|
variables: props.envVars(),
|
||||||
}
|
}
|
||||||
@@ -331,7 +331,7 @@ watch(
|
|||||||
: "variables"
|
: "variables"
|
||||||
|
|
||||||
if (props.editingEnvironmentIndex !== "Global") {
|
if (props.editingEnvironmentIndex !== "Global") {
|
||||||
editingID.value = workingEnv.value?.id ?? uniqueId()
|
editingID.value = workingEnv.value?.id || uniqueID()
|
||||||
}
|
}
|
||||||
vars.value = pipe(
|
vars.value = pipe(
|
||||||
workingEnv.value?.variables ?? [],
|
workingEnv.value?.variables ?? [],
|
||||||
@@ -416,14 +416,12 @@ const saveEnvironment = () => {
|
|||||||
|
|
||||||
const variables = pipe(
|
const variables = pipe(
|
||||||
filteredVariables,
|
filteredVariables,
|
||||||
A.map((e) =>
|
A.map((e) => (e.secret ? { key: e.key, secret: e.secret } : e))
|
||||||
e.secret ? { key: e.key, secret: e.secret, value: undefined } : e
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const environmentUpdated: Environment = {
|
const environmentUpdated: Environment = {
|
||||||
v: 1,
|
v: 1,
|
||||||
id: uniqueId(),
|
id: uniqueID(),
|
||||||
name: editingName.value,
|
name: editingName.value,
|
||||||
variables,
|
variables,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ const saveEnvironment = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterdVariables = pipe(
|
const filteredVariables = pipe(
|
||||||
vars.value,
|
vars.value,
|
||||||
A.filterMap(
|
A.filterMap(
|
||||||
flow(
|
flow(
|
||||||
@@ -371,17 +371,15 @@ const saveEnvironment = async () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const secretVariables = pipe(
|
const secretVariables = pipe(
|
||||||
filterdVariables,
|
filteredVariables,
|
||||||
A.filterMapWithIndex((i, e) =>
|
A.filterMapWithIndex((i, e) =>
|
||||||
e.secret ? O.some({ key: e.key, value: e.value, varIndex: i }) : O.none
|
e.secret ? O.some({ key: e.key, value: e.value, varIndex: i }) : O.none
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const variables = pipe(
|
const variables = pipe(
|
||||||
filterdVariables,
|
filteredVariables,
|
||||||
A.map((e) =>
|
A.map((e) => (e.secret ? { key: e.key, secret: e.secret } : e))
|
||||||
e.secret ? { key: e.key, secret: e.secret, value: undefined } : e
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const environmentUpdated: Environment = {
|
const environmentUpdated: Environment = {
|
||||||
|
|||||||
@@ -23,10 +23,10 @@
|
|||||||
@click="provider.action"
|
@click="provider.action"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<hr v-if="additonalLoginItems.length > 0" />
|
<hr v-if="additionalLoginItems.length > 0" />
|
||||||
|
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
v-for="loginItem in additonalLoginItems"
|
v-for="loginItem in additionalLoginItems"
|
||||||
:key="loginItem.id"
|
:key="loginItem.id"
|
||||||
:icon="loginItem.icon"
|
:icon="loginItem.icon"
|
||||||
:label="loginItem.text(t)"
|
:label="loginItem.text(t)"
|
||||||
@@ -170,7 +170,7 @@ type AuthProviderItem = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let allowedAuthProviders: AuthProviderItem[] = []
|
let allowedAuthProviders: AuthProviderItem[] = []
|
||||||
let additonalLoginItems: LoginItemDef[] = []
|
const additionalLoginItems: LoginItemDef[] = []
|
||||||
|
|
||||||
const doAdditionalLoginItemClickAction = async (item: LoginItemDef) => {
|
const doAdditionalLoginItemClickAction = async (item: LoginItemDef) => {
|
||||||
await item.onClick()
|
await item.onClick()
|
||||||
@@ -199,10 +199,33 @@ onMounted(async () => {
|
|||||||
allowedAuthProviders = enabledAuthProviders
|
allowedAuthProviders = enabledAuthProviders
|
||||||
|
|
||||||
// setup the additional login items
|
// setup the additional login items
|
||||||
additonalLoginItems =
|
platform.auth.additionalLoginItems?.forEach((item) => {
|
||||||
platform.auth.additionalLoginItems?.filter((item) =>
|
if (res.right.includes(item.id)) {
|
||||||
res.right.includes(item.id)
|
additionalLoginItems.push(item)
|
||||||
) ?? []
|
}
|
||||||
|
|
||||||
|
// since the BE send the OIDC auth providers as OIDC:providerName,
|
||||||
|
// we need to split the string and use the providerName as the text
|
||||||
|
if (item.id === "OIDC") {
|
||||||
|
res.right
|
||||||
|
.filter((provider) => provider.startsWith("OIDC"))
|
||||||
|
.forEach((provider) => {
|
||||||
|
const OIDCName = provider.split(":")[1]
|
||||||
|
const loginItemText = OIDCName
|
||||||
|
? () =>
|
||||||
|
t("auth.continue_with_auth_provider", {
|
||||||
|
provider: OIDCName,
|
||||||
|
})
|
||||||
|
: item.text
|
||||||
|
|
||||||
|
const OIDCLoginItem = {
|
||||||
|
...item,
|
||||||
|
text: loginItemText,
|
||||||
|
}
|
||||||
|
additionalLoginItems.push(OIDCLoginItem)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
isLoadingAllowedAuthProviders.value = false
|
isLoadingAllowedAuthProviders.value = false
|
||||||
})
|
})
|
||||||
@@ -311,6 +334,14 @@ const authProvidersAvailable: AuthProviderItem[] = [
|
|||||||
action: signInWithGithub,
|
action: signInWithGithub,
|
||||||
isLoading: signingInWithGitHub,
|
isLoading: signingInWithGitHub,
|
||||||
},
|
},
|
||||||
|
// the authprovider either send github or github:enterprise and both are handled by the same route
|
||||||
|
{
|
||||||
|
id: "GITHUB:ENTERPRISE",
|
||||||
|
icon: IconGithub,
|
||||||
|
label: t("auth.continue_with_github_enterprise"),
|
||||||
|
action: signInWithGithub,
|
||||||
|
isLoading: signingInWithGitHub,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "GOOGLE",
|
id: "GOOGLE",
|
||||||
icon: IconGoogle,
|
icon: IconGoogle,
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
:active="authName === 'OAuth 2.0'"
|
:active="authName === 'OAuth 2.0'"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
auth.authType = 'oauth-2'
|
selectOAuth2AuthType()
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -189,12 +189,12 @@
|
|||||||
<div v-if="auth.authType === 'oauth-2'">
|
<div v-if="auth.authType === 'oauth-2'">
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<SmartEnvInput
|
<SmartEnvInput
|
||||||
v-model="auth.token"
|
v-model="auth.grantTypeInfo.token"
|
||||||
:environment-highlights="false"
|
:environment-highlights="false"
|
||||||
placeholder="Token"
|
placeholder="Token"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<HttpOAuth2Authorization v-model="auth" />
|
<HttpOAuth2Authorization v-model="auth" source="GraphQL" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="auth.authType === 'api-key'">
|
<div v-if="auth.authType === 'api-key'">
|
||||||
<HttpAuthorizationApiKey v-model="auth" />
|
<HttpAuthorizationApiKey v-model="auth" />
|
||||||
@@ -220,19 +220,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { pluckRef } from "@composables/ref"
|
||||||
|
import { useColorMode } from "@composables/theming"
|
||||||
|
import { HoppGQLAuth, HoppGQLAuthOAuth2 } from "@hoppscotch/data"
|
||||||
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { computed, onMounted, ref } from "vue"
|
||||||
|
|
||||||
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
|
|
||||||
|
import IconCircle from "~icons/lucide/circle"
|
||||||
|
import IconCircleDot from "~icons/lucide/circle-dot"
|
||||||
|
import IconExternalLink from "~icons/lucide/external-link"
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
import IconExternalLink from "~icons/lucide/external-link"
|
|
||||||
import IconCircleDot from "~icons/lucide/circle-dot"
|
import { getDefaultAuthCodeOauthFlowParams } from "~/services/oauth/flows/authCode"
|
||||||
import IconCircle from "~icons/lucide/circle"
|
|
||||||
import { computed, ref } from "vue"
|
|
||||||
import { HoppGQLAuth } from "@hoppscotch/data"
|
|
||||||
import { pluckRef } from "@composables/ref"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
import { useColorMode } from "@composables/theming"
|
|
||||||
import { useVModel } from "@vueuse/core"
|
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
|
||||||
import { onMounted } from "vue"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -280,6 +283,30 @@ const getAuthName = (type: HoppGQLAuth["authType"] | undefined) => {
|
|||||||
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
|
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectOAuth2AuthType = () => {
|
||||||
|
const defaultGrantTypeInfo: HoppGQLAuthOAuth2["grantTypeInfo"] = {
|
||||||
|
...getDefaultAuthCodeOauthFlowParams(),
|
||||||
|
grantType: "AUTHORIZATION_CODE",
|
||||||
|
token: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error - the existing grantTypeInfo might be in the auth object, typescript doesnt know that
|
||||||
|
const existingGrantTypeInfo = auth.value.grantTypeInfo as
|
||||||
|
| HoppGQLAuthOAuth2["grantTypeInfo"]
|
||||||
|
| undefined
|
||||||
|
|
||||||
|
const grantTypeInfo = existingGrantTypeInfo
|
||||||
|
? existingGrantTypeInfo
|
||||||
|
: defaultGrantTypeInfo
|
||||||
|
|
||||||
|
auth.value = {
|
||||||
|
...auth.value,
|
||||||
|
authType: "oauth-2",
|
||||||
|
addTo: "HEADERS",
|
||||||
|
grantTypeInfo: grantTypeInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const authActive = pluckRef(auth, "authActive")
|
const authActive = pluckRef(auth, "authActive")
|
||||||
|
|
||||||
const clearContent = () => {
|
const clearContent = () => {
|
||||||
|
|||||||
@@ -579,12 +579,18 @@ const getComputedAuthHeaders = (
|
|||||||
})
|
})
|
||||||
} else if (
|
} else if (
|
||||||
request.auth.authType === "bearer" ||
|
request.auth.authType === "bearer" ||
|
||||||
request.auth.authType === "oauth-2"
|
(request.auth.authType === "oauth-2" && request.auth.addTo === "HEADERS")
|
||||||
) {
|
) {
|
||||||
|
const requestAuth = request.auth
|
||||||
|
|
||||||
|
const isOAuth2 = requestAuth.authType === "oauth-2"
|
||||||
|
|
||||||
|
const token = isOAuth2 ? requestAuth.grantTypeInfo.token : requestAuth.token
|
||||||
|
|
||||||
headers.push({
|
headers.push({
|
||||||
active: true,
|
active: true,
|
||||||
key: "Authorization",
|
key: "Authorization",
|
||||||
value: `Bearer ${request.auth.token}`,
|
value: `Bearer ${token}`,
|
||||||
})
|
})
|
||||||
} else if (request.auth.authType === "api-key") {
|
} else if (request.auth.authType === "api-key") {
|
||||||
const { key, addTo } = request.auth
|
const { key, addTo } = request.auth
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
:active="authName === 'OAuth 2.0'"
|
:active="authName === 'OAuth 2.0'"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
auth.authType = 'oauth-2'
|
selectOAuth2AuthType()
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -177,15 +177,24 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="auth.authType === 'oauth-2'">
|
<div v-if="auth.authType === 'oauth-2'" class="w-full">
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
|
<!-- Ensure a new object is assigned here to avoid reactivity issues -->
|
||||||
<SmartEnvInput
|
<SmartEnvInput
|
||||||
v-model="auth.token"
|
:model-value="auth.grantTypeInfo.token"
|
||||||
placeholder="Token"
|
placeholder="Token"
|
||||||
:envs="envs"
|
:envs="envs"
|
||||||
|
@update:model-value="
|
||||||
|
auth.grantTypeInfo = { ...auth.grantTypeInfo, token: $event }
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<HttpOAuth2Authorization v-model="auth" :envs="envs" />
|
<HttpOAuth2Authorization
|
||||||
|
v-model="auth"
|
||||||
|
:is-collection-property="isCollectionProperty"
|
||||||
|
:envs="envs"
|
||||||
|
:source="source"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="auth.authType === 'api-key'">
|
<div v-if="auth.authType === 'api-key'">
|
||||||
<HttpAuthorizationApiKey v-model="auth" :envs="envs" />
|
<HttpAuthorizationApiKey v-model="auth" :envs="envs" />
|
||||||
@@ -217,7 +226,7 @@ import IconExternalLink from "~icons/lucide/external-link"
|
|||||||
import IconCircleDot from "~icons/lucide/circle-dot"
|
import IconCircleDot from "~icons/lucide/circle-dot"
|
||||||
import IconCircle from "~icons/lucide/circle"
|
import IconCircle from "~icons/lucide/circle"
|
||||||
import { computed, ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
import { HoppRESTAuth } from "@hoppscotch/data"
|
import { HoppRESTAuth, HoppRESTAuthOAuth2 } from "@hoppscotch/data"
|
||||||
import { pluckRef } from "@composables/ref"
|
import { pluckRef } from "@composables/ref"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
@@ -226,17 +235,27 @@ import { onMounted } from "vue"
|
|||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
import { AggregateEnvironment } from "~/newstore/environments"
|
import { AggregateEnvironment } from "~/newstore/environments"
|
||||||
|
|
||||||
|
import { getDefaultAuthCodeOauthFlowParams } from "~/services/oauth/flows/authCode"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
modelValue: HoppRESTAuth
|
defineProps<{
|
||||||
isCollectionProperty?: boolean
|
modelValue: HoppRESTAuth
|
||||||
isRootCollection?: boolean
|
isCollectionProperty?: boolean
|
||||||
inheritedProperties?: HoppInheritedProperty
|
isRootCollection?: boolean
|
||||||
envs?: AggregateEnvironment[]
|
inheritedProperties?: HoppInheritedProperty
|
||||||
}>()
|
envs?: AggregateEnvironment[]
|
||||||
|
source?: "REST" | "GraphQL"
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
source: "REST",
|
||||||
|
envs: undefined,
|
||||||
|
inheritedProperties: undefined,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:modelValue", value: HoppRESTAuth): void
|
(e: "update:modelValue", value: HoppRESTAuth): void
|
||||||
@@ -272,6 +291,30 @@ const getAuthName = (type: HoppRESTAuth["authType"] | undefined) => {
|
|||||||
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
|
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectOAuth2AuthType = () => {
|
||||||
|
const defaultGrantTypeInfo: HoppRESTAuthOAuth2["grantTypeInfo"] = {
|
||||||
|
...getDefaultAuthCodeOauthFlowParams(),
|
||||||
|
grantType: "AUTHORIZATION_CODE",
|
||||||
|
token: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error - the existing grantTypeInfo might be in the auth object, typescript doesnt know that
|
||||||
|
const existingGrantTypeInfo = auth.value.grantTypeInfo as
|
||||||
|
| HoppRESTAuthOAuth2["grantTypeInfo"]
|
||||||
|
| undefined
|
||||||
|
|
||||||
|
const grantTypeInfo = existingGrantTypeInfo
|
||||||
|
? existingGrantTypeInfo
|
||||||
|
: defaultGrantTypeInfo
|
||||||
|
|
||||||
|
auth.value = {
|
||||||
|
...auth.value,
|
||||||
|
authType: "oauth-2",
|
||||||
|
addTo: "HEADERS",
|
||||||
|
grantTypeInfo: grantTypeInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const authActive = pluckRef(auth, "authActive")
|
const authActive = pluckRef(auth, "authActive")
|
||||||
|
|
||||||
const clearContent = () => {
|
const clearContent = () => {
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ import { useColorMode } from "@composables/theming"
|
|||||||
import { computed, reactive, ref, watch } from "vue"
|
import { computed, reactive, ref, watch } from "vue"
|
||||||
import { isEqual, cloneDeep } from "lodash-es"
|
import { isEqual, cloneDeep } from "lodash-es"
|
||||||
import {
|
import {
|
||||||
|
HoppRESTAuth,
|
||||||
HoppRESTHeader,
|
HoppRESTHeader,
|
||||||
HoppRESTRequest,
|
HoppRESTRequest,
|
||||||
parseRawKeyValueEntriesE,
|
parseRawKeyValueEntriesE,
|
||||||
@@ -364,7 +365,12 @@ const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
|||||||
|
|
||||||
// v-model integration with props and emit
|
// v-model integration with props and emit
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: HoppRESTRequest
|
modelValue:
|
||||||
|
| HoppRESTRequest
|
||||||
|
| {
|
||||||
|
headers: HoppRESTHeader[]
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
}
|
||||||
isCollectionProperty?: boolean
|
isCollectionProperty?: boolean
|
||||||
inheritedProperties?: HoppInheritedProperty
|
inheritedProperties?: HoppInheritedProperty
|
||||||
envs?: AggregateEnvironment[]
|
envs?: AggregateEnvironment[]
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ import { RESTTabService } from "~/services/tab/rest"
|
|||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { useNestedSetting } from "~/composables/settings"
|
import { useNestedSetting } from "~/composables/settings"
|
||||||
import { toggleNestedSetting } from "~/newstore/settings"
|
import { toggleNestedSetting } from "~/newstore/settings"
|
||||||
|
import { EditorView } from "@codemirror/view"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -124,6 +125,7 @@ useCodemirror(
|
|||||||
linter: null,
|
linter: null,
|
||||||
completer: null,
|
completer: null,
|
||||||
environmentHighlights: false,
|
environmentHighlights: false,
|
||||||
|
onInit: (view: EditorView) => view.focus(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -273,6 +273,10 @@ const loading = computed(
|
|||||||
)
|
)
|
||||||
|
|
||||||
onLoggedIn(() => {
|
onLoggedIn(() => {
|
||||||
|
if (adapter.isInitialized()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// wait for a bit to let the auth token to be set
|
// wait for a bit to let the auth token to be set
|
||||||
// because in some race conditions, the token is not set this fixes that
|
// because in some race conditions, the token is not set this fixes that
|
||||||
|
|||||||
@@ -417,7 +417,9 @@ function handleTextSelection() {
|
|||||||
const { from, to } = selection
|
const { from, to } = selection
|
||||||
if (from === to) return
|
if (from === to) return
|
||||||
const text = view.value?.state.doc.sliceString(from, to)
|
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) {
|
if (text) {
|
||||||
invokeAction("contextmenu.open", {
|
invokeAction("contextmenu.open", {
|
||||||
position: {
|
position: {
|
||||||
@@ -439,16 +441,17 @@ function handleTextSelection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const initView = (el: any) => {
|
// Debounce to prevent double click from selecting the word
|
||||||
// Debounce to prevent double click from selecting the word
|
const debouncedTextSelection = (time: number) =>
|
||||||
const debounceFn = useDebounceFn(() => {
|
useDebounceFn(() => {
|
||||||
handleTextSelection()
|
handleTextSelection()
|
||||||
}, 140)
|
}, time)
|
||||||
|
|
||||||
|
const initView = (el: any) => {
|
||||||
// Only add event listeners if context menu is enabled in the component
|
// Only add event listeners if context menu is enabled in the component
|
||||||
if (props.contextMenuEnabled) {
|
if (props.contextMenuEnabled) {
|
||||||
el.addEventListener("mouseup", debounceFn)
|
el.addEventListener("mouseup", debouncedTextSelection(140))
|
||||||
el.addEventListener("keyup", debounceFn)
|
el.addEventListener("keyup", debouncedTextSelection(140))
|
||||||
}
|
}
|
||||||
|
|
||||||
const extensions: Extension = getExtensions(props.readonly || isSecret.value)
|
const extensions: Extension = getExtensions(props.readonly || isSecret.value)
|
||||||
@@ -498,7 +501,8 @@ const getExtensions = (readonly: boolean): Extension => {
|
|||||||
},
|
},
|
||||||
scroll(event) {
|
scroll(event) {
|
||||||
if (event.target && props.contextMenuEnabled) {
|
if (event.target && props.contextMenuEnabled) {
|
||||||
handleTextSelection()
|
// Debounce to make the performance better
|
||||||
|
debouncedTextSelection(30)()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div class="p-4">
|
<div class="p-4 truncate">
|
||||||
<label
|
<label
|
||||||
class="font-semibold text-secondaryDark"
|
class="font-semibold text-secondaryDark"
|
||||||
:class="{ 'cursor-pointer': compact && team.myRole === 'OWNER' }"
|
:class="{ 'cursor-pointer': compact && team.myRole === 'OWNER' }"
|
||||||
@@ -131,6 +131,7 @@
|
|||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
:show="confirmRemove"
|
:show="confirmRemove"
|
||||||
:title="t('confirm.remove_team')"
|
:title="t('confirm.remove_team')"
|
||||||
|
:loading-state="loading"
|
||||||
@hide-modal="confirmRemove = false"
|
@hide-modal="confirmRemove = false"
|
||||||
@resolve="deleteTeam()"
|
@resolve="deleteTeam()"
|
||||||
/>
|
/>
|
||||||
@@ -161,6 +162,8 @@ import IconMoreVertical from "~icons/lucide/more-vertical"
|
|||||||
import IconUserX from "~icons/lucide/user-x"
|
import IconUserX from "~icons/lucide/user-x"
|
||||||
import IconUserPlus from "~icons/lucide/user-plus"
|
import IconUserPlus from "~icons/lucide/user-plus"
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -173,6 +176,7 @@ const props = defineProps<{
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "edit-team"): void
|
(e: "edit-team"): void
|
||||||
(e: "invite-team"): void
|
(e: "invite-team"): void
|
||||||
|
(e: "refetch-teams"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -180,7 +184,12 @@ const toast = useToast()
|
|||||||
const confirmRemove = ref(false)
|
const confirmRemove = ref(false)
|
||||||
const confirmExit = ref(false)
|
const confirmExit = ref(false)
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const workspaceService = useService(WorkspaceService)
|
||||||
|
|
||||||
const deleteTeam = () => {
|
const deleteTeam = () => {
|
||||||
|
loading.value = true
|
||||||
pipe(
|
pipe(
|
||||||
backendDeleteTeam(props.teamID),
|
backendDeleteTeam(props.teamID),
|
||||||
TE.match(
|
TE.match(
|
||||||
@@ -188,9 +197,25 @@ const deleteTeam = () => {
|
|||||||
// TODO: Better errors ? We know the possible errors now
|
// TODO: Better errors ? We know the possible errors now
|
||||||
toast.error(`${t("error.something_went_wrong")}`)
|
toast.error(`${t("error.something_went_wrong")}`)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
loading.value = false
|
||||||
|
confirmRemove.value = false
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
toast.success(`${t("team.deleted")}`)
|
toast.success(`${t("team.deleted")}`)
|
||||||
|
loading.value = false
|
||||||
|
emit("refetch-teams")
|
||||||
|
|
||||||
|
const currentWorkspace = workspaceService.currentWorkspace.value
|
||||||
|
|
||||||
|
// If the current workspace is the deleted workspace, change the workspace to personal
|
||||||
|
if (
|
||||||
|
currentWorkspace.type === "team" &&
|
||||||
|
currentWorkspace.teamID === props.teamID
|
||||||
|
) {
|
||||||
|
workspaceService.changeWorkspace({ type: "personal" })
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmRemove.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)() // Tasks (and TEs) are lazy, so call the function returned
|
)() // Tasks (and TEs) are lazy, so call the function returned
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="`${t('team.create_new')}`"
|
:label="`${t('team.create_new')}`"
|
||||||
outline
|
outline
|
||||||
|
:icon="IconPlus"
|
||||||
@click="displayModalAdd(true)"
|
@click="displayModalAdd(true)"
|
||||||
/>
|
/>
|
||||||
<div v-if="loading" class="flex flex-col items-center justify-center">
|
<div v-if="loading" class="flex flex-col items-center justify-center">
|
||||||
@@ -16,13 +17,6 @@
|
|||||||
:alt="`${t('empty.teams')}`"
|
:alt="`${t('empty.teams')}`"
|
||||||
:text="`${t('empty.teams')}`"
|
:text="`${t('empty.teams')}`"
|
||||||
>
|
>
|
||||||
<template #body>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
:label="`${t('team.create_new')}`"
|
|
||||||
filled
|
|
||||||
@click="displayModalAdd(true)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div
|
<div
|
||||||
v-else-if="!loading"
|
v-else-if="!loading"
|
||||||
@@ -39,6 +33,7 @@
|
|||||||
:compact="modal"
|
:compact="modal"
|
||||||
@edit-team="editTeam(team, team.id)"
|
@edit-team="editTeam(team, team.id)"
|
||||||
@invite-team="inviteTeam(team, team.id)"
|
@invite-team="inviteTeam(team, team.id)"
|
||||||
|
@refetch-teams="refetchTeams"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!loading && adapterError" class="flex flex-col items-center">
|
<div v-if="!loading && adapterError" class="flex flex-col items-center">
|
||||||
@@ -76,6 +71,7 @@ import { useReadonlyStream } from "@composables/stream"
|
|||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
|
import IconPlus from "~icons/lucide/plus"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
|||||||
@@ -27,13 +27,10 @@ const workspaceService = useService(WorkspaceService)
|
|||||||
const workspace = workspaceService.currentWorkspace
|
const workspace = workspaceService.currentWorkspace
|
||||||
|
|
||||||
const currentWorkspace = computed(() => {
|
const currentWorkspace = computed(() => {
|
||||||
if (props.isOnlyPersonal) {
|
if (props.isOnlyPersonal || workspace.value.type === "personal") {
|
||||||
return `${t("workspace.personal")}`
|
return t("workspace.personal")
|
||||||
}
|
}
|
||||||
if (workspace.value.type === "team") {
|
return teamWorkspaceName.value
|
||||||
return teamWorkspaceName.value
|
|
||||||
}
|
|
||||||
return `${t("workspace.personal")}`
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const teamWorkspaceName = computed(() => {
|
const teamWorkspaceName = computed(() => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
label="My Workspace"
|
:label="t('workspace.personal')"
|
||||||
:icon="IconUser"
|
:icon="IconUser"
|
||||||
:info-icon="workspace.type === 'personal' ? IconDone : undefined"
|
:info-icon="workspace.type === 'personal' ? IconDone : undefined"
|
||||||
:active-info-icon="workspace.type === 'personal'"
|
: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"
|
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">
|
<div class="flex items-center px-2 font-semibold text-secondaryLight">
|
||||||
{{ t("team.title") }}
|
{{ t("workspace.other_workspaces") }}
|
||||||
</div>
|
</div>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function onLoggedIn(exec: (user: HoppUser) => void) {
|
|||||||
* the auth system.
|
* the auth system.
|
||||||
*
|
*
|
||||||
* NOTE: Unlike `onLoggedIn` for which the callback will be called once on mount with the current state,
|
* NOTE: Unlike `onLoggedIn` for which the callback will be called once on mount with the current state,
|
||||||
* here the callback will only be called on authentication event occurances.
|
* here the callback will only be called on authentication event occurrences.
|
||||||
* You might want to check the auth state from an `onMounted` hook or something
|
* You might want to check the auth state from an `onMounted` hook or something
|
||||||
* if you want to access the initial state
|
* if you want to access the initial state
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ type CodeMirrorOptions = {
|
|||||||
|
|
||||||
// callback on editor update
|
// callback on editor update
|
||||||
onUpdate?: (view: ViewUpdate) => void
|
onUpdate?: (view: ViewUpdate) => void
|
||||||
|
|
||||||
|
// callback on view initialization
|
||||||
|
onInit?: (view: EditorView) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const hoppCompleterExt = (completer: Completer): Extension => {
|
const hoppCompleterExt = (completer: Completer): Extension => {
|
||||||
@@ -208,7 +211,9 @@ export function useCodemirror(
|
|||||||
el: Ref<any | null>,
|
el: Ref<any | null>,
|
||||||
value: Ref<string | undefined>,
|
value: Ref<string | undefined>,
|
||||||
options: CodeMirrorOptions
|
options: CodeMirrorOptions
|
||||||
): { cursor: Ref<{ line: number; ch: number }> } {
|
): {
|
||||||
|
cursor: Ref<{ line: number; ch: number }>
|
||||||
|
} {
|
||||||
const { subscribeToStream } = useStreamSubscriber()
|
const { subscribeToStream } = useStreamSubscriber()
|
||||||
|
|
||||||
// Set default value for contextMenuEnabled if not provided
|
// Set default value for contextMenuEnabled if not provided
|
||||||
@@ -242,7 +247,9 @@ export function useCodemirror(
|
|||||||
const { from, to } = selection
|
const { from, to } = selection
|
||||||
if (from === to) return
|
if (from === to) return
|
||||||
const text = view.value?.state.doc.sliceString(from, to)
|
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?.trim()) {
|
if (text?.trim()) {
|
||||||
invokeAction("contextmenu.open", {
|
invokeAction("contextmenu.open", {
|
||||||
position: {
|
position: {
|
||||||
@@ -263,6 +270,12 @@ export function useCodemirror(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debounce to prevent double click from selecting the word
|
||||||
|
const debouncedTextSelection = (time: number) =>
|
||||||
|
useDebounceFn(() => {
|
||||||
|
handleTextSelection()
|
||||||
|
}, time)
|
||||||
|
|
||||||
const initView = (el: any) => {
|
const initView = (el: any) => {
|
||||||
if (el) platform.ui?.onCodemirrorInstanceMount?.(el)
|
if (el) platform.ui?.onCodemirrorInstanceMount?.(el)
|
||||||
|
|
||||||
@@ -274,15 +287,10 @@ export function useCodemirror(
|
|||||||
ViewPlugin.fromClass(
|
ViewPlugin.fromClass(
|
||||||
class {
|
class {
|
||||||
update(update: ViewUpdate) {
|
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
|
// Only add event listeners if context menu is enabled in the editor
|
||||||
if (options.contextMenuEnabled) {
|
if (options.contextMenuEnabled) {
|
||||||
el.addEventListener("mouseup", debounceFn)
|
el.addEventListener("mouseup", debouncedTextSelection(140))
|
||||||
el.addEventListener("keyup", debounceFn)
|
el.addEventListener("keyup", debouncedTextSelection(140))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.onUpdate) {
|
if (options.onUpdate) {
|
||||||
@@ -324,7 +332,8 @@ export function useCodemirror(
|
|||||||
EditorView.domEventHandlers({
|
EditorView.domEventHandlers({
|
||||||
scroll(event) {
|
scroll(event) {
|
||||||
if (event.target && options.contextMenuEnabled) {
|
if (event.target && options.contextMenuEnabled) {
|
||||||
handleTextSelection()
|
// Debounce to make the performance better
|
||||||
|
debouncedTextSelection(30)()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -379,6 +388,8 @@ export function useCodemirror(
|
|||||||
extensions,
|
extensions,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
options.onInit?.(view.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { customRef, onBeforeUnmount, Ref, watch } from "vue"
|
import { customRef, onBeforeUnmount, ref, Ref, UnwrapRef, watch } from "vue"
|
||||||
|
|
||||||
export function pluckRef<T, K extends keyof T>(ref: Ref<T>, key: K): Ref<T[K]> {
|
export function pluckRef<T, K extends keyof T>(ref: Ref<T>, key: K): Ref<T[K]> {
|
||||||
return customRef((track, trigger) => {
|
return customRef((track, trigger) => {
|
||||||
@@ -31,3 +31,16 @@ export function pluckMultipleFromRef<T, K extends Array<keyof T>>(
|
|||||||
): { [key in K[number]]: Ref<T[key]> } {
|
): { [key in K[number]]: Ref<T[key]> } {
|
||||||
return Object.fromEntries(keys.map((x) => [x, pluckRef(sourceRef, x)])) as any
|
return Object.fromEntries(keys.map((x) => [x, pluckRef(sourceRef, x)])) as any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const refWithCallbackOnChange = <T>(
|
||||||
|
initialValue: T,
|
||||||
|
callback: (value: UnwrapRef<T>) => void
|
||||||
|
) => {
|
||||||
|
const targetRef = ref(initialValue)
|
||||||
|
|
||||||
|
watch(targetRef, (value) => {
|
||||||
|
callback(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
return targetRef
|
||||||
|
}
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ export function updateSaveContextForAffectedRequests(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to check the new folder path is close to the save context folder path or not
|
* Used to check the new folder path is close to the save context folder path or not
|
||||||
* @param folderPathCurrent The path saved as the inherited path in the inherited properties
|
* @param folderPathCurrent The path saved as the inherited path in the inherited properties
|
||||||
@@ -123,120 +122,109 @@ function folderPathCloseToSaveContext(
|
|||||||
saveContextPath: string
|
saveContextPath: string
|
||||||
) {
|
) {
|
||||||
if (!folderPathCurrent) return newFolderPath
|
if (!folderPathCurrent) return newFolderPath
|
||||||
|
|
||||||
const folderPathCurrentArray = folderPathCurrent.split("/")
|
const folderPathCurrentArray = folderPathCurrent.split("/")
|
||||||
const newFolderPathArray = newFolderPath.split("/")
|
const newFolderPathArray = newFolderPath.split("/")
|
||||||
|
|
||||||
const saveContextFolderPathArray = saveContextPath.split("/")
|
const saveContextFolderPathArray = saveContextPath.split("/")
|
||||||
|
|
||||||
let folderPathCurrentMatch = 0
|
const folderPathCurrentMatch = folderPathCurrentArray.filter(
|
||||||
|
(folder, i) => folder === saveContextFolderPathArray[i]
|
||||||
|
).length
|
||||||
|
|
||||||
for (let i = 0; i < folderPathCurrentArray.length; i++) {
|
const newFolderPathMatch = newFolderPathArray.filter(
|
||||||
if (folderPathCurrentArray[i] === saveContextFolderPathArray[i]) {
|
(folder, i) => folder === saveContextFolderPathArray[i]
|
||||||
folderPathCurrentMatch++
|
).length
|
||||||
|
|
||||||
|
return folderPathCurrentMatch > newFolderPathMatch
|
||||||
|
? folderPathCurrent
|
||||||
|
: newFolderPath
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDuplicatesAndKeepLast(arr: HoppInheritedProperty["headers"]) {
|
||||||
|
const keyMap: { [key: string]: number[] } = {} // Map to store array of indices for each key
|
||||||
|
|
||||||
|
// Populate keyMap with the indices of each key
|
||||||
|
arr.forEach((item, index) => {
|
||||||
|
const key = item.inheritedHeader.key
|
||||||
|
if (!(key in keyMap)) {
|
||||||
|
keyMap[key] = []
|
||||||
|
}
|
||||||
|
keyMap[key].push(index)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a new array containing only the last occurrence of each key
|
||||||
|
const result = []
|
||||||
|
for (const key in keyMap) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(keyMap, key)) {
|
||||||
|
const lastIndex = keyMap[key][keyMap[key].length - 1]
|
||||||
|
result.push(arr[lastIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let newFolderPathMatch = 0
|
// Sort the result array based on the parentID
|
||||||
|
result.sort((a, b) => a.parentID.localeCompare(b.parentID))
|
||||||
for (let i = 0; i < newFolderPathArray.length; i++) {
|
return result
|
||||||
if (newFolderPathArray[i] === saveContextFolderPathArray[i]) {
|
|
||||||
newFolderPathMatch++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folderPathCurrentMatch > newFolderPathMatch) {
|
|
||||||
return folderPathCurrent
|
|
||||||
}
|
|
||||||
return newFolderPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateInheritedPropertiesForAffectedRequests(
|
export function updateInheritedPropertiesForAffectedRequests(
|
||||||
path: string,
|
path: string,
|
||||||
inheritedProperties: HoppInheritedProperty,
|
inheritedProperties: HoppInheritedProperty,
|
||||||
type: "rest" | "graphql",
|
type: "rest" | "graphql"
|
||||||
workspace: "personal" | "team" = "personal"
|
|
||||||
) {
|
) {
|
||||||
const tabService =
|
const tabService =
|
||||||
type === "rest" ? getService(RESTTabService) : getService(GQLTabService)
|
type === "rest" ? getService(RESTTabService) : getService(GQLTabService)
|
||||||
|
|
||||||
let tabs
|
const effectedTabs = tabService.getTabsRefTo((tab) => {
|
||||||
if (workspace === "personal") {
|
const saveContext = tab.document.saveContext
|
||||||
tabs = tabService.getTabsRefTo((tab) => {
|
|
||||||
return (
|
|
||||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
|
||||||
tab.document.saveContext.folderPath.startsWith(path)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
tabs = tabService.getTabsRefTo((tab) => {
|
|
||||||
return (
|
|
||||||
tab.document.saveContext?.originLocation === "team-collection" &&
|
|
||||||
tab.document.saveContext.collectionID?.startsWith(path)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabsEffectedByAuth = tabs.filter((tab) => {
|
const saveContextPath =
|
||||||
if (workspace === "personal") {
|
saveContext?.originLocation === "team-collection"
|
||||||
return (
|
? saveContext.collectionID
|
||||||
tab.value.document.saveContext?.originLocation === "user-collection" &&
|
: saveContext?.folderPath
|
||||||
tab.value.document.saveContext.folderPath.startsWith(path) &&
|
|
||||||
path ===
|
|
||||||
folderPathCloseToSaveContext(
|
|
||||||
tab.value.document.inheritedProperties?.auth.parentID,
|
|
||||||
path,
|
|
||||||
tab.value.document.saveContext.folderPath
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return saveContextPath?.startsWith(path) ?? false
|
||||||
tab.value.document.saveContext?.originLocation === "team-collection" &&
|
|
||||||
tab.value.document.saveContext.collectionID?.startsWith(path) &&
|
|
||||||
path ===
|
|
||||||
folderPathCloseToSaveContext(
|
|
||||||
tab.value.document.inheritedProperties?.auth.parentID,
|
|
||||||
path,
|
|
||||||
tab.value.document.saveContext.collectionID
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabsEffectedByHeaders = tabs.filter((tab) => {
|
effectedTabs.map((tab) => {
|
||||||
return (
|
const inheritedParentID =
|
||||||
tab.value.document.inheritedProperties &&
|
tab.value.document.inheritedProperties?.auth.parentID
|
||||||
tab.value.document.inheritedProperties.headers.some(
|
|
||||||
|
const contextPath =
|
||||||
|
tab.value.document.saveContext?.originLocation === "team-collection"
|
||||||
|
? tab.value.document.saveContext.collectionID
|
||||||
|
: tab.value.document.saveContext?.folderPath
|
||||||
|
|
||||||
|
const effectedPath = folderPathCloseToSaveContext(
|
||||||
|
inheritedParentID,
|
||||||
|
path,
|
||||||
|
contextPath ?? ""
|
||||||
|
)
|
||||||
|
|
||||||
|
if (effectedPath === path) {
|
||||||
|
if (tab.value.document.inheritedProperties) {
|
||||||
|
tab.value.document.inheritedProperties.auth = inheritedProperties.auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab.value.document.inheritedProperties?.headers) {
|
||||||
|
// filter out the headers with the parentID not as the path
|
||||||
|
const headers = tab.value.document.inheritedProperties.headers.filter(
|
||||||
|
(header) => header.parentID !== path
|
||||||
|
)
|
||||||
|
|
||||||
|
// filter out the headers with the parentID as the path in the inheritedProperties
|
||||||
|
const inheritedHeaders = inheritedProperties.headers.filter(
|
||||||
(header) => header.parentID === path
|
(header) => header.parentID === path
|
||||||
)
|
)
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const tab of tabsEffectedByAuth) {
|
// merge the headers with the parentID as the path
|
||||||
tab.value.document.inheritedProperties = inheritedProperties
|
const mergedHeaders = removeDuplicatesAndKeepLast([
|
||||||
}
|
...new Set([...inheritedHeaders, ...headers]),
|
||||||
|
])
|
||||||
|
|
||||||
for (const tab of tabsEffectedByHeaders) {
|
tab.value.document.inheritedProperties.headers = mergedHeaders
|
||||||
const headers = tab.value.document.inheritedProperties?.headers.map(
|
|
||||||
(header) => {
|
|
||||||
if (header.parentID === path) {
|
|
||||||
return {
|
|
||||||
...header,
|
|
||||||
inheritedHeader: inheritedProperties.headers.find(
|
|
||||||
(inheritedHeader) =>
|
|
||||||
inheritedHeader.inheritedHeader?.key ===
|
|
||||||
header.inheritedHeader?.key
|
|
||||||
)?.inheritedHeader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return header
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
tab.value.document.inheritedProperties = {
|
|
||||||
...tab.value.document.inheritedProperties,
|
|
||||||
headers,
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetSaveContextForAffectedRequests(folderPath: string) {
|
function resetSaveContextForAffectedRequests(folderPath: string) {
|
||||||
|
|||||||
@@ -66,12 +66,20 @@ export function getRequestsByPath(
|
|||||||
let currentCollection = collections[pathArray[0]]
|
let currentCollection = collections[pathArray[0]]
|
||||||
|
|
||||||
if (pathArray.length === 1) {
|
if (pathArray.length === 1) {
|
||||||
return currentCollection.requests
|
const latestVersionedRequests = currentCollection.requests.filter(
|
||||||
|
(req): req is HoppRESTRequest => req.v === "3"
|
||||||
|
)
|
||||||
|
|
||||||
|
return latestVersionedRequests
|
||||||
}
|
}
|
||||||
for (let i = 1; i < pathArray.length; i++) {
|
for (let i = 1; i < pathArray.length; i++) {
|
||||||
const folder = currentCollection.folders[pathArray[i]]
|
const folder = currentCollection.folders[pathArray[i]]
|
||||||
if (folder) currentCollection = folder
|
if (folder) currentCollection = folder
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentCollection.requests
|
const latestVersionedRequests = currentCollection.requests.filter(
|
||||||
|
(req): req is HoppRESTRequest => req.v === "3"
|
||||||
|
)
|
||||||
|
|
||||||
|
return latestVersionedRequests
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -868,6 +868,38 @@ const samples = [
|
|||||||
requestVariables: [],
|
requestVariables: [],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
command: `curl --location 'https://api.example.net/id/1164/requests' \
|
||||||
|
--header 'Accept: application/vnd.test-data.v2.1+json' \
|
||||||
|
--header 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data-urlencode 'data={"type":"test","typeId":"101"}' \
|
||||||
|
--data-urlencode 'data2={"type":"test2","typeId":"123"}'`,
|
||||||
|
response: makeRESTRequest({
|
||||||
|
method: "POST",
|
||||||
|
name: "Untitled",
|
||||||
|
endpoint: "https://api.example.net/id/1164/requests",
|
||||||
|
auth: {
|
||||||
|
authType: "inherit",
|
||||||
|
authActive: true,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: "application/x-www-form-urlencoded",
|
||||||
|
body: `data: {"type":"test","typeId":"101"}
|
||||||
|
data2: {"type":"test2","typeId":"123"}`,
|
||||||
|
},
|
||||||
|
params: [],
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
key: "Accept",
|
||||||
|
value: "application/vnd.test-data.v2.1+json",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
preRequestScript: "",
|
||||||
|
testScript: "",
|
||||||
|
requestVariables: [],
|
||||||
|
}),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
describe("Parse curl command to Hopp REST Request", () => {
|
describe("Parse curl command to Hopp REST Request", () => {
|
||||||
|
|||||||
@@ -33,7 +33,27 @@ export const parseCurlCommand = (curlCommand: string) => {
|
|||||||
// const compressed = !!parsedArguments.compressed
|
// const compressed = !!parsedArguments.compressed
|
||||||
|
|
||||||
curlCommand = preProcessCurlCommand(curlCommand)
|
curlCommand = preProcessCurlCommand(curlCommand)
|
||||||
const parsedArguments = parser(curlCommand)
|
|
||||||
|
const args: parser.Arguments = parser(curlCommand)
|
||||||
|
|
||||||
|
const parsedArguments = pipe(
|
||||||
|
args,
|
||||||
|
O.fromPredicate(
|
||||||
|
(args) =>
|
||||||
|
objHasProperty("dataUrlencode", "string")(args) ||
|
||||||
|
objHasProperty("dataUrlencode", "object")(args)
|
||||||
|
),
|
||||||
|
O.map((args) => {
|
||||||
|
const urlEncodedData: string[] = Array.isArray(args.dataUrlencode)
|
||||||
|
? args.dataUrlencode
|
||||||
|
: [args.dataUrlencode]
|
||||||
|
|
||||||
|
const data = A.map(decodeURIComponent)(urlEncodedData)
|
||||||
|
|
||||||
|
return { ...args, d: data }
|
||||||
|
}),
|
||||||
|
O.getOrElse(() => args)
|
||||||
|
)
|
||||||
|
|
||||||
const headerObject = getHeaders(parsedArguments)
|
const headerObject = getHeaders(parsedArguments)
|
||||||
const { headers } = headerObject
|
const { headers } = headerObject
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Converts an array of key-value tuples (for e.g ["key", "value"]), into a record.
|
* Converts an array of key-value tuples (for e.g ["key", "value"]), into a record.
|
||||||
* (for eg. output -> { "key": "value" })
|
* (for eg. output -> { "key": "value" })
|
||||||
* NOTE: This function will discard duplicate key occurances and only keep the last occurance. If you do not want that behaviour,
|
* NOTE: This function will discard duplicate key occurrences and only keep the last occurrence. If you do not want that behaviour,
|
||||||
* use `tupleWithSamesKeysToRecord`.
|
* use `tupleWithSamesKeysToRecord`.
|
||||||
* @param tuples Array of tuples ([key, value])
|
* @param tuples Array of tuples ([key, value])
|
||||||
* @returns A record with value corresponding to the last occurance of that key
|
* @returns A record with value corresponding to the last occurrence of that key
|
||||||
*/
|
*/
|
||||||
export const tupleToRecord = <
|
export const tupleToRecord = <
|
||||||
KeyType extends string | number | symbol,
|
KeyType extends string | number | symbol,
|
||||||
|
|||||||
@@ -269,8 +269,16 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||||||
const username = auth.username
|
const username = auth.username
|
||||||
const password = auth.password
|
const password = auth.password
|
||||||
finalHeaders.Authorization = `Basic ${btoa(`${username}:${password}`)}`
|
finalHeaders.Authorization = `Basic ${btoa(`${username}:${password}`)}`
|
||||||
} else if (auth.authType === "bearer" || auth.authType === "oauth-2") {
|
} else if (auth.authType === "bearer") {
|
||||||
finalHeaders.Authorization = `Bearer ${auth.token}`
|
finalHeaders.Authorization = `Bearer ${auth.token}`
|
||||||
|
} else if (auth.authType === "oauth-2") {
|
||||||
|
const { addTo } = auth
|
||||||
|
|
||||||
|
if (addTo === "HEADERS") {
|
||||||
|
finalHeaders.Authorization = `Bearer ${auth.grantTypeInfo.token}`
|
||||||
|
} else if (addTo === "QUERY_PARAMS") {
|
||||||
|
params["access_token"] = auth.grantTypeInfo.token
|
||||||
|
}
|
||||||
} else if (auth.authType === "api-key") {
|
} else if (auth.authType === "api-key") {
|
||||||
const { key, value, addTo } = auth
|
const { key, value, addTo } = auth
|
||||||
if (addTo === "Headers") {
|
if (addTo === "Headers") {
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import { pipe, flow } from "fp-ts/function"
|
import {
|
||||||
import * as TE from "fp-ts/TaskEither"
|
HoppCollection,
|
||||||
|
HoppRESTRequest,
|
||||||
|
getDefaultGQLRequest,
|
||||||
|
getDefaultRESTRequest,
|
||||||
|
translateToNewRESTCollection,
|
||||||
|
} from "@hoppscotch/data"
|
||||||
|
import * as A from "fp-ts/Array"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import * as RA from "fp-ts/ReadonlyArray"
|
import * as RA from "fp-ts/ReadonlyArray"
|
||||||
import * as A from "fp-ts/Array"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { translateToNewRESTCollection, HoppCollection } from "@hoppscotch/data"
|
import { flow, pipe } from "fp-ts/function"
|
||||||
import { isPlainObject as _isPlainObject } from "lodash-es"
|
|
||||||
|
|
||||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
import { HoppGQLRequest, translateToNewGQLCollection } from "@hoppscotch/data"
|
||||||
import { safeParseJSON } from "~/helpers/functional/json"
|
import { safeParseJSON } from "~/helpers/functional/json"
|
||||||
import { translateToNewGQLCollection } from "@hoppscotch/data"
|
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||||
|
|
||||||
export const hoppRESTImporter = (content: string[]) =>
|
export const hoppRESTImporter = (content: string[]) =>
|
||||||
pipe(
|
pipe(
|
||||||
@@ -26,26 +31,30 @@ export const hoppRESTImporter = (content: string[]) =>
|
|||||||
TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT)
|
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.
|
* checks if a collection is a valid hoppscotch collection.
|
||||||
* else translate it into one.
|
* else translate it into one.
|
||||||
*/
|
*/
|
||||||
const validateCollection = (collection: unknown) => {
|
const validateCollection = (collection: unknown) => {
|
||||||
if (isValidCollection(collection)) {
|
const collectionSchemaParsedResult = HoppCollection.safeParse(collection)
|
||||||
return O.some(collection)
|
|
||||||
|
if (collectionSchemaParsedResult.type === "ok") {
|
||||||
|
const requests = collectionSchemaParsedResult.value.requests.map(
|
||||||
|
(request) => {
|
||||||
|
const requestSchemaParsedResult = HoppRESTRequest.safeParse(request)
|
||||||
|
|
||||||
|
return requestSchemaParsedResult.type === "ok"
|
||||||
|
? requestSchemaParsedResult.value
|
||||||
|
: getDefaultRESTRequest()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return O.some({
|
||||||
|
...collectionSchemaParsedResult.value,
|
||||||
|
requests,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return O.some(translateToNewRESTCollection(collection))
|
return O.some(translateToNewRESTCollection(collection))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,8 +84,24 @@ export const hoppGQLImporter = (content: string) =>
|
|||||||
* @returns the collection if it is valid, else a translated version of the collection
|
* @returns the collection if it is valid, else a translated version of the collection
|
||||||
*/
|
*/
|
||||||
export const validateGQLCollection = (collection: unknown) => {
|
export const validateGQLCollection = (collection: unknown) => {
|
||||||
if (isValidCollection(collection)) {
|
const collectionSchemaParsedResult = HoppCollection.safeParse(collection)
|
||||||
return O.some(collection)
|
|
||||||
|
if (collectionSchemaParsedResult.type === "ok") {
|
||||||
|
const requests = collectionSchemaParsedResult.value.requests.map(
|
||||||
|
(request) => {
|
||||||
|
const requestSchemaParsedResult = HoppGQLRequest.safeParse(request)
|
||||||
|
|
||||||
|
return requestSchemaParsedResult.type === "ok"
|
||||||
|
? requestSchemaParsedResult.value
|
||||||
|
: getDefaultGQLRequest()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return O.some({
|
||||||
|
...collectionSchemaParsedResult.value,
|
||||||
|
requests,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return O.some(translateToNewGQLCollection(collection))
|
return O.some(translateToNewGQLCollection(collection))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,12 +111,16 @@ const getHoppReqAuth = (req: InsomniaRequestResource): HoppRESTAuth => {
|
|||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: !(auth.disabled ?? false),
|
authActive: !(auth.disabled ?? false),
|
||||||
accessTokenURL: replaceVarTemplating(auth.accessTokenUrl ?? ""),
|
grantTypeInfo: {
|
||||||
authURL: replaceVarTemplating(auth.authorizationUrl ?? ""),
|
authEndpoint: replaceVarTemplating(auth.authorizationUrl ?? ""),
|
||||||
clientID: replaceVarTemplating(auth.clientId ?? ""),
|
clientID: replaceVarTemplating(auth.clientId ?? ""),
|
||||||
oidcDiscoveryURL: "",
|
clientSecret: "",
|
||||||
scope: replaceVarTemplating(auth.scope ?? ""),
|
grantType: "AUTHORIZATION_CODE",
|
||||||
token: "",
|
scopes: replaceVarTemplating(auth.scope ?? ""),
|
||||||
|
token: "",
|
||||||
|
isPKCE: false,
|
||||||
|
tokenEndpoint: replaceVarTemplating(auth.accessTokenUrl ?? ""),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
else if (auth.type === "bearer")
|
else if (auth.type === "bearer")
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { NonSecretEnvironment } from "@hoppscotch/data"
|
import { NonSecretEnvironment } from "@hoppscotch/data"
|
||||||
import { safeParseJSONOrYAML } from "~/helpers/functional/yaml"
|
import { safeParseJSONOrYAML } from "~/helpers/functional/yaml"
|
||||||
import { uniqueId } from "lodash-es"
|
import { uniqueID } from "~/helpers/utils/uniqueID"
|
||||||
|
|
||||||
const insomniaResourcesSchema = z.object({
|
const insomniaResourcesSchema = z.object({
|
||||||
resources: z.array(
|
resources: z.array(
|
||||||
@@ -67,7 +67,7 @@ export const insomniaEnvImporter = (contents: string[]) => {
|
|||||||
|
|
||||||
if (parsedInsomniaEnv.success) {
|
if (parsedInsomniaEnv.success) {
|
||||||
const environment: NonSecretEnvironment = {
|
const environment: NonSecretEnvironment = {
|
||||||
id: uniqueId(),
|
id: uniqueID(),
|
||||||
v: 1,
|
v: 1,
|
||||||
name: parsedInsomniaEnv.data.name,
|
name: parsedInsomniaEnv.data.name,
|
||||||
variables: Object.entries(parsedInsomniaEnv.data.data).map(
|
variables: Object.entries(parsedInsomniaEnv.data.data).map(
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
HoppCollection,
|
HoppCollection,
|
||||||
makeCollection,
|
makeCollection,
|
||||||
HoppRESTRequestVariable,
|
HoppRESTRequestVariable,
|
||||||
|
HoppRESTRequest,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { pipe, flow } from "fp-ts/function"
|
import { pipe, flow } from "fp-ts/function"
|
||||||
import * as A from "fp-ts/Array"
|
import * as A from "fp-ts/Array"
|
||||||
@@ -25,6 +26,7 @@ import * as O from "fp-ts/Option"
|
|||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import * as RA from "fp-ts/ReadonlyArray"
|
import * as RA from "fp-ts/ReadonlyArray"
|
||||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||||
|
import { cloneDeep } from "lodash-es"
|
||||||
|
|
||||||
export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const
|
export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const
|
||||||
|
|
||||||
@@ -277,67 +279,92 @@ const resolveOpenAPIV3SecurityObj = (
|
|||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
accessTokenURL: scheme.flows.authorizationCode.tokenUrl ?? "",
|
grantTypeInfo: {
|
||||||
authURL: scheme.flows.authorizationCode.authorizationUrl ?? "",
|
grantType: "AUTHORIZATION_CODE",
|
||||||
clientID: "",
|
authEndpoint: scheme.flows.authorizationCode.authorizationUrl ?? "",
|
||||||
oidcDiscoveryURL: "",
|
clientID: "",
|
||||||
scope: _schemeData.join(" "),
|
scopes: _schemeData.join(" "),
|
||||||
token: "",
|
token: "",
|
||||||
|
isPKCE: false,
|
||||||
|
tokenEndpoint: scheme.flows.authorizationCode.tokenUrl ?? "",
|
||||||
|
clientSecret: "",
|
||||||
|
},
|
||||||
|
addTo: "HEADERS",
|
||||||
}
|
}
|
||||||
} else if (scheme.flows.implicit) {
|
} else if (scheme.flows.implicit) {
|
||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
authURL: scheme.flows.implicit.authorizationUrl ?? "",
|
grantTypeInfo: {
|
||||||
accessTokenURL: "",
|
grantType: "IMPLICIT",
|
||||||
clientID: "",
|
authEndpoint: scheme.flows.implicit.authorizationUrl ?? "",
|
||||||
oidcDiscoveryURL: "",
|
clientID: "",
|
||||||
scope: _schemeData.join(" "),
|
token: "",
|
||||||
token: "",
|
scopes: _schemeData.join(" "),
|
||||||
|
},
|
||||||
|
addTo: "HEADERS",
|
||||||
}
|
}
|
||||||
} else if (scheme.flows.password) {
|
} else if (scheme.flows.password) {
|
||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
authURL: "",
|
grantTypeInfo: {
|
||||||
accessTokenURL: scheme.flows.password.tokenUrl ?? "",
|
grantType: "PASSWORD",
|
||||||
clientID: "",
|
clientID: "",
|
||||||
oidcDiscoveryURL: "",
|
authEndpoint: scheme.flows.password.tokenUrl,
|
||||||
scope: _schemeData.join(" "),
|
clientSecret: "",
|
||||||
token: "",
|
password: "",
|
||||||
|
username: "",
|
||||||
|
token: "",
|
||||||
|
scopes: _schemeData.join(" "),
|
||||||
|
},
|
||||||
|
addTo: "HEADERS",
|
||||||
}
|
}
|
||||||
} else if (scheme.flows.clientCredentials) {
|
} else if (scheme.flows.clientCredentials) {
|
||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
accessTokenURL: scheme.flows.clientCredentials.tokenUrl ?? "",
|
grantTypeInfo: {
|
||||||
authURL: "",
|
grantType: "CLIENT_CREDENTIALS",
|
||||||
clientID: "",
|
authEndpoint: scheme.flows.clientCredentials.tokenUrl ?? "",
|
||||||
oidcDiscoveryURL: "",
|
clientID: "",
|
||||||
scope: _schemeData.join(" "),
|
clientSecret: "",
|
||||||
token: "",
|
scopes: _schemeData.join(" "),
|
||||||
|
token: "",
|
||||||
|
},
|
||||||
|
addTo: "HEADERS",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
accessTokenURL: "",
|
grantTypeInfo: {
|
||||||
authURL: "",
|
grantType: "AUTHORIZATION_CODE",
|
||||||
clientID: "",
|
authEndpoint: "",
|
||||||
oidcDiscoveryURL: "",
|
clientID: "",
|
||||||
scope: _schemeData.join(" "),
|
scopes: _schemeData.join(" "),
|
||||||
token: "",
|
token: "",
|
||||||
|
isPKCE: false,
|
||||||
|
tokenEndpoint: "",
|
||||||
|
clientSecret: "",
|
||||||
|
},
|
||||||
|
addTo: "HEADERS",
|
||||||
}
|
}
|
||||||
} else if (scheme.type === "openIdConnect") {
|
} else if (scheme.type === "openIdConnect") {
|
||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
accessTokenURL: "",
|
grantTypeInfo: {
|
||||||
authURL: "",
|
grantType: "AUTHORIZATION_CODE",
|
||||||
clientID: "",
|
authEndpoint: "",
|
||||||
oidcDiscoveryURL: scheme.openIdConnectUrl ?? "",
|
clientID: "",
|
||||||
scope: _schemeData.join(" "),
|
scopes: _schemeData.join(" "),
|
||||||
token: "",
|
token: "",
|
||||||
|
isPKCE: false,
|
||||||
|
tokenEndpoint: "",
|
||||||
|
clientSecret: "",
|
||||||
|
},
|
||||||
|
addTo: "HEADERS",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,56 +441,76 @@ const resolveOpenAPIV2SecurityScheme = (
|
|||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
accessTokenURL: scheme.tokenUrl ?? "",
|
grantTypeInfo: {
|
||||||
authURL: scheme.authorizationUrl ?? "",
|
authEndpoint: scheme.authorizationUrl ?? "",
|
||||||
clientID: "",
|
clientID: "",
|
||||||
oidcDiscoveryURL: "",
|
clientSecret: "",
|
||||||
scope: _schemeData.join(" "),
|
grantType: "AUTHORIZATION_CODE",
|
||||||
token: "",
|
scopes: _schemeData.join(" "),
|
||||||
|
token: "",
|
||||||
|
isPKCE: false,
|
||||||
|
tokenEndpoint: scheme.tokenUrl ?? "",
|
||||||
|
},
|
||||||
|
addTo: "HEADERS",
|
||||||
}
|
}
|
||||||
} else if (scheme.flow === "implicit") {
|
} else if (scheme.flow === "implicit") {
|
||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
accessTokenURL: "",
|
grantTypeInfo: {
|
||||||
authURL: scheme.authorizationUrl ?? "",
|
authEndpoint: scheme.authorizationUrl ?? "",
|
||||||
clientID: "",
|
clientID: "",
|
||||||
oidcDiscoveryURL: "",
|
grantType: "IMPLICIT",
|
||||||
scope: _schemeData.join(" "),
|
scopes: _schemeData.join(" "),
|
||||||
token: "",
|
token: "",
|
||||||
|
},
|
||||||
|
addTo: "HEADERS",
|
||||||
}
|
}
|
||||||
} else if (scheme.flow === "application") {
|
} else if (scheme.flow === "application") {
|
||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
accessTokenURL: scheme.tokenUrl ?? "",
|
grantTypeInfo: {
|
||||||
authURL: "",
|
authEndpoint: scheme.tokenUrl ?? "",
|
||||||
clientID: "",
|
clientID: "",
|
||||||
oidcDiscoveryURL: "",
|
clientSecret: "",
|
||||||
scope: _schemeData.join(" "),
|
grantType: "CLIENT_CREDENTIALS",
|
||||||
token: "",
|
scopes: _schemeData.join(" "),
|
||||||
|
token: "",
|
||||||
|
},
|
||||||
|
addTo: "HEADERS",
|
||||||
}
|
}
|
||||||
} else if (scheme.flow === "password") {
|
} else if (scheme.flow === "password") {
|
||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
accessTokenURL: scheme.tokenUrl ?? "",
|
grantTypeInfo: {
|
||||||
authURL: "",
|
grantType: "PASSWORD",
|
||||||
clientID: "",
|
authEndpoint: scheme.tokenUrl ?? "",
|
||||||
oidcDiscoveryURL: "",
|
clientID: "",
|
||||||
scope: _schemeData.join(" "),
|
clientSecret: "",
|
||||||
token: "",
|
password: "",
|
||||||
|
scopes: _schemeData.join(" "),
|
||||||
|
token: "",
|
||||||
|
username: "",
|
||||||
|
},
|
||||||
|
addTo: "HEADERS",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
accessTokenURL: "",
|
grantTypeInfo: {
|
||||||
authURL: "",
|
authEndpoint: "",
|
||||||
clientID: "",
|
clientID: "",
|
||||||
oidcDiscoveryURL: "",
|
clientSecret: "",
|
||||||
scope: _schemeData.join(" "),
|
grantType: "AUTHORIZATION_CODE",
|
||||||
token: "",
|
scopes: _schemeData.join(" "),
|
||||||
|
token: "",
|
||||||
|
isPKCE: false,
|
||||||
|
tokenEndpoint: "",
|
||||||
|
},
|
||||||
|
addTo: "HEADERS",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,30 +627,42 @@ const convertPathToHoppReqs = (
|
|||||||
? openAPIUrl + openAPIPath.slice(1)
|
? openAPIUrl + openAPIPath.slice(1)
|
||||||
: openAPIUrl + openAPIPath
|
: openAPIUrl + openAPIPath
|
||||||
|
|
||||||
return makeRESTRequest({
|
const res: {
|
||||||
name: info.operationId ?? info.summary ?? "Untitled Request",
|
request: HoppRESTRequest
|
||||||
method: method.toUpperCase(),
|
metadata: {
|
||||||
endpoint,
|
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
|
// We don't need to worry about reference types as the Dereferencing pass should remove them
|
||||||
params: parseOpenAPIParams(
|
params: parseOpenAPIParams(
|
||||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||||
),
|
),
|
||||||
headers: parseOpenAPIHeaders(
|
headers: parseOpenAPIHeaders(
|
||||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||||
),
|
),
|
||||||
|
|
||||||
auth: parseOpenAPIAuth(doc, info),
|
auth: parseOpenAPIAuth(doc, info),
|
||||||
|
|
||||||
body: parseOpenAPIBody(doc, info),
|
body: parseOpenAPIBody(doc, info),
|
||||||
|
|
||||||
preRequestScript: "",
|
preRequestScript: "",
|
||||||
testScript: "",
|
testScript: "",
|
||||||
|
|
||||||
requestVariables: parseOpenAPIVariables(
|
requestVariables: parseOpenAPIVariables(
|
||||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||||
),
|
),
|
||||||
})
|
}),
|
||||||
|
metadata: {
|
||||||
|
tags: info.tags ?? [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Disable Readonly
|
// Disable Readonly
|
||||||
@@ -622,10 +681,38 @@ const convertOpenApiDocsToHopp = (
|
|||||||
)
|
)
|
||||||
.flat()
|
.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({
|
return makeCollection({
|
||||||
name,
|
name,
|
||||||
folders: [],
|
folders: Object.entries(requestsByTags).map(([name, paths]) =>
|
||||||
requests: paths,
|
makeCollection({
|
||||||
|
name,
|
||||||
|
requests: paths,
|
||||||
|
folders: [],
|
||||||
|
auth: { authType: "inherit", authActive: true },
|
||||||
|
headers: [],
|
||||||
|
})
|
||||||
|
),
|
||||||
|
requests: requestsWithoutTags,
|
||||||
auth: { authType: "inherit", authActive: true },
|
auth: { authType: "inherit", authActive: true },
|
||||||
headers: [],
|
headers: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -162,25 +162,36 @@ const getHoppReqAuth = (item: Item): HoppRESTAuth => {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
} else if (auth.type === "oauth2") {
|
} else if (auth.type === "oauth2") {
|
||||||
|
const accessTokenURL = replacePMVarTemplating(
|
||||||
|
getVariableValue(auth.oauth2, "accessTokenUrl") ?? ""
|
||||||
|
)
|
||||||
|
const authURL = replacePMVarTemplating(
|
||||||
|
getVariableValue(auth.oauth2, "authUrl") ?? ""
|
||||||
|
)
|
||||||
|
const clientId = replacePMVarTemplating(
|
||||||
|
getVariableValue(auth.oauth2, "clientId") ?? ""
|
||||||
|
)
|
||||||
|
const scope = replacePMVarTemplating(
|
||||||
|
getVariableValue(auth.oauth2, "scope") ?? ""
|
||||||
|
)
|
||||||
|
const token = replacePMVarTemplating(
|
||||||
|
getVariableValue(auth.oauth2, "accessToken") ?? ""
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authType: "oauth-2",
|
authType: "oauth-2",
|
||||||
authActive: true,
|
authActive: true,
|
||||||
accessTokenURL: replacePMVarTemplating(
|
grantTypeInfo: {
|
||||||
getVariableValue(auth.oauth2, "accessTokenUrl") ?? ""
|
grantType: "AUTHORIZATION_CODE",
|
||||||
),
|
authEndpoint: authURL,
|
||||||
authURL: replacePMVarTemplating(
|
clientID: clientId,
|
||||||
getVariableValue(auth.oauth2, "authUrl") ?? ""
|
scopes: scope,
|
||||||
),
|
token: token,
|
||||||
clientID: replacePMVarTemplating(
|
tokenEndpoint: accessTokenURL,
|
||||||
getVariableValue(auth.oauth2, "clientId") ?? ""
|
clientSecret: "",
|
||||||
),
|
isPKCE: false,
|
||||||
scope: replacePMVarTemplating(
|
},
|
||||||
getVariableValue(auth.oauth2, "scope") ?? ""
|
addTo: "HEADERS",
|
||||||
),
|
|
||||||
token: replacePMVarTemplating(
|
|
||||||
getVariableValue(auth.oauth2, "accessToken") ?? ""
|
|
||||||
),
|
|
||||||
oidcDiscoveryURL: "",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Environment } from "@hoppscotch/data"
|
import { Environment } from "@hoppscotch/data"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { uniqueId } from "lodash-es"
|
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { safeParseJSON } from "~/helpers/functional/json"
|
import { safeParseJSON } from "~/helpers/functional/json"
|
||||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||||
|
import { uniqueID } from "~/helpers/utils/uniqueID"
|
||||||
|
|
||||||
const postmanEnvSchema = z.object({
|
const postmanEnvSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
@@ -49,7 +49,7 @@ export const postmanEnvImporter = (contents: string[]) => {
|
|||||||
// Convert `values` to `variables` to match the format expected by the system
|
// Convert `values` to `variables` to match the format expected by the system
|
||||||
const environments: Environment[] = validationResult.data.map(
|
const environments: Environment[] = validationResult.data.map(
|
||||||
({ name, values }) => ({
|
({ name, values }) => ({
|
||||||
id: uniqueId(),
|
id: uniqueID(),
|
||||||
v: 1,
|
v: 1,
|
||||||
name,
|
name,
|
||||||
variables: values.map((entires) => ({ ...entires, secret: false })),
|
variables: values.map((entires) => ({ ...entires, secret: false })),
|
||||||
|
|||||||
@@ -0,0 +1,625 @@
|
|||||||
|
import {
|
||||||
|
HoppRESTAuth,
|
||||||
|
HoppRESTHeader,
|
||||||
|
HoppRESTRequest,
|
||||||
|
getDefaultRESTRequest,
|
||||||
|
} from "@hoppscotch/data"
|
||||||
|
import axios from "axios"
|
||||||
|
import { Service } from "dioc"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { Ref, ref } from "vue"
|
||||||
|
|
||||||
|
import { runGQLQuery } from "../backend/GQLClient"
|
||||||
|
import {
|
||||||
|
GetCollectionChildrenDocument,
|
||||||
|
GetCollectionRequestsDocument,
|
||||||
|
GetSingleCollectionDocument,
|
||||||
|
GetSingleRequestDocument,
|
||||||
|
} from "../backend/graphql"
|
||||||
|
import { TeamCollection } from "./TeamCollection"
|
||||||
|
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||||
|
import { TeamRequest } from "./TeamRequest"
|
||||||
|
|
||||||
|
type CollectionSearchMeta = {
|
||||||
|
isSearchResult?: boolean
|
||||||
|
insertedWhileExpanding?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectionSearchNode =
|
||||||
|
| {
|
||||||
|
type: "request"
|
||||||
|
title: string
|
||||||
|
method: string
|
||||||
|
id: string
|
||||||
|
// parent collections
|
||||||
|
path: CollectionSearchNode[]
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "collection"
|
||||||
|
title: string
|
||||||
|
id: string
|
||||||
|
// parent collections
|
||||||
|
path: CollectionSearchNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type _SearchCollection = TeamCollection & {
|
||||||
|
parentID: string | null
|
||||||
|
meta?: CollectionSearchMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
type _SearchRequest = {
|
||||||
|
id: string
|
||||||
|
collectionID: string
|
||||||
|
title: string
|
||||||
|
request: {
|
||||||
|
name: string
|
||||||
|
method: string
|
||||||
|
}
|
||||||
|
meta?: CollectionSearchMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToTeamCollection(
|
||||||
|
node: CollectionSearchNode & {
|
||||||
|
meta?: CollectionSearchMeta
|
||||||
|
},
|
||||||
|
existingCollections: Record<string, _SearchCollection>,
|
||||||
|
existingRequests: Record<string, _SearchRequest>
|
||||||
|
) {
|
||||||
|
if (node.type === "request") {
|
||||||
|
existingRequests[node.id] = {
|
||||||
|
id: node.id,
|
||||||
|
collectionID: node.path[0].id,
|
||||||
|
title: node.title,
|
||||||
|
request: {
|
||||||
|
name: node.title,
|
||||||
|
method: node.method,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
isSearchResult: node.meta?.isSearchResult || false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.path[0]) {
|
||||||
|
// add parent collections to the collections array recursively
|
||||||
|
convertToTeamCollection(
|
||||||
|
node.path[0],
|
||||||
|
existingCollections,
|
||||||
|
existingRequests
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
existingCollections[node.id] = {
|
||||||
|
id: node.id,
|
||||||
|
title: node.title,
|
||||||
|
children: [],
|
||||||
|
requests: [],
|
||||||
|
data: null,
|
||||||
|
parentID: node.path[0]?.id,
|
||||||
|
meta: {
|
||||||
|
isSearchResult: node.meta?.isSearchResult || false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.path[0]) {
|
||||||
|
// add parent collections to the collections array recursively
|
||||||
|
convertToTeamCollection(
|
||||||
|
node.path[0],
|
||||||
|
existingCollections,
|
||||||
|
existingRequests
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
existingCollections,
|
||||||
|
existingRequests,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToTeamTree(
|
||||||
|
collections: (TeamCollection & { parentID: string | null })[],
|
||||||
|
requests: TeamRequest[]
|
||||||
|
) {
|
||||||
|
const collectionTree: TeamCollection[] = []
|
||||||
|
|
||||||
|
collections.forEach((collection) => {
|
||||||
|
const parentCollection = collection.parentID
|
||||||
|
? collections.find((c) => c.id === collection.parentID)
|
||||||
|
: null
|
||||||
|
|
||||||
|
const isAlreadyInserted = parentCollection?.children?.find(
|
||||||
|
(c) => c.id === collection.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isAlreadyInserted) return
|
||||||
|
|
||||||
|
if (parentCollection) {
|
||||||
|
parentCollection.children = parentCollection.children || []
|
||||||
|
parentCollection.children.push(collection)
|
||||||
|
} else {
|
||||||
|
collectionTree.push(collection)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
requests.forEach((request) => {
|
||||||
|
const parentCollection = collections.find(
|
||||||
|
(c) => c.id === request.collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
const isAlreadyInserted = parentCollection?.requests?.find(
|
||||||
|
(r) => r.id === request.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isAlreadyInserted) return
|
||||||
|
|
||||||
|
if (parentCollection) {
|
||||||
|
const requestSchemaParsedResult = HoppRESTRequest.safeParse(
|
||||||
|
request.request
|
||||||
|
)
|
||||||
|
|
||||||
|
const effectiveRequest =
|
||||||
|
requestSchemaParsedResult.type === "ok"
|
||||||
|
? requestSchemaParsedResult.value
|
||||||
|
: getDefaultRESTRequest()
|
||||||
|
|
||||||
|
parentCollection.requests = parentCollection.requests || []
|
||||||
|
parentCollection.requests.push({
|
||||||
|
id: request.id,
|
||||||
|
collectionID: request.collectionID,
|
||||||
|
title: request.title,
|
||||||
|
request: effectiveRequest,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return collectionTree
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TeamSearchService extends Service {
|
||||||
|
public static readonly ID = "TeamSearchService"
|
||||||
|
|
||||||
|
public endpoint = import.meta.env.VITE_BACKEND_API_URL
|
||||||
|
|
||||||
|
public teamsSearchResultsLoading = ref(false)
|
||||||
|
public teamsSearchResults = ref<TeamCollection[]>([])
|
||||||
|
public teamsSearchResultsFormattedForSpotlight = ref<
|
||||||
|
{
|
||||||
|
collectionTitles: string[]
|
||||||
|
request: {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
method: string
|
||||||
|
}
|
||||||
|
}[]
|
||||||
|
>([])
|
||||||
|
|
||||||
|
searchResultsCollections: Record<string, _SearchCollection> = {}
|
||||||
|
searchResultsRequests: Record<string, _SearchRequest> = {}
|
||||||
|
|
||||||
|
expandingCollections: Ref<string[]> = ref([])
|
||||||
|
expandedCollections: Ref<string[]> = ref([])
|
||||||
|
|
||||||
|
// FUTURE-TODO: ideally this should return the search results / formatted results instead of directly manipulating the result set
|
||||||
|
// eg: do the spotlight formatting in the spotlight searcher and not here
|
||||||
|
searchTeams = async (query: string, teamID: string) => {
|
||||||
|
if (!query.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.teamsSearchResultsLoading.value = true
|
||||||
|
|
||||||
|
this.searchResultsCollections = {}
|
||||||
|
this.searchResultsRequests = {}
|
||||||
|
this.expandedCollections.value = []
|
||||||
|
|
||||||
|
const axiosPlatformConfig = platform.auth.axiosPlatformConfig?.() ?? {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const searchResponse = await axios.get(
|
||||||
|
`${
|
||||||
|
this.endpoint
|
||||||
|
}/team-collection/search/${teamID}?searchQuery=${encodeURIComponent(
|
||||||
|
query
|
||||||
|
)}`,
|
||||||
|
axiosPlatformConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
if (searchResponse.status !== 200) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchResults = searchResponse.data.data as CollectionSearchNode[]
|
||||||
|
|
||||||
|
searchResults
|
||||||
|
.map((node) => {
|
||||||
|
const { existingCollections, existingRequests } =
|
||||||
|
convertToTeamCollection(
|
||||||
|
{
|
||||||
|
...node,
|
||||||
|
meta: {
|
||||||
|
isSearchResult: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
collections: existingCollections,
|
||||||
|
requests: existingRequests,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.forEach(({ collections, requests }) => {
|
||||||
|
this.searchResultsCollections = {
|
||||||
|
...this.searchResultsCollections,
|
||||||
|
...collections,
|
||||||
|
}
|
||||||
|
this.searchResultsRequests = {
|
||||||
|
...this.searchResultsRequests,
|
||||||
|
...requests,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const collectionFetchingPromises = Object.values(
|
||||||
|
this.searchResultsCollections
|
||||||
|
).map((col) => {
|
||||||
|
return getSingleCollection(col.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
const requestFetchingPromises = Object.values(
|
||||||
|
this.searchResultsRequests
|
||||||
|
).map((req) => {
|
||||||
|
return getSingleRequest(req.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
const collectionResponses = await Promise.all(collectionFetchingPromises)
|
||||||
|
const requestResponses = await Promise.all(requestFetchingPromises)
|
||||||
|
|
||||||
|
requestResponses.map((res) => {
|
||||||
|
if (E.isLeft(res)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = res.right.request
|
||||||
|
|
||||||
|
if (!request) return
|
||||||
|
|
||||||
|
this.searchResultsRequests[request.id] = {
|
||||||
|
id: request.id,
|
||||||
|
title: request.title,
|
||||||
|
request: JSON.parse(request.request) as TeamRequest["request"],
|
||||||
|
collectionID: request.collectionID,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
collectionResponses.map((res) => {
|
||||||
|
if (E.isLeft(res)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const collection = res.right.collection
|
||||||
|
|
||||||
|
if (!collection) return
|
||||||
|
|
||||||
|
this.searchResultsCollections[collection.id].data =
|
||||||
|
collection.data ?? null
|
||||||
|
})
|
||||||
|
|
||||||
|
const collectionTree = convertToTeamTree(
|
||||||
|
Object.values(this.searchResultsCollections),
|
||||||
|
// asserting because we've already added the missing properties after fetching the full details
|
||||||
|
Object.values(this.searchResultsRequests) as TeamRequest[]
|
||||||
|
)
|
||||||
|
|
||||||
|
this.teamsSearchResults.value = collectionTree
|
||||||
|
|
||||||
|
this.teamsSearchResultsFormattedForSpotlight.value = Object.values(
|
||||||
|
this.searchResultsRequests
|
||||||
|
).map((request) => {
|
||||||
|
return formatTeamsSearchResultsForSpotlight(
|
||||||
|
{
|
||||||
|
collectionID: request.collectionID,
|
||||||
|
name: request.title,
|
||||||
|
method: request.request.method,
|
||||||
|
id: request.id,
|
||||||
|
},
|
||||||
|
Object.values(this.searchResultsCollections)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.teamsSearchResultsLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
cascadeParentCollectionForHeaderAuthForSearchResults = (
|
||||||
|
collectionID: string
|
||||||
|
): HoppInheritedProperty => {
|
||||||
|
const defaultInheritedAuth: HoppInheritedProperty["auth"] = {
|
||||||
|
parentID: "",
|
||||||
|
parentName: "",
|
||||||
|
inheritedAuth: {
|
||||||
|
authType: "none",
|
||||||
|
authActive: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultInheritedHeaders: HoppInheritedProperty["headers"] = []
|
||||||
|
|
||||||
|
const collection = Object.values(this.searchResultsCollections).find(
|
||||||
|
(col) => col.id === collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!collection)
|
||||||
|
return { auth: defaultInheritedAuth, headers: defaultInheritedHeaders }
|
||||||
|
|
||||||
|
const inheritedAuthData = this.findInheritableParentAuth(collectionID)
|
||||||
|
const inheritedHeadersData = this.findInheritableParentHeaders(collectionID)
|
||||||
|
|
||||||
|
return {
|
||||||
|
auth: E.isRight(inheritedAuthData)
|
||||||
|
? inheritedAuthData.right
|
||||||
|
: defaultInheritedAuth,
|
||||||
|
headers: E.isRight(inheritedHeadersData)
|
||||||
|
? Object.values(inheritedHeadersData.right)
|
||||||
|
: defaultInheritedHeaders,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findInheritableParentAuth = (
|
||||||
|
collectionID: string
|
||||||
|
): E.Either<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
parentID: string
|
||||||
|
parentName: string
|
||||||
|
inheritedAuth: HoppRESTAuth
|
||||||
|
}
|
||||||
|
> => {
|
||||||
|
const collection = Object.values(this.searchResultsCollections).find(
|
||||||
|
(col) => col.id === collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!collection) {
|
||||||
|
return E.left("PARENT_NOT_FOUND" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
// has inherited data
|
||||||
|
if (collection.data) {
|
||||||
|
const parentInheritedData = JSON.parse(collection.data) as {
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
headers: HoppRESTHeader[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const inheritedAuth = parentInheritedData.auth
|
||||||
|
|
||||||
|
if (inheritedAuth.authType !== "inherit") {
|
||||||
|
return E.right({
|
||||||
|
parentID: collectionID,
|
||||||
|
parentName: collection.title,
|
||||||
|
inheritedAuth: inheritedAuth,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!collection.parentID) {
|
||||||
|
return E.left("PARENT_INHERITED_DATA_NOT_FOUND")
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.findInheritableParentAuth(collection.parentID)
|
||||||
|
}
|
||||||
|
|
||||||
|
findInheritableParentHeaders = (
|
||||||
|
collectionID: string,
|
||||||
|
existingHeaders: Record<
|
||||||
|
string,
|
||||||
|
HoppInheritedProperty["headers"][number]
|
||||||
|
> = {}
|
||||||
|
): E.Either<
|
||||||
|
string,
|
||||||
|
Record<string, HoppInheritedProperty["headers"][number]>
|
||||||
|
> => {
|
||||||
|
const collection = Object.values(this.searchResultsCollections).find(
|
||||||
|
(col) => col.id === collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!collection) {
|
||||||
|
return E.left("PARENT_NOT_FOUND" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
// see if it has headers to inherit, if yes, add it to the existing headers
|
||||||
|
if (collection.data) {
|
||||||
|
const parentInheritedData = JSON.parse(collection.data) as {
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
headers: HoppRESTHeader[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const inheritedHeaders = parentInheritedData.headers
|
||||||
|
|
||||||
|
if (inheritedHeaders) {
|
||||||
|
inheritedHeaders.forEach((header) => {
|
||||||
|
if (!existingHeaders[header.key]) {
|
||||||
|
existingHeaders[header.key] = {
|
||||||
|
parentID: collection.id,
|
||||||
|
parentName: collection.title,
|
||||||
|
inheritedHeader: header,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collection.parentID) {
|
||||||
|
return this.findInheritableParentHeaders(
|
||||||
|
collection.parentID,
|
||||||
|
existingHeaders
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(existingHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
expandCollection = async (collectionID: string) => {
|
||||||
|
if (this.expandingCollections.value.includes(collectionID)) return
|
||||||
|
|
||||||
|
const collectionToExpand = Object.values(
|
||||||
|
this.searchResultsCollections
|
||||||
|
).find((col) => col.id === collectionID)
|
||||||
|
|
||||||
|
const isAlreadyExpanded =
|
||||||
|
this.expandedCollections.value.includes(collectionID)
|
||||||
|
|
||||||
|
// only allow search result collections to be expanded
|
||||||
|
if (
|
||||||
|
isAlreadyExpanded ||
|
||||||
|
!collectionToExpand ||
|
||||||
|
!(
|
||||||
|
collectionToExpand.meta?.isSearchResult ||
|
||||||
|
collectionToExpand.meta?.insertedWhileExpanding
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.expandingCollections.value.push(collectionID)
|
||||||
|
|
||||||
|
const childCollectionsPromise = getCollectionChildCollections(collectionID)
|
||||||
|
const childRequestsPromise = getCollectionChildRequests(collectionID)
|
||||||
|
|
||||||
|
const [childCollections, childRequests] = await Promise.all([
|
||||||
|
childCollectionsPromise,
|
||||||
|
childRequestsPromise,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (E.isLeft(childCollections)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (E.isLeft(childRequests)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
childCollections.right.collection?.children
|
||||||
|
.map((child) => ({
|
||||||
|
id: child.id,
|
||||||
|
title: child.title,
|
||||||
|
data: child.data ?? null,
|
||||||
|
children: [],
|
||||||
|
requests: [],
|
||||||
|
}))
|
||||||
|
.forEach((child) => {
|
||||||
|
this.searchResultsCollections[child.id] = {
|
||||||
|
...child,
|
||||||
|
parentID: collectionID,
|
||||||
|
meta: {
|
||||||
|
isSearchResult: false,
|
||||||
|
insertedWhileExpanding: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
childRequests.right.requestsInCollection
|
||||||
|
.map((request) => ({
|
||||||
|
id: request.id,
|
||||||
|
collectionID: collectionID,
|
||||||
|
title: request.title,
|
||||||
|
request: JSON.parse(request.request) as TeamRequest["request"],
|
||||||
|
}))
|
||||||
|
.forEach((request) => {
|
||||||
|
this.searchResultsRequests[request.id] = {
|
||||||
|
...request,
|
||||||
|
meta: {
|
||||||
|
isSearchResult: false,
|
||||||
|
insertedWhileExpanding: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.teamsSearchResults.value = convertToTeamTree(
|
||||||
|
Object.values(this.searchResultsCollections),
|
||||||
|
// asserting because we've already added the missing properties after fetching the full details
|
||||||
|
Object.values(this.searchResultsRequests) as TeamRequest[]
|
||||||
|
)
|
||||||
|
|
||||||
|
// remove the collection after expanding
|
||||||
|
this.expandingCollections.value = this.expandingCollections.value.filter(
|
||||||
|
(colID) => colID !== collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
this.expandedCollections.value.push(collectionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSingleCollection = (collectionID: string) =>
|
||||||
|
runGQLQuery({
|
||||||
|
query: GetSingleCollectionDocument,
|
||||||
|
variables: {
|
||||||
|
collectionID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const getSingleRequest = (requestID: string) =>
|
||||||
|
runGQLQuery({
|
||||||
|
query: GetSingleRequestDocument,
|
||||||
|
variables: {
|
||||||
|
requestID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const getCollectionChildCollections = (collectionID: string) =>
|
||||||
|
runGQLQuery({
|
||||||
|
query: GetCollectionChildrenDocument,
|
||||||
|
variables: {
|
||||||
|
collectionID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const getCollectionChildRequests = (collectionID: string) =>
|
||||||
|
runGQLQuery({
|
||||||
|
query: GetCollectionRequestsDocument,
|
||||||
|
variables: {
|
||||||
|
collectionID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const formatTeamsSearchResultsForSpotlight = (
|
||||||
|
request: {
|
||||||
|
collectionID: string
|
||||||
|
name: string
|
||||||
|
method: string
|
||||||
|
id: string
|
||||||
|
},
|
||||||
|
parentCollections: (TeamCollection & { parentID: string | null })[]
|
||||||
|
) => {
|
||||||
|
let collectionTitles: string[] = []
|
||||||
|
|
||||||
|
let parentCollectionID: string | null = request.collectionID
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (!parentCollectionID) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentCollection = parentCollections.find(
|
||||||
|
(col) => col.id === parentCollectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!parentCollection) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionTitles = [parentCollection.title, ...collectionTitles]
|
||||||
|
parentCollectionID = parentCollection.parentID
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
collectionTitles,
|
||||||
|
request: {
|
||||||
|
name: request.name,
|
||||||
|
method: request.method,
|
||||||
|
id: request.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
HoppRESTParam,
|
HoppRESTParam,
|
||||||
parseRawKeyValueEntriesE,
|
parseRawKeyValueEntriesE,
|
||||||
parseTemplateStringE,
|
parseTemplateStringE,
|
||||||
|
HoppRESTAuth,
|
||||||
|
HoppRESTHeaders,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { arrayFlatMap, arraySort } from "../functional/array"
|
import { arrayFlatMap, arraySort } from "../functional/array"
|
||||||
import { toFormData } from "../functional/formData"
|
import { toFormData } from "../functional/formData"
|
||||||
@@ -44,7 +46,12 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
|||||||
*/
|
*/
|
||||||
export const getComputedAuthHeaders = (
|
export const getComputedAuthHeaders = (
|
||||||
envVars: Environment["variables"],
|
envVars: Environment["variables"],
|
||||||
req?: HoppRESTRequest,
|
req?:
|
||||||
|
| HoppRESTRequest
|
||||||
|
| {
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
headers: HoppRESTHeaders
|
||||||
|
},
|
||||||
auth?: HoppRESTRequest["auth"],
|
auth?: HoppRESTRequest["auth"],
|
||||||
parse = true
|
parse = true
|
||||||
) => {
|
) => {
|
||||||
@@ -75,16 +82,17 @@ export const getComputedAuthHeaders = (
|
|||||||
})
|
})
|
||||||
} else if (
|
} else if (
|
||||||
request.auth.authType === "bearer" ||
|
request.auth.authType === "bearer" ||
|
||||||
request.auth.authType === "oauth-2"
|
(request.auth.authType === "oauth-2" && request.auth.addTo === "HEADERS")
|
||||||
) {
|
) {
|
||||||
|
const token =
|
||||||
|
request.auth.authType === "bearer"
|
||||||
|
? request.auth.token
|
||||||
|
: request.auth.grantTypeInfo.token
|
||||||
|
|
||||||
headers.push({
|
headers.push({
|
||||||
active: true,
|
active: true,
|
||||||
key: "Authorization",
|
key: "Authorization",
|
||||||
value: `Bearer ${
|
value: `Bearer ${parse ? parseTemplateString(token, envVars) : token}`,
|
||||||
parse
|
|
||||||
? parseTemplateString(request.auth.token, envVars)
|
|
||||||
: request.auth.token
|
|
||||||
}`,
|
|
||||||
})
|
})
|
||||||
} else if (request.auth.authType === "api-key") {
|
} else if (request.auth.authType === "api-key") {
|
||||||
const { key, addTo } = request.auth
|
const { key, addTo } = request.auth
|
||||||
@@ -108,7 +116,12 @@ export const getComputedAuthHeaders = (
|
|||||||
* @returns The list of headers
|
* @returns The list of headers
|
||||||
*/
|
*/
|
||||||
export const getComputedBodyHeaders = (
|
export const getComputedBodyHeaders = (
|
||||||
req: HoppRESTRequest
|
req:
|
||||||
|
| HoppRESTRequest
|
||||||
|
| {
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
headers: HoppRESTHeaders
|
||||||
|
}
|
||||||
): HoppRESTHeader[] => {
|
): HoppRESTHeader[] => {
|
||||||
// If a content-type is already defined, that will override this
|
// If a content-type is already defined, that will override this
|
||||||
if (
|
if (
|
||||||
@@ -118,8 +131,10 @@ export const getComputedBodyHeaders = (
|
|||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
if (!("body" in req)) return []
|
||||||
|
|
||||||
// Body should have a non-null content-type
|
// Body should have a non-null content-type
|
||||||
if (req.body.contentType === null) return []
|
if (!req.body || req.body.contentType === null) return []
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -143,7 +158,12 @@ export type ComputedHeader = {
|
|||||||
* @returns The headers that are generated along with the source of that header
|
* @returns The headers that are generated along with the source of that header
|
||||||
*/
|
*/
|
||||||
export const getComputedHeaders = (
|
export const getComputedHeaders = (
|
||||||
req: HoppRESTRequest,
|
req:
|
||||||
|
| HoppRESTRequest
|
||||||
|
| {
|
||||||
|
auth: HoppRESTAuth
|
||||||
|
headers: HoppRESTHeaders
|
||||||
|
},
|
||||||
envVars: Environment["variables"],
|
envVars: Environment["variables"],
|
||||||
parse = true
|
parse = true
|
||||||
): ComputedHeader[] => {
|
): ComputedHeader[] => {
|
||||||
@@ -177,17 +197,40 @@ export const getComputedParams = (
|
|||||||
): ComputedParam[] => {
|
): ComputedParam[] => {
|
||||||
// When this gets complex, its best to split this function off (like with getComputedHeaders)
|
// When this gets complex, its best to split this function off (like with getComputedHeaders)
|
||||||
// API-key auth can be added to query params
|
// API-key auth can be added to query params
|
||||||
if (!req.auth || !req.auth.authActive) return []
|
if (!req.auth || !req.auth.authActive) {
|
||||||
if (req.auth.authType !== "api-key") return []
|
return []
|
||||||
if (req.auth.addTo !== "Query params") return []
|
}
|
||||||
|
|
||||||
|
if (req.auth.authType !== "api-key" && req.auth.authType !== "oauth-2") {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.auth.addTo !== "QUERY_PARAMS") {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.auth.authType === "api-key") {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: "auth" as const,
|
||||||
|
param: {
|
||||||
|
active: true,
|
||||||
|
key: parseTemplateString(req.auth.key, envVars),
|
||||||
|
value: parseTemplateString(req.auth.value, envVars),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const { grantTypeInfo } = req.auth
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: "auth",
|
source: "auth",
|
||||||
param: {
|
param: {
|
||||||
active: true,
|
active: true,
|
||||||
key: parseTemplateString(req.auth.key, envVars),
|
key: "access_token",
|
||||||
value: parseTemplateString(req.auth.value, envVars),
|
value: parseTemplateString(grantTypeInfo.token, envVars),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -231,7 +274,7 @@ function getFinalBodyFromRequest(
|
|||||||
|
|
||||||
if (request.body.contentType === "application/x-www-form-urlencoded") {
|
if (request.body.contentType === "application/x-www-form-urlencoded") {
|
||||||
const parsedBodyRecord = pipe(
|
const parsedBodyRecord = pipe(
|
||||||
request.body.body,
|
request.body.body ?? "",
|
||||||
parseRawKeyValueEntriesE,
|
parseRawKeyValueEntriesE,
|
||||||
E.map(
|
E.map(
|
||||||
flow(
|
flow(
|
||||||
@@ -268,7 +311,7 @@ function getFinalBodyFromRequest(
|
|||||||
|
|
||||||
if (request.body.contentType === "multipart/form-data") {
|
if (request.body.contentType === "multipart/form-data") {
|
||||||
return pipe(
|
return pipe(
|
||||||
request.body.body,
|
request.body.body ?? [],
|
||||||
A.filter((x) => (x.key !== "" || x.isFile) && x.active), // Remove empty keys
|
A.filter((x) => (x.key !== "" || x.isFile) && x.active), // Remove empty keys
|
||||||
|
|
||||||
// Sort files down
|
// Sort files down
|
||||||
|
|||||||
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,
|
to: RouteLocationNormalized,
|
||||||
from: RouteLocationNormalized,
|
from: RouteLocationNormalized,
|
||||||
router: Router
|
router: Router
|
||||||
) => void
|
) => void | Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the router to tell all the modules that a route navigation has completed
|
* Called by the router to tell all the modules that a route navigation has completed
|
||||||
|
|||||||
@@ -47,15 +47,21 @@ export default <HoppModule>{
|
|||||||
routes,
|
routes,
|
||||||
})
|
})
|
||||||
|
|
||||||
router.beforeEach((to, from) => {
|
router.beforeEach(async (to, from) => {
|
||||||
_isLoadingInitialRoute.value = isInitialRoute(from)
|
_isLoadingInitialRoute.value = isInitialRoute(from)
|
||||||
|
|
||||||
|
const onBeforeRouteChangePromises: Promise<any>[] = []
|
||||||
|
|
||||||
HOPP_MODULES.forEach((mod) => {
|
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) => {
|
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
|
// Instead of this a better architecture is for the router
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Environment } from "@hoppscotch/data"
|
import { Environment } from "@hoppscotch/data"
|
||||||
import { cloneDeep, isEqual, uniqueId } from "lodash-es"
|
import { cloneDeep, isEqual } from "lodash-es"
|
||||||
import { combineLatest, Observable } from "rxjs"
|
import { combineLatest, Observable } from "rxjs"
|
||||||
import { distinctUntilChanged, map, pluck } from "rxjs/operators"
|
import { distinctUntilChanged, map, pluck } from "rxjs/operators"
|
||||||
|
import { uniqueID } from "~/helpers/utils/uniqueID"
|
||||||
import { getService } from "~/modules/dioc"
|
import { getService } from "~/modules/dioc"
|
||||||
import DispatchingStore, {
|
import DispatchingStore, {
|
||||||
defineDispatchers,
|
defineDispatchers,
|
||||||
@@ -22,7 +23,7 @@ const defaultEnvironmentsState = {
|
|||||||
environments: [
|
environments: [
|
||||||
{
|
{
|
||||||
v: 1,
|
v: 1,
|
||||||
id: uniqueId(),
|
id: uniqueID(),
|
||||||
name: "My Environment Variables",
|
name: "My Environment Variables",
|
||||||
variables: [],
|
variables: [],
|
||||||
},
|
},
|
||||||
@@ -100,7 +101,7 @@ const dispatchers = defineDispatchers({
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
v: 1,
|
v: 1,
|
||||||
id: "",
|
id: uniqueID(),
|
||||||
name,
|
name,
|
||||||
variables,
|
variables,
|
||||||
},
|
},
|
||||||
@@ -123,7 +124,7 @@ const dispatchers = defineDispatchers({
|
|||||||
...environments,
|
...environments,
|
||||||
{
|
{
|
||||||
...cloneDeep(newEnvironment),
|
...cloneDeep(newEnvironment),
|
||||||
id: uniqueId(),
|
id: uniqueID(),
|
||||||
name: `${newEnvironment.name} - Duplicate`,
|
name: `${newEnvironment.name} - Duplicate`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -44,4 +44,5 @@ export default defineComponent({
|
|||||||
<route lang="yaml">
|
<route lang="yaml">
|
||||||
meta:
|
meta:
|
||||||
layout: empty
|
layout: empty
|
||||||
|
onlyGuest: true
|
||||||
</route>
|
</route>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ const importCollections = (url: unknown, type: unknown) =>
|
|||||||
content.data,
|
content.data,
|
||||||
TO.fromPredicate(isOfType("string")),
|
TO.fromPredicate(isOfType("string")),
|
||||||
TE.fromTaskOption(() => IMPORTER_INVALID_FILE_FORMAT),
|
TE.fromTaskOption(() => IMPORTER_INVALID_FILE_FORMAT),
|
||||||
TE.chain((data) => importer.importer(data))
|
TE.chain((data) => importer.importer([data]))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
<h1 class="heading">
|
<h1 class="heading">
|
||||||
{{
|
{{
|
||||||
t("team.join_team", {
|
t("team.join_team", {
|
||||||
team: inviteDetails.data.right.teamInvitation.team.name,
|
workspace: inviteDetails.data.right.teamInvitation.team.name,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
owner:
|
owner:
|
||||||
inviteDetails.data.right.teamInvitation.creator.displayName ??
|
inviteDetails.data.right.teamInvitation.creator.displayName ??
|
||||||
inviteDetails.data.right.teamInvitation.creator.email,
|
inviteDetails.data.right.teamInvitation.creator.email,
|
||||||
team: inviteDetails.data.right.teamInvitation.team.name,
|
workspace: inviteDetails.data.right.teamInvitation.team.name,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
:label="
|
:label="
|
||||||
t('team.join_team', {
|
t('team.join_team', {
|
||||||
team: inviteDetails.data.right.teamInvitation.team.name,
|
workspace: inviteDetails.data.right.teamInvitation.team.name,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -109,14 +109,14 @@
|
|||||||
<h1 class="heading">
|
<h1 class="heading">
|
||||||
{{
|
{{
|
||||||
t("team.joined_team", {
|
t("team.joined_team", {
|
||||||
team: inviteDetails.data.right.teamInvitation.team.name,
|
workspace: inviteDetails.data.right.teamInvitation.team.name,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-secondaryLight">
|
<p class="mt-2 text-secondaryLight">
|
||||||
{{
|
{{
|
||||||
t("team.joined_team_description", {
|
t("team.joined_team_description", {
|
||||||
team: inviteDetails.data.right.teamInvitation.team.name,
|
workspace: inviteDetails.data.right.teamInvitation.team.name,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -5,23 +5,31 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { handleOAuthRedirect } from "~/helpers/oauth"
|
|
||||||
import { useToast } from "~/composables/toast"
|
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import * as E from "fp-ts/Either"
|
||||||
import { onMounted } from "vue"
|
import { onMounted } from "vue"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
|
||||||
import { useRouter } from "vue-router"
|
import { useRouter } from "vue-router"
|
||||||
|
|
||||||
|
import {
|
||||||
|
PersistedOAuthConfig,
|
||||||
|
routeOAuthRedirect,
|
||||||
|
} from "~/services/oauth/oauth.service"
|
||||||
|
import { PersistenceService } from "~/services/persistence"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const tabs = useService(RESTTabService)
|
const gqlTabs = useService(GQLTabService)
|
||||||
|
const persistenceService = useService(PersistenceService)
|
||||||
|
const restTabs = useService(RESTTabService)
|
||||||
|
|
||||||
function translateOAuthRedirectError(error: string) {
|
function translateOAuthRedirectError(error: string) {
|
||||||
switch (error) {
|
switch (error) {
|
||||||
@@ -60,22 +68,59 @@ function translateOAuthRedirectError(error: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const tokenInfo = await handleOAuthRedirect()
|
const localOAuthTempConfig =
|
||||||
|
persistenceService.getLocalConfig("oauth_temp_config")
|
||||||
|
|
||||||
|
if (!localOAuthTempConfig) {
|
||||||
|
toast.error(t("authorization.oauth.something_went_wrong_on_oauth_redirect"))
|
||||||
|
router.push("/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistedOAuthConfig: PersistedOAuthConfig =
|
||||||
|
JSON.parse(localOAuthTempConfig)
|
||||||
|
|
||||||
|
const { context, source } = persistedOAuthConfig
|
||||||
|
|
||||||
|
const tokenInfo = await routeOAuthRedirect()
|
||||||
|
|
||||||
if (E.isLeft(tokenInfo)) {
|
if (E.isLeft(tokenInfo)) {
|
||||||
toast.error(translateOAuthRedirectError(tokenInfo.left))
|
toast.error(translateOAuthRedirectError(tokenInfo.left))
|
||||||
router.push("/")
|
router.push(source === "REST" ? "/" : "/graphql")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Indicates the access token generation flow originated from the modal for setting authorization/headers at the collection level
|
||||||
|
if (context?.type === "collection-properties") {
|
||||||
|
// Set the access token in `localStorage` to retrieve from the modal while redirecting back
|
||||||
|
const authConfig: PersistedOAuthConfig = {
|
||||||
|
...persistedOAuthConfig,
|
||||||
|
token: tokenInfo.right.access_token,
|
||||||
|
}
|
||||||
|
persistenceService.setLocalConfig(
|
||||||
|
"oauth_temp_config",
|
||||||
|
JSON.stringify(authConfig)
|
||||||
|
)
|
||||||
|
|
||||||
|
toast.success(t("authorization.oauth.token_fetched_successfully"))
|
||||||
|
|
||||||
|
router.push(source === "REST" ? "/" : "/graphql")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const routeToRedirect = source === "GraphQL" ? "/graphql" : "/"
|
||||||
|
const tabService = source === "GraphQL" ? gqlTabs : restTabs
|
||||||
|
|
||||||
if (
|
if (
|
||||||
tabs.currentActiveTab.value.document.request.auth.authType === "oauth-2"
|
tabService.currentActiveTab.value.document.request.auth.authType ===
|
||||||
|
"oauth-2"
|
||||||
) {
|
) {
|
||||||
tabs.currentActiveTab.value.document.request.auth.token =
|
tabService.currentActiveTab.value.document.request.auth.grantTypeInfo.token =
|
||||||
tokenInfo.right.access_token
|
tokenInfo.right.access_token
|
||||||
|
|
||||||
router.push("/")
|
toast.success(t("authorization.oauth.token_fetched_successfully"))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router.push(routeToRedirect)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -210,6 +210,8 @@ import { toggleSetting } from "~/newstore/settings"
|
|||||||
import IconVerified from "~icons/lucide/verified"
|
import IconVerified from "~icons/lucide/verified"
|
||||||
import IconSettings from "~icons/lucide/settings"
|
import IconSettings from "~icons/lucide/settings"
|
||||||
|
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
|
||||||
type ProfileTabs = "sync" | "teams"
|
type ProfileTabs = "sync" | "teams"
|
||||||
|
|
||||||
const selectedProfileTab = ref<ProfileTabs>("sync")
|
const selectedProfileTab = ref<ProfileTabs>("sync")
|
||||||
@@ -244,19 +246,28 @@ const displayName = ref(currentUser.value?.displayName || "")
|
|||||||
const updatingDisplayName = ref(false)
|
const updatingDisplayName = ref(false)
|
||||||
watchEffect(() => (displayName.value = currentUser.value?.displayName || ""))
|
watchEffect(() => (displayName.value = currentUser.value?.displayName || ""))
|
||||||
|
|
||||||
const updateDisplayName = () => {
|
const updateDisplayName = async () => {
|
||||||
|
if (!displayName.value) {
|
||||||
|
toast.error(`${t("error.empty_profile_name")}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentUser.value?.displayName === displayName.value) {
|
||||||
|
toast.error(`${t("error.same_profile_name")}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
updatingDisplayName.value = true
|
updatingDisplayName.value = true
|
||||||
platform.auth
|
|
||||||
.setDisplayName(displayName.value as string)
|
const res = await platform.auth.setDisplayName(displayName.value)
|
||||||
.then(() => {
|
|
||||||
toast.success(`${t("profile.updated")}`)
|
if (E.isLeft(res)) {
|
||||||
})
|
toast.error(t("error.something_went_wrong"))
|
||||||
.catch(() => {
|
} else if (E.isRight(res)) {
|
||||||
toast.error(`${t("error.something_went_wrong")}`)
|
toast.success(`${t("profile.updated")}`)
|
||||||
})
|
}
|
||||||
.finally(() => {
|
|
||||||
updatingDisplayName.value = false
|
updatingDisplayName.value = false
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailAddress = ref(currentUser.value?.email || "")
|
const emailAddress = ref(currentUser.value?.email || "")
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Observable } from "rxjs"
|
|||||||
import { Component } from "vue"
|
import { Component } from "vue"
|
||||||
import { getI18n } from "~/modules/i18n"
|
import { getI18n } from "~/modules/i18n"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
|
import { AxiosRequestConfig } from "axios"
|
||||||
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A common (and required) set of fields that describe a user.
|
* A common (and required) set of fields that describe a user.
|
||||||
@@ -135,6 +137,15 @@ export type AuthPlatformDef = {
|
|||||||
*/
|
*/
|
||||||
getGQLClientOptions?: () => Partial<ClientOptions>
|
getGQLClientOptions?: () => Partial<ClientOptions>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called by the platform to provide additional/different config options when
|
||||||
|
* sending requests with axios
|
||||||
|
* eg: SH needs to include cookies in the request, while Central doesn't and throws a cors error if it does
|
||||||
|
*
|
||||||
|
* @returns AxiosRequestConfig
|
||||||
|
*/
|
||||||
|
axiosPlatformConfig?: () => AxiosRequestConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the string content that should be returned when the user selects to
|
* Returns the string content that should be returned when the user selects to
|
||||||
* copy auth token from Developer Options.
|
* copy auth token from Developer Options.
|
||||||
@@ -219,9 +230,11 @@ export type AuthPlatformDef = {
|
|||||||
/**
|
/**
|
||||||
* Updates the display name of the user
|
* Updates the display name of the user
|
||||||
* @param name The new name to set this to.
|
* @param name The new name to set this to.
|
||||||
* @returns An empty promise that is resolved when the operation is complete
|
* @returns A promise that resolves with the display name update status when the operation is complete
|
||||||
*/
|
*/
|
||||||
setDisplayName: (name: string) => Promise<void>
|
setDisplayName: (
|
||||||
|
name: string
|
||||||
|
) => Promise<E.Either<GQLError<string>, undefined>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of allowed auth providers for the platform ( the currently supported ones are GOOGLE, GITHUB, EMAIL, MICROSOFT, SAML )
|
* Returns the list of allowed auth providers for the platform ( the currently supported ones are GOOGLE, GITHUB, EMAIL, MICROSOFT, SAML )
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ export class ExtensionInterceptorService
|
|||||||
try {
|
try {
|
||||||
const result = await extensionHook.sendRequest({
|
const result = await extensionHook.sendRequest({
|
||||||
...req,
|
...req,
|
||||||
|
headers: req.headers ?? {},
|
||||||
wantsBinary: true,
|
wantsBinary: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ export class HeaderInspectorService extends Service implements Inspector {
|
|||||||
index: index,
|
index: index,
|
||||||
},
|
},
|
||||||
doc: {
|
doc: {
|
||||||
text: this.t("action.learn_more"),
|
text: this.t("action.download_here"),
|
||||||
link: "https://docs.hoppscotch.io/documentation/features/inspections",
|
link: "https://hoppscotch.com/download",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
293
packages/hoppscotch-common/src/services/oauth/flows/authCode.ts
Normal file
293
packages/hoppscotch-common/src/services/oauth/flows/authCode.ts
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
import { PersistenceService } from "~/services/persistence"
|
||||||
|
import {
|
||||||
|
OauthAuthService,
|
||||||
|
PersistedOAuthConfig,
|
||||||
|
createFlowConfig,
|
||||||
|
decodeResponseAsJSON,
|
||||||
|
generateRandomString,
|
||||||
|
} from "../oauth.service"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { AuthCodeGrantTypeParams } from "@hoppscotch/data"
|
||||||
|
|
||||||
|
const persistenceService = getService(PersistenceService)
|
||||||
|
const interceptorService = getService(InterceptorService)
|
||||||
|
|
||||||
|
const AuthCodeOauthFlowParamsSchema = AuthCodeGrantTypeParams.pick({
|
||||||
|
authEndpoint: true,
|
||||||
|
tokenEndpoint: true,
|
||||||
|
clientID: true,
|
||||||
|
clientSecret: true,
|
||||||
|
scopes: true,
|
||||||
|
isPKCE: true,
|
||||||
|
codeVerifierMethod: true,
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(params) => {
|
||||||
|
return (
|
||||||
|
params.authEndpoint.length >= 1 &&
|
||||||
|
params.tokenEndpoint.length >= 1 &&
|
||||||
|
params.clientID.length >= 1 &&
|
||||||
|
params.clientSecret.length >= 1 &&
|
||||||
|
(!params.scopes || params.scopes.trim().length >= 1)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Minimum length requirement not met for one or more parameters",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.refine((params) => (params.isPKCE ? !!params.codeVerifierMethod : true), {
|
||||||
|
message: "codeVerifierMethod is required when using PKCE",
|
||||||
|
path: ["codeVerifierMethod"],
|
||||||
|
})
|
||||||
|
|
||||||
|
export type AuthCodeOauthFlowParams = z.infer<
|
||||||
|
typeof AuthCodeOauthFlowParamsSchema
|
||||||
|
>
|
||||||
|
|
||||||
|
export const getDefaultAuthCodeOauthFlowParams =
|
||||||
|
(): AuthCodeOauthFlowParams => ({
|
||||||
|
authEndpoint: "",
|
||||||
|
tokenEndpoint: "",
|
||||||
|
clientID: "",
|
||||||
|
clientSecret: "",
|
||||||
|
scopes: undefined,
|
||||||
|
isPKCE: false,
|
||||||
|
codeVerifierMethod: "S256",
|
||||||
|
})
|
||||||
|
|
||||||
|
const initAuthCodeOauthFlow = async ({
|
||||||
|
tokenEndpoint,
|
||||||
|
clientID,
|
||||||
|
clientSecret,
|
||||||
|
scopes,
|
||||||
|
authEndpoint,
|
||||||
|
isPKCE,
|
||||||
|
codeVerifierMethod,
|
||||||
|
}: AuthCodeOauthFlowParams) => {
|
||||||
|
const state = generateRandomString()
|
||||||
|
|
||||||
|
let codeVerifier: string | undefined
|
||||||
|
let codeChallenge: string | undefined
|
||||||
|
|
||||||
|
if (isPKCE) {
|
||||||
|
codeVerifier = generateCodeVerifier()
|
||||||
|
codeChallenge = await generateCodeChallenge(
|
||||||
|
codeVerifier,
|
||||||
|
codeVerifierMethod
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let oauthTempConfig: {
|
||||||
|
state: string
|
||||||
|
grant_type: "AUTHORIZATION_CODE"
|
||||||
|
authEndpoint: string
|
||||||
|
tokenEndpoint: string
|
||||||
|
clientSecret: string
|
||||||
|
clientID: string
|
||||||
|
isPKCE: boolean
|
||||||
|
codeVerifier?: string
|
||||||
|
codeVerifierMethod?: string
|
||||||
|
codeChallenge?: string
|
||||||
|
scopes?: string
|
||||||
|
} = {
|
||||||
|
state,
|
||||||
|
grant_type: "AUTHORIZATION_CODE",
|
||||||
|
authEndpoint,
|
||||||
|
tokenEndpoint,
|
||||||
|
clientSecret,
|
||||||
|
clientID,
|
||||||
|
isPKCE,
|
||||||
|
codeVerifierMethod,
|
||||||
|
scopes,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codeVerifier && codeChallenge) {
|
||||||
|
oauthTempConfig = {
|
||||||
|
...oauthTempConfig,
|
||||||
|
codeVerifier,
|
||||||
|
codeChallenge,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const localOAuthTempConfig =
|
||||||
|
persistenceService.getLocalConfig("oauth_temp_config")
|
||||||
|
|
||||||
|
const persistedOAuthConfig: PersistedOAuthConfig = localOAuthTempConfig
|
||||||
|
? { ...JSON.parse(localOAuthTempConfig) }
|
||||||
|
: {}
|
||||||
|
|
||||||
|
const { grant_type, ...rest } = oauthTempConfig
|
||||||
|
|
||||||
|
// persist the state so we can compare it when we get redirected back
|
||||||
|
// also persist the grant_type,tokenEndpoint and clientSecret so we can use them when we get redirected back
|
||||||
|
persistenceService.setLocalConfig(
|
||||||
|
"oauth_temp_config",
|
||||||
|
JSON.stringify(<PersistedOAuthConfig>{
|
||||||
|
...persistedOAuthConfig,
|
||||||
|
fields: rest,
|
||||||
|
grant_type,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
let url: URL
|
||||||
|
|
||||||
|
try {
|
||||||
|
url = new URL(authEndpoint)
|
||||||
|
} catch (e) {
|
||||||
|
return E.left("INVALID_AUTH_ENDPOINT")
|
||||||
|
}
|
||||||
|
|
||||||
|
url.searchParams.set("grant_type", "authorization_code")
|
||||||
|
url.searchParams.set("client_id", clientID)
|
||||||
|
url.searchParams.set("state", state)
|
||||||
|
url.searchParams.set("response_type", "code")
|
||||||
|
url.searchParams.set("redirect_uri", OauthAuthService.redirectURI)
|
||||||
|
|
||||||
|
if (scopes) url.searchParams.set("scope", scopes)
|
||||||
|
|
||||||
|
if (codeVerifierMethod && codeChallenge) {
|
||||||
|
url.searchParams.set("code_challenge", codeChallenge)
|
||||||
|
url.searchParams.set("code_challenge_method", codeVerifierMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to the authorization server
|
||||||
|
window.location.assign(url.toString())
|
||||||
|
|
||||||
|
return E.right(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRedirectForAuthCodeOauthFlow = async (localConfig: string) => {
|
||||||
|
// parse the query string
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
|
||||||
|
const code = params.get("code")
|
||||||
|
const state = params.get("state")
|
||||||
|
const error = params.get("error")
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return E.left("AUTH_SERVER_RETURNED_ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
return E.left("AUTH_TOKEN_REQUEST_FAILED")
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedSchema = z.object({
|
||||||
|
source: z.optional(z.string()),
|
||||||
|
state: z.string(),
|
||||||
|
tokenEndpoint: z.string(),
|
||||||
|
clientSecret: z.string(),
|
||||||
|
clientID: z.string(),
|
||||||
|
codeVerifier: z.string().optional(),
|
||||||
|
codeChallenge: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const decodedLocalConfig = expectedSchema.safeParse(
|
||||||
|
JSON.parse(localConfig).fields
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!decodedLocalConfig.success) {
|
||||||
|
return E.left("INVALID_LOCAL_CONFIG")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the state matches
|
||||||
|
if (decodedLocalConfig.data.state !== state) {
|
||||||
|
return E.left("INVALID_STATE")
|
||||||
|
}
|
||||||
|
|
||||||
|
// exchange the code for a token
|
||||||
|
const formData = new URLSearchParams()
|
||||||
|
formData.append("grant_type", "authorization_code")
|
||||||
|
formData.append("code", code)
|
||||||
|
formData.append("client_id", decodedLocalConfig.data.clientID)
|
||||||
|
formData.append("client_secret", decodedLocalConfig.data.clientSecret)
|
||||||
|
formData.append("redirect_uri", OauthAuthService.redirectURI)
|
||||||
|
|
||||||
|
if (decodedLocalConfig.data.codeVerifier) {
|
||||||
|
formData.append("code_verifier", decodedLocalConfig.data.codeVerifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { response } = interceptorService.runRequest({
|
||||||
|
url: decodedLocalConfig.data.tokenEndpoint,
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
data: formData.toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await response
|
||||||
|
|
||||||
|
if (E.isLeft(res)) {
|
||||||
|
return E.left("AUTH_TOKEN_REQUEST_FAILED" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
const responsePayload = decodeResponseAsJSON(res.right)
|
||||||
|
|
||||||
|
if (E.isLeft(responsePayload)) {
|
||||||
|
return E.left("AUTH_TOKEN_REQUEST_FAILED" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
const withAccessTokenSchema = z.object({
|
||||||
|
access_token: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const parsedTokenResponse = withAccessTokenSchema.safeParse(
|
||||||
|
responsePayload.right
|
||||||
|
)
|
||||||
|
|
||||||
|
return parsedTokenResponse.success
|
||||||
|
? E.right(parsedTokenResponse.data)
|
||||||
|
: E.left("AUTH_TOKEN_REQUEST_INVALID_RESPONSE" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateCodeVerifier = () => {
|
||||||
|
const characters =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
|
||||||
|
const length = Math.floor(Math.random() * (128 - 43 + 1)) + 43 // Random length between 43 and 128
|
||||||
|
let codeVerifier = ""
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * characters.length)
|
||||||
|
codeVerifier += characters[randomIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
return codeVerifier
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateCodeChallenge = async (
|
||||||
|
codeVerifier: string,
|
||||||
|
strategy: AuthCodeOauthFlowParams["codeVerifierMethod"]
|
||||||
|
) => {
|
||||||
|
if (strategy === "plain") {
|
||||||
|
return codeVerifier
|
||||||
|
}
|
||||||
|
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const data = encoder.encode(codeVerifier)
|
||||||
|
|
||||||
|
const buffer = await crypto.subtle.digest("SHA-256", data)
|
||||||
|
|
||||||
|
return encodeArrayBufferAsUrlEncodedBase64(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
const encodeArrayBufferAsUrlEncodedBase64 = (buffer: ArrayBuffer) => {
|
||||||
|
const hashArray = Array.from(new Uint8Array(buffer))
|
||||||
|
const hashBase64URL = btoa(String.fromCharCode(...hashArray))
|
||||||
|
.replace(/=/g, "")
|
||||||
|
.replace(/\+/g, "-")
|
||||||
|
.replace(/\//g, "_")
|
||||||
|
|
||||||
|
return hashBase64URL
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createFlowConfig(
|
||||||
|
"AUTHORIZATION_CODE" as const,
|
||||||
|
AuthCodeOauthFlowParamsSchema,
|
||||||
|
initAuthCodeOauthFlow,
|
||||||
|
handleRedirectForAuthCodeOauthFlow
|
||||||
|
)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user