chore: merge hoppscotch/main into hoppscotch/next
This commit is contained in:
commit
bf9e6c423d
101 changed files with 1654 additions and 1315 deletions
|
|
@ -9,7 +9,7 @@ curlCheck() {
|
|||
}
|
||||
|
||||
if [ "$ENABLE_SUBPATH_BASED_ACCESS" = "true" ]; then
|
||||
curlCheck "http://localhost:80/backend/ping" || exit 1
|
||||
curlCheck "http://localhost:${HOPP_AIO_ALTERNATE_PORT:-80}/backend/ping" || exit 1
|
||||
else
|
||||
curlCheck "http://localhost:3000" || exit 1
|
||||
curlCheck "http://localhost:3100" || exit 1
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "hoppscotch-agent",
|
||||
"private": true,
|
||||
"version": "0.1.10",
|
||||
"version": "0.1.11",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
|
|
|||
16
packages/hoppscotch-agent/src-tauri/Cargo.lock
generated
16
packages/hoppscotch-agent/src-tauri/Cargo.lock
generated
|
|
@ -1476,6 +1476,16 @@ dependencies = [
|
|||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "file-rotate"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e8e2fa049328a1f3295991407a88585805d126dfaadf74b9fe8c194c730aafc"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"flate2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.25"
|
||||
|
|
@ -2066,7 +2076,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hoppscotch-agent"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"axum",
|
||||
|
|
@ -2074,6 +2084,8 @@ dependencies = [
|
|||
"base16",
|
||||
"chrono",
|
||||
"dashmap",
|
||||
"dirs 6.0.0",
|
||||
"file-rotate",
|
||||
"lazy_static",
|
||||
"native-dialog",
|
||||
"rand 0.8.5",
|
||||
|
|
@ -6208,7 +6220,7 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "hoppscotch-agent"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
description = "A cross-platform HTTP request agent for Hoppscotch for advanced request handling including custom headers, certificates, proxies, and local system integration."
|
||||
authors = ["AndrewBastin", "CuriousCorrelation"]
|
||||
edition = "2021"
|
||||
|
|
@ -45,6 +45,8 @@ tauri-plugin-single-instance = "2.0.1"
|
|||
tauri-plugin-http = { version = "2.0.1", features = ["gzip"] }
|
||||
native-dialog = "0.7.0"
|
||||
sha2 = "0.10.8"
|
||||
file-rotate = "0.8.0"
|
||||
dirs = "6.0.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
tempfile = { version = "3.13.0" }
|
||||
|
|
|
|||
|
|
@ -53,11 +53,17 @@ pub enum AgentError {
|
|||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Log init error: {0}")]
|
||||
LogInit(#[from] tracing_appender::rolling::InitError),
|
||||
LogInit(String),
|
||||
#[error("Log init global error: {0}")]
|
||||
LogInitGlobal(#[from] tracing::subscriber::SetGlobalDefaultError),
|
||||
}
|
||||
|
||||
impl From<tracing_appender::rolling::InitError> for AgentError {
|
||||
fn from(err: tracing_appender::rolling::InitError) -> Self {
|
||||
AgentError::LogInit(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for AgentError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, error_message) = match self {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ pub mod controller;
|
|||
pub mod dialog;
|
||||
pub mod error;
|
||||
pub mod global;
|
||||
pub mod logger;
|
||||
pub mod model;
|
||||
pub mod route;
|
||||
pub mod server;
|
||||
|
|
@ -16,12 +17,13 @@ use std::sync::Arc;
|
|||
use tauri::{AppHandle, Emitter, Listener, Manager, WebviewWindowBuilder};
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing_subscriber::{fmt::format::JsonFields, EnvFilter};
|
||||
|
||||
use error::{AgentError, AgentResult};
|
||||
use model::{LogGuard, Payload};
|
||||
use model::Payload;
|
||||
use state::AppState;
|
||||
|
||||
pub const HOPPSCOTCH_AGENT_IDENTIFIER: &str = "io.hoppscotch.agent";
|
||||
|
||||
#[tracing::instrument(skip(app_handle))]
|
||||
fn create_main_window(app_handle: &AppHandle) -> AgentResult<()> {
|
||||
tracing::info!("Creating main application window");
|
||||
|
|
@ -100,8 +102,6 @@ pub fn run() {
|
|||
}))
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
.setup(move |app| {
|
||||
// let _ = setup_logging(&app.handle())?;
|
||||
|
||||
tracing::info!("Setting up application");
|
||||
let app_handle = app.handle();
|
||||
|
||||
|
|
@ -258,47 +258,3 @@ pub fn run() {
|
|||
_ => {}
|
||||
});
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(app_handle))]
|
||||
pub fn setup_logging(app_handle: &AppHandle) -> AgentResult<()> {
|
||||
tracing::info!("Setting up logging system");
|
||||
|
||||
let app_data_dir = app_handle.path().app_data_dir()?;
|
||||
tracing::debug!(path = ?app_data_dir, "Creating app data directory");
|
||||
std::fs::create_dir_all(&app_data_dir)?;
|
||||
|
||||
tracing::debug!("Configuring file appender");
|
||||
let file_appender = tracing_appender::rolling::RollingFileAppender::builder()
|
||||
.rotation(tracing_appender::rolling::Rotation::DAILY)
|
||||
.filename_prefix("hoppscotch-agent")
|
||||
.filename_suffix("log")
|
||||
.max_log_files(1)
|
||||
.build(&app_data_dir)?;
|
||||
|
||||
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
|
||||
|
||||
tracing::debug!("Building subscriber with JSON formatting");
|
||||
let subscriber = tracing_subscriber::fmt()
|
||||
.fmt_fields(JsonFields::new())
|
||||
.with_target(false)
|
||||
.with_writer(non_blocking)
|
||||
.with_ansi(false)
|
||||
.with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339())
|
||||
.with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
if cfg!(debug_assertions) {
|
||||
"debug"
|
||||
} else {
|
||||
"info"
|
||||
}
|
||||
.into()
|
||||
}))
|
||||
.with_filter_reloading()
|
||||
.finish();
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber)?;
|
||||
|
||||
app_handle.manage(LogGuard(_guard));
|
||||
|
||||
tracing::info!("Logging system initialized successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
53
packages/hoppscotch-agent/src-tauri/src/logger.rs
Normal file
53
packages/hoppscotch-agent/src-tauri/src/logger.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use file_rotate::{compression::Compression, suffix::AppendCount, ContentLimit, FileRotate};
|
||||
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
use crate::HOPPSCOTCH_AGENT_IDENTIFIER;
|
||||
|
||||
pub struct LogGuard(pub tracing_appender::non_blocking::WorkerGuard);
|
||||
|
||||
pub fn setup(log_dir: &PathBuf) -> Result<LogGuard, Box<dyn std::error::Error>> {
|
||||
std::fs::create_dir_all(log_dir)?;
|
||||
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| "debug".into());
|
||||
|
||||
let log_file_path = log_dir.join(&format!("{}.log", HOPPSCOTCH_AGENT_IDENTIFIER));
|
||||
tracing::info!(log_file_path =? &log_file_path);
|
||||
|
||||
let file = FileRotate::new(
|
||||
&log_file_path,
|
||||
AppendCount::new(5),
|
||||
ContentLimit::Bytes(10 * 1024 * 1024),
|
||||
Compression::None,
|
||||
None,
|
||||
);
|
||||
|
||||
let (non_blocking, guard) = tracing_appender::non_blocking(file);
|
||||
|
||||
let console_layer = fmt::layer()
|
||||
.with_writer(std::io::stdout)
|
||||
.with_thread_ids(true)
|
||||
.with_thread_names(true)
|
||||
.with_ansi(!cfg!(target_os = "windows"));
|
||||
|
||||
let file_layer = fmt::layer()
|
||||
.with_writer(non_blocking)
|
||||
.with_ansi(false)
|
||||
.with_thread_ids(true)
|
||||
.with_thread_names(true)
|
||||
.with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339());
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(env_filter)
|
||||
.with(file_layer)
|
||||
.with(console_layer)
|
||||
.init();
|
||||
|
||||
tracing::info!(
|
||||
log_file = %log_file_path.display(),
|
||||
"Logging initialized with rotating file"
|
||||
);
|
||||
|
||||
Ok(LogGuard(guard))
|
||||
}
|
||||
|
|
@ -1,16 +1,46 @@
|
|||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use hoppscotch_agent_lib::{
|
||||
logger::{self, LogGuard},
|
||||
HOPPSCOTCH_AGENT_IDENTIFIER,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| format!("{}=debug", env!("CARGO_CRATE_NAME")).into()),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer().without_time())
|
||||
.init();
|
||||
// Follows how `tauri` does this and exactly matches desktop's approach
|
||||
// see: https://github.com/tauri-apps/tauri/blob/dev/crates/tauri/src/path/desktop.rs
|
||||
let path = {
|
||||
#[cfg(target_os = "macos")]
|
||||
let path =
|
||||
dirs::home_dir().map(|dir| dir.join("Library/Logs").join(HOPPSCOTCH_AGENT_IDENTIFIER));
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let path =
|
||||
dirs::data_local_dir().map(|dir| dir.join(HOPPSCOTCH_AGENT_IDENTIFIER).join("logs"));
|
||||
|
||||
path
|
||||
};
|
||||
|
||||
let Some(log_file_path) = path else {
|
||||
eprint!("Failed to setup logging!");
|
||||
|
||||
println!("Starting Hoppscotch Agent...");
|
||||
|
||||
return hoppscotch_agent_lib::run();
|
||||
};
|
||||
|
||||
let Ok(LogGuard(guard)) = logger::setup(&log_file_path) else {
|
||||
eprint!("Failed to setup logging!");
|
||||
|
||||
println!("Starting Hoppscotch Agent...");
|
||||
|
||||
return hoppscotch_agent_lib::run();
|
||||
};
|
||||
|
||||
// This keeps the guard alive, this is scoped to `main`
|
||||
// so it can only drop when the entire app exits,
|
||||
// so safe to have it like this.
|
||||
let _guard = guard;
|
||||
|
||||
tracing::info!("Starting Hoppscotch Agent...");
|
||||
|
||||
|
|
|
|||
|
|
@ -96,10 +96,9 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> {
|
|||
}
|
||||
}
|
||||
"maximize_window" => {
|
||||
app.emit("maximize-window", ())
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::error!("Failed to emit maximize-window event: {}", e);
|
||||
});
|
||||
app.emit("maximize-window", ()).unwrap_or_else(|e| {
|
||||
tracing::error!("Failed to emit maximize-window event: {}", e);
|
||||
});
|
||||
if let Err(e) = show_main_window(&app) {
|
||||
tracing::error!("Failed to maximize window: {}", e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2.0.0-rc",
|
||||
"productName": "Hoppscotch Agent",
|
||||
"version": "0.1.10",
|
||||
"version": "0.1.11",
|
||||
"identifier": "io.hoppscotch.agent",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2.0.0-rc",
|
||||
"productName": "Hoppscotch Agent Portable",
|
||||
"version": "0.1.10",
|
||||
"version": "0.1.11",
|
||||
"identifier": "io.hoppscotch.agent",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "hoppscotch-backend",
|
||||
"version": "2025.5.1",
|
||||
"version": "2025.5.2",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@hoppscotch/cli",
|
||||
"version": "0.23.1",
|
||||
"version": "0.23.2",
|
||||
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
||||
"homepage": "https://hoppscotch.io",
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@hoppscotch/common",
|
||||
"private": true,
|
||||
"version": "2025.5.1",
|
||||
"version": "2025.5.2",
|
||||
"scripts": {
|
||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||
"test": "vitest --run",
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
"do-lintfix": "pnpm run lintfix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "10.1.1",
|
||||
"@apidevtools/swagger-parser": "11.0.1",
|
||||
"@codemirror/autocomplete": "6.18.1",
|
||||
"@codemirror/commands": "6.7.0",
|
||||
"@codemirror/lang-javascript": "6.2.2",
|
||||
|
|
@ -39,8 +39,8 @@
|
|||
"@hoppscotch/httpsnippet": "3.0.7",
|
||||
"@hoppscotch/js-sandbox": "workspace:^",
|
||||
"@hoppscotch/kernel": "workspace:^",
|
||||
"@hoppscotch/plugin-appload": "github:CuriousCorrelation/tauri-plugin-appload",
|
||||
"@hoppscotch/ui": "0.2.2",
|
||||
"@hoppscotch/plugin-appload": "github:CuriousCorrelation/tauri-plugin-appload#5939b8f",
|
||||
"@hoppscotch/ui": "0.2.5",
|
||||
"@hoppscotch/vue-toasted": "0.1.0",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@noble/curves": "1.6.0",
|
||||
|
|
|
|||
|
|
@ -124,7 +124,6 @@ declare module 'vue' {
|
|||
HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
|
||||
HistoryPersonal: typeof import('./components/history/Personal.vue')['default']
|
||||
HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
|
||||
HoppAccordion: typeof import('@hoppscotch/ui')['HoppAccordion']
|
||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
||||
|
|
@ -146,7 +145,6 @@ declare module 'vue' {
|
|||
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
||||
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
|
||||
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
||||
HoppSmartTextarea: typeof import('@hoppscotch/ui')['HoppSmartTextarea']
|
||||
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
|
||||
HoppSmartTree: typeof import('@hoppscotch/ui')['HoppSmartTree']
|
||||
HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow']
|
||||
|
|
|
|||
|
|
@ -22,11 +22,23 @@
|
|||
:on-shown="() => instanceSwitcherRef.focus()"
|
||||
>
|
||||
<div class="flex items-center cursor-pointer">
|
||||
<span
|
||||
class="!font-bold uppercase tracking-wide !text-secondaryDark pr-1"
|
||||
>
|
||||
{{ instanceDisplayName }}
|
||||
</span>
|
||||
<div class="flex">
|
||||
<span
|
||||
class="!font-bold uppercase tracking-wide !text-secondaryDark pr-1"
|
||||
>
|
||||
{{ instanceDisplayName }}
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
currentState.status === 'connected' &&
|
||||
'type' in currentState.instance &&
|
||||
currentState.instance.type === 'vendored'
|
||||
"
|
||||
class="!font-bold uppercase tracking-wide !text-secondaryDark pr-1"
|
||||
>
|
||||
{{ platform.instance.displayConfig.description }}
|
||||
</span>
|
||||
</div>
|
||||
<IconChevronDown class="h-4 w-4 text-secondaryDark" />
|
||||
</div>
|
||||
<template #content="{ hide }">
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
class="flex items-start px-4 py-2 text-tiny text-secondaryDark rounded-md"
|
||||
:class="color"
|
||||
>
|
||||
<component :is="icon" class="mr-2 shrink-0" />
|
||||
<component :is="icon" class="mr-2 shrink-0 svg-icons" />
|
||||
|
||||
<div class="flex flex-col space-y-1 overflow-x-auto text-xs">
|
||||
<div class="text-secondaryLight">{{ formattedTimestamp }}</div>
|
||||
<div class="flex flex-col space-y-2 overflow-x-auto text-xs flex-1">
|
||||
<div class="text-secondary">{{ formattedTimestamp }}</div>
|
||||
|
||||
<div class="flex flex-col space-y-1">
|
||||
<ConsoleValue
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
<template>
|
||||
<div class="whitespace-pre-wrap font-mono text-sm">
|
||||
<div class="whitespace-pre-wrap font-mono text-[12px]">
|
||||
<VueJsonPretty
|
||||
v-if="isObjectOrArray"
|
||||
:data="parsedValue"
|
||||
:theme="treeViewTheme"
|
||||
:show-line="false"
|
||||
:show-line-numbers="true"
|
||||
:deep="2"
|
||||
:class="snippetColors"
|
||||
class="p-4 bg-primary text-secondaryDark border !border-dividerLight !text-[12px] rounded !font-mono"
|
||||
/>
|
||||
|
||||
<pre
|
||||
v-else-if="isStringifiedObject"
|
||||
class="overflow-auto max-h-96 p-4"
|
||||
:class="snippetColors"
|
||||
>{{ prettyStringified }}
|
||||
v-else-if="parsedJSON"
|
||||
class="overflow-auto max-h-96 p-4 bg-primary text-secondaryDark border !border-dividerLight rounded"
|
||||
>{{ formattedJSONString }}
|
||||
</pre>
|
||||
|
||||
<pre v-else
|
||||
<pre v-else class="truncate"
|
||||
>{{ formattedPrimitive }}
|
||||
</pre>
|
||||
</div>
|
||||
|
|
@ -25,52 +27,51 @@ import { computed } from "vue"
|
|||
import VueJsonPretty from "vue-json-pretty"
|
||||
|
||||
import "vue-json-pretty/lib/styles.css"
|
||||
import { useColorMode } from "~/composables/theming"
|
||||
|
||||
const props = defineProps<{ value: unknown }>()
|
||||
|
||||
const snippetColors = `
|
||||
border rounded-md bg-gray-50 text-black !border-gray-200
|
||||
dark:bg-gray-900 dark:text-gray-100 dark:!border-gray-700
|
||||
`
|
||||
const theme = useColorMode()
|
||||
|
||||
const isObjectOrArray = computed(() => {
|
||||
return typeof props.value === "object" && props.value !== null
|
||||
})
|
||||
const isObjectOrArray = computed(
|
||||
() => typeof props.value === "object" && props.value !== null
|
||||
)
|
||||
|
||||
const parsedJSON = computed(() => {
|
||||
if (typeof props.value !== "string") {
|
||||
return null
|
||||
}
|
||||
|
||||
const isStringifiedObject = computed(() => {
|
||||
if (typeof props.value !== "string") return false
|
||||
try {
|
||||
const parsed = JSON.parse(props.value)
|
||||
return typeof parsed === "object" && parsed !== null
|
||||
return typeof parsed === "object" && parsed !== null ? parsed : null
|
||||
} catch {
|
||||
return false
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
const parsedValue = computed(() => {
|
||||
if (isObjectOrArray.value) return props.value
|
||||
const parsedValue = computed(() =>
|
||||
isObjectOrArray.value ? props.value : parsedJSON.value
|
||||
)
|
||||
|
||||
if (isStringifiedObject.value && typeof props.value === "string") {
|
||||
try {
|
||||
return JSON.parse(props.value)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
const formattedJSONString = computed(() => {
|
||||
if (typeof props.value !== "string") {
|
||||
return ""
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
if (parsedJSON.value) {
|
||||
// Return the original string if it looks already formatted
|
||||
const hasNewlines = props.value.includes("\n")
|
||||
const hasIndentation = props.value.match(/^\s{2,}["[{]/m) !== null
|
||||
|
||||
const prettyStringified = computed(() => {
|
||||
if (typeof props.value === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(props.value)
|
||||
return JSON.stringify(parsed, null, 2)
|
||||
} catch {
|
||||
if (hasNewlines || hasIndentation) {
|
||||
return props.value
|
||||
}
|
||||
|
||||
return JSON.stringify(parsedJSON.value, null, 2)
|
||||
}
|
||||
return ""
|
||||
|
||||
return props.value
|
||||
})
|
||||
|
||||
const formattedPrimitive = computed(() => {
|
||||
|
|
@ -98,4 +99,16 @@ const formattedPrimitive = computed(() => {
|
|||
return "[Unserializable]"
|
||||
}
|
||||
})
|
||||
|
||||
const isDarkTheme = computed(() => ["dark", "black"].includes(theme.value))
|
||||
|
||||
const treeViewTheme = computed(() => (isDarkTheme.value ? "dark" : "light"))
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vjs-tree-node.is-highlight,
|
||||
.vjs-tree-node:hover {
|
||||
background-color: var(--primary-light-color) !important;
|
||||
color: var(--secondary-dark-color) !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@
|
|||
:placeholder="`${t('action.search')}`"
|
||||
:context-menu-enabled="false"
|
||||
class="border border-dividerDark focus:border-primaryDark rounded"
|
||||
:readonly="isFilterInputDisabled"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-if="!isScopeSelector"
|
||||
|
|
@ -701,14 +700,6 @@ const editGlobalEnv = () => {
|
|||
invokeAction("modals.global.environment.update", {})
|
||||
}
|
||||
|
||||
// Filter input disabled if no environments are available
|
||||
const isFilterInputDisabled = computed(() => {
|
||||
if (selectedEnvTab.value === "my-environments") {
|
||||
return myEnvironments.value.length === 0
|
||||
}
|
||||
return teamEnvironmentList.value.length === 0
|
||||
})
|
||||
|
||||
const editEnv = () => {
|
||||
if (selectedEnv.value.type === "MY_ENV" && selectedEnv.value.name) {
|
||||
invokeAction("modals.my.environment.edit", {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
autocomplete="off"
|
||||
class="flex w-full bg-transparent px-4 py-2 h-8 border-b border-dividerLight"
|
||||
:placeholder="t('action.search')"
|
||||
:disabled="!environments.length"
|
||||
/>
|
||||
<div
|
||||
class="sticky top-upperPrimaryStickyFold z-10 flex flex-1 flex-shrink-0 justify-between overflow-x-auto border-b border-dividerLight bg-primary"
|
||||
|
|
|
|||
|
|
@ -88,7 +88,10 @@
|
|||
:placeholder="`${t('count.variable', {
|
||||
count: index + 1,
|
||||
})}`"
|
||||
:name="'param' + index"
|
||||
:class="{
|
||||
'opacity-25': isViewer,
|
||||
}"
|
||||
:name="'variable' + index"
|
||||
:disabled="isViewer"
|
||||
/>
|
||||
<SmartEnvInput
|
||||
|
|
@ -100,6 +103,7 @@
|
|||
:select-text-on-mount="
|
||||
env.key ? env.key === editingVariableName : false
|
||||
"
|
||||
:readonly="isViewer"
|
||||
/>
|
||||
<SmartEnvInput
|
||||
v-model="env.currentValue"
|
||||
|
|
@ -495,8 +499,13 @@ const saveEnvironment = async () => {
|
|||
secretVariables
|
||||
)
|
||||
|
||||
currentEnvironmentValueService.addEnvironment(
|
||||
editingID.value,
|
||||
nonSecretVariables
|
||||
)
|
||||
|
||||
// If the user is a viewer, we don't need to update the environment in BE
|
||||
// just update the secret environment in the local storage
|
||||
// just update the secret environment and current environment in the local storage
|
||||
if (props.isViewer) {
|
||||
hideModal()
|
||||
toast.success(`${t("environment.updated")}`)
|
||||
|
|
@ -521,12 +530,6 @@ const saveEnvironment = async () => {
|
|||
toast.success(`${t("environment.updated")}`)
|
||||
|
||||
isLoading.value = false
|
||||
if (editingID.value) {
|
||||
currentEnvironmentValueService.addEnvironment(
|
||||
editingID.value,
|
||||
nonSecretVariables
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
)()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
autocomplete="off"
|
||||
class="flex w-full bg-transparent px-4 py-2 h-8 border-b border-dividerLight"
|
||||
:placeholder="t('action.search')"
|
||||
:disabled="loading || !teamEnvironments.length"
|
||||
:disabled="loading"
|
||||
/>
|
||||
<div
|
||||
class="sticky top-upperPrimaryStickyFold z-10 flex flex-1 flex-shrink-0 justify-between overflow-x-auto border-b border-dividerLight bg-primary"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@
|
|||
:value="tab.document.request.method"
|
||||
:readonly="!isCustomMethod"
|
||||
:placeholder="`${t('request.method')}`"
|
||||
:style="{
|
||||
color: getMethodLabelColor(tab.document.request.method),
|
||||
}"
|
||||
@input="onSelectMethod($event)"
|
||||
/>
|
||||
</HoppSmartSelectWrapper>
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import { HoppRequestDocument } from "~/helpers/rest/document"
|
|||
import { useResponseBody } from "@composables/lens-actions"
|
||||
import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
||||
import {
|
||||
HoppRESTResponseOriginalRequest,
|
||||
HoppRESTRequestResponse,
|
||||
RESTResOriginalReqSchemaVersion,
|
||||
HoppRESTResponseOriginalRequest,
|
||||
makeHoppRESTResponseOriginalRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { editRESTRequest } from "~/newstore/collections"
|
||||
import { useToast } from "@composables/toast"
|
||||
|
|
@ -94,17 +94,17 @@ const onSaveAsExample = () => {
|
|||
requestVariables,
|
||||
} = response.req
|
||||
|
||||
const originalRequest: HoppRESTResponseOriginalRequest = {
|
||||
v: RESTResOriginalReqSchemaVersion,
|
||||
method,
|
||||
endpoint,
|
||||
headers,
|
||||
body,
|
||||
auth,
|
||||
params,
|
||||
name,
|
||||
requestVariables,
|
||||
}
|
||||
const originalRequest: HoppRESTResponseOriginalRequest =
|
||||
makeHoppRESTResponseOriginalRequest({
|
||||
method,
|
||||
endpoint,
|
||||
headers,
|
||||
body,
|
||||
auth,
|
||||
params,
|
||||
name,
|
||||
requestVariables,
|
||||
})
|
||||
|
||||
const resName = responseName.value.trim()
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
</span>
|
||||
<span class="inline-flex text-secondaryDark">
|
||||
<icon-lucide-minus class="svg-icons mr-2" />
|
||||
{{ env.value }}
|
||||
{{ env.currentValue }}
|
||||
</span>
|
||||
<span
|
||||
v-if="status === 'updations'"
|
||||
|
|
@ -45,7 +45,7 @@ type Status = "updations" | "additions" | "deletions"
|
|||
type Props = {
|
||||
env: {
|
||||
key: string
|
||||
value: string
|
||||
currentValue: string
|
||||
previousValue?: string
|
||||
}
|
||||
status: Status
|
||||
|
|
|
|||
|
|
@ -11,9 +11,27 @@
|
|||
<div class="flex items-center gap-4">
|
||||
<IconLucidePackage />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-semibold uppercase">Hoppscotch</span>
|
||||
<span class="font-semibold uppercase">{{
|
||||
platform.instance.displayConfig.displayName
|
||||
}}</span>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-xs">On-prem</span>
|
||||
<!-- NOTE:
|
||||
If this is set to `platform.instance.displayConfig.description`
|
||||
it'll be bound to app's perspective, i.e.
|
||||
when in vendored cloud app, it'll show `Cloud`
|
||||
and in vendored self-hosted app, it'll show `On-Prem`
|
||||
even tho both are actually pointing to the same bundle.
|
||||
Essentially switching instance is a **perspective shift**
|
||||
for the underlying desktop app launcher.
|
||||
|
||||
The best way to solve this would be to make instance information
|
||||
into "links" to the bundles hosted by the `appload` plugin,
|
||||
which is already underway in HFE-829.
|
||||
|
||||
This is a workaround for the time being. See `Header.vue`
|
||||
for code that maintains backwards compatibility.
|
||||
-->
|
||||
<span class="text-xs">Default</span>
|
||||
<span class="text-xs"> app </span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -235,6 +253,7 @@ import {
|
|||
InstanceSwitcherService,
|
||||
InstanceType,
|
||||
} from "~/services/instance-switcher.service"
|
||||
import { platform } from "~/platform"
|
||||
|
||||
import IconLucideGlobe from "~icons/lucide/globe"
|
||||
import IconLucideCheck from "~icons/lucide/check"
|
||||
|
|
@ -285,7 +304,7 @@ const connectionError = computed(() => {
|
|||
})
|
||||
|
||||
const isVendored = computed(() => {
|
||||
return currentInstance.value?.type === "vendored"
|
||||
return currentInstance.value?.type === platform.instance.instanceType
|
||||
})
|
||||
|
||||
const isValidUrl = computed(() => {
|
||||
|
|
|
|||
|
|
@ -455,10 +455,10 @@ export function runRESTRequest$(
|
|||
}))
|
||||
) as E.Right<SandboxTestResult>
|
||||
|
||||
const updatedRunResult = updateEnvsAfterTestScript(combinedResult)
|
||||
|
||||
tab.value.document.testResults =
|
||||
translateToSandboxTestResults(updatedRunResult)
|
||||
tab.value.document.testResults = translateToSandboxTestResults(
|
||||
combinedResult.right
|
||||
)
|
||||
updateEnvsAfterTestScript(combinedResult)
|
||||
} else {
|
||||
tab.value.document.testResults = {
|
||||
description: "",
|
||||
|
|
@ -492,26 +492,6 @@ export function runRESTRequest$(
|
|||
}
|
||||
|
||||
function updateEnvsAfterTestScript(runResult: E.Right<SandboxTestResult>) {
|
||||
const updatedGlobalEnvVariables = updateEnvironments(
|
||||
// @ts-expect-error Typescript can't figure out this inference for some reason
|
||||
cloneDeep(runResult.right.envs.global),
|
||||
"global"
|
||||
)
|
||||
|
||||
const updatedSelectedEnvVariables = updateEnvironments(
|
||||
// @ts-expect-error Typescript can't figure out this inference for some reason
|
||||
cloneDeep(runResult.right.envs.selected),
|
||||
"selected"
|
||||
)
|
||||
|
||||
const updatedRunResult = {
|
||||
...runResult.right,
|
||||
envs: {
|
||||
global: updatedGlobalEnvVariables,
|
||||
selected: updatedSelectedEnvVariables,
|
||||
},
|
||||
}
|
||||
|
||||
const globalEnvVariables = updateEnvironments(
|
||||
// @ts-expect-error Typescript can't figure out this inference for some reason
|
||||
runResult.right.envs.global,
|
||||
|
|
@ -522,6 +502,11 @@ function updateEnvsAfterTestScript(runResult: E.Right<SandboxTestResult>) {
|
|||
v: 2,
|
||||
variables: globalEnvVariables,
|
||||
})
|
||||
updateEnvironments(
|
||||
// @ts-expect-error Typescript can't figure out this inference for some reason
|
||||
cloneDeep(runResult.right.envs.selected),
|
||||
"selected"
|
||||
)
|
||||
if (environmentsStore.value.selectedEnvironmentIndex.type === "MY_ENV") {
|
||||
const env = getEnvironment({
|
||||
type: "MY_ENV",
|
||||
|
|
@ -531,7 +516,7 @@ function updateEnvsAfterTestScript(runResult: E.Right<SandboxTestResult>) {
|
|||
name: env.name,
|
||||
v: 2,
|
||||
id: "id" in env ? env.id : "",
|
||||
variables: updatedRunResult.envs.selected,
|
||||
variables: runResult.right.envs.selected,
|
||||
})
|
||||
} else if (
|
||||
environmentsStore.value.selectedEnvironmentIndex.type === "TEAM_ENV"
|
||||
|
|
@ -541,14 +526,12 @@ function updateEnvsAfterTestScript(runResult: E.Right<SandboxTestResult>) {
|
|||
})
|
||||
pipe(
|
||||
updateTeamEnvironment(
|
||||
JSON.stringify(updatedRunResult.envs.selected),
|
||||
JSON.stringify(runResult.right.envs.selected),
|
||||
environmentsStore.value.selectedEnvironmentIndex.teamEnvID,
|
||||
env.name
|
||||
)
|
||||
)()
|
||||
}
|
||||
|
||||
return updatedRunResult
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
import {
|
||||
Environment,
|
||||
translateToNewEnvironmentVariables,
|
||||
} from "@hoppscotch/data"
|
||||
|
||||
/**
|
||||
* Fixes broken environment versions in the given environments.
|
||||
* This function ensures that all environment variables are translated
|
||||
* to the new format, which is necessary for compatibility with the latest
|
||||
* version of the application.
|
||||
*
|
||||
* Some environments may have been created with an unsupported
|
||||
* variable format, which can lead to issues when trying to access or manipulate those environments.
|
||||
*
|
||||
*
|
||||
* @param envs - The array of environments to fix.
|
||||
* @returns The fixed array of environments with updated variable formats.
|
||||
*/
|
||||
export const fixBrokenEnvironmentVersion = (envs: Environment[]) => {
|
||||
if (!Array.isArray(envs)) {
|
||||
return envs
|
||||
}
|
||||
return envs.map((env) => ({
|
||||
...env,
|
||||
variables: (env.variables ?? []).map(translateToNewEnvironmentVariables),
|
||||
}))
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import {
|
||||
getDefaultRESTRequest,
|
||||
safelyExtractRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { z } from "zod"
|
||||
import { REST_TAB_STATE_SCHEMA } from "~/services/persistence/validation-schemas"
|
||||
|
||||
type HoppRESTab = z.infer<typeof REST_TAB_STATE_SCHEMA>
|
||||
|
||||
/**
|
||||
* Fixes broken request versions in the given REST tab documents.
|
||||
* This function ensures that all requests and test runners have valid
|
||||
* request data, defaulting to the default REST request structure if necessary.
|
||||
*
|
||||
* There were requests in the REST tab that had an invalid version
|
||||
* structure, with response and parent request which could lead to issues when trying to access or
|
||||
* manipulate those requests. This function iterates through the
|
||||
* ordered documents of the REST tab and checks each request.
|
||||
*
|
||||
* @param docs - The ordered documents of the REST tab to fix.
|
||||
* @returns The fixed ordered documents with valid request structures.
|
||||
*/
|
||||
export const fixBrokenRequestVersion = (
|
||||
docs: HoppRESTab["orderedDocs"]
|
||||
): HoppRESTab["orderedDocs"] => {
|
||||
return docs.map((x: HoppRESTab["orderedDocs"][number]) => {
|
||||
if (x.doc.type === "request") {
|
||||
const req = safelyExtractRESTRequest(
|
||||
x.doc.request,
|
||||
getDefaultRESTRequest()
|
||||
)
|
||||
if (req) {
|
||||
x.doc.request = req
|
||||
}
|
||||
}
|
||||
|
||||
if (x.doc.type === "test-runner") {
|
||||
x.doc.request = safelyExtractRESTRequest(
|
||||
x.doc.request,
|
||||
getDefaultRESTRequest()
|
||||
)
|
||||
|
||||
if (x.doc.resultCollection) {
|
||||
x.doc.resultCollection.requests = x.doc.resultCollection?.requests.map(
|
||||
(req) => {
|
||||
return safelyExtractRESTRequest(req, getDefaultRESTRequest())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return x
|
||||
})
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ import {
|
|||
HoppRESTRequest,
|
||||
HoppRESTRequestResponses,
|
||||
HoppRESTResponseOriginalRequest,
|
||||
RESTResOriginalReqSchemaVersion,
|
||||
makeHoppRESTResponseOriginalRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import * as A from "fp-ts/Array"
|
||||
|
|
@ -828,24 +828,27 @@ const convertPathToHoppReqs = (
|
|||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
|
||||
responses: parseOpenAPIResponses(doc, info, {
|
||||
name: info.operationId ?? info.summary ?? "Untitled Request",
|
||||
auth: parseOpenAPIAuth(doc, info),
|
||||
body: parseOpenAPIBody(doc, info),
|
||||
endpoint,
|
||||
// We don't need to worry about reference types as the Dereferencing pass should remove them
|
||||
params: parseOpenAPIParams(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
headers: parseOpenAPIHeaders(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
method: method.toUpperCase(),
|
||||
requestVariables: parseOpenAPIVariables(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
v: RESTResOriginalReqSchemaVersion,
|
||||
}),
|
||||
responses: parseOpenAPIResponses(
|
||||
doc,
|
||||
info,
|
||||
makeHoppRESTResponseOriginalRequest({
|
||||
name: info.operationId ?? info.summary ?? "Untitled Request",
|
||||
auth: parseOpenAPIAuth(doc, info),
|
||||
body: parseOpenAPIBody(doc, info),
|
||||
endpoint,
|
||||
// We don't need to worry about reference types as the Dereferencing pass should remove them
|
||||
params: parseOpenAPIParams(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
headers: parseOpenAPIHeaders(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
method: method.toUpperCase(),
|
||||
requestVariables: parseOpenAPIVariables(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
})
|
||||
),
|
||||
}),
|
||||
metadata: {
|
||||
tags: info.tags ?? [],
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ import {
|
|||
knownContentTypes,
|
||||
makeCollection,
|
||||
makeRESTRequest,
|
||||
RESTResOriginalReqSchemaVersion,
|
||||
ValidContentTypes,
|
||||
HoppRESTRequestResponses,
|
||||
makeHoppRESTResponseOriginalRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import * as A from "fp-ts/Array"
|
||||
import { flow, pipe } from "fp-ts/function"
|
||||
|
|
@ -31,7 +32,6 @@ import {
|
|||
import { stringArrayJoin } from "~/helpers/functional/array"
|
||||
import { PMRawLanguage } from "~/types/pm-coll-exts"
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { HoppRESTRequestResponses } from "@hoppscotch/data"
|
||||
|
||||
const safeParseJSON = (jsonStr: string) => O.tryCatch(() => JSON.parse(jsonStr))
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ const getHoppResponses = (
|
|||
body: response.body ?? "",
|
||||
headers: getHoppReqHeaders(response.headers),
|
||||
code: response.code,
|
||||
originalRequest: {
|
||||
originalRequest: makeHoppRESTResponseOriginalRequest({
|
||||
auth: getHoppReqAuth(response.originalRequest?.auth),
|
||||
body: getHoppReqBody({
|
||||
body: response.originalRequest?.body,
|
||||
|
|
@ -178,8 +178,7 @@ const getHoppResponses = (
|
|||
requestVariables: getHoppReqVariables(
|
||||
response.originalRequest?.url.variables ?? null
|
||||
),
|
||||
v: RESTResOriginalReqSchemaVersion,
|
||||
},
|
||||
}),
|
||||
}
|
||||
return [response.name, res]
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {
|
|||
} from "../backend/graphql"
|
||||
import { TeamEnvironment } from "./TeamEnvironment"
|
||||
import { Environment, EnvironmentSchemaVersion } from "@hoppscotch/data"
|
||||
import { entityReference } from "verzod"
|
||||
|
||||
type EntityType = "environment"
|
||||
type EntityID = `${EntityType}-${string}`
|
||||
|
|
@ -121,18 +120,18 @@ export default class TeamEnvironmentAdapter {
|
|||
variables: JSON.parse(x.variables),
|
||||
}
|
||||
|
||||
const parsedEnvironment =
|
||||
entityReference(Environment).safeParse(environment)
|
||||
const parsedEnvironment = Environment.safeParse(environment)
|
||||
|
||||
return <TeamEnvironment>{
|
||||
id: x.id,
|
||||
teamID: x.teamID,
|
||||
environment: parsedEnvironment.success
|
||||
? parsedEnvironment.data
|
||||
: {
|
||||
...environment,
|
||||
v: EnvironmentSchemaVersion,
|
||||
},
|
||||
environment:
|
||||
parsedEnvironment.type === "ok"
|
||||
? parsedEnvironment.value
|
||||
: {
|
||||
...environment,
|
||||
v: EnvironmentSchemaVersion,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export const getFullStatusCodePhrase = () => {
|
|||
// return all status codes and their phrases
|
||||
// like code • phrase
|
||||
export const getStatusCodePhrase = (
|
||||
code: number | undefined,
|
||||
code: number | undefined | null,
|
||||
statusText: string
|
||||
) => {
|
||||
if (!code) return statusText
|
||||
|
|
|
|||
|
|
@ -57,11 +57,16 @@ export const Store = (() => {
|
|||
return module().listNamespaces(STORE_PATH)
|
||||
},
|
||||
|
||||
listKeys: async (namespace: string): Promise<E.Either<StoreError, string[]>> => {
|
||||
listKeys: async (
|
||||
namespace: string
|
||||
): Promise<E.Either<StoreError, string[]>> => {
|
||||
return module().listKeys(STORE_PATH, namespace)
|
||||
},
|
||||
|
||||
watch: async (namespace: string, key: string): Promise<StoreEventEmitter<StoreEvents>> => {
|
||||
watch: async (
|
||||
namespace: string,
|
||||
key: string
|
||||
): Promise<StoreEventEmitter<StoreEvents>> => {
|
||||
return module().watch(STORE_PATH, namespace, key)
|
||||
},
|
||||
} as const
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { BackendPlatformDef } from "./backend"
|
|||
import { OrganizationPlatformDef } from "./organization"
|
||||
import { KernelIO } from "./kernel-io"
|
||||
import { AdditionalLinksPlatformDef } from "./additionalLinks"
|
||||
import { InstancePlatformDef } from "./instance"
|
||||
|
||||
export type PlatformDef = {
|
||||
ui?: UIPlatformDef
|
||||
|
|
@ -31,6 +32,7 @@ export type PlatformDef = {
|
|||
// NOTE: To be deprecated
|
||||
// io: IOPlatformDef
|
||||
kernelIO: KernelIO
|
||||
instance: InstancePlatformDef
|
||||
sync: {
|
||||
environments: EnvironmentsPlatformDef
|
||||
collections: CollectionsPlatformDef
|
||||
|
|
|
|||
10
packages/hoppscotch-common/src/platform/instance.ts
Normal file
10
packages/hoppscotch-common/src/platform/instance.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export type InstancePlatformDef = {
|
||||
instanceType: "vendored"
|
||||
displayConfig: {
|
||||
displayName: string
|
||||
description: string
|
||||
version: string
|
||||
connectingMessage: string
|
||||
connectedMessage: string
|
||||
}
|
||||
}
|
||||
|
|
@ -92,17 +92,14 @@ export class KernelInterceptorAgentStore extends Service {
|
|||
|
||||
private async setupWatchers() {
|
||||
const watcher = await Store.watch(STORE_NAMESPACE, STORE_KEYS.SETTINGS)
|
||||
watcher.on(
|
||||
"change",
|
||||
async ({ value }) => {
|
||||
if (value) {
|
||||
const store = value as StoredData
|
||||
this.domainSettings = new Map(Object.entries(store.domains))
|
||||
this.authKey.value = store.auth.key
|
||||
this.sharedSecretB16.value = store.auth.sharedSecret
|
||||
}
|
||||
watcher.on("change", async ({ value }) => {
|
||||
if (value) {
|
||||
const store = value as StoredData
|
||||
this.domainSettings = new Map(Object.entries(store.domains))
|
||||
this.authKey.value = store.auth.key
|
||||
this.sharedSecretB16.value = store.auth.sharedSecret
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
private async persistStore(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -89,15 +89,12 @@ export class KernelInterceptorExtensionStore extends Service {
|
|||
}
|
||||
|
||||
const watcher = await Store.watch(STORE_NAMESPACE, SETTINGS_KEY)
|
||||
watcher.on(
|
||||
"change",
|
||||
async ({ value }) => {
|
||||
if (value) {
|
||||
const storedData = value as StoredData
|
||||
this.settings = storedData.settings
|
||||
}
|
||||
watcher.on("change", async ({ value }) => {
|
||||
if (value) {
|
||||
const storedData = value as StoredData
|
||||
this.settings = storedData.settings
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
public getExtensionVersion(): O.Option<ExtensionVersion> {
|
||||
|
|
|
|||
|
|
@ -74,15 +74,12 @@ export class KernelInterceptorNativeStore extends Service {
|
|||
|
||||
private async setupWatchers() {
|
||||
const watcher = await Store.watch(STORE_NAMESPACE, STORE_KEYS.SETTINGS)
|
||||
watcher.on(
|
||||
"change",
|
||||
async ({ value }) => {
|
||||
if (value) {
|
||||
const store = value as StoredData
|
||||
this.domainSettings = new Map(Object.entries(store.domains))
|
||||
}
|
||||
watcher.on("change", async ({ value }) => {
|
||||
if (value) {
|
||||
const store = value as StoredData
|
||||
this.domainSettings = new Map(Object.entries(store.domains))
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
private async persistStore(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -39,15 +39,12 @@ export class KernelInterceptorProxyStore extends Service {
|
|||
await this.loadSettings()
|
||||
|
||||
const watcher = await Store.watch(STORE_NAMESPACE, SETTINGS_KEY)
|
||||
watcher.on(
|
||||
"change",
|
||||
async ({ value }) => {
|
||||
if (value) {
|
||||
const storedData = value as StoredData
|
||||
this.settings = storedData.settings
|
||||
}
|
||||
watcher.on("change", async ({ value }) => {
|
||||
if (value) {
|
||||
const storedData = value as StoredData
|
||||
this.settings = storedData.settings
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
private async loadSettings(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { computed } from "vue"
|
|||
import { LazyStore } from "@tauri-apps/plugin-store"
|
||||
import { download, load, clear, remove } from "@hoppscotch/plugin-appload"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { platform } from "~/platform"
|
||||
|
||||
const STORE_PATH = "hopp.store.json"
|
||||
const MAX_RECENT_INSTANCES = 10
|
||||
|
|
@ -39,21 +40,26 @@ export class InstanceSwitcherService extends Service<ConnectionState> {
|
|||
private store!: LazyStore
|
||||
private toast = useToast()
|
||||
|
||||
public getVendoredInstance(): VendoredInstance {
|
||||
const { instanceType, displayConfig } = platform.instance
|
||||
const { displayName, version } = displayConfig
|
||||
|
||||
return {
|
||||
type: instanceType,
|
||||
displayName,
|
||||
version,
|
||||
}
|
||||
}
|
||||
|
||||
override async onServiceInit(): Promise<void> {
|
||||
this.store = new LazyStore(STORE_PATH)
|
||||
await this.store.init()
|
||||
await this.loadRecentInstances()
|
||||
|
||||
if (this.inVendoredEnvironment()) {
|
||||
const instance: VendoredInstance = {
|
||||
type: "vendored",
|
||||
displayName: "Hoppscotch",
|
||||
version: "25.5.1",
|
||||
}
|
||||
|
||||
this.state$.next({
|
||||
status: "connected",
|
||||
instance,
|
||||
instance: this.getVendoredInstance(),
|
||||
})
|
||||
this.emit(this.state$.value)
|
||||
} else {
|
||||
|
|
@ -113,26 +119,25 @@ export class InstanceSwitcherService extends Service<ConnectionState> {
|
|||
|
||||
this.state$.next({
|
||||
status: "connecting",
|
||||
target: "Vendored",
|
||||
target: this.getVendoredInstance().displayName,
|
||||
})
|
||||
this.emit(this.state$.value)
|
||||
|
||||
try {
|
||||
const instance: VendoredInstance = {
|
||||
type: "vendored",
|
||||
displayName: "Hoppscotch",
|
||||
version: "25.5.1",
|
||||
}
|
||||
|
||||
this.state$.next({
|
||||
status: "connected",
|
||||
instance,
|
||||
instance: this.getVendoredInstance(),
|
||||
})
|
||||
this.emit(this.state$.value)
|
||||
|
||||
await this.saveCurrentState()
|
||||
|
||||
this.toast.success("Connecting to Vendored")
|
||||
this.toast.success(
|
||||
platform.instance.displayConfig.connectingMessage.replace(
|
||||
"{instanceName}",
|
||||
this.getVendoredInstance().displayName
|
||||
)
|
||||
)
|
||||
|
||||
const loadResponse = await load({
|
||||
bundleName: "Hoppscotch",
|
||||
|
|
@ -140,17 +145,24 @@ export class InstanceSwitcherService extends Service<ConnectionState> {
|
|||
})
|
||||
|
||||
if (!loadResponse.success) {
|
||||
throw new Error("Failed to load vendored bundle")
|
||||
throw new Error(
|
||||
`Failed to load ${this.getVendoredInstance().type} bundle`
|
||||
)
|
||||
}
|
||||
|
||||
this.toast.success("Connected to Vendored")
|
||||
this.toast.success(
|
||||
platform.instance.displayConfig.connectedMessage.replace(
|
||||
"{instanceName}",
|
||||
this.getVendoredInstance().displayName
|
||||
)
|
||||
)
|
||||
return true
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error)
|
||||
this.state$.next({
|
||||
status: "error",
|
||||
target: "Vendored",
|
||||
target: this.getVendoredInstance().displayName,
|
||||
message: errorMessage,
|
||||
})
|
||||
this.emit(this.state$.value)
|
||||
|
|
@ -325,7 +337,10 @@ export class InstanceSwitcherService extends Service<ConnectionState> {
|
|||
|
||||
public isCurrentlyVendored(): boolean {
|
||||
const state = this.state$.value
|
||||
return state.status === "connected" && state.instance.type === "vendored"
|
||||
return (
|
||||
state.status === "connected" &&
|
||||
state.instance.type === this.getVendoredInstance().type
|
||||
)
|
||||
}
|
||||
|
||||
public isCurrentlyConnectedTo(serverUrl: string): boolean {
|
||||
|
|
|
|||
|
|
@ -954,10 +954,10 @@ describe("PersistenceService", () => {
|
|||
// Invalid shape for `environments`
|
||||
const environments = [
|
||||
// `entries` -> `variables`
|
||||
// no name for the environment
|
||||
{
|
||||
v: 1,
|
||||
id: "ENV_1",
|
||||
name: "Test",
|
||||
entries: [{ key: "test-key", value: "test-value", secret: false }],
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ import {
|
|||
CurrentValueService,
|
||||
Variable,
|
||||
} from "../current-environment-value.service"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { fixBrokenRequestVersion } from "~/helpers/fixBrokenRequestVersion"
|
||||
import { fixBrokenEnvironmentVersion } from "~/helpers/fixBrokenEnvironmentVersion"
|
||||
|
||||
export const STORE_NAMESPACE = "persistence.v1"
|
||||
|
||||
|
|
@ -528,7 +531,9 @@ export class PersistenceService extends Service {
|
|||
try {
|
||||
if (E.isRight(loadResult)) {
|
||||
const data = loadResult.right ?? []
|
||||
const result = ENVIRONMENTS_SCHEMA.safeParse(data)
|
||||
const environments = fixBrokenEnvironmentVersion(data)
|
||||
|
||||
const result = ENVIRONMENTS_SCHEMA.safeParse(environments)
|
||||
|
||||
if (result.success) {
|
||||
// Check for and handle globals
|
||||
|
|
@ -866,8 +871,16 @@ export class PersistenceService extends Service {
|
|||
|
||||
try {
|
||||
if (E.isRight(loadResult) && loadResult.right) {
|
||||
const result = REST_TAB_STATE_SCHEMA.safeParse(loadResult.right)
|
||||
// Correcting the request schema for broken data
|
||||
const orderedDocs = fixBrokenRequestVersion(
|
||||
cloneDeep(loadResult.right.orderedDocs) ?? []
|
||||
)
|
||||
|
||||
const transformedTabs = {
|
||||
...loadResult.right,
|
||||
orderedDocs,
|
||||
}
|
||||
const result = REST_TAB_STATE_SCHEMA.safeParse(transformedTabs)
|
||||
if (result.success) {
|
||||
// SAFETY: We know the schema matches
|
||||
this.restTabService.loadTabsFromPersistedState(
|
||||
|
|
|
|||
|
|
@ -405,12 +405,9 @@ const HoppTestResultSchema = z
|
|||
.object({
|
||||
additions: z.array(EnvironmentVariablesSchema),
|
||||
updations: z.array(
|
||||
EnvironmentVariablesSchema.refine(
|
||||
(x) => "secret" in x && !x.secret
|
||||
).and(
|
||||
z.object({
|
||||
previousValue: z.optional(z.string()),
|
||||
})
|
||||
z.intersection(
|
||||
EnvironmentVariablesSchema,
|
||||
z.object({ previousValue: z.optional(z.string()) })
|
||||
)
|
||||
),
|
||||
deletions: z.array(EnvironmentVariablesSchema),
|
||||
|
|
@ -420,12 +417,9 @@ const HoppTestResultSchema = z
|
|||
.object({
|
||||
additions: z.array(EnvironmentVariablesSchema),
|
||||
updations: z.array(
|
||||
EnvironmentVariablesSchema.refine(
|
||||
(x) => "secret" in x && !x.secret
|
||||
).and(
|
||||
z.object({
|
||||
previousValue: z.optional(z.string()),
|
||||
})
|
||||
z.intersection(
|
||||
EnvironmentVariablesSchema,
|
||||
z.object({ previousValue: z.optional(z.string()) })
|
||||
)
|
||||
),
|
||||
deletions: z.array(EnvironmentVariablesSchema),
|
||||
|
|
@ -582,7 +576,7 @@ export const REST_TAB_STATE_SCHEMA = z
|
|||
}),
|
||||
z.object({
|
||||
type: z.literal("example-response").catch("example-response"),
|
||||
response: HoppRESTRequestResponse,
|
||||
response: entityReference(HoppRESTRequestResponse),
|
||||
saveContext: z.optional(HoppRESTSaveContextSchema),
|
||||
isDirty: z.boolean(),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { defineVersion, entityRefUptoVersion } from "verzod"
|
|||
import { z } from "zod"
|
||||
|
||||
import { HoppGQLAuth } from "../../graphql/v/7"
|
||||
import { HoppRESTAuth } from "../../rest/v/8"
|
||||
import { HoppRESTAuth } from "../../rest/v/8/auth"
|
||||
|
||||
import { V3_SCHEMA, v3_baseCollectionSchema } from "./3"
|
||||
import { HoppCollection } from ".."
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { defineVersion, entityRefUptoVersion } from "verzod"
|
|||
import { z } from "zod"
|
||||
|
||||
import { HoppGQLAuth } from "../../graphql/v/8"
|
||||
import { HoppRESTAuth } from "../../rest/v/11"
|
||||
import { HoppRESTAuth } from "../../rest/v/11/auth"
|
||||
|
||||
import { V5_SCHEMA, v5_baseCollectionSchema } from "./5"
|
||||
import { HoppCollection } from ".."
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { defineVersion, entityRefUptoVersion } from "verzod"
|
|||
import { z } from "zod"
|
||||
|
||||
import { HoppGQLAuth } from "../../graphql/v/8"
|
||||
import { HoppRESTAuth } from "../../rest/v/12"
|
||||
import { HoppRESTAuth } from "../../rest/v/12/auth"
|
||||
|
||||
import { V6_SCHEMA, v6_baseCollectionSchema } from "./6"
|
||||
import { HoppCollection } from ".."
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { defineVersion, entityRefUptoVersion } from "verzod"
|
|||
import { z } from "zod"
|
||||
|
||||
import { HoppGQLAuth } from "../../graphql/v/8"
|
||||
import { HoppRESTAuth } from "../../rest/v/13"
|
||||
import { HoppRESTAuth } from "../../rest/v/13/auth"
|
||||
|
||||
import { HoppCollection } from ".."
|
||||
import { v7_baseCollectionSchema, V7_SCHEMA } from "./7"
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ export const translateToNewEnvironmentVariables = (
|
|||
key: x.key,
|
||||
initialValue: x.initialValue ?? x.value ?? "",
|
||||
currentValue: x.currentValue ?? x.value ?? "",
|
||||
secret: false,
|
||||
secret: x.secret ?? false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,14 +22,15 @@ export default defineVersion({
|
|||
...old,
|
||||
v: 2,
|
||||
variables: old.variables.map((variable) => {
|
||||
const { key, secret } = variable
|
||||
// if the variable is secret, set initialValue and currentValue to empty string
|
||||
// else set initialValue and currentValue to value
|
||||
// and delete value
|
||||
return {
|
||||
...variable,
|
||||
key,
|
||||
secret: secret ?? false,
|
||||
initialValue: variable.secret ? "" : variable.value,
|
||||
currentValue: variable.secret ? "" : variable.value,
|
||||
value: undefined,
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ import {
|
|||
} from "./2"
|
||||
import { HoppGQLAuthAPIKey } from "./4"
|
||||
import { HoppGQLAuthAWSSignature } from "./6"
|
||||
import { HoppRESTAuthOAuth2 } from "./../../rest/v/11"
|
||||
import { HoppRESTAuthOAuth2 } from "../../rest/v/11/auth"
|
||||
import { V7_SCHEMA } from "./7"
|
||||
|
||||
export { HoppRESTAuthOAuth2 as HoppGQLAuthOAuth2 } from "../../rest/v/11"
|
||||
export { HoppRESTAuthOAuth2 as HoppGQLAuthOAuth2 } from "../../rest/v/11/auth"
|
||||
|
||||
export const HoppGQLAuth = z
|
||||
.discriminatedUnion("authType", [
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@ export * from "./utils/collection"
|
|||
export * from "./utils/hawk"
|
||||
export * from "./utils/akamai-eg"
|
||||
export * from "./utils/jwt"
|
||||
export * from "./rest-request-response"
|
||||
|
|
|
|||
50
packages/hoppscotch-data/src/rest-request-response/index.ts
Normal file
50
packages/hoppscotch-data/src/rest-request-response/index.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { InferredEntity, createVersionedEntity, entityReference } from "verzod"
|
||||
import { z } from "zod"
|
||||
import V0_VERSION from "./v/0"
|
||||
import {
|
||||
HoppRESTResOriginalReqSchemaVersion,
|
||||
HoppRESTResponseOriginalRequest,
|
||||
} from "./original-request"
|
||||
|
||||
export { HoppRESTResponseOriginalRequest } from "./original-request"
|
||||
|
||||
const versionedObject = z.object({
|
||||
v: z.number(),
|
||||
})
|
||||
|
||||
export const HoppRESTRequestResponse = createVersionedEntity({
|
||||
latestVersion: 0,
|
||||
versionMap: {
|
||||
0: V0_VERSION,
|
||||
},
|
||||
getVersion(data) {
|
||||
const versionCheck = versionedObject.safeParse(data)
|
||||
|
||||
if (versionCheck.success) return versionCheck.data.v
|
||||
|
||||
// Schema starts from version 0, so if the version is not present,
|
||||
// we assume it's version 0
|
||||
const result = V0_VERSION.schema.safeParse(data)
|
||||
return result.success ? 0 : null
|
||||
},
|
||||
})
|
||||
|
||||
export type HoppRESTRequestResponse = InferredEntity<
|
||||
typeof HoppRESTRequestResponse
|
||||
>
|
||||
|
||||
export const HoppRESTRequestResponses = z.record(
|
||||
z.string(),
|
||||
entityReference(HoppRESTRequestResponse)
|
||||
)
|
||||
|
||||
export type HoppRESTRequestResponses = z.infer<typeof HoppRESTRequestResponses>
|
||||
|
||||
export function makeHoppRESTResponseOriginalRequest(
|
||||
x: Omit<HoppRESTResponseOriginalRequest, "v">
|
||||
): HoppRESTResponseOriginalRequest {
|
||||
return {
|
||||
v: HoppRESTResOriginalReqSchemaVersion,
|
||||
...x,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { InferredEntity, createVersionedEntity } from "verzod"
|
||||
import { z } from "zod"
|
||||
import V1_VERSION from "./v/1"
|
||||
import V2_VERSION from "./v/2"
|
||||
import V3_VERSION from "./v/3"
|
||||
import V4_VERSION from "./v/4"
|
||||
import V5_VERSION from "./v/5"
|
||||
|
||||
const versionedObject = z.object({
|
||||
// v is a stringified number
|
||||
v: z.string().regex(/^\d+$/).transform(Number),
|
||||
})
|
||||
|
||||
export const HoppRESTResponseOriginalRequest = createVersionedEntity({
|
||||
latestVersion: 5,
|
||||
versionMap: {
|
||||
1: V1_VERSION,
|
||||
2: V2_VERSION,
|
||||
3: V3_VERSION,
|
||||
4: V4_VERSION,
|
||||
5: V5_VERSION,
|
||||
},
|
||||
getVersion(data) {
|
||||
const versionCheck = versionedObject.safeParse(data)
|
||||
|
||||
if (versionCheck.success) return versionCheck.data.v
|
||||
|
||||
// For V1 we have to check the schema
|
||||
const result = V1_VERSION.schema.safeParse(data)
|
||||
|
||||
return result.success ? 1 : null
|
||||
},
|
||||
})
|
||||
|
||||
export const HoppRESTResOriginalReqSchemaVersion = "5"
|
||||
|
||||
export type HoppRESTResponseOriginalRequest = InferredEntity<
|
||||
typeof HoppRESTResponseOriginalRequest
|
||||
>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { defineVersion } from "verzod"
|
||||
import { z } from "zod"
|
||||
import { HoppRESTHeaders, HoppRESTParams } from "../../../rest/v/7"
|
||||
import { HoppRESTReqBody } from "../../../rest/v/6"
|
||||
import { HoppRESTRequestVariables } from "../../../rest/v/2"
|
||||
|
||||
import { HoppRESTAuth } from "../../../rest/v/8/auth"
|
||||
|
||||
export const V1_SCHEMA = z.object({
|
||||
v: z.literal("1"),
|
||||
name: z.string(),
|
||||
method: z.string(),
|
||||
endpoint: z.string(),
|
||||
headers: HoppRESTHeaders,
|
||||
params: HoppRESTParams,
|
||||
body: HoppRESTReqBody,
|
||||
auth: HoppRESTAuth,
|
||||
requestVariables: HoppRESTRequestVariables,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
initial: true,
|
||||
schema: V1_SCHEMA,
|
||||
})
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { defineVersion } from "verzod"
|
||||
import { z } from "zod"
|
||||
import { V1_SCHEMA } from "./1"
|
||||
import { HoppRESTReqBody } from "../../../rest/v/9/body"
|
||||
|
||||
export const V2_SCHEMA = V1_SCHEMA.extend({
|
||||
v: z.literal("2"),
|
||||
body: HoppRESTReqBody,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
initial: false,
|
||||
schema: V2_SCHEMA,
|
||||
up(old: z.infer<typeof V1_SCHEMA>) {
|
||||
return {
|
||||
...old,
|
||||
v: "2" as const,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { defineVersion } from "verzod"
|
||||
import { z } from "zod"
|
||||
|
||||
import { V2_SCHEMA } from "./2"
|
||||
import { HoppRESTAuth } from "../../../rest/v/11/auth"
|
||||
import { HoppRESTReqBody } from "../../../rest/v/10/body"
|
||||
|
||||
export const V3_SCHEMA = V2_SCHEMA.extend({
|
||||
v: z.literal("3"),
|
||||
auth: HoppRESTAuth,
|
||||
body: HoppRESTReqBody,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
initial: false,
|
||||
schema: V3_SCHEMA,
|
||||
up(old: z.infer<typeof V2_SCHEMA>) {
|
||||
const auth = old.auth
|
||||
|
||||
return {
|
||||
...old,
|
||||
v: "3" as const,
|
||||
auth:
|
||||
auth.authType === "oauth-2"
|
||||
? {
|
||||
...auth,
|
||||
grantTypeInfo:
|
||||
auth.grantTypeInfo.grantType === "CLIENT_CREDENTIALS"
|
||||
? {
|
||||
...auth.grantTypeInfo,
|
||||
clientAuthentication: "IN_BODY" as const,
|
||||
}
|
||||
: auth.grantTypeInfo,
|
||||
}
|
||||
: auth,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { defineVersion } from "verzod"
|
||||
import { z } from "zod"
|
||||
import { V3_SCHEMA } from "./3"
|
||||
import { HoppRESTAuth } from "../../../rest/v/12/auth"
|
||||
|
||||
export const V4_SCHEMA = V3_SCHEMA.extend({
|
||||
v: z.literal("4"),
|
||||
auth: HoppRESTAuth,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
initial: false,
|
||||
schema: V4_SCHEMA,
|
||||
up(old: z.infer<typeof V3_SCHEMA>) {
|
||||
return {
|
||||
...old,
|
||||
v: "4" as const,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { defineVersion } from "verzod"
|
||||
import { z } from "zod"
|
||||
import { V4_SCHEMA } from "./4"
|
||||
import { HoppRESTAuth } from "../../../rest/v/13/auth"
|
||||
|
||||
export const V5_SCHEMA = V4_SCHEMA.extend({
|
||||
v: z.literal("5"),
|
||||
auth: HoppRESTAuth,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
initial: false,
|
||||
schema: V5_SCHEMA,
|
||||
up(old: z.infer<typeof V4_SCHEMA>) {
|
||||
return {
|
||||
...old,
|
||||
v: "5" as const,
|
||||
}
|
||||
},
|
||||
})
|
||||
35
packages/hoppscotch-data/src/rest-request-response/v/0.ts
Normal file
35
packages/hoppscotch-data/src/rest-request-response/v/0.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { defineVersion, entityReference } from "verzod"
|
||||
import { z } from "zod"
|
||||
import { HoppRESTResponseOriginalRequest } from "../original-request"
|
||||
import { StatusCodes } from "../../utils/statusCodes"
|
||||
|
||||
export const ValidCodes = z.union(
|
||||
Object.keys(StatusCodes).map((code) => z.literal(parseInt(code))) as [
|
||||
z.ZodLiteral<number>,
|
||||
z.ZodLiteral<number>,
|
||||
...z.ZodLiteral<number>[]
|
||||
]
|
||||
)
|
||||
|
||||
export const HoppRESTResponseHeaders = z.array(
|
||||
z.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTResponseHeader = z.infer<typeof HoppRESTResponseHeaders>
|
||||
|
||||
export const V0_SCHEMA = z.object({
|
||||
name: z.string(),
|
||||
originalRequest: entityReference(HoppRESTResponseOriginalRequest),
|
||||
status: z.string(),
|
||||
code: z.optional(ValidCodes).nullable().catch(null),
|
||||
headers: HoppRESTResponseHeaders,
|
||||
body: z.string(),
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
initial: true,
|
||||
schema: V0_SCHEMA,
|
||||
})
|
||||
|
|
@ -16,10 +16,14 @@ import V6_VERSION from "./v/6"
|
|||
import V7_VERSION, { HoppRESTHeaders, HoppRESTParams } from "./v/7"
|
||||
import V8_VERSION from "./v/8"
|
||||
import V9_VERSION from "./v/9"
|
||||
import V10_VERSION, { HoppRESTReqBody } from "./v/10"
|
||||
import V10_VERSION from "./v/10"
|
||||
import { HoppRESTReqBody } from "./v/10/body"
|
||||
import V11_VERSION from "./v/11"
|
||||
import V12_VERSION from "./v/12"
|
||||
import V13_VERSION, { HoppRESTAuth, HoppRESTRequestResponses } from "./v/13"
|
||||
import V13_VERSION from "./v/13"
|
||||
import { HoppRESTAuth } from "./v/13/auth"
|
||||
import V14_VERSION from "./v/14"
|
||||
import { HoppRESTRequestResponses } from "../rest-request-response"
|
||||
|
||||
export * from "./content-types"
|
||||
|
||||
|
|
@ -44,23 +48,25 @@ export {
|
|||
HoppRESTParams,
|
||||
} from "./v/7"
|
||||
|
||||
export { HoppRESTAuthDigest, PasswordGrantTypeParams } from "./v/8"
|
||||
export { HoppRESTAuthDigest, PasswordGrantTypeParams } from "./v/8/auth"
|
||||
|
||||
export { FormDataKeyValue } from "./v/9"
|
||||
|
||||
export { HoppRESTAuthOAuth2, ClientCredentialsGrantTypeParams } from "./v/11"
|
||||
|
||||
export { HoppRESTReqBody } from "./v/10"
|
||||
|
||||
export { HoppRESTAuthHAWK, HoppRESTAuthAkamaiEdgeGrid } from "./v/12"
|
||||
export { FormDataKeyValue } from "./v/9/body"
|
||||
|
||||
export {
|
||||
HoppRESTAuthOAuth2,
|
||||
ClientCredentialsGrantTypeParams,
|
||||
} from "./v/11/auth"
|
||||
|
||||
export { HoppRESTReqBody } from "./v/10/body"
|
||||
|
||||
export { HoppRESTAuthHAWK, HoppRESTAuthAkamaiEdgeGrid } from "./v/12/auth"
|
||||
|
||||
export { HoppRESTAuth, HoppRESTAuthJWT } from "./v/13/auth"
|
||||
|
||||
export {
|
||||
HoppRESTAuth,
|
||||
HoppRESTAuthJWT,
|
||||
HoppRESTRequestResponses,
|
||||
HoppRESTResponseOriginalRequest,
|
||||
HoppRESTRequestResponse,
|
||||
} from "./v/13"
|
||||
HoppRESTRequestResponses,
|
||||
} from "../rest-request-response"
|
||||
|
||||
const versionedObject = z.object({
|
||||
// v is a stringified number
|
||||
|
|
@ -68,7 +74,7 @@ const versionedObject = z.object({
|
|||
})
|
||||
|
||||
export const HoppRESTRequest = createVersionedEntity({
|
||||
latestVersion: 13,
|
||||
latestVersion: 14,
|
||||
versionMap: {
|
||||
0: V0_VERSION,
|
||||
1: V1_VERSION,
|
||||
|
|
@ -84,6 +90,7 @@ export const HoppRESTRequest = createVersionedEntity({
|
|||
11: V11_VERSION,
|
||||
12: V12_VERSION,
|
||||
13: V13_VERSION,
|
||||
14: V14_VERSION,
|
||||
},
|
||||
getVersion(data) {
|
||||
// For V1 onwards we have the v string storing the number
|
||||
|
|
@ -126,8 +133,7 @@ const HoppRESTRequestEq = Eq.struct<HoppRESTRequest>({
|
|||
responses: lodashIsEqualEq,
|
||||
})
|
||||
|
||||
export const RESTReqSchemaVersion = "13"
|
||||
export const RESTResOriginalReqSchemaVersion = "5" as const
|
||||
export const RESTReqSchemaVersion = "14"
|
||||
|
||||
export type HoppRESTParam = HoppRESTRequest["params"][number]
|
||||
export type HoppRESTHeader = HoppRESTRequest["headers"][number]
|
||||
|
|
@ -210,7 +216,6 @@ export function safelyExtractRESTRequest(
|
|||
|
||||
if ("responses" in x) {
|
||||
const result = HoppRESTRequestResponses.safeParse(x.responses)
|
||||
|
||||
if (result.success) {
|
||||
req.responses = result.data
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { z } from "zod"
|
||||
import { FormDataKeyValue, V9_SCHEMA } from "./9"
|
||||
import { defineVersion } from "verzod"
|
||||
import { FormDataKeyValue } from "../9/body"
|
||||
|
||||
export const HoppRESTReqBody = z.union([
|
||||
z.object({
|
||||
|
|
@ -39,19 +38,3 @@ export const HoppRESTReqBody = z.union([
|
|||
])
|
||||
|
||||
export type HoppRESTReqBody = z.infer<typeof HoppRESTReqBody>
|
||||
|
||||
export const V10_SCHEMA = V9_SCHEMA.extend({
|
||||
v: z.literal("10"),
|
||||
body: HoppRESTReqBody,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V10_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V9_SCHEMA>) {
|
||||
return {
|
||||
...old,
|
||||
v: "10" as const,
|
||||
}
|
||||
},
|
||||
})
|
||||
20
packages/hoppscotch-data/src/rest/v/10/index.ts
Normal file
20
packages/hoppscotch-data/src/rest/v/10/index.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { z } from "zod"
|
||||
import { V9_SCHEMA } from "../9"
|
||||
import { HoppRESTReqBody } from "./body"
|
||||
import { defineVersion } from "verzod"
|
||||
|
||||
export const V10_SCHEMA = V9_SCHEMA.extend({
|
||||
v: z.literal("10"),
|
||||
body: HoppRESTReqBody,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V10_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V9_SCHEMA>) {
|
||||
return {
|
||||
...old,
|
||||
v: "10" as const,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
import {
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthNone,
|
||||
} from "./1"
|
||||
import { HoppRESTAuthAPIKey } from "./4"
|
||||
import { AuthCodeGrantTypeParams, HoppRESTAuthAWSSignature } from "./7"
|
||||
import {
|
||||
ClientCredentialsGrantTypeParams as ClientCredentialsGrantTypeParamsOld,
|
||||
HoppRESTAuthDigest,
|
||||
PasswordGrantTypeParams,
|
||||
} from "./8"
|
||||
import { ImplicitOauthFlowParams } from "./3"
|
||||
import { z } from "zod"
|
||||
|
||||
import { HoppRESTReqBody, V10_SCHEMA } from "./10"
|
||||
import { defineVersion } from "verzod"
|
||||
import {
|
||||
HoppRESTResponseOriginalRequest as HoppRESTResponseOriginalRequestOld,
|
||||
HoppRESTRequestResponse as HoppRESTRequestResponseOld,
|
||||
} from "./9"
|
||||
|
||||
export const ClientCredentialsGrantTypeParams =
|
||||
ClientCredentialsGrantTypeParamsOld.extend({
|
||||
clientAuthentication: z.enum(["AS_BASIC_AUTH_HEADERS", "IN_BODY"]),
|
||||
})
|
||||
|
||||
export const HoppRESTAuthOAuth2 = z.object({
|
||||
authType: z.literal("oauth-2"),
|
||||
grantTypeInfo: z.discriminatedUnion("grantType", [
|
||||
AuthCodeGrantTypeParams,
|
||||
ClientCredentialsGrantTypeParams,
|
||||
PasswordGrantTypeParams,
|
||||
ImplicitOauthFlowParams,
|
||||
]),
|
||||
addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthOAuth2 = z.infer<typeof HoppRESTAuthOAuth2>
|
||||
|
||||
export const HoppRESTAuth = z
|
||||
.discriminatedUnion("authType", [
|
||||
HoppRESTAuthNone,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTAuthAPIKey,
|
||||
HoppRESTAuthAWSSignature,
|
||||
HoppRESTAuthDigest,
|
||||
])
|
||||
.and(
|
||||
z.object({
|
||||
authActive: z.boolean(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
|
||||
|
||||
export const HoppRESTResponseOriginalRequest =
|
||||
HoppRESTResponseOriginalRequestOld.extend({
|
||||
v: z.literal("3"),
|
||||
auth: HoppRESTAuth,
|
||||
body: HoppRESTReqBody,
|
||||
})
|
||||
|
||||
export type HoppRESTResponseOriginalRequest = z.infer<
|
||||
typeof HoppRESTResponseOriginalRequest
|
||||
>
|
||||
|
||||
export const HoppRESTRequestResponse = HoppRESTRequestResponseOld.extend({
|
||||
originalRequest: HoppRESTResponseOriginalRequest,
|
||||
})
|
||||
|
||||
export type HoppRESTRequestResponse = z.infer<typeof HoppRESTRequestResponse>
|
||||
|
||||
export const HoppRESTRequestResponses = z.record(
|
||||
z.string(),
|
||||
HoppRESTRequestResponse
|
||||
)
|
||||
|
||||
export type HoppRESTRequestResponses = z.infer<typeof HoppRESTRequestResponses>
|
||||
|
||||
export const V11_SCHEMA = V10_SCHEMA.extend({
|
||||
v: z.literal("11"),
|
||||
auth: HoppRESTAuth,
|
||||
responses: HoppRESTRequestResponses,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V11_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V10_SCHEMA>) {
|
||||
const auth = old.auth
|
||||
|
||||
// update auth for responses
|
||||
const responses = Object.fromEntries(
|
||||
Object.entries(old.responses).map(([key, response]) => [
|
||||
key,
|
||||
{
|
||||
...response,
|
||||
originalRequest: {
|
||||
...response.originalRequest,
|
||||
|
||||
auth:
|
||||
auth.authType === "oauth-2"
|
||||
? {
|
||||
...auth,
|
||||
grantTypeInfo:
|
||||
auth.grantTypeInfo.grantType === "CLIENT_CREDENTIALS"
|
||||
? {
|
||||
...auth.grantTypeInfo,
|
||||
clientAuthentication: "IN_BODY" as const,
|
||||
}
|
||||
: auth.grantTypeInfo,
|
||||
}
|
||||
: auth,
|
||||
|
||||
// just following the previous pattern here, but is this a good idea to overwrite the request version ?
|
||||
v: "3" as const,
|
||||
},
|
||||
},
|
||||
])
|
||||
)
|
||||
|
||||
return {
|
||||
...old,
|
||||
v: "11" as const,
|
||||
auth:
|
||||
auth.authType === "oauth-2"
|
||||
? {
|
||||
...auth,
|
||||
grantTypeInfo:
|
||||
auth.grantTypeInfo.grantType === "CLIENT_CREDENTIALS"
|
||||
? {
|
||||
...auth.grantTypeInfo,
|
||||
clientAuthentication: "IN_BODY" as const,
|
||||
}
|
||||
: auth.grantTypeInfo,
|
||||
}
|
||||
: auth,
|
||||
responses,
|
||||
}
|
||||
},
|
||||
})
|
||||
52
packages/hoppscotch-data/src/rest/v/11/auth.ts
Normal file
52
packages/hoppscotch-data/src/rest/v/11/auth.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import {
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthNone,
|
||||
} from "../1"
|
||||
import { HoppRESTAuthAPIKey } from "../4"
|
||||
import { AuthCodeGrantTypeParams, HoppRESTAuthAWSSignature } from "../7"
|
||||
import {
|
||||
ClientCredentialsGrantTypeParams as ClientCredentialsGrantTypeParamsOld,
|
||||
HoppRESTAuthDigest,
|
||||
PasswordGrantTypeParams,
|
||||
} from "../8/auth"
|
||||
import { ImplicitOauthFlowParams } from "../3"
|
||||
import { z } from "zod"
|
||||
|
||||
export const ClientCredentialsGrantTypeParams =
|
||||
ClientCredentialsGrantTypeParamsOld.extend({
|
||||
clientAuthentication: z.enum(["AS_BASIC_AUTH_HEADERS", "IN_BODY"]),
|
||||
})
|
||||
|
||||
export const HoppRESTAuthOAuth2 = z.object({
|
||||
authType: z.literal("oauth-2"),
|
||||
grantTypeInfo: z.discriminatedUnion("grantType", [
|
||||
AuthCodeGrantTypeParams,
|
||||
ClientCredentialsGrantTypeParams,
|
||||
PasswordGrantTypeParams,
|
||||
ImplicitOauthFlowParams,
|
||||
]),
|
||||
addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthOAuth2 = z.infer<typeof HoppRESTAuthOAuth2>
|
||||
|
||||
export const HoppRESTAuth = z
|
||||
.discriminatedUnion("authType", [
|
||||
HoppRESTAuthNone,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTAuthAPIKey,
|
||||
HoppRESTAuthAWSSignature,
|
||||
HoppRESTAuthDigest,
|
||||
])
|
||||
.and(
|
||||
z.object({
|
||||
authActive: z.boolean(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
|
||||
37
packages/hoppscotch-data/src/rest/v/11/index.ts
Normal file
37
packages/hoppscotch-data/src/rest/v/11/index.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { z } from "zod"
|
||||
|
||||
import { V10_SCHEMA } from "../10"
|
||||
import { defineVersion } from "verzod"
|
||||
|
||||
import { HoppRESTAuth } from "./auth"
|
||||
|
||||
export const V11_SCHEMA = V10_SCHEMA.extend({
|
||||
v: z.literal("11"),
|
||||
auth: HoppRESTAuth,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V11_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V10_SCHEMA>) {
|
||||
const auth = old.auth
|
||||
|
||||
return {
|
||||
...old,
|
||||
v: "11" as const,
|
||||
auth:
|
||||
auth.authType === "oauth-2"
|
||||
? {
|
||||
...auth,
|
||||
grantTypeInfo:
|
||||
auth.grantTypeInfo.grantType === "CLIENT_CREDENTIALS"
|
||||
? {
|
||||
...auth.grantTypeInfo,
|
||||
clientAuthentication: "IN_BODY" as const,
|
||||
}
|
||||
: auth.grantTypeInfo,
|
||||
}
|
||||
: auth,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
import {
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthNone,
|
||||
} from "./1"
|
||||
import { HoppRESTAuthAPIKey } from "./4"
|
||||
import { HoppRESTAuthAWSSignature } from "./7"
|
||||
import { HoppRESTAuthDigest } from "./8"
|
||||
|
||||
import { z } from "zod"
|
||||
import { defineVersion } from "verzod"
|
||||
import { HoppRESTAuthOAuth2, V11_SCHEMA } from "./11"
|
||||
|
||||
import {
|
||||
HoppRESTResponseOriginalRequest as HoppRESTResponseOriginalRequestOld,
|
||||
HoppRESTRequestResponse as HoppRESTRequestResponseOld,
|
||||
} from "./9"
|
||||
|
||||
export const HoppRESTAuthHAWK = z.object({
|
||||
authType: z.literal("hawk"),
|
||||
authId: z.string().catch(""),
|
||||
authKey: z.string().catch(""),
|
||||
algorithm: z.enum(["sha256", "sha1"]).catch("sha256"),
|
||||
includePayloadHash: z.boolean().catch(false),
|
||||
|
||||
// Optional fields
|
||||
user: z.string().optional(),
|
||||
nonce: z.string().optional(),
|
||||
ext: z.string().optional(),
|
||||
app: z.string().optional(),
|
||||
dlg: z.string().optional(),
|
||||
timestamp: z.string().optional(),
|
||||
})
|
||||
|
||||
export const HoppRESTAuthAkamaiEdgeGrid = z.object({
|
||||
authType: z.literal("akamai-eg"),
|
||||
accessToken: z.string().catch(""),
|
||||
clientToken: z.string().catch(""),
|
||||
clientSecret: z.string().catch(""),
|
||||
|
||||
// Optional fields
|
||||
nonce: z.string().optional(),
|
||||
timestamp: z.string().optional(),
|
||||
host: z.string().optional(),
|
||||
headersToSign: z.string().optional(),
|
||||
maxBodySize: z.string().optional(),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthHAWK = z.infer<typeof HoppRESTAuthHAWK>
|
||||
export type HoppRESTAuthAkamaiEdgeGrid = z.infer<
|
||||
typeof HoppRESTAuthAkamaiEdgeGrid
|
||||
>
|
||||
|
||||
export const HoppRESTAuth = z
|
||||
.discriminatedUnion("authType", [
|
||||
HoppRESTAuthNone,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTAuthAPIKey,
|
||||
HoppRESTAuthAWSSignature,
|
||||
HoppRESTAuthDigest,
|
||||
HoppRESTAuthHAWK,
|
||||
HoppRESTAuthAkamaiEdgeGrid,
|
||||
])
|
||||
.and(
|
||||
z.object({
|
||||
authActive: z.boolean(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
|
||||
|
||||
export const HoppRESTResponseOriginalRequest =
|
||||
HoppRESTResponseOriginalRequestOld.extend({
|
||||
v: z.literal("4"),
|
||||
auth: HoppRESTAuth,
|
||||
})
|
||||
|
||||
export type HoppRESTResponseOriginalRequest = z.infer<
|
||||
typeof HoppRESTResponseOriginalRequest
|
||||
>
|
||||
|
||||
export const HoppRESTRequestResponse = HoppRESTRequestResponseOld.extend({
|
||||
originalRequest: HoppRESTResponseOriginalRequest,
|
||||
})
|
||||
|
||||
export type HoppRESTRequestResponse = z.infer<typeof HoppRESTRequestResponse>
|
||||
|
||||
export const HoppRESTRequestResponses = z.record(
|
||||
z.string(),
|
||||
HoppRESTRequestResponse
|
||||
)
|
||||
|
||||
export type HoppRESTRequestResponses = z.infer<typeof HoppRESTRequestResponses>
|
||||
|
||||
export const V12_SCHEMA = V11_SCHEMA.extend({
|
||||
v: z.literal("12"),
|
||||
auth: HoppRESTAuth,
|
||||
responses: HoppRESTRequestResponses,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V12_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V11_SCHEMA>) {
|
||||
// update the version number of response original request
|
||||
const responses = Object.fromEntries(
|
||||
Object.entries(old.responses).map(([key, response]) => [
|
||||
key,
|
||||
{
|
||||
...response,
|
||||
originalRequest: {
|
||||
...response.originalRequest,
|
||||
v: "4" as const,
|
||||
},
|
||||
},
|
||||
])
|
||||
)
|
||||
return {
|
||||
...old,
|
||||
v: "12" as const,
|
||||
responses,
|
||||
}
|
||||
},
|
||||
})
|
||||
67
packages/hoppscotch-data/src/rest/v/12/auth.ts
Normal file
67
packages/hoppscotch-data/src/rest/v/12/auth.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { z } from "zod"
|
||||
import {
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthNone,
|
||||
} from "../1"
|
||||
import { HoppRESTAuthAPIKey } from "../4"
|
||||
import { HoppRESTAuthAWSSignature } from "../7"
|
||||
import { HoppRESTAuthDigest } from "../8/auth"
|
||||
import { HoppRESTAuthOAuth2 } from "../11/auth"
|
||||
|
||||
export const HoppRESTAuthHAWK = z.object({
|
||||
authType: z.literal("hawk"),
|
||||
authId: z.string().catch(""),
|
||||
authKey: z.string().catch(""),
|
||||
algorithm: z.enum(["sha256", "sha1"]).catch("sha256"),
|
||||
includePayloadHash: z.boolean().catch(false),
|
||||
|
||||
// Optional fields
|
||||
user: z.string().optional(),
|
||||
nonce: z.string().optional(),
|
||||
ext: z.string().optional(),
|
||||
app: z.string().optional(),
|
||||
dlg: z.string().optional(),
|
||||
timestamp: z.string().optional(),
|
||||
})
|
||||
|
||||
export const HoppRESTAuthAkamaiEdgeGrid = z.object({
|
||||
authType: z.literal("akamai-eg"),
|
||||
accessToken: z.string().catch(""),
|
||||
clientToken: z.string().catch(""),
|
||||
clientSecret: z.string().catch(""),
|
||||
|
||||
// Optional fields
|
||||
nonce: z.string().optional(),
|
||||
timestamp: z.string().optional(),
|
||||
host: z.string().optional(),
|
||||
headersToSign: z.string().optional(),
|
||||
maxBodySize: z.string().optional(),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthHAWK = z.infer<typeof HoppRESTAuthHAWK>
|
||||
export type HoppRESTAuthAkamaiEdgeGrid = z.infer<
|
||||
typeof HoppRESTAuthAkamaiEdgeGrid
|
||||
>
|
||||
|
||||
export const HoppRESTAuth = z
|
||||
.discriminatedUnion("authType", [
|
||||
HoppRESTAuthNone,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTAuthAPIKey,
|
||||
HoppRESTAuthAWSSignature,
|
||||
HoppRESTAuthDigest,
|
||||
HoppRESTAuthHAWK,
|
||||
HoppRESTAuthAkamaiEdgeGrid,
|
||||
])
|
||||
.and(
|
||||
z.object({
|
||||
authActive: z.boolean(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
|
||||
21
packages/hoppscotch-data/src/rest/v/12/index.ts
Normal file
21
packages/hoppscotch-data/src/rest/v/12/index.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { z } from "zod"
|
||||
import { defineVersion } from "verzod"
|
||||
import { V11_SCHEMA } from "../11"
|
||||
|
||||
import { HoppRESTAuth } from "./auth"
|
||||
|
||||
export const V12_SCHEMA = V11_SCHEMA.extend({
|
||||
v: z.literal("12"),
|
||||
auth: HoppRESTAuth,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V12_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V11_SCHEMA>) {
|
||||
return {
|
||||
...old,
|
||||
v: "12" as const,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
import {
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthNone,
|
||||
} from "./1"
|
||||
import { HoppRESTAuthAPIKey } from "./4"
|
||||
import { HoppRESTAuthAWSSignature } from "./7"
|
||||
import { HoppRESTAuthDigest } from "./8"
|
||||
|
||||
import { HoppRESTAuthHAWK, HoppRESTAuthAkamaiEdgeGrid, V12_SCHEMA } from "./12"
|
||||
|
||||
import { z } from "zod"
|
||||
import { defineVersion } from "verzod"
|
||||
import { HoppRESTAuthOAuth2 } from "./11"
|
||||
|
||||
import {
|
||||
HoppRESTResponseOriginalRequest as HoppRESTResponseOriginalRequestOld,
|
||||
HoppRESTRequestResponse as HoppRESTRequestResponseOld,
|
||||
} from "./9"
|
||||
|
||||
export const HoppRESTAuthJWT = z.object({
|
||||
authType: z.literal("jwt"),
|
||||
secret: z.string().catch(""),
|
||||
privateKey: z.string().catch(""), // For RSA/ECDSA algorithms
|
||||
algorithm: z
|
||||
.enum([
|
||||
"HS256",
|
||||
"HS384",
|
||||
"HS512",
|
||||
"RS256",
|
||||
"RS384",
|
||||
"RS512",
|
||||
"PS256",
|
||||
"PS384",
|
||||
"PS512",
|
||||
"ES256",
|
||||
"ES384",
|
||||
"ES512",
|
||||
])
|
||||
.catch("HS256"),
|
||||
payload: z.string().catch("{}"),
|
||||
addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"),
|
||||
isSecretBase64Encoded: z.boolean().catch(false),
|
||||
headerPrefix: z.string().catch("Bearer "),
|
||||
paramName: z.string().catch("token"),
|
||||
jwtHeaders: z.string().catch("{}"),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthJWT = z.infer<typeof HoppRESTAuthJWT>
|
||||
|
||||
export const HoppRESTAuth = z
|
||||
.discriminatedUnion("authType", [
|
||||
HoppRESTAuthNone,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTAuthAPIKey,
|
||||
HoppRESTAuthAWSSignature,
|
||||
HoppRESTAuthDigest,
|
||||
HoppRESTAuthHAWK,
|
||||
HoppRESTAuthAkamaiEdgeGrid,
|
||||
HoppRESTAuthJWT,
|
||||
])
|
||||
.and(
|
||||
z.object({
|
||||
authActive: z.boolean(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
|
||||
|
||||
export const HoppRESTResponseOriginalRequest =
|
||||
HoppRESTResponseOriginalRequestOld.extend({
|
||||
v: z.literal("5"),
|
||||
auth: HoppRESTAuth,
|
||||
})
|
||||
|
||||
export type HoppRESTResponseOriginalRequest = z.infer<
|
||||
typeof HoppRESTResponseOriginalRequest
|
||||
>
|
||||
|
||||
export const HoppRESTRequestResponse = HoppRESTRequestResponseOld.extend({
|
||||
originalRequest: HoppRESTResponseOriginalRequest,
|
||||
})
|
||||
|
||||
export type HoppRESTRequestResponse = z.infer<typeof HoppRESTRequestResponse>
|
||||
|
||||
export const HoppRESTRequestResponses = z.record(
|
||||
z.string(),
|
||||
HoppRESTRequestResponse
|
||||
)
|
||||
|
||||
export type HoppRESTRequestResponses = z.infer<typeof HoppRESTRequestResponses>
|
||||
|
||||
export const V13_SCHEMA = V12_SCHEMA.extend({
|
||||
v: z.literal("13"),
|
||||
auth: HoppRESTAuth,
|
||||
responses: HoppRESTRequestResponses,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V13_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V12_SCHEMA>) {
|
||||
// update the version number of response original request
|
||||
const responses = Object.fromEntries(
|
||||
Object.entries(old.responses).map(([key, response]) => [
|
||||
key,
|
||||
{
|
||||
...response,
|
||||
originalRequest: {
|
||||
...response.originalRequest,
|
||||
v: "5" as const,
|
||||
},
|
||||
},
|
||||
])
|
||||
)
|
||||
return {
|
||||
...old,
|
||||
v: "13" as const,
|
||||
responses,
|
||||
}
|
||||
},
|
||||
})
|
||||
64
packages/hoppscotch-data/src/rest/v/13/auth.ts
Normal file
64
packages/hoppscotch-data/src/rest/v/13/auth.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { z } from "zod"
|
||||
import {
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthNone,
|
||||
} from "../1"
|
||||
import { HoppRESTAuthAPIKey } from "../4"
|
||||
import { HoppRESTAuthAWSSignature } from "../7"
|
||||
import { HoppRESTAuthDigest } from "../8/auth"
|
||||
import { HoppRESTAuthOAuth2 } from "../11/auth"
|
||||
import { HoppRESTAuthAkamaiEdgeGrid, HoppRESTAuthHAWK } from "../12/auth"
|
||||
|
||||
export const HoppRESTAuthJWT = z.object({
|
||||
authType: z.literal("jwt"),
|
||||
secret: z.string().catch(""),
|
||||
privateKey: z.string().catch(""), // For RSA/ECDSA algorithms
|
||||
algorithm: z
|
||||
.enum([
|
||||
"HS256",
|
||||
"HS384",
|
||||
"HS512",
|
||||
"RS256",
|
||||
"RS384",
|
||||
"RS512",
|
||||
"PS256",
|
||||
"PS384",
|
||||
"PS512",
|
||||
"ES256",
|
||||
"ES384",
|
||||
"ES512",
|
||||
])
|
||||
.catch("HS256"),
|
||||
payload: z.string().catch("{}"),
|
||||
addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"),
|
||||
isSecretBase64Encoded: z.boolean().catch(false),
|
||||
headerPrefix: z.string().catch("Bearer "),
|
||||
paramName: z.string().catch("token"),
|
||||
jwtHeaders: z.string().catch("{}"),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthJWT = z.infer<typeof HoppRESTAuthJWT>
|
||||
|
||||
export const HoppRESTAuth = z
|
||||
.discriminatedUnion("authType", [
|
||||
HoppRESTAuthNone,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTAuthAPIKey,
|
||||
HoppRESTAuthAWSSignature,
|
||||
HoppRESTAuthDigest,
|
||||
HoppRESTAuthHAWK,
|
||||
HoppRESTAuthAkamaiEdgeGrid,
|
||||
HoppRESTAuthJWT,
|
||||
])
|
||||
.and(
|
||||
z.object({
|
||||
authActive: z.boolean(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
|
||||
22
packages/hoppscotch-data/src/rest/v/13/index.ts
Normal file
22
packages/hoppscotch-data/src/rest/v/13/index.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { V12_SCHEMA } from "../12"
|
||||
|
||||
import { z } from "zod"
|
||||
import { defineVersion } from "verzod"
|
||||
|
||||
import { HoppRESTAuth } from "./auth"
|
||||
|
||||
export const V13_SCHEMA = V12_SCHEMA.extend({
|
||||
v: z.literal("13"),
|
||||
auth: HoppRESTAuth,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V13_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V12_SCHEMA>) {
|
||||
return {
|
||||
...old,
|
||||
v: "13" as const,
|
||||
}
|
||||
},
|
||||
})
|
||||
19
packages/hoppscotch-data/src/rest/v/14.ts
Normal file
19
packages/hoppscotch-data/src/rest/v/14.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { z } from "zod"
|
||||
import { defineVersion } from "verzod"
|
||||
import { V13_SCHEMA } from "./13"
|
||||
|
||||
// Update the HoppRESTRequestResponses while migrating HoppRESTRequest
|
||||
export const V14_SCHEMA = V13_SCHEMA.extend({
|
||||
v: z.literal("14"),
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V14_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V13_SCHEMA>) {
|
||||
return {
|
||||
...old,
|
||||
v: "14" as const,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
import { defineVersion } from "verzod"
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthNone,
|
||||
} from "./1"
|
||||
|
||||
import { HoppRESTAuthAPIKey } from "./4"
|
||||
|
||||
import {
|
||||
ClientCredentialsGrantTypeParams as ClientCredentialsGrantTypeParamsOld,
|
||||
ImplicitOauthFlowParams,
|
||||
PasswordGrantTypeParams as PasswordGrantTypeParamsOld,
|
||||
} from "./3"
|
||||
|
||||
import {
|
||||
AuthCodeGrantTypeParams,
|
||||
HoppRESTAuthAWSSignature,
|
||||
HoppRESTHeaders,
|
||||
HoppRESTParams,
|
||||
V7_SCHEMA,
|
||||
} from "./7"
|
||||
|
||||
import { StatusCodes } from "../../utils/statusCodes"
|
||||
import { HoppRESTReqBody } from "./6"
|
||||
import { HoppRESTRequestVariables } from "./2"
|
||||
|
||||
export const ClientCredentialsGrantTypeParams =
|
||||
ClientCredentialsGrantTypeParamsOld.extend({
|
||||
clientSecret: z.string().optional(),
|
||||
})
|
||||
|
||||
export const PasswordGrantTypeParams = PasswordGrantTypeParamsOld.extend({
|
||||
clientSecret: z.string().optional(),
|
||||
})
|
||||
|
||||
export const HoppRESTAuthOAuth2 = z.object({
|
||||
authType: z.literal("oauth-2"),
|
||||
grantTypeInfo: z.discriminatedUnion("grantType", [
|
||||
AuthCodeGrantTypeParams,
|
||||
ClientCredentialsGrantTypeParams,
|
||||
PasswordGrantTypeParams,
|
||||
ImplicitOauthFlowParams,
|
||||
]),
|
||||
addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthOAuth2 = z.infer<typeof HoppRESTAuthOAuth2>
|
||||
|
||||
// in this new version, we add a new auth type for Digest authentication
|
||||
export const HoppRESTAuthDigest = z.object({
|
||||
authType: z.literal("digest"),
|
||||
username: z.string().catch(""),
|
||||
password: z.string().catch(""),
|
||||
realm: z.string().catch(""),
|
||||
nonce: z.string().catch(""),
|
||||
algorithm: z.enum(["MD5", "MD5-sess"]).catch("MD5"),
|
||||
qop: z.enum(["auth", "auth-int"]).catch("auth"),
|
||||
nc: z.string().catch(""),
|
||||
cnonce: z.string().catch(""),
|
||||
opaque: z.string().catch(""),
|
||||
disableRetry: z.boolean().catch(false),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthDigest = z.infer<typeof HoppRESTAuthDigest>
|
||||
|
||||
export const HoppRESTAuth = z
|
||||
.discriminatedUnion("authType", [
|
||||
HoppRESTAuthNone,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTAuthAPIKey,
|
||||
HoppRESTAuthAWSSignature,
|
||||
HoppRESTAuthDigest,
|
||||
])
|
||||
.and(
|
||||
z.object({
|
||||
authActive: z.boolean(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
|
||||
|
||||
export const ValidCodes = z.union(
|
||||
Object.keys(StatusCodes).map((code) => z.literal(parseInt(code))) as [
|
||||
z.ZodLiteral<number>,
|
||||
z.ZodLiteral<number>,
|
||||
...z.ZodLiteral<number>[]
|
||||
]
|
||||
)
|
||||
|
||||
export const HoppRESTResponseHeaders = z.array(
|
||||
z.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTResponseHeader = z.infer<typeof HoppRESTResponseHeaders>
|
||||
|
||||
/**
|
||||
* The original request that was made to get this response
|
||||
* Only the necessary fields are saved
|
||||
*/
|
||||
export const HoppRESTResponseOriginalRequest = z.object({
|
||||
v: z.literal("1"),
|
||||
name: z.string(),
|
||||
method: z.string(),
|
||||
endpoint: z.string(),
|
||||
headers: HoppRESTHeaders,
|
||||
params: HoppRESTParams,
|
||||
body: HoppRESTReqBody,
|
||||
auth: HoppRESTAuth,
|
||||
requestVariables: HoppRESTRequestVariables,
|
||||
})
|
||||
|
||||
export type HoppRESTResponseOriginalRequest = z.infer<
|
||||
typeof HoppRESTResponseOriginalRequest
|
||||
>
|
||||
|
||||
export const HoppRESTRequestResponse = z.object({
|
||||
name: z.string(),
|
||||
originalRequest: HoppRESTResponseOriginalRequest,
|
||||
status: z.string(),
|
||||
code: z.optional(ValidCodes),
|
||||
headers: HoppRESTResponseHeaders,
|
||||
body: z.string(),
|
||||
})
|
||||
|
||||
export type HoppRESTRequestResponse = z.infer<typeof HoppRESTRequestResponse>
|
||||
|
||||
/**
|
||||
* The responses saved for a request
|
||||
* The key is the name of the response saved by the user
|
||||
* The value is the response
|
||||
*/
|
||||
export const HoppRESTRequestResponses = z.record(
|
||||
z.string(),
|
||||
HoppRESTRequestResponse
|
||||
)
|
||||
|
||||
export type HoppRESTRequestResponses = z.infer<typeof HoppRESTRequestResponses>
|
||||
|
||||
export const V8_SCHEMA = V7_SCHEMA.extend({
|
||||
v: z.literal("8"),
|
||||
auth: HoppRESTAuth,
|
||||
responses: HoppRESTRequestResponses,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V8_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V7_SCHEMA>) {
|
||||
return {
|
||||
...old,
|
||||
v: "8" as const,
|
||||
// no need to update anything for HoppRESTAuth, because we loosened the previous schema by making `clientSecret` optional
|
||||
responses: {},
|
||||
}
|
||||
},
|
||||
})
|
||||
75
packages/hoppscotch-data/src/rest/v/8/auth.ts
Normal file
75
packages/hoppscotch-data/src/rest/v/8/auth.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import {
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthNone,
|
||||
} from "../1"
|
||||
|
||||
import { HoppRESTAuthAPIKey } from "../4"
|
||||
|
||||
import {
|
||||
ClientCredentialsGrantTypeParams as ClientCredentialsGrantTypeParamsOld,
|
||||
ImplicitOauthFlowParams,
|
||||
PasswordGrantTypeParams as PasswordGrantTypeParamsOld,
|
||||
} from "../3"
|
||||
|
||||
import { AuthCodeGrantTypeParams, HoppRESTAuthAWSSignature } from "../7"
|
||||
import { z } from "zod"
|
||||
|
||||
export const ClientCredentialsGrantTypeParams =
|
||||
ClientCredentialsGrantTypeParamsOld.extend({
|
||||
clientSecret: z.string().optional(),
|
||||
})
|
||||
|
||||
export const PasswordGrantTypeParams = PasswordGrantTypeParamsOld.extend({
|
||||
clientSecret: z.string().optional(),
|
||||
})
|
||||
|
||||
export const HoppRESTAuthOAuth2 = z.object({
|
||||
authType: z.literal("oauth-2"),
|
||||
grantTypeInfo: z.discriminatedUnion("grantType", [
|
||||
AuthCodeGrantTypeParams,
|
||||
ClientCredentialsGrantTypeParams,
|
||||
PasswordGrantTypeParams,
|
||||
ImplicitOauthFlowParams,
|
||||
]),
|
||||
addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthOAuth2 = z.infer<typeof HoppRESTAuthOAuth2>
|
||||
|
||||
// in this new version, we add a new auth type for Digest authentication
|
||||
export const HoppRESTAuthDigest = z.object({
|
||||
authType: z.literal("digest"),
|
||||
username: z.string().catch(""),
|
||||
password: z.string().catch(""),
|
||||
realm: z.string().catch(""),
|
||||
nonce: z.string().catch(""),
|
||||
algorithm: z.enum(["MD5", "MD5-sess"]).catch("MD5"),
|
||||
qop: z.enum(["auth", "auth-int"]).catch("auth"),
|
||||
nc: z.string().catch(""),
|
||||
cnonce: z.string().catch(""),
|
||||
opaque: z.string().catch(""),
|
||||
disableRetry: z.boolean().catch(false),
|
||||
})
|
||||
|
||||
export type HoppRESTAuthDigest = z.infer<typeof HoppRESTAuthDigest>
|
||||
|
||||
export const HoppRESTAuth = z
|
||||
.discriminatedUnion("authType", [
|
||||
HoppRESTAuthNone,
|
||||
HoppRESTAuthInherit,
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTAuthAPIKey,
|
||||
HoppRESTAuthAWSSignature,
|
||||
HoppRESTAuthDigest,
|
||||
])
|
||||
.and(
|
||||
z.object({
|
||||
authActive: z.boolean(),
|
||||
})
|
||||
)
|
||||
|
||||
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
|
||||
27
packages/hoppscotch-data/src/rest/v/8/index.ts
Normal file
27
packages/hoppscotch-data/src/rest/v/8/index.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { defineVersion } from "verzod"
|
||||
import { z } from "zod"
|
||||
|
||||
import { V7_SCHEMA } from "../7"
|
||||
|
||||
import { HoppRESTRequestResponses } from "../../../rest-request-response"
|
||||
|
||||
import { HoppRESTAuth } from "./auth"
|
||||
|
||||
export const V8_SCHEMA = V7_SCHEMA.extend({
|
||||
v: z.literal("8"),
|
||||
auth: HoppRESTAuth,
|
||||
responses: HoppRESTRequestResponses,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V8_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V7_SCHEMA>) {
|
||||
return {
|
||||
...old,
|
||||
v: "8" as const,
|
||||
// no need to update anything for HoppRESTAuth, because we loosened the previous schema by making `clientSecret` optional
|
||||
responses: {},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
import { defineVersion } from "verzod"
|
||||
import { z } from "zod"
|
||||
|
||||
import { HoppRESTRequestVariables } from "./2"
|
||||
import { HoppRESTHeaders, HoppRESTParams } from "./7"
|
||||
import {
|
||||
HoppRESTAuth,
|
||||
HoppRESTResponseHeaders,
|
||||
V8_SCHEMA,
|
||||
ValidCodes,
|
||||
} from "./8"
|
||||
|
||||
export const FormDataKeyValue = z
|
||||
.object({
|
||||
key: z.string(),
|
||||
active: z.boolean(),
|
||||
contentType: z.string().optional().catch(undefined),
|
||||
})
|
||||
.and(
|
||||
z.union([
|
||||
z.object({
|
||||
isFile: z.literal(true),
|
||||
value: z.array(z.instanceof(Blob).nullable()).catch([]),
|
||||
}),
|
||||
z.object({
|
||||
isFile: z.literal(false),
|
||||
value: z.string(),
|
||||
}),
|
||||
])
|
||||
)
|
||||
.transform((data) => {
|
||||
// Sample use case about restoring the `value` field in an empty state during page reload
|
||||
// for files chosen in the previous attempt
|
||||
if (data.isFile && Array.isArray(data.value) && data.value.length === 0) {
|
||||
return {
|
||||
...data,
|
||||
isFile: false,
|
||||
value: "",
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
export type FormDataKeyValue = z.infer<typeof FormDataKeyValue>
|
||||
|
||||
export const HoppRESTReqBody = z.union([
|
||||
z.object({
|
||||
contentType: z.literal(null),
|
||||
body: z.literal(null).catch(null),
|
||||
}),
|
||||
z.object({
|
||||
contentType: z.literal("multipart/form-data"),
|
||||
body: z.array(FormDataKeyValue).catch([]),
|
||||
showIndividualContentType: z.boolean().optional().catch(false),
|
||||
}),
|
||||
z.object({
|
||||
contentType: z.literal("application/octet-stream"),
|
||||
body: z.instanceof(File).nullable().catch(null),
|
||||
}),
|
||||
z.object({
|
||||
contentType: z.union([
|
||||
z.literal("application/json"),
|
||||
z.literal("application/ld+json"),
|
||||
z.literal("application/hal+json"),
|
||||
z.literal("application/vnd.api+json"),
|
||||
z.literal("application/xml"),
|
||||
z.literal("text/xml"),
|
||||
z.literal("application/x-www-form-urlencoded"),
|
||||
z.literal("binary"),
|
||||
z.literal("text/html"),
|
||||
z.literal("text/plain"),
|
||||
]),
|
||||
body: z.string().catch(""),
|
||||
}),
|
||||
])
|
||||
|
||||
export type HoppRESTReqBody = z.infer<typeof HoppRESTReqBody>
|
||||
|
||||
/**
|
||||
* The original request that was made to get this response
|
||||
* Only the necessary fields are saved
|
||||
*/
|
||||
export const HoppRESTResponseOriginalRequest = z.object({
|
||||
v: z.literal("2"),
|
||||
name: z.string(),
|
||||
method: z.string(),
|
||||
endpoint: z.string(),
|
||||
headers: HoppRESTHeaders,
|
||||
params: HoppRESTParams,
|
||||
body: HoppRESTReqBody,
|
||||
auth: HoppRESTAuth,
|
||||
requestVariables: HoppRESTRequestVariables,
|
||||
})
|
||||
|
||||
export type HoppRESTResponseOriginalRequest = z.infer<
|
||||
typeof HoppRESTResponseOriginalRequest
|
||||
>
|
||||
|
||||
export const HoppRESTRequestResponse = z.object({
|
||||
name: z.string(),
|
||||
originalRequest: HoppRESTResponseOriginalRequest,
|
||||
status: z.string(),
|
||||
code: z.optional(ValidCodes).nullable().catch(null),
|
||||
headers: HoppRESTResponseHeaders,
|
||||
body: z.string(),
|
||||
})
|
||||
|
||||
export type HoppRESTRequestResponse = z.infer<typeof HoppRESTRequestResponse>
|
||||
|
||||
/**
|
||||
* The responses saved for a request
|
||||
* The key is the name of the response saved by the user
|
||||
* The value is the response
|
||||
*/
|
||||
export const HoppRESTRequestResponses = z.record(
|
||||
z.string(),
|
||||
HoppRESTRequestResponse
|
||||
)
|
||||
|
||||
export type HoppRESTRequestResponses = z.infer<typeof HoppRESTRequestResponses>
|
||||
|
||||
export const V9_SCHEMA = V8_SCHEMA.extend({
|
||||
v: z.literal("9"),
|
||||
body: HoppRESTReqBody,
|
||||
responses: HoppRESTRequestResponses,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V9_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V8_SCHEMA>) {
|
||||
// update the version number of response original request
|
||||
const responses = Object.fromEntries(
|
||||
Object.entries(old.responses).map(([key, response]) => [
|
||||
key,
|
||||
{
|
||||
...response,
|
||||
originalRequest: {
|
||||
...response.originalRequest,
|
||||
v: "2" as const,
|
||||
},
|
||||
},
|
||||
])
|
||||
)
|
||||
|
||||
// No migration for body, the new contentType added to each formdata field is optional
|
||||
return {
|
||||
...old,
|
||||
v: "9" as const,
|
||||
responses,
|
||||
}
|
||||
},
|
||||
})
|
||||
68
packages/hoppscotch-data/src/rest/v/9/body.ts
Normal file
68
packages/hoppscotch-data/src/rest/v/9/body.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { z } from "zod"
|
||||
|
||||
export const FormDataKeyValue = z
|
||||
.object({
|
||||
key: z.string(),
|
||||
active: z.boolean(),
|
||||
contentType: z.string().optional().catch(undefined),
|
||||
})
|
||||
.and(
|
||||
z.union([
|
||||
z.object({
|
||||
isFile: z.literal(true),
|
||||
value: z.array(z.instanceof(Blob).nullable()).catch([]),
|
||||
}),
|
||||
z.object({
|
||||
isFile: z.literal(false),
|
||||
value: z.string(),
|
||||
}),
|
||||
])
|
||||
)
|
||||
.transform((data) => {
|
||||
// Sample use case about restoring the `value` field in an empty state during page reload
|
||||
// for files chosen in the previous attempt
|
||||
if (data.isFile && Array.isArray(data.value) && data.value.length === 0) {
|
||||
return {
|
||||
...data,
|
||||
isFile: false,
|
||||
value: "",
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
export type FormDataKeyValue = z.infer<typeof FormDataKeyValue>
|
||||
|
||||
export const HoppRESTReqBody = z.union([
|
||||
z.object({
|
||||
contentType: z.literal(null),
|
||||
body: z.literal(null).catch(null),
|
||||
}),
|
||||
z.object({
|
||||
contentType: z.literal("multipart/form-data"),
|
||||
body: z.array(FormDataKeyValue).catch([]),
|
||||
showIndividualContentType: z.boolean().optional().catch(false),
|
||||
}),
|
||||
z.object({
|
||||
contentType: z.literal("application/octet-stream"),
|
||||
body: z.instanceof(File).nullable().catch(null),
|
||||
}),
|
||||
z.object({
|
||||
contentType: z.union([
|
||||
z.literal("application/json"),
|
||||
z.literal("application/ld+json"),
|
||||
z.literal("application/hal+json"),
|
||||
z.literal("application/vnd.api+json"),
|
||||
z.literal("application/xml"),
|
||||
z.literal("text/xml"),
|
||||
z.literal("application/x-www-form-urlencoded"),
|
||||
z.literal("binary"),
|
||||
z.literal("text/html"),
|
||||
z.literal("text/plain"),
|
||||
]),
|
||||
body: z.string().catch(""),
|
||||
}),
|
||||
])
|
||||
|
||||
export type HoppRESTReqBody = z.infer<typeof HoppRESTReqBody>
|
||||
22
packages/hoppscotch-data/src/rest/v/9/index.ts
Normal file
22
packages/hoppscotch-data/src/rest/v/9/index.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { defineVersion } from "verzod"
|
||||
import { z } from "zod"
|
||||
|
||||
import { V8_SCHEMA } from "../8"
|
||||
import { HoppRESTReqBody } from "./body"
|
||||
|
||||
export const V9_SCHEMA = V8_SCHEMA.extend({
|
||||
v: z.literal("9"),
|
||||
body: HoppRESTReqBody,
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
schema: V9_SCHEMA,
|
||||
initial: false,
|
||||
up(old: z.infer<typeof V8_SCHEMA>) {
|
||||
// No migration for body, the new contentType added to each formdata field is optional
|
||||
return {
|
||||
...old,
|
||||
v: "9" as const,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "hoppscotch-desktop",
|
||||
"private": true,
|
||||
"version": "25.5.1",
|
||||
"version": "25.5.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
"@fontsource-variable/material-symbols-rounded": "5.1.3",
|
||||
"@fontsource-variable/roboto-mono": "5.1.0",
|
||||
"@hoppscotch/kernel": "workspace:^",
|
||||
"@hoppscotch/plugin-appload": "github:CuriousCorrelation/tauri-plugin-appload",
|
||||
"@hoppscotch/plugin-appload": "github:CuriousCorrelation/tauri-plugin-appload#5939b8f",
|
||||
"@hoppscotch/ui": "0.2.1",
|
||||
"@tauri-apps/api": "2.1.1",
|
||||
"@tauri-apps/plugin-process": "2.2.0",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ exclude = ["/examples", "/webview-dist", "/webview-src", "/node_modules"]
|
|||
links = "tauri-plugin-appload"
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.0.6", features = ["unstable"] }
|
||||
tauri = { version = "2.0.6" }
|
||||
serde = "1.0"
|
||||
serde_json = { version = "1", features = [] }
|
||||
thiserror = { version = "2.0.3", features = [] }
|
||||
|
|
|
|||
6
packages/hoppscotch-desktop/src-tauri/Cargo.lock
generated
6
packages/hoppscotch-desktop/src-tauri/Cargo.lock
generated
|
|
@ -2054,7 +2054,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hoppscotch-desktop"
|
||||
version = "25.5.1"
|
||||
version = "25.5.2"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"dirs 6.0.0",
|
||||
|
|
@ -5038,7 +5038,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "tauri-plugin-appload"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/CuriousCorrelation/tauri-plugin-appload#87e78a8d35c02e53366d3b56286d0350f772efb7"
|
||||
source = "git+https://github.com/CuriousCorrelation/tauri-plugin-appload?rev=5939b8f#5939b8ff3fdffeebd72749f32b3a06cd5a0c37fc"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"blake3",
|
||||
|
|
@ -5148,7 +5148,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "tauri-plugin-relay"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/CuriousCorrelation/tauri-plugin-relay#3273b9b075f1f6fa9171799a961c435454c0c9a2"
|
||||
source = "git+https://github.com/CuriousCorrelation/tauri-plugin-relay?rev=3273b9b#3273b9b075f1f6fa9171799a961c435454c0c9a2"
|
||||
dependencies = [
|
||||
"relay",
|
||||
"serde",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "hoppscotch-desktop"
|
||||
version = "25.5.1"
|
||||
version = "25.5.2"
|
||||
description = "Desktop App for hoppscotch.io"
|
||||
authors = ["CuriousCorrelation"]
|
||||
edition = "2021"
|
||||
|
|
@ -29,8 +29,8 @@ tauri-plugin-store = "2.2.0"
|
|||
tauri-plugin-dialog = "2.2.0"
|
||||
tauri-plugin-fs = "2.2.0"
|
||||
tauri-plugin-deep-link = "2.2.0"
|
||||
tauri-plugin-appload = { git = "https://github.com/CuriousCorrelation/tauri-plugin-appload" }
|
||||
tauri-plugin-relay = { git = "https://github.com/CuriousCorrelation/tauri-plugin-relay" }
|
||||
tauri-plugin-appload = { git = "https://github.com/CuriousCorrelation/tauri-plugin-appload", rev = "5939b8f" }
|
||||
tauri-plugin-relay = { git = "https://github.com/CuriousCorrelation/tauri-plugin-relay", rev = "3273b9b" }
|
||||
axum = "0.8.1"
|
||||
tower-http = { version = "0.6.2", features = ["cors"] }
|
||||
portpicker = "0.1.1"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ pub struct LogGuard(pub tracing_appender::non_blocking::WorkerGuard);
|
|||
pub fn setup(log_dir: &PathBuf) -> Result<LogGuard, Box<dyn std::error::Error>> {
|
||||
std::fs::create_dir_all(log_dir)?;
|
||||
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| format!("debug").into());
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| "debug".into());
|
||||
|
||||
let log_file_path = log_dir.join(&format!("{}.log", HOPPSCOTCH_DESKTOP_IDENTIFIER));
|
||||
tracing::info!(log_file_path =? &log_file_path);
|
||||
|
|
@ -45,7 +45,7 @@ pub fn setup(log_dir: &PathBuf) -> Result<LogGuard, Box<dyn std::error::Error>>
|
|||
|
||||
tracing::info!(
|
||||
log_file = %log_file_path.display(),
|
||||
"Logging initialized with single file"
|
||||
"Logging initialized with rotating file"
|
||||
);
|
||||
|
||||
Ok(LogGuard(guard))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Hoppscotch",
|
||||
"version": "25.5.1",
|
||||
"version": "25.5.2",
|
||||
"identifier": "io.hoppscotch.desktop",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
|
|
|
|||
|
|
@ -31,8 +31,4 @@ export interface UpdateState {
|
|||
status: UpdateStatus;
|
||||
version?: string;
|
||||
message?: string;
|
||||
progress?: {
|
||||
downloaded: number;
|
||||
total?: number;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { type LazyStore } from "@tauri-apps/plugin-store";
|
|||
import { UpdateStatus, CheckResult, UpdateState } from "~/types";
|
||||
|
||||
export class UpdaterService {
|
||||
private currentProgress: { downloaded: number; total?: number } = { downloaded: 0 };
|
||||
|
||||
constructor(private store: LazyStore) {}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
|
|
@ -12,6 +14,10 @@ export class UpdaterService {
|
|||
});
|
||||
}
|
||||
|
||||
getCurrentProgress(): { downloaded: number; total?: number } {
|
||||
return this.currentProgress;
|
||||
}
|
||||
|
||||
async checkForUpdates(timeout = 5000): Promise<CheckResult> {
|
||||
try {
|
||||
await this.saveUpdateState({
|
||||
|
|
@ -86,41 +92,36 @@ export class UpdaterService {
|
|||
throw new Error("No update available to install");
|
||||
}
|
||||
|
||||
await this.saveUpdateState({
|
||||
status: UpdateStatus.DOWNLOADING,
|
||||
progress: {
|
||||
downloaded: 0,
|
||||
total: undefined
|
||||
}
|
||||
});
|
||||
|
||||
let totalBytes: number | undefined;
|
||||
let downloadedBytes = 0;
|
||||
|
||||
await this.saveUpdateState({
|
||||
status: UpdateStatus.DOWNLOADING,
|
||||
});
|
||||
|
||||
await updateResult.downloadAndInstall(
|
||||
(event: DownloadEvent) => {
|
||||
if (event.event === 'Started') {
|
||||
totalBytes = event.data.contentLength;
|
||||
this.saveUpdateState({
|
||||
status: UpdateStatus.DOWNLOADING,
|
||||
progress: {
|
||||
downloaded: 0,
|
||||
total: totalBytes
|
||||
}
|
||||
});
|
||||
} else if (event.event === 'Progress') {
|
||||
downloadedBytes += event.data.chunkLength;
|
||||
this.saveUpdateState({
|
||||
status: UpdateStatus.DOWNLOADING,
|
||||
progress: {
|
||||
try {
|
||||
if (event.event === 'Started') {
|
||||
totalBytes = event.data.contentLength;
|
||||
downloadedBytes = 0;
|
||||
console.log(`Download started, total size: ${totalBytes} bytes`);
|
||||
} else if (event.event === 'Progress') {
|
||||
downloadedBytes += event.data.chunkLength;
|
||||
console.log(`Download progress: ${downloadedBytes}/${totalBytes} bytes`);
|
||||
|
||||
this.currentProgress = {
|
||||
downloaded: downloadedBytes,
|
||||
total: totalBytes
|
||||
}
|
||||
});
|
||||
} else if (event.event === 'Finished') {
|
||||
this.saveUpdateState({
|
||||
status: UpdateStatus.INSTALLING
|
||||
});
|
||||
};
|
||||
} else if (event.event === 'Finished') {
|
||||
console.log("Download finished, starting installation");
|
||||
this.saveUpdateState({
|
||||
status: UpdateStatus.INSTALLING
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Progress tracking error:', error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<div class="flex flex-col items-center justify-center w-full h-screen bg-primary">
|
||||
<div
|
||||
class="flex flex-col items-center justify-center w-full h-screen bg-primary"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-6 max-w-md text-center">
|
||||
<div class="flex items-center space-x-4">
|
||||
<img src="/logo.svg" alt="Hoppscotch" class="h-7 w-7" />
|
||||
|
|
@ -9,39 +11,70 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="appState === AppState.LOADING" class="flex flex-col items-center space-y-4">
|
||||
<div
|
||||
v-if="appState === AppState.LOADING"
|
||||
class="flex flex-col items-center space-y-4"
|
||||
>
|
||||
<HoppSmartSpinner />
|
||||
<p class="text-secondaryDark">{{ statusMessage }}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="appState === AppState.UPDATE_AVAILABLE || appState === AppState.UPDATE_IN_PROGRESS || appState === AppState.UPDATE_READY"
|
||||
v-else-if="
|
||||
appState === AppState.UPDATE_AVAILABLE ||
|
||||
appState === AppState.UPDATE_IN_PROGRESS ||
|
||||
appState === AppState.UPDATE_READY
|
||||
"
|
||||
class="flex flex-col items-center space-y-4"
|
||||
>
|
||||
<IconLucideDownload class="h-16 w-16 text-accent" />
|
||||
<div class="text-center">
|
||||
<h2 class="text-xl font-semibold text-secondaryDark">Update Available</h2>
|
||||
<p class="text-secondary mt-1">{{ updateMessage || 'A new version of Hoppscotch is available, downloading...' }}</p>
|
||||
<h2 class="text-xl font-semibold text-secondaryDark">
|
||||
Update Available
|
||||
</h2>
|
||||
<p class="text-secondary mt-1">
|
||||
{{
|
||||
updateMessage ||
|
||||
"A new version of Hoppscotch is available, downloading..."
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="downloadProgress.total && downloadProgress.downloaded" class="w-full">
|
||||
<div
|
||||
v-if="downloadProgress.total && downloadProgress.downloaded"
|
||||
class="w-full"
|
||||
>
|
||||
<div class="w-full bg-primaryLight rounded-full h-2.5">
|
||||
<div
|
||||
class="bg-accent h-2.5 rounded-full"
|
||||
:style="{ width: `${(downloadProgress.downloaded / downloadProgress.total) * 100}%` }"
|
||||
:style="{
|
||||
width: `${
|
||||
(downloadProgress.downloaded / downloadProgress.total) * 100
|
||||
}%`,
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm text-secondaryLight mt-1">
|
||||
<span>{{ Math.round((downloadProgress.downloaded / downloadProgress.total) * 100) }}%</span>
|
||||
<span
|
||||
>{{
|
||||
Math.round(
|
||||
(downloadProgress.downloaded / downloadProgress.total) * 100
|
||||
)
|
||||
}}%</span
|
||||
>
|
||||
<span class="text-xs">
|
||||
{{ formatBytes(downloadProgress.downloaded) }} / {{ formatBytes(downloadProgress.total) }}
|
||||
{{ formatBytes(downloadProgress.downloaded) }} /
|
||||
{{ formatBytes(downloadProgress.total) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="downloadProgress.downloaded > 0" class="w-full">
|
||||
<div class="w-full bg-primaryLight rounded-full h-2.5">
|
||||
<div class="bg-accent h-2.5 rounded-full animate-pulse" style="width: 100%"></div>
|
||||
<div
|
||||
class="bg-accent h-2.5 rounded-full animate-pulse"
|
||||
style="width: 100%"
|
||||
></div>
|
||||
</div>
|
||||
<p class="text-sm text-secondaryLight text-center mt-1">
|
||||
Downloaded {{ formatBytes(downloadProgress.downloaded) }}
|
||||
|
|
@ -70,10 +103,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="appState === AppState.ERROR" class="flex flex-col items-center space-y-4">
|
||||
<div
|
||||
v-else-if="appState === AppState.ERROR"
|
||||
class="flex flex-col items-center space-y-4"
|
||||
>
|
||||
<IconLucideAlertCircle class="h-16 w-16 text-red-500" />
|
||||
<div class="text-center">
|
||||
<h2 class="text-xl font-semibold text-secondaryDark">Something went wrong</h2>
|
||||
<h2 class="text-xl font-semibold text-secondaryDark">
|
||||
Something went wrong
|
||||
</h2>
|
||||
<p class="text-red-500 mt-2">{{ error }}</p>
|
||||
</div>
|
||||
<HoppButtonPrimary
|
||||
|
|
@ -91,19 +129,24 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { LazyStore } from '@tauri-apps/plugin-store';
|
||||
import { load } from "@hoppscotch/plugin-appload";
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { ref, onMounted, onUnmounted } from "vue"
|
||||
import { LazyStore } from "@tauri-apps/plugin-store"
|
||||
import { load } from "@hoppscotch/plugin-appload"
|
||||
import { getVersion } from "@tauri-apps/api/app"
|
||||
|
||||
import { UpdateStatus, CheckResult, UpdateState } from "~/types";
|
||||
import { UpdaterService } from "~/utils/updater";
|
||||
import { UpdateStatus, CheckResult, UpdateState } from "~/types"
|
||||
import { UpdaterService } from "~/utils/updater"
|
||||
|
||||
import IconLucideAlertCircle from "~icons/lucide/alert-circle";
|
||||
import IconLucideRefreshCw from "~icons/lucide/refresh-cw";
|
||||
import IconLucideDownload from "~icons/lucide/download";
|
||||
import IconLucideAlertCircle from "~icons/lucide/alert-circle"
|
||||
import IconLucideRefreshCw from "~icons/lucide/refresh-cw"
|
||||
import IconLucideDownload from "~icons/lucide/download"
|
||||
|
||||
const APP_STORE_PATH = "hoppscotch-desktop.store";
|
||||
const APP_STORE_PATH = "hoppscotch-desktop.store"
|
||||
|
||||
// `InstanceSwitcherService` store path.
|
||||
// NOTE: This should be removed eventually,
|
||||
// right now this is part 1/5 of HFE-864
|
||||
const INSTANCE_STORE_PATH = "hopp.store.json"
|
||||
|
||||
enum AppState {
|
||||
LOADING = "loading",
|
||||
|
|
@ -111,184 +154,251 @@ enum AppState {
|
|||
UPDATE_IN_PROGRESS = "update_in_progress",
|
||||
UPDATE_READY = "update_ready",
|
||||
ERROR = "error",
|
||||
LOADED = "loaded"
|
||||
LOADED = "loaded",
|
||||
}
|
||||
|
||||
interface VendoredInstance {
|
||||
type: "vendored";
|
||||
displayName: string;
|
||||
version: string;
|
||||
type: "vendored"
|
||||
displayName: string
|
||||
version: string
|
||||
}
|
||||
|
||||
interface ConnectionState {
|
||||
status: "idle" | "connecting" | "connected" | "error";
|
||||
instance?: VendoredInstance;
|
||||
target?: string;
|
||||
message?: string;
|
||||
status: "idle" | "connecting" | "connected" | "error"
|
||||
instance?: VendoredInstance
|
||||
target?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
const appStore = new LazyStore(APP_STORE_PATH);
|
||||
const appState = ref<AppState>(AppState.LOADING);
|
||||
const updateStatus = ref("");
|
||||
const updateMessage = ref("");
|
||||
const downloadProgress = ref<{ downloaded: number; total?: number }>({ downloaded: 0 });
|
||||
const error = ref("");
|
||||
const statusMessage = ref("Initializing...");
|
||||
const appVersion = ref("...");
|
||||
const appStore = new LazyStore(APP_STORE_PATH)
|
||||
const appState = ref<AppState>(AppState.LOADING)
|
||||
const updateStatus = ref("")
|
||||
const updateMessage = ref("")
|
||||
const downloadProgress = ref<{ downloaded: number; total?: number }>({
|
||||
downloaded: 0,
|
||||
})
|
||||
const error = ref("")
|
||||
const statusMessage = ref("Initializing...")
|
||||
const appVersion = ref("...")
|
||||
|
||||
const updaterService = new UpdaterService(appStore);
|
||||
const updaterService = new UpdaterService(appStore)
|
||||
|
||||
let progressPollingInterval: ReturnType<typeof setInterval> | undefined
|
||||
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
if (bytes === 0) return "0 Bytes"
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
const k = 1024
|
||||
const sizes = ["Bytes", "KB", "MB", "GB"]
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
|
||||
}
|
||||
|
||||
const saveConnectionState = async (state: ConnectionState) => {
|
||||
try {
|
||||
await appStore.set("connectionState", state);
|
||||
await appStore.save();
|
||||
await appStore.set("connectionState", state)
|
||||
await appStore.save()
|
||||
} catch (err) {
|
||||
console.error("Failed to save connection state:", err);
|
||||
console.error("Failed to save connection state:", err)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const setupUpdateStateWatcher = async () => {
|
||||
const unsubscribe = await appStore.onKeyChange<UpdateState>("updateState", (newValue) => {
|
||||
if (!newValue) return;
|
||||
const unsubscribe = await appStore.onKeyChange<UpdateState>(
|
||||
"updateState",
|
||||
(newValue) => {
|
||||
if (!newValue) return
|
||||
|
||||
updateStatus.value = newValue.status;
|
||||
updateMessage.value = newValue.message || "";
|
||||
updateStatus.value = newValue.status
|
||||
updateMessage.value = newValue.message || ""
|
||||
|
||||
if (newValue.progress) {
|
||||
downloadProgress.value = newValue.progress;
|
||||
if (newValue.status === UpdateStatus.AVAILABLE) {
|
||||
appState.value = AppState.UPDATE_AVAILABLE
|
||||
} else if (newValue.status === UpdateStatus.ERROR) {
|
||||
error.value = newValue.message || "Unknown error"
|
||||
appState.value = AppState.ERROR
|
||||
// Stop progress polling on error
|
||||
stopProgressPolling()
|
||||
} else if (
|
||||
newValue.status === UpdateStatus.DOWNLOADING ||
|
||||
newValue.status === UpdateStatus.INSTALLING
|
||||
) {
|
||||
appState.value = AppState.UPDATE_IN_PROGRESS
|
||||
// Start progress polling when downloading
|
||||
if (newValue.status === UpdateStatus.DOWNLOADING) {
|
||||
startProgressPolling()
|
||||
} else {
|
||||
// Stop progress polling when installing
|
||||
stopProgressPolling()
|
||||
}
|
||||
} else if (newValue.status === UpdateStatus.READY_TO_RESTART) {
|
||||
appState.value = AppState.UPDATE_READY
|
||||
// Stop progress polling when ready to restart
|
||||
stopProgressPolling()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (newValue.status === UpdateStatus.AVAILABLE) {
|
||||
appState.value = AppState.UPDATE_AVAILABLE;
|
||||
} else if (newValue.status === UpdateStatus.ERROR) {
|
||||
error.value = newValue.message || "Unknown error";
|
||||
appState.value = AppState.ERROR;
|
||||
} else if (newValue.status === UpdateStatus.DOWNLOADING || newValue.status === UpdateStatus.INSTALLING) {
|
||||
appState.value = AppState.UPDATE_IN_PROGRESS;
|
||||
} else if (newValue.status === UpdateStatus.READY_TO_RESTART) {
|
||||
appState.value = AppState.UPDATE_READY;
|
||||
return unsubscribe
|
||||
}
|
||||
|
||||
const startProgressPolling = () => {
|
||||
if (progressPollingInterval) return
|
||||
|
||||
progressPollingInterval = setInterval(() => {
|
||||
const currentProgress = updaterService.getCurrentProgress()
|
||||
if (currentProgress.downloaded > downloadProgress.value.downloaded) {
|
||||
downloadProgress.value = currentProgress
|
||||
}
|
||||
});
|
||||
}, 100)
|
||||
}
|
||||
|
||||
return unsubscribe;
|
||||
};
|
||||
const stopProgressPolling = () => {
|
||||
if (progressPollingInterval) {
|
||||
clearInterval(progressPollingInterval)
|
||||
progressPollingInterval = undefined
|
||||
}
|
||||
}
|
||||
|
||||
const installUpdate = async () => {
|
||||
try {
|
||||
appState.value = AppState.UPDATE_IN_PROGRESS;
|
||||
await updaterService.downloadAndInstall();
|
||||
appState.value = AppState.UPDATE_IN_PROGRESS
|
||||
await updaterService.downloadAndInstall()
|
||||
// In a rare occurrence where we reach here but automatic restart didn't happen,
|
||||
// we'll just show a restart button instead
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
error.value = `Failed to install update: ${errorMessage}`;
|
||||
appState.value = AppState.ERROR;
|
||||
const errorMessage = err instanceof Error ? err.message : String(err)
|
||||
error.value = `Failed to install update: ${errorMessage}`
|
||||
appState.value = AppState.ERROR
|
||||
stopProgressPolling()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const skipUpdate = async () => {
|
||||
await loadVendored();
|
||||
};
|
||||
await loadVendored()
|
||||
}
|
||||
|
||||
const restartApp = async () => {
|
||||
try {
|
||||
await updaterService.restartApp();
|
||||
await updaterService.restartApp()
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
error.value = `Failed to restart app: ${errorMessage}`;
|
||||
appState.value = AppState.ERROR;
|
||||
const errorMessage = err instanceof Error ? err.message : String(err)
|
||||
error.value = `Failed to restart app: ${errorMessage}`
|
||||
appState.value = AppState.ERROR
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const loadVendored = async () => {
|
||||
try {
|
||||
statusMessage.value = "Loading application...";
|
||||
statusMessage.value = "Loading application..."
|
||||
|
||||
// Standardized vendored instance data.
|
||||
// NOTE: This should be removed eventually,
|
||||
// right now this is part 1/5 of HFE-864
|
||||
const vendoredInstance: VendoredInstance = {
|
||||
type: "vendored",
|
||||
displayName: "Vendored",
|
||||
version: "vendored"
|
||||
};
|
||||
|
||||
await saveConnectionState({
|
||||
status: "connected",
|
||||
instance: vendoredInstance
|
||||
});
|
||||
|
||||
console.log("Loading vendored app...");
|
||||
const loadResp = await load({
|
||||
bundleName: "Hoppscotch",
|
||||
window: { title: "Hoppscotch" }
|
||||
});
|
||||
|
||||
if (!loadResp.success) {
|
||||
throw new Error("Failed to load Hoppscotch Vendored");
|
||||
displayName: "Hoppscotch",
|
||||
version: "25.5.2",
|
||||
}
|
||||
|
||||
console.log("Vendored app loaded successfully");
|
||||
const connectionState: ConnectionState = {
|
||||
status: "connected",
|
||||
instance: vendoredInstance,
|
||||
}
|
||||
|
||||
// Save to current app store.
|
||||
// NOTE: This is existing behavior
|
||||
await saveConnectionState(connectionState)
|
||||
|
||||
// ALSO save to `InstanceSwitcherService` store,
|
||||
// NOTE: This should be removed eventually,
|
||||
// right now this is part 1/5 of HFE-864
|
||||
try {
|
||||
const instanceStore = new LazyStore(INSTANCE_STORE_PATH)
|
||||
await instanceStore.init()
|
||||
await instanceStore.set("connectionState", connectionState)
|
||||
await instanceStore.save()
|
||||
console.log(
|
||||
"Successfully saved vendored state to `InstanceSwitcherService` store"
|
||||
)
|
||||
} catch (instanceStoreError) {
|
||||
console.error(
|
||||
"Failed to save to `InstanceSwitcherService` store:",
|
||||
instanceStoreError
|
||||
)
|
||||
// Don't need to fail the flow if this fails.
|
||||
}
|
||||
|
||||
console.log("Loading vendored app...")
|
||||
const loadResp = await load({
|
||||
bundleName: "Hoppscotch",
|
||||
window: { title: "Hoppscotch" },
|
||||
})
|
||||
|
||||
if (!loadResp.success) {
|
||||
throw new Error("Failed to load Hoppscotch Vendored")
|
||||
}
|
||||
|
||||
console.log("Vendored app loaded successfully")
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
console.error("Error loading vendored app:", errorMessage);
|
||||
error.value = errorMessage;
|
||||
const errorMessage = err instanceof Error ? err.message : String(err)
|
||||
console.error("Error loading vendored app:", errorMessage)
|
||||
error.value = errorMessage
|
||||
|
||||
await saveConnectionState({
|
||||
status: "error",
|
||||
target: "Vendored",
|
||||
message: errorMessage
|
||||
});
|
||||
message: errorMessage,
|
||||
})
|
||||
|
||||
appState.value = AppState.ERROR;
|
||||
appState.value = AppState.ERROR
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const initialize = async () => {
|
||||
appState.value = AppState.LOADING;
|
||||
error.value = "";
|
||||
downloadProgress.value = { downloaded: 0 };
|
||||
appState.value = AppState.LOADING
|
||||
error.value = ""
|
||||
downloadProgress.value = { downloaded: 0 }
|
||||
stopProgressPolling()
|
||||
|
||||
try {
|
||||
try {
|
||||
appVersion.value = await getVersion();
|
||||
appVersion.value = await getVersion()
|
||||
} catch (error) {
|
||||
console.error("Failed to get app version:", error);
|
||||
appVersion.value = "unknown";
|
||||
console.error("Failed to get app version:", error)
|
||||
appVersion.value = "unknown"
|
||||
}
|
||||
|
||||
statusMessage.value = "Initializing stores...";
|
||||
await appStore.init();
|
||||
await updaterService.initialize();
|
||||
statusMessage.value = "Initializing stores..."
|
||||
await appStore.init()
|
||||
await updaterService.initialize()
|
||||
|
||||
await setupUpdateStateWatcher();
|
||||
await setupUpdateStateWatcher()
|
||||
|
||||
statusMessage.value = "Checking for updates...";
|
||||
const checkResult = await updaterService.checkForUpdates();
|
||||
statusMessage.value = "Checking for updates..."
|
||||
const checkResult = await updaterService.checkForUpdates()
|
||||
|
||||
if (checkResult === CheckResult.AVAILABLE) {
|
||||
console.log("Updates available, prompting for install");
|
||||
appState.value = AppState.UPDATE_AVAILABLE;
|
||||
return;
|
||||
console.log("Updates available, prompting for install")
|
||||
appState.value = AppState.UPDATE_AVAILABLE
|
||||
return
|
||||
}
|
||||
|
||||
await loadVendored();
|
||||
await loadVendored()
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
console.error("Initialization error:", errorMessage);
|
||||
error.value = errorMessage;
|
||||
appState.value = AppState.ERROR;
|
||||
const errorMessage = err instanceof Error ? err.message : String(err)
|
||||
console.error("Initialization error:", errorMessage)
|
||||
error.value = errorMessage
|
||||
appState.value = AppState.ERROR
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initialize();
|
||||
});
|
||||
initialize()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stopProgressPolling()
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
"dependencies": {
|
||||
"@hoppscotch/data": "workspace:^",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"faraday-cage": "0.0.16",
|
||||
"faraday-cage": "0.1.0",
|
||||
"fp-ts": "2.16.9",
|
||||
"lodash": "4.17.21",
|
||||
"lodash-es": "4.17.21"
|
||||
|
|
|
|||
|
|
@ -57,6 +57,6 @@
|
|||
"@tauri-apps/plugin-dialog": "2.0.1",
|
||||
"@tauri-apps/plugin-fs": "2.0.2",
|
||||
"@tauri-apps/plugin-store": "2.2.0",
|
||||
"@hoppscotch/plugin-relay": "github:CuriousCorrelation/tauri-plugin-relay"
|
||||
"@hoppscotch/plugin-relay": "github:CuriousCorrelation/tauri-plugin-relay#3273b9b"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,11 +96,10 @@ async function loadUserEnvironments() {
|
|||
|
||||
replaceEnvironments(
|
||||
formatedEnvironments.map((environment) => {
|
||||
const parsedEnv =
|
||||
entityReference(Environment).safeParse(environment)
|
||||
const parsedEnv = Environment.safeParse(environment)
|
||||
|
||||
return parsedEnv.success
|
||||
? parsedEnv.data
|
||||
return parsedEnv.type === "ok"
|
||||
? parsedEnv.value
|
||||
: {
|
||||
...environment,
|
||||
v: EnvironmentSchemaVersion,
|
||||
|
|
@ -191,12 +190,24 @@ function setupUserEnvironmentUpdatedSubscription() {
|
|||
)
|
||||
|
||||
if ((localIndex || localIndex == 0) && name) {
|
||||
const environment = {
|
||||
id,
|
||||
name,
|
||||
variables: JSON.parse(variables),
|
||||
}
|
||||
|
||||
const parsedEnvResult = Environment.safeParse(environment)
|
||||
|
||||
const parsedEnv: Environment =
|
||||
parsedEnvResult.type === "ok"
|
||||
? parsedEnvResult.value
|
||||
: {
|
||||
...environment,
|
||||
v: EnvironmentSchemaVersion,
|
||||
}
|
||||
|
||||
runDispatchWithOutSyncing(() => {
|
||||
updateEnvironment(localIndex, {
|
||||
id,
|
||||
name,
|
||||
variables: JSON.parse(variables),
|
||||
})
|
||||
updateEnvironment(localIndex, parsedEnv)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@hoppscotch/selfhost-web",
|
||||
"private": true,
|
||||
"version": "2025.5.1",
|
||||
"version": "2025.5.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:vite": "vite",
|
||||
|
|
|
|||
|
|
@ -78,6 +78,16 @@ async function initApp() {
|
|||
},
|
||||
auth: platformDefs.auth.get(kernelMode),
|
||||
kernelIO,
|
||||
instance: {
|
||||
instanceType: "vendored",
|
||||
displayConfig: {
|
||||
displayName: "Hoppscotch",
|
||||
description: "On-Prem",
|
||||
version: "25.5.2",
|
||||
connectingMessage: "Connecting to On-prem",
|
||||
connectedMessage: "Connected to On-prem",
|
||||
},
|
||||
},
|
||||
sync: {
|
||||
environments: platformDefs.environments.get(kernelMode),
|
||||
collections: platformDefs.collections.get(kernelMode),
|
||||
|
|
|
|||
|
|
@ -97,11 +97,10 @@ async function loadUserEnvironments() {
|
|||
|
||||
replaceEnvironments(
|
||||
formatedEnvironments.map((environment) => {
|
||||
const parsedEnv =
|
||||
entityReference(Environment).safeParse(environment)
|
||||
const parsedEnv = Environment.safeParse(environment)
|
||||
|
||||
return parsedEnv.success
|
||||
? parsedEnv.data
|
||||
return parsedEnv.type === "ok"
|
||||
? parsedEnv.value
|
||||
: {
|
||||
...environment,
|
||||
v: EnvironmentSchemaVersion,
|
||||
|
|
|
|||
|
|
@ -97,11 +97,10 @@ async function loadUserEnvironments() {
|
|||
|
||||
replaceEnvironments(
|
||||
formatedEnvironments.map((environment) => {
|
||||
const parsedEnv =
|
||||
entityReference(Environment).safeParse(environment)
|
||||
const parsedEnv = Environment.safeParse(environment)
|
||||
|
||||
return parsedEnv.success
|
||||
? parsedEnv.data
|
||||
return parsedEnv.type === "ok"
|
||||
? parsedEnv.value
|
||||
: {
|
||||
...environment,
|
||||
v: EnvironmentSchemaVersion,
|
||||
|
|
@ -192,13 +191,24 @@ function setupUserEnvironmentUpdatedSubscription() {
|
|||
)
|
||||
|
||||
if ((localIndex || localIndex == 0) && name) {
|
||||
const environment = {
|
||||
id,
|
||||
name,
|
||||
variables: JSON.parse(variables),
|
||||
}
|
||||
|
||||
const parsedEnvResult = Environment.safeParse(environment)
|
||||
|
||||
const parsedEnv: Environment =
|
||||
parsedEnvResult.type === "ok"
|
||||
? parsedEnvResult.value
|
||||
: {
|
||||
...environment,
|
||||
v: EnvironmentSchemaVersion,
|
||||
}
|
||||
|
||||
runDispatchWithOutSyncing(() => {
|
||||
updateEnvironment(localIndex, {
|
||||
v: 2,
|
||||
id,
|
||||
name,
|
||||
variables: JSON.parse(variables),
|
||||
})
|
||||
updateEnvironment(localIndex, parsedEnv)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ pub struct Bundle {
|
|||
impl Bundle {
|
||||
pub fn new(bundle_version: Option<String>, content: Vec<u8>, signature: Signature, files: Vec<FileEntry>) -> Self {
|
||||
let metadata = BundleMetadata {
|
||||
version: "2025.5.1".to_string(),
|
||||
version: "2025.5.2".to_string(),
|
||||
created_at: Utc::now(),
|
||||
signature,
|
||||
manifest: Manifest { files },
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ impl Default for ServerConfig {
|
|||
Self {
|
||||
port: default_port(),
|
||||
max_bundle_size: default_max_bundle_size(),
|
||||
bundle_version: Some("2025.5.1".to_string()),
|
||||
bundle_version: Some("2025.5.2".to_string()),
|
||||
csp_directives: None,
|
||||
signing_key: None,
|
||||
verifying_key: None,
|
||||
|
|
@ -75,7 +75,7 @@ impl ServerConfig {
|
|||
Self {
|
||||
signing_key: Some(key_pair.signing_key),
|
||||
verifying_key: Some(key_pair.verifying_key),
|
||||
bundle_version: Some("2025.5.1".to_string()),
|
||||
bundle_version: Some("2025.5.2".to_string()),
|
||||
frontend_path,
|
||||
is_dev: cfg!(debug_assertions),
|
||||
..Default::default()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "hoppscotch-sh-admin",
|
||||
"private": true,
|
||||
"version": "2025.5.1",
|
||||
"version": "2025.5.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ query TeamList($cursor: ID, $take: Int) {
|
|||
allTeams(cursor: $cursor, take: $take) {
|
||||
id
|
||||
name
|
||||
members {
|
||||
teamMembers {
|
||||
membershipID
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
</td>
|
||||
|
||||
<td class="py-4 px-8">
|
||||
{{ team.members?.length }}
|
||||
{{ team.teamMembers?.length }}
|
||||
</td>
|
||||
|
||||
<td @click.stop class="flex justify-end mr-10">
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue