Compare commits

...

9 Commits

Author SHA1 Message Date
nivedin
850954efdf chore: minoir ui and code refactor 2024-05-29 14:09:19 +05:30
nivedin
bf65654126 chore: update settings page interceptor section UI 2024-05-29 14:09:19 +05:30
nivedin
27ea170d85 refactor: update interceptor section in settings page 2024-05-29 14:09:19 +05:30
nivedin
6cf0c427a7 fix: persist interceptor state for loged out user 2024-05-29 14:09:19 +05:30
Mir Arif Hasan
b7a3ae231b HSB-431 fix: email case sensitive in email provider (#4042)
* feat: code level email insensitivity added

* test: fix broken test case

* chore: updated testcase for findUserByEmail in user module

---------

Co-authored-by: Balu Babu <balub997@gmail.com>
2024-05-15 12:37:35 +05:30
Nivedin
f8ac6dfeb1 chore: add workspace switcher login A/B testing flow (#4053)
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
2024-05-10 16:35:42 +05:30
Andrew Bastin
7d2d335b37 chore: revert back default interceptor for sh app to browser 2024-05-10 16:13:51 +05:30
Andrew Bastin
76875db865 chore: bump selfhost-desktop lockfile version 2024-05-10 15:04:16 +05:30
Balu Babu
96e2d87b57 feat: update node version to node20-apline3.19 (#4040) 2024-05-10 14:24:34 +05:30
19 changed files with 153 additions and 157 deletions

View File

@@ -1,4 +1,4 @@
FROM node:18.8.0 AS builder FROM node:20.12.2 AS builder
WORKDIR /usr/src/app WORKDIR /usr/src/app

View File

@@ -121,6 +121,7 @@ describe('AdminService', () => {
NOT: { NOT: {
inviteeEmail: { inviteeEmail: {
in: [dbAdminUsers[0].email], in: [dbAdminUsers[0].email],
mode: 'insensitive',
}, },
}, },
}, },
@@ -229,7 +230,10 @@ describe('AdminService', () => {
expect(mockPrisma.invitedUsers.deleteMany).toHaveBeenCalledWith({ expect(mockPrisma.invitedUsers.deleteMany).toHaveBeenCalledWith({
where: { where: {
inviteeEmail: { in: [invitedUsers[0].inviteeEmail] }, inviteeEmail: {
in: [invitedUsers[0].inviteeEmail],
mode: 'insensitive',
},
}, },
}); });
expect(result).toEqualRight(true); expect(result).toEqualRight(true);

View File

@@ -89,12 +89,17 @@ export class AdminService {
adminEmail: string, adminEmail: string,
inviteeEmail: string, inviteeEmail: string,
) { ) {
if (inviteeEmail == adminEmail) return E.left(DUPLICATE_EMAIL); if (inviteeEmail.toLowerCase() == adminEmail.toLowerCase()) {
return E.left(DUPLICATE_EMAIL);
}
if (!validateEmail(inviteeEmail)) return E.left(INVALID_EMAIL); if (!validateEmail(inviteeEmail)) return E.left(INVALID_EMAIL);
const alreadyInvitedUser = await this.prisma.invitedUsers.findFirst({ const alreadyInvitedUser = await this.prisma.invitedUsers.findFirst({
where: { where: {
inviteeEmail: inviteeEmail, inviteeEmail: {
equals: inviteeEmail,
mode: 'insensitive',
},
}, },
}); });
if (alreadyInvitedUser != null) return E.left(USER_ALREADY_INVITED); if (alreadyInvitedUser != null) return E.left(USER_ALREADY_INVITED);
@@ -159,7 +164,7 @@ export class AdminService {
try { try {
await this.prisma.invitedUsers.deleteMany({ await this.prisma.invitedUsers.deleteMany({
where: { where: {
inviteeEmail: { in: inviteeEmails }, inviteeEmail: { in: inviteeEmails, mode: 'insensitive' },
}, },
}); });
return E.right(true); return E.right(true);
@@ -189,6 +194,7 @@ export class AdminService {
NOT: { NOT: {
inviteeEmail: { inviteeEmail: {
in: userEmailObjs.map((user) => user.email), in: userEmailObjs.map((user) => user.email),
mode: 'insensitive',
}, },
}, },
}, },

View File

@@ -299,7 +299,10 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
where: userEmail where: userEmail
? { ? {
User: { User: {
email: userEmail, email: {
equals: userEmail,
mode: 'insensitive',
},
}, },
} }
: undefined, : undefined,

View File

@@ -75,12 +75,13 @@ export class TeamInvitationService {
if (!isEmailValid) return E.left(INVALID_EMAIL); if (!isEmailValid) return E.left(INVALID_EMAIL);
try { try {
const teamInvite = await this.prisma.teamInvitation.findUniqueOrThrow({ const teamInvite = await this.prisma.teamInvitation.findFirstOrThrow({
where: { where: {
teamID_inviteeEmail: { inviteeEmail: {
inviteeEmail: inviteeEmail, equals: inviteeEmail,
teamID: teamID, mode: 'insensitive',
}, },
teamID,
}, },
}); });

View File

@@ -149,7 +149,7 @@ beforeEach(() => {
describe('UserService', () => { describe('UserService', () => {
describe('findUserByEmail', () => { describe('findUserByEmail', () => {
test('should successfully return a valid user given a valid email', async () => { test('should successfully return a valid user given a valid email', async () => {
mockPrisma.user.findUniqueOrThrow.mockResolvedValueOnce(user); mockPrisma.user.findFirst.mockResolvedValueOnce(user);
const result = await userService.findUserByEmail( const result = await userService.findUserByEmail(
'dwight@dundermifflin.com', 'dwight@dundermifflin.com',
@@ -158,7 +158,7 @@ describe('UserService', () => {
}); });
test('should return a null user given a invalid email', async () => { test('should return a null user given a invalid email', async () => {
mockPrisma.user.findUniqueOrThrow.mockRejectedValueOnce('NotFoundError'); mockPrisma.user.findFirst.mockResolvedValueOnce(null);
const result = await userService.findUserByEmail('jim@dundermifflin.com'); const result = await userService.findUserByEmail('jim@dundermifflin.com');
expect(result).resolves.toBeNone; expect(result).resolves.toBeNone;

View File

@@ -62,16 +62,16 @@ export class UserService {
* @returns Option of found User * @returns Option of found User
*/ */
async findUserByEmail(email: string): Promise<O.None | O.Some<AuthUser>> { async findUserByEmail(email: string): Promise<O.None | O.Some<AuthUser>> {
try { const user = await this.prisma.user.findFirst({
const user = await this.prisma.user.findUniqueOrThrow({ where: {
where: { email: {
email: email, equals: email,
mode: 'insensitive',
}, },
}); },
return O.some(user); });
} catch (error) { if (!user) return O.none;
return O.none; return O.some(user);
}
} }
/** /**

View File

@@ -43,12 +43,19 @@
@click="invokeAction('modals.support.toggle')" @click="invokeAction('modals.support.toggle')"
/> />
</div> </div>
<div class="flex"> <div
class="flex"
:class="{
'flex-row-reverse gap-2':
workspaceSelectorFlagEnabled && !currentUser,
}"
>
<div <div
v-if="currentUser === null" v-if="currentUser === null"
class="inline-flex items-center space-x-2" class="inline-flex items-center space-x-2"
> >
<HoppButtonSecondary <HoppButtonSecondary
v-if="!workspaceSelectorFlagEnabled"
:icon="IconUploadCloud" :icon="IconUploadCloud"
:label="t('header.save_workspace')" :label="t('header.save_workspace')"
class="!focus-visible:text-emerald-600 !hover:text-emerald-600 hidden h-8 border border-emerald-600/25 bg-emerald-500/10 !text-emerald-500 hover:border-emerald-600/20 hover:bg-emerald-600/20 focus-visible:border-emerald-600/20 focus-visible:bg-emerald-600/20 md:flex" class="!focus-visible:text-emerald-600 !hover:text-emerald-600 hidden h-8 border border-emerald-600/25 bg-emerald-500/10 !text-emerald-500 hover:border-emerald-600/20 hover:bg-emerald-600/20 focus-visible:border-emerald-600/20 focus-visible:bg-emerald-600/20 md:flex"
@@ -60,18 +67,22 @@
@click="invokeAction('modals.login.toggle')" @click="invokeAction('modals.login.toggle')"
/> />
</div> </div>
<div v-else class="inline-flex items-center space-x-2"> <TeamsMemberStack
<TeamsMemberStack v-else-if="
v-if=" currentUser !== null &&
workspace.type === 'team' && workspace.type === 'team' &&
selectedTeam && selectedTeam &&
selectedTeam.teamMembers.length > 1 selectedTeam.teamMembers.length > 1
" "
:team-members="selectedTeam.teamMembers" :team-members="selectedTeam.teamMembers"
show-count show-count
class="mx-2" class="mx-2"
@handle-click="handleTeamEdit()" @handle-click="handleTeamEdit()"
/> />
<div
v-if="workspaceSelectorFlagEnabled || currentUser"
class="inline-flex items-center space-x-2"
>
<div <div
class="flex h-8 divide-x divide-emerald-600/25 rounded border border-emerald-600/25 bg-emerald-500/10 focus-within:divide-emerald-600/20 focus-within:border-emerald-600/20 focus-within:bg-emerald-600/20 hover:divide-emerald-600/20 hover:border-emerald-600/20 hover:bg-emerald-600/20" class="flex h-8 divide-x divide-emerald-600/25 rounded border border-emerald-600/25 bg-emerald-500/10 focus-within:divide-emerald-600/20 focus-within:border-emerald-600/20 focus-within:bg-emerald-600/20 hover:divide-emerald-600/20 hover:border-emerald-600/20 hover:bg-emerald-600/20"
> >
@@ -84,6 +95,7 @@
/> />
<HoppButtonSecondary <HoppButtonSecondary
v-if=" v-if="
currentUser &&
workspace.type === 'team' && workspace.type === 'team' &&
selectedTeam && selectedTeam &&
selectedTeam?.myRole === 'OWNER' selectedTeam?.myRole === 'OWNER'
@@ -124,7 +136,7 @@
</div> </div>
</template> </template>
</tippy> </tippy>
<span class="px-2"> <span v-if="currentUser" class="px-2">
<tippy <tippy
interactive interactive
trigger="click" trigger="click"
@@ -259,6 +271,13 @@ import {
const t = useI18n() const t = useI18n()
const toast = useToast() const toast = useToast()
/**
* Feature flag to enable the workspace selector login conversion
*/
const workspaceSelectorFlagEnabled = computed(
() => !!platform.platformFeatureFlags.workspaceSwitcherLogin?.value
)
/** /**
* Once the PWA code is initialized, this holds a method * Once the PWA code is initialized, this holds a method
* that can be called to show the user the installation * that can be called to show the user the installation
@@ -380,6 +399,8 @@ const inviteTeam = (team: { name: string }, teamID: string) => {
// Show the workspace selected team invite modal if the user is an owner of the team else show the default invite modal // Show the workspace selected team invite modal if the user is an owner of the team else show the default invite modal
const handleInvite = () => { const handleInvite = () => {
if (!currentUser.value) return invokeAction("modals.login.toggle")
if ( if (
workspace.value.type === "team" && workspace.value.type === "team" &&
workspace.value.teamID && workspace.value.teamID &&

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="flex flex-col space-y-2"> <div class="flex flex-col space-y-2">
<div class="flex flex-col px-4 pt-2"> <div v-if="isTooltipComponent" class="flex flex-col px-4 pt-2">
<h2 class="inline-flex pb-1 font-semibold text-secondaryDark"> <h2 class="inline-flex pb-1 font-semibold text-secondaryDark">
{{ t("settings.interceptor") }} {{ t("settings.interceptor") }}
</h2> </h2>
@@ -19,6 +19,9 @@
:value="interceptor.interceptorID" :value="interceptor.interceptorID"
:label="unref(interceptor.name(t))" :label="unref(interceptor.name(t))"
:selected="interceptorSelection === interceptor.interceptorID" :selected="interceptorSelection === interceptor.interceptorID"
:class="{
'!px-0 hover:bg-transparent': !isTooltipComponent,
}"
@change="interceptorSelection = interceptor.interceptorID" @change="interceptorSelection = interceptor.interceptorID"
/> />
@@ -39,6 +42,15 @@ import { InterceptorService } from "~/services/interceptor.service"
const t = useI18n() const t = useI18n()
withDefaults(
defineProps<{
isTooltipComponent?: boolean
}>(),
{
isTooltipComponent: true,
}
)
const interceptorService = useService(InterceptorService) const interceptorService = useService(InterceptorService)
const interceptorSelection = const interceptorSelection =

View File

@@ -36,16 +36,6 @@
/> />
</span> </span>
</div> </div>
<div class="space-y-4 py-4">
<div class="flex items-center">
<HoppSmartToggle
:on="extensionEnabled"
@change="extensionEnabled = !extensionEnabled"
>
{{ t("settings.extensions_use_toggle") }}
</HoppSmartToggle>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -55,34 +45,12 @@ import IconCheckCircle from "~icons/lucide/check-circle"
import { useI18n } from "@composables/i18n" import { useI18n } from "@composables/i18n"
import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension" import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension"
import { useService } from "dioc/vue" import { useService } from "dioc/vue"
import { computed } from "vue"
import { InterceptorService } from "~/services/interceptor.service"
import { platform } from "~/platform"
const t = useI18n() const t = useI18n()
const interceptorService = useService(InterceptorService)
const extensionService = useService(ExtensionInterceptorService) const extensionService = useService(ExtensionInterceptorService)
const extensionVersion = extensionService.extensionVersion const extensionVersion = extensionService.extensionVersion
const hasChromeExtInstalled = extensionService.chromeExtensionInstalled const hasChromeExtInstalled = extensionService.chromeExtensionInstalled
const hasFirefoxExtInstalled = extensionService.firefoxExtensionInstalled const hasFirefoxExtInstalled = extensionService.firefoxExtensionInstalled
const extensionEnabled = computed({
get() {
return (
interceptorService.currentInterceptorID.value ===
extensionService.interceptorID
)
},
set(active) {
if (active) {
interceptorService.currentInterceptorID.value =
extensionService.interceptorID
} else {
interceptorService.currentInterceptorID.value =
platform.interceptors.default
}
},
})
</script> </script>

View File

@@ -8,16 +8,6 @@
:label="t('app.proxy_privacy_policy')" :label="t('app.proxy_privacy_policy')"
/>. />.
</div> </div>
<div class="space-y-4 py-4">
<div class="flex items-center">
<HoppSmartToggle
:on="proxyEnabled"
@change="proxyEnabled = !proxyEnabled"
>
{{ t("settings.proxy_use_toggle") }}
</HoppSmartToggle>
</div>
</div>
<div class="flex items-center space-x-2 py-4"> <div class="flex items-center space-x-2 py-4">
<HoppSmartInput <HoppSmartInput
v-model="PROXY_URL" v-model="PROXY_URL"
@@ -50,7 +40,6 @@ import { computed } from "vue"
import { useService } from "dioc/vue" import { useService } from "dioc/vue"
import { InterceptorService } from "~/services/interceptor.service" import { InterceptorService } from "~/services/interceptor.service"
import { proxyInterceptor } from "~/platform/std/interceptors/proxy" import { proxyInterceptor } from "~/platform/std/interceptors/proxy"
import { platform } from "~/platform"
const t = useI18n() const t = useI18n()
const toast = useToast() const toast = useToast()
@@ -59,23 +48,11 @@ const interceptorService = useService(InterceptorService)
const PROXY_URL = useSetting("PROXY_URL") const PROXY_URL = useSetting("PROXY_URL")
const proxyEnabled = computed({ const proxyEnabled = computed(
get() { () =>
return ( interceptorService.currentInterceptorID.value ===
interceptorService.currentInterceptorID.value === proxyInterceptor.interceptorID
proxyInterceptor.interceptorID )
)
},
set(active) {
if (active) {
interceptorService.currentInterceptorID.value =
proxyInterceptor.interceptorID
} else {
interceptorService.currentInterceptorID.value =
platform.interceptors.default
}
},
})
const clearIcon = refAutoReset<typeof IconRotateCCW | typeof IconCheck>( const clearIcon = refAutoReset<typeof IconRotateCCW | typeof IconCheck>(
IconRotateCCW, IconRotateCCW,

View File

@@ -59,7 +59,7 @@
/> />
</div> </div>
<div <div
v-if="!loading && teamListAdapterError" v-else-if="teamListAdapterError"
class="flex flex-col items-center py-4" class="flex flex-col items-center py-4"
> >
<icon-lucide-help-circle class="svg-icons mb-4" /> <icon-lucide-help-circle class="svg-icons mb-4" />
@@ -85,7 +85,7 @@ import { useColorMode } from "@composables/theming"
import { GetMyTeamsQuery } from "~/helpers/backend/graphql" import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
import IconDone from "~icons/lucide/check" import IconDone from "~icons/lucide/check"
import { useLocalState } from "~/newstore/localstate" import { useLocalState } from "~/newstore/localstate"
import { defineActionHandler } from "~/helpers/actions" import { defineActionHandler, invokeAction } from "~/helpers/actions"
import { WorkspaceService } from "~/services/workspace.service" import { WorkspaceService } from "~/services/workspace.service"
import { useService } from "dioc/vue" import { useService } from "dioc/vue"
import { useElementVisibility, useIntervalFn } from "@vueuse/core" import { useElementVisibility, useIntervalFn } from "@vueuse/core"
@@ -157,8 +157,8 @@ const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
workspaceService.changeWorkspace({ workspaceService.changeWorkspace({
teamID: team.id, teamID: team.id,
teamName: team.name, teamName: team.name,
role: team.myRole,
type: "team", type: "team",
role: team.myRole,
}) })
} }
@@ -174,12 +174,16 @@ watch(
(user) => { (user) => {
if (!user) { if (!user) {
switchToPersonalWorkspace() switchToPersonalWorkspace()
teamListadapter.dispose()
} }
} }
) )
const displayModalAdd = (shouldDisplay: boolean) => { const displayModalAdd = (shouldDisplay: boolean) => {
if (!currentUser.value) return invokeAction("modals.login.toggle")
showModalAdd.value = shouldDisplay showModalAdd.value = shouldDisplay
teamListadapter.fetchList()
} }
defineActionHandler("modals.team.new", () => { defineActionHandler("modals.team.new", () => {

View File

@@ -50,6 +50,7 @@ export default class TeamListAdapter {
} }
public dispose() { public dispose() {
this.teamList$.next([])
this.isDispose = true this.isDispose = true
clearTimeout(this.timeoutHandle as any) clearTimeout(this.timeoutHandle as any)
this.timeoutHandle = null this.timeoutHandle = null

View File

@@ -1,8 +1,6 @@
import { cloneDeep, defaultsDeep, has } from "lodash-es" import { cloneDeep, defaultsDeep, has } from "lodash-es"
import { Observable } from "rxjs" import { Observable } from "rxjs"
import { distinctUntilChanged, pluck } from "rxjs/operators" import { distinctUntilChanged, pluck } from "rxjs/operators"
import { nextTick } from "vue"
import { platform } from "~/platform"
import type { KeysMatching } from "~/types/ts-utils" import type { KeysMatching } from "~/types/ts-utils"
import DispatchingStore, { defineDispatchers } from "./DispatchingStore" import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
@@ -70,63 +68,52 @@ export type SettingsDef = {
HAS_OPENED_SPOTLIGHT: boolean HAS_OPENED_SPOTLIGHT: boolean
} }
export const getDefaultSettings = (): SettingsDef => { export const getDefaultSettings = (): SettingsDef => ({
const defaultSettings: SettingsDef = { syncCollections: true,
syncCollections: true, syncHistory: true,
syncHistory: true, syncEnvironments: true,
syncEnvironments: true,
WRAP_LINES: { WRAP_LINES: {
httpRequestBody: true, httpRequestBody: true,
httpResponseBody: true, httpResponseBody: true,
httpHeaders: true, httpHeaders: true,
httpParams: true, httpParams: true,
httpUrlEncoded: true, httpUrlEncoded: true,
httpPreRequest: true, httpPreRequest: true,
httpTest: true, httpTest: true,
httpRequestVariables: true, httpRequestVariables: true,
graphqlQuery: true, graphqlQuery: true,
graphqlResponseBody: true, graphqlResponseBody: true,
graphqlHeaders: false, graphqlHeaders: false,
graphqlVariables: false, graphqlVariables: false,
graphqlSchema: true, graphqlSchema: true,
importCurl: true, importCurl: true,
codeGen: true, codeGen: true,
cookie: true, cookie: true,
}, },
CURRENT_INTERCEPTOR_ID: "", // Set empty because interceptor module will set the default value
CURRENT_INTERCEPTOR_ID: "",
// TODO: Interceptor related settings should move under the interceptor systems // TODO: Interceptor related settings should move under the interceptor systems
PROXY_URL: "https://proxy.hoppscotch.io/", PROXY_URL: "https://proxy.hoppscotch.io/",
URL_EXCLUDES: { URL_EXCLUDES: {
auth: true, auth: true,
httpUser: true, httpUser: true,
httpPassword: true, httpPassword: true,
bearerToken: true, bearerToken: true,
oauth2Token: true, oauth2Token: true,
}, },
THEME_COLOR: "indigo", THEME_COLOR: "indigo",
BG_COLOR: "system", BG_COLOR: "system",
TELEMETRY_ENABLED: true, TELEMETRY_ENABLED: true,
EXPAND_NAVIGATION: false, EXPAND_NAVIGATION: false,
SIDEBAR: true, SIDEBAR: true,
SIDEBAR_ON_LEFT: false, SIDEBAR_ON_LEFT: false,
COLUMN_LAYOUT: true, COLUMN_LAYOUT: true,
HAS_OPENED_SPOTLIGHT: false, HAS_OPENED_SPOTLIGHT: false,
} })
// Wait for platform to initialize before setting CURRENT_INTERCEPTOR_ID
nextTick(() => {
applySetting(
"CURRENT_INTERCEPTOR_ID",
platform?.interceptors.default || "browser"
)
})
return defaultSettings
}
type ApplySettingPayload = { type ApplySettingPayload = {
[K in keyof SettingsDef]: { [K in keyof SettingsDef]: {

View File

@@ -98,6 +98,12 @@
</p> </p>
</div> </div>
<div class="space-y-8 p-8 md:col-span-2"> <div class="space-y-8 p-8 md:col-span-2">
<section class="flex flex-col space-y-2">
<h4 class="font-semibold text-secondaryDark">
{{ t("settings.interceptor") }}
</h4>
<AppInterceptor :is-tooltip-component="false" />
</section>
<section v-for="[id, settings] in interceptorsWithSettings" :key="id"> <section v-for="[id, settings] in interceptorsWithSettings" :key="id">
<h4 class="font-semibold text-secondaryDark"> <h4 class="font-semibold text-secondaryDark">
{{ settings.entryTitle(t) }} {{ settings.entryTitle(t) }}

View File

@@ -11,6 +11,7 @@ import { InspectorsPlatformDef } from "./inspectors"
import { ServiceClassInstance } from "dioc" import { ServiceClassInstance } from "dioc"
import { IOPlatformDef } from "./io" import { IOPlatformDef } from "./io"
import { SpotlightPlatformDef } from "./spotlight" import { SpotlightPlatformDef } from "./spotlight"
import { Ref } from "vue"
export type PlatformDef = { export type PlatformDef = {
ui?: UIPlatformDef ui?: UIPlatformDef
@@ -45,6 +46,11 @@ export type PlatformDef = {
* If a value is not given, then the value is assumed to be true * If a value is not given, then the value is assumed to be true
*/ */
promptAsUsingCookies?: boolean promptAsUsingCookies?: boolean
/**
* Whether to show the A/B testing workspace switcher click login flow or not
*/
workspaceSwitcherLogin?: Ref<boolean>
} }
} }

View File

@@ -1260,7 +1260,7 @@ dependencies = [
[[package]] [[package]]
name = "hoppscotch-desktop" name = "hoppscotch-desktop"
version = "24.3.2" version = "24.3.3"
dependencies = [ dependencies = [
"cocoa 0.25.0", "cocoa 0.25.0",
"hex_color", "hex_color",

View File

@@ -26,7 +26,7 @@ createHoppApp("#app", {
history: historyDef, history: historyDef,
}, },
interceptors: { interceptors: {
default: "proxy", default: "browser",
interceptors: [ interceptors: [
{ type: "standalone", interceptor: browserInterceptor }, { type: "standalone", interceptor: browserInterceptor },
{ type: "standalone", interceptor: proxyInterceptor }, { type: "standalone", interceptor: proxyInterceptor },

View File

@@ -1,4 +1,4 @@
FROM node:18-alpine3.19 as base_builder FROM node:20-alpine3.19 as base_builder
WORKDIR /usr/src/app WORKDIR /usr/src/app