diff options
Diffstat (limited to 'app/assets/javascripts/jira_connect/subscriptions')
10 files changed, 277 insertions, 51 deletions
diff --git a/app/assets/javascripts/jira_connect/subscriptions/api.js b/app/assets/javascripts/jira_connect/subscriptions/api.js index de67703356f..c79d7002111 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/api.js +++ b/app/assets/javascripts/jira_connect/subscriptions/api.js @@ -1,10 +1,25 @@ import axios from 'axios'; +import { buildApiUrl } from '~/api/api_utils'; + +import { GITLAB_COM_BASE_PATH } from '~/jira_connect/subscriptions/constants'; import { getJwt } from './utils'; +const CURRENT_USER_PATH = '/api/:version/user'; +const JIRA_CONNECT_SUBSCRIPTIONS_PATH = '/api/:version/integrations/jira_connect/subscriptions'; +const JIRA_CONNECT_INSTALLATIONS_PATH = '/-/jira_connect/installations'; +const JIRA_CONNECT_OAUTH_APPLICATION_ID_PATH = '/-/jira_connect/oauth_application_id'; + +// This export is only used for testing purposes +export const axiosInstance = axios.create(); + +export const setApiBaseURL = (baseURL = null) => { + axiosInstance.defaults.baseURL = baseURL; +}; + export const addSubscription = async (addPath, namespace) => { const jwt = await getJwt(); - return axios.post(addPath, { + return axiosInstance.post(addPath, { jwt, namespace_path: namespace, }); @@ -13,7 +28,7 @@ export const addSubscription = async (addPath, namespace) => { export const removeSubscription = async (removePath) => { const jwt = await getJwt(); - return axios.delete(removePath, { + return axiosInstance.delete(removePath, { params: { jwt, }, @@ -21,7 +36,7 @@ export const removeSubscription = async (removePath) => { }; export const fetchGroups = async (groupsPath, { page, perPage, search }) => { - return axios.get(groupsPath, { + return axiosInstance.get(groupsPath, { params: { page, per_page: perPage, @@ -33,9 +48,50 @@ export const fetchGroups = async (groupsPath, { page, perPage, search }) => { export const fetchSubscriptions = async (subscriptionsPath) => { const jwt = await getJwt(); - return axios.get(subscriptionsPath, { + return axiosInstance.get(subscriptionsPath, { params: { jwt, }, }); }; + +export const getCurrentUser = (options) => { + const url = buildApiUrl(CURRENT_USER_PATH); + return axiosInstance.get(url, { ...options }); +}; + +export const addJiraConnectSubscription = (namespacePath, { jwt, accessToken }) => { + const url = buildApiUrl(JIRA_CONNECT_SUBSCRIPTIONS_PATH); + + return axiosInstance.post( + url, + { + jwt, + namespace_path: namespacePath, + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + ); +}; + +export const updateInstallation = async (instanceUrl) => { + const jwt = await getJwt(); + + return axiosInstance.put(JIRA_CONNECT_INSTALLATIONS_PATH, { + jwt, + installation: { + instance_url: instanceUrl === GITLAB_COM_BASE_PATH ? null : instanceUrl, + }, + }); +}; + +export const fetchOAuthApplicationId = () => { + return axiosInstance.get(JIRA_CONNECT_OAUTH_APPLICATION_ID_PATH); +}; + +export const fetchOAuthToken = (oauthTokenPath, data = {}) => { + return axiosInstance.post(oauthTokenPath, data); +}; diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue index 66aea60c5b5..22a6c0751f4 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue +++ b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue @@ -83,7 +83,7 @@ export default { * if the jiraConnectOauth flag is enabled. */ fetchSubscriptionsOauth() { - if (!this.isOauthEnabled) return; + if (!this.isOauthEnabled || !this.userSignedIn) return; this.fetchSubscriptions(this.subscriptionsPath); }, @@ -146,12 +146,12 @@ export default { <div class="gl-layout-w-limited gl-mx-auto gl-px-5 gl-mb-7"> <sign-in-page - v-if="!userSignedIn" + v-show="!userSignedIn" :has-subscriptions="hasSubscriptions" @sign-in-oauth="onSignInOauth" @error="onSignInError" /> - <subscriptions-page v-else :has-subscriptions="hasSubscriptions" /> + <subscriptions-page v-if="userSignedIn" :has-subscriptions="hasSubscriptions" /> </div> </div> </main> diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue b/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue index c5b56535247..9b50681515e 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue +++ b/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue @@ -3,6 +3,7 @@ import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; import { s__ } from '~/locale'; import { helpPagePath } from '~/helpers/help_page_helper'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; const COMPATIBILITY_ALERT_STATE_KEY = 'compatibility_alert_dismissed'; @@ -14,6 +15,7 @@ export default { GlLink, LocalStorageSync, }, + mixins: [glFeatureFlagMixin()], data() { return { alertDismissed: false, @@ -23,6 +25,14 @@ export default { shouldShowAlert() { return !this.alertDismissed; }, + isOauthSelfManagedEnabled() { + return this.glFeatures.jiraConnectOauth && this.glFeatures.jiraConnectOauthSelfManaged; + }, + alertBody() { + return this.isOauthSelfManagedEnabled + ? this.$options.i18n.body + : this.$options.i18n.bodyDotCom; + }, }, methods: { dismissAlert() { @@ -32,6 +42,9 @@ export default { i18n: { title: s__('Integrations|Known limitations'), body: s__( + 'Integrations|Adding a namespace only works in browsers that allow cross-site cookies. %{linkStart}Learn more%{linkEnd}.', + ), + bodyDotCom: s__( 'Integrations|This integration only works with GitLab.com. Adding a namespace only works in browsers that allow cross-site cookies. %{linkStart}Learn more%{linkEnd}.', ), }, @@ -50,7 +63,7 @@ export default { :title="$options.i18n.title" @dismiss="dismissAlert" > - <gl-sprintf :message="$options.i18n.body"> + <gl-sprintf :message="alertBody"> <template #link="{ content }"> <gl-link :href="$options.DOCS_LINK_URL" target="_blank">{{ content }}</gl-link> </template> diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue index ad3e70bcb5f..4cf3a1a0279 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue +++ b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue @@ -1,30 +1,53 @@ <script> import { mapActions, mapMutations } from 'vuex'; import { GlButton } from '@gitlab/ui'; -import axios from '~/lib/utils/axios_utils'; +import { sprintf } from '~/locale'; + import { I18N_DEFAULT_SIGN_IN_BUTTON_TEXT, + I18N_CUSTOM_SIGN_IN_BUTTON_TEXT, + I18N_OAUTH_APPLICATION_ID_ERROR_MESSAGE, + I18N_OAUTH_FAILED_TITLE, + I18N_OAUTH_FAILED_MESSAGE, + OAUTH_SELF_MANAGED_DOC_LINK, OAUTH_WINDOW_OPTIONS, PKCE_CODE_CHALLENGE_DIGEST_ALGORITHM, } from '~/jira_connect/subscriptions/constants'; +import { fetchOAuthApplicationId, fetchOAuthToken } from '~/jira_connect/subscriptions/api'; import { setUrlParams } from '~/lib/utils/url_utility'; import AccessorUtilities from '~/lib/utils/accessor'; import { createCodeVerifier, createCodeChallenge } from '../pkce'; -import { SET_ACCESS_TOKEN } from '../store/mutation_types'; +import { SET_ACCESS_TOKEN, SET_ALERT } from '../store/mutation_types'; export default { components: { GlButton, }, inject: ['oauthMetadata'], + props: { + gitlabBasePath: { + type: String, + required: false, + default: undefined, + }, + }, data() { return { - token: null, loading: false, codeVerifier: null, + clientId: null, canUseCrypto: AccessorUtilities.canUseCrypto(), }; }, + computed: { + buttonText() { + if (!this.gitlabBasePath) { + return I18N_DEFAULT_SIGN_IN_BUTTON_TEXT; + } + + return sprintf(I18N_CUSTOM_SIGN_IN_BUTTON_TEXT, { url: this.gitlabBasePath }); + }, + }, created() { window.addEventListener('message', this.handleWindowMessage); }, @@ -35,30 +58,72 @@ export default { ...mapActions(['loadCurrentUser']), ...mapMutations({ setAccessToken: SET_ACCESS_TOKEN, + setAlert: SET_ALERT, }), - async startOAuthFlow() { - this.loading = true; - + async fetchOauthClientId() { + const { + data: { application_id: clientId }, + } = await fetchOAuthApplicationId(); + return clientId; + }, + async getOauthAuthorizeURL() { // Generate state necessary for PKCE OAuth flow this.codeVerifier = createCodeVerifier(); const codeChallenge = await createCodeChallenge(this.codeVerifier); + try { + this.clientId = this.gitlabBasePath + ? await this.fetchOauthClientId() + : this.oauthMetadata?.oauth_token_payload?.client_id; + } catch { + throw new Error(I18N_OAUTH_APPLICATION_ID_ERROR_MESSAGE); + } // Build the initial OAuth authorization URL const { oauth_authorize_url: oauthAuthorizeURL } = this.oauthMetadata; - - const oauthAuthorizeURLWithChallenge = setUrlParams( - { - code_challenge: codeChallenge, - code_challenge_method: PKCE_CODE_CHALLENGE_DIGEST_ALGORITHM.short, - }, - oauthAuthorizeURL, + const oauthAuthorizeURLWithChallenge = new URL( + setUrlParams( + { + code_challenge: codeChallenge, + code_challenge_method: PKCE_CODE_CHALLENGE_DIGEST_ALGORITHM.short, + client_id: this.clientId, + }, + oauthAuthorizeURL, + ), ); - window.open( - oauthAuthorizeURLWithChallenge, - this.$options.i18n.defaultButtonText, - OAUTH_WINDOW_OPTIONS, - ); + // Rebase URL on the specified GitLab base path (if specified). + if (this.gitlabBasePath) { + const gitlabBasePathURL = new URL(this.gitlabBasePath); + oauthAuthorizeURLWithChallenge.hostname = gitlabBasePathURL.hostname; + oauthAuthorizeURLWithChallenge.pathname = `${ + gitlabBasePathURL.pathname === '/' ? '' : gitlabBasePathURL.pathname + }${oauthAuthorizeURLWithChallenge.pathname}`; + } + + return oauthAuthorizeURLWithChallenge.toString(); + }, + async startOAuthFlow() { + try { + this.loading = true; + const oauthAuthorizeURL = await this.getOauthAuthorizeURL(); + + window.open(oauthAuthorizeURL, I18N_DEFAULT_SIGN_IN_BUTTON_TEXT, OAUTH_WINDOW_OPTIONS); + } catch (e) { + if (e.message) { + this.setAlert({ + message: e.message, + variant: 'danger', + }); + } else { + this.setAlert({ + linkUrl: OAUTH_SELF_MANAGED_DOC_LINK, + title: I18N_OAUTH_FAILED_TITLE, + message: this.gitlabBasePath ? I18N_OAUTH_FAILED_MESSAGE : '', + variant: 'danger', + }); + } + this.loading = false; + } }, async handleWindowMessage(event) { if (window.origin !== event.origin) { @@ -94,20 +159,18 @@ export default { async getOAuthToken(code) { const { oauth_token_payload: oauthTokenPayload, - oauth_token_url: oauthTokenURL, + oauth_token_path: oauthTokenPath, } = this.oauthMetadata; - const { data } = await axios.post(oauthTokenURL, { + const { data } = await fetchOAuthToken(oauthTokenPath, { ...oauthTokenPayload, code, code_verifier: this.codeVerifier, + client_id: this.clientId, }); return data.access_token; }, }, - i18n: { - defaultButtonText: I18N_DEFAULT_SIGN_IN_BUTTON_TEXT, - }, }; </script> <template> @@ -119,7 +182,7 @@ export default { @click="startOAuthFlow" > <slot> - {{ $options.i18n.defaultButtonText }} + {{ buttonText }} </slot> </gl-button> </template> diff --git a/app/assets/javascripts/jira_connect/subscriptions/constants.js b/app/assets/javascripts/jira_connect/subscriptions/constants.js index 8faafb1b0d0..fc365746b54 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/constants.js +++ b/app/assets/javascripts/jira_connect/subscriptions/constants.js @@ -3,11 +3,13 @@ import { helpPagePath } from '~/helpers/help_page_helper'; export const DEFAULT_GROUPS_PER_PAGE = 10; export const ALERT_LOCALSTORAGE_KEY = 'gitlab_alert'; +export const BASE_URL_LOCALSTORAGE_KEY = 'gitlab_base_url'; export const MINIMUM_SEARCH_TERM_LENGTH = 3; export const ADD_NAMESPACE_MODAL_ID = 'add-namespace-modal'; export const I18N_DEFAULT_SIGN_IN_BUTTON_TEXT = s__('Integrations|Sign in to GitLab'); +export const I18N_CUSTOM_SIGN_IN_BUTTON_TEXT = s__('Integrations|Sign in to %{url}'); export const I18N_DEFAULT_SIGN_IN_ERROR_MESSAGE = s__('Integrations|Failed to sign in to GitLab.'); export const I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE = s__( 'Integrations|Failed to load subscriptions.', @@ -18,13 +20,28 @@ export const I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE = s__( export const I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE = s__( 'Integrations|You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}', ); -export const INTEGRATIONS_DOC_LINK = helpPagePath('integration/jira_development_panel', { - anchor: 'use-the-integration', -}); - export const I18N_ADD_SUBSCRIPTIONS_ERROR_MESSAGE = s__( 'Integrations|Failed to link namespace. Please try again.', ); +export const I18N_UPDATE_INSTALLATION_ERROR_MESSAGE = s__( + 'Integrations|Failed to update GitLab version. Please try again.', +); +export const I18N_OAUTH_APPLICATION_ID_ERROR_MESSAGE = s__( + 'Integrations|Failed to load Jira Connect Application ID. Please try again.', +); +export const I18N_OAUTH_FAILED_TITLE = s__('Integrations|Failed to sign in to GitLab.'); +export const I18N_OAUTH_FAILED_MESSAGE = s__( + 'Integrations|Ensure your instance URL is correct and your instance is configured correctly. %{linkStart}Learn more%{linkEnd}.', +); + +export const INTEGRATIONS_DOC_LINK = helpPagePath('integration/jira/development_panel', { + anchor: 'use-the-integration', +}); +export const OAUTH_SELF_MANAGED_DOC_LINK = helpPagePath('integration/jira/connect-app', { + anchor: 'install-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances', +}); + +export const GITLAB_COM_BASE_PATH = 'https://gitlab.com'; const OAUTH_WINDOW_SIZE = 800; export const OAUTH_WINDOW_OPTIONS = [ diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue index 4f5aa4c255c..5ff75e19425 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue +++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue @@ -1,6 +1,13 @@ <script> +import { mapMutations } from 'vuex'; import { GlButton } from '@gitlab/ui'; import { s__ } from '~/locale'; + +import { reloadPage, persistBaseUrl, retrieveBaseUrl } from '~/jira_connect/subscriptions/utils'; +import { updateInstallation, setApiBaseURL } from '~/jira_connect/subscriptions/api'; +import { I18N_UPDATE_INSTALLATION_ERROR_MESSAGE } from '~/jira_connect/subscriptions/constants'; +import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types'; + import SignInOauthButton from '../../../components/sign_in_oauth_button.vue'; import VersionSelectForm from './version_select_form.vue'; @@ -14,6 +21,7 @@ export default { data() { return { gitlabBasePath: null, + loadingVersionSelect: false, }; }, computed: { @@ -26,12 +34,32 @@ export default { : this.$options.i18n.versionSelectSubtitle; }, }, + mounted() { + this.gitlabBasePath = retrieveBaseUrl(); + setApiBaseURL(this.gitlabBasePath); + }, methods: { + ...mapMutations({ + setAlert: SET_ALERT, + }), resetGitlabBasePath() { this.gitlabBasePath = null; + setApiBaseURL(); }, onVersionSelect(gitlabBasePath) { - this.gitlabBasePath = gitlabBasePath; + this.loadingVersionSelect = true; + updateInstallation(gitlabBasePath) + .then(() => { + persistBaseUrl(gitlabBasePath); + reloadPage(); + }) + .catch(() => { + this.setAlert({ + message: I18N_UPDATE_INSTALLATION_ERROR_MESSAGE, + variant: 'danger', + }); + this.loadingVersionSelect = false; + }); }, onSignInError() { this.$emit('error'); @@ -53,11 +81,17 @@ export default { <p data-testid="subtitle">{{ subtitle }}</p> </div> - <version-select-form v-if="!hasSelectedVersion" class="gl-mt-7" @submit="onVersionSelect" /> + <version-select-form + v-if="!hasSelectedVersion" + class="gl-mt-7" + :loading="loadingVersionSelect" + @submit="onVersionSelect" + /> <div v-else class="gl-text-center"> <sign-in-oauth-button class="gl-mb-5" + :gitlab-base-path="gitlabBasePath" @sign-in="$emit('sign-in-oauth', $event)" @error="onSignInError" /> diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue index 0fa745ed7e3..6b32225ed11 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue +++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue @@ -9,13 +9,14 @@ import { } from '@gitlab/ui'; import { __, s__ } from '~/locale'; +import { GITLAB_COM_BASE_PATH } from '~/jira_connect/subscriptions/constants'; + const RADIO_OPTIONS = { saas: 'saas', selfManaged: 'selfManaged', }; const DEFAULT_RADIO_OPTION = RADIO_OPTIONS.saas; -const GITLAB_COM_BASE_PATH = 'https://gitlab.com'; export default { name: 'VersionSelectForm', @@ -27,6 +28,13 @@ export default { GlFormRadio, GlButton, }, + props: { + loading: { + type: Boolean, + required: false, + default: false, + }, + }, data() { return { selected: DEFAULT_RADIO_OPTION, @@ -82,7 +90,7 @@ export default { </gl-form-group> <div class="gl-display-flex gl-justify-content-end"> - <gl-button variant="confirm" type="submit">{{ __('Save') }}</gl-button> + <gl-button variant="confirm" type="submit" :loading="loading">{{ __('Save') }}</gl-button> </div> </gl-form> </template> diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/actions.js b/app/assets/javascripts/jira_connect/subscriptions/store/actions.js index 4a83ee8671d..fff34e1d75d 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/store/actions.js +++ b/app/assets/javascripts/jira_connect/subscriptions/store/actions.js @@ -1,6 +1,8 @@ -import { fetchSubscriptions as fetchSubscriptionsREST } from '~/jira_connect/subscriptions/api'; -import { getCurrentUser } from '~/rest_api'; -import { addJiraConnectSubscription } from '~/api/integrations_api'; +import { + fetchSubscriptions as fetchSubscriptionsREST, + getCurrentUser, + addJiraConnectSubscription, +} from '~/jira_connect/subscriptions/api'; import { I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE, I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE, diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/state.js b/app/assets/javascripts/jira_connect/subscriptions/store/state.js index 03a83f18b4c..82a8517b511 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/store/state.js +++ b/app/assets/javascripts/jira_connect/subscriptions/store/state.js @@ -1,4 +1,8 @@ -export default function createState({ subscriptions = [], subscriptionsLoading = false } = {}) { +export default function createState({ + subscriptions = [], + subscriptionsLoading = false, + currentUser = null, +} = {}) { return { alert: undefined, @@ -9,7 +13,7 @@ export default function createState({ subscriptions = [], subscriptionsLoading = addSubscriptionLoading: false, addSubscriptionError: false, - currentUser: null, + currentUser, currentUserError: null, accessToken: null, diff --git a/app/assets/javascripts/jira_connect/subscriptions/utils.js b/app/assets/javascripts/jira_connect/subscriptions/utils.js index b2d03a1fbba..6db8b62d692 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/utils.js +++ b/app/assets/javascripts/jira_connect/subscriptions/utils.js @@ -1,32 +1,45 @@ import AccessorUtilities from '~/lib/utils/accessor'; import { objectToQuery } from '~/lib/utils/url_utility'; -import { ALERT_LOCALSTORAGE_KEY } from './constants'; +import { ALERT_LOCALSTORAGE_KEY, BASE_URL_LOCALSTORAGE_KEY } from './constants'; const isFunction = (fn) => typeof fn === 'function'; +const { canUseLocalStorage } = AccessorUtilities; + +const persistToStorage = (key, payload) => { + localStorage.setItem(key, payload); +}; + +const retrieveFromStorage = (key) => { + return localStorage.getItem(key); +}; + +const removeFromStorage = (key) => { + localStorage.removeItem(key); +}; /** * Persist alert data to localStorage. */ export const persistAlert = ({ title, message, linkUrl, variant } = {}) => { - if (!AccessorUtilities.canUseLocalStorage()) { + if (!canUseLocalStorage()) { return; } const payload = JSON.stringify({ title, message, linkUrl, variant }); - localStorage.setItem(ALERT_LOCALSTORAGE_KEY, payload); + persistToStorage(ALERT_LOCALSTORAGE_KEY, payload); }; /** * Return alert data from localStorage. */ export const retrieveAlert = () => { - if (!AccessorUtilities.canUseLocalStorage()) { + if (!canUseLocalStorage()) { return null; } - const initialAlertJSON = localStorage.getItem(ALERT_LOCALSTORAGE_KEY); + const initialAlertJSON = retrieveFromStorage(ALERT_LOCALSTORAGE_KEY); // immediately clean up - localStorage.removeItem(ALERT_LOCALSTORAGE_KEY); + removeFromStorage(ALERT_LOCALSTORAGE_KEY); if (!initialAlertJSON) { return null; @@ -35,6 +48,22 @@ export const retrieveAlert = () => { return JSON.parse(initialAlertJSON); }; +export const persistBaseUrl = (baseUrl) => { + if (!canUseLocalStorage()) { + return; + } + + persistToStorage(BASE_URL_LOCALSTORAGE_KEY, baseUrl); +}; + +export const retrieveBaseUrl = () => { + if (!canUseLocalStorage()) { + return null; + } + + return retrieveFromStorage(BASE_URL_LOCALSTORAGE_KEY); +}; + export const getJwt = () => { return new Promise((resolve) => { if (isFunction(AP?.context?.getToken)) { |