0

I am working on an app with frontend using Vue 3. I am initializing a keycloak-js configuration in my main.ts file. I login successfully with my Keycloak running on Docker. I receive the JWT token. Afterwards, I save my token in localStorage so that when I close the browser I don't need to do another login. Up until this point everything is working perfectly. However, when I get the token from localStorage I save in my Pinia store in order to be accessible in one of my Vue components. Unfortunately, when I open my frontend app Vue loads first the NavigationBar.vue and afterwords the main.ts file. This causes an issue because the property in my Pinia store is still empty (the logic for populating it is in main.ts).

Can you give me any suggestions how can I make Vue to load first everything from main.ts and then start rendering the vue components?

I am attaching code so that you can see what I've done.

main.ts

import { createApp } from 'vue'
import './style.css'
import Keycloak from "keycloak-js";
import { createPinia } from 'pinia'
import {useAuthStore} from "./store/auth";
// @ts-ignore
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App);
app.use(pinia);

let keycloak = new Keycloak({
    url: "http://192.168.0.2:8082/",
    realm: "test",
    clientId: "test"
});
keycloak.init({
    redirectUri: "http://localhost:5173",
    onLoad: "check-sso",
    checkLoginIframe: false,
    // @ts-ignore
    token: localStorage.getItem("token"),
    // @ts-ignore
    refreshToken: localStorage.getItem("refreshToken")
}).then(() => {
    const authStore = useAuthStore();
    authStore.storedToken = keycloak.token as string;
    console.log("main.ts check authStore, storedToken: " + keycloak.token);
    if (keycloak != undefined && keycloak.authenticated) {

        if (keycloak.token != undefined) {
            localStorage.setItem("token", keycloak.token)
        }
        if (keycloak.refreshToken != undefined) {
            localStorage.setItem("refreshToken", keycloak.refreshToken)
        }
    }
    console.log(keycloak);
});

app.mount('#app');

export default keycloak;

NavigationBar.vue

<script lang="ts">

import keycloak from "../main";
import {useAuthStore} from "../store/auth";

export default {
    name: "NavigationBar",
    mounted() {
        const authStore = useAuthStore();
        console.log("authStore.storedToken " + authStore.storedToken)
        this.userToken = authStore.storedToken
    },
    data() {
        return {
            userToken: ""
        }
    },
    methods: {
        keycloak() {
            return keycloak
        }
    }
}
</script>

App.vue

<script setup lang="ts">
import NavigationBar from "./components/NavigationBar.vue";
</script>

<template>

  <NavigationBar></NavigationBar>

</template>

<style scoped>

</style>

Output from the console of Firefox browser

[vite] connecting... client.ts:18:8
[vite] connected. client.ts:133:14
authStore.storedToken NavigationBar.vue:69:16
main.ts check authStore, storedToken: eyJhbGciOiJSUzI1NiIsInR5cCI... main.ts:29:12
Object { init: init(initOptions), login: login(options), createLoginUrl: createLoginUrl(options), logout: logout(options), createLogoutUrl: createLogoutUrl(options), register: register(options), createRegisterUrl: createRegisterUrl(options), createAccountUrl: createAccountUrl(options), accountManagement: accountManagement(), hasRealmRole: hasRealmRole(role)
, … }
main.ts:39:12
Decrux
  • 177
  • 1
  • 6
  • 17
  • If you don't want `app.mount('#app')` to be executed until some moment then don't execute it. Check how promises work. FWIW what you're trying to do is handled by Vue suspense. See https://meta.stackoverflow.com/questions/285551/why-should-i-not-upload-images-of-code-data-errors , the screenshot is unreadable – Estus Flask May 17 '23 at 19:32
  • I replaced the screenshot with text. However, I don't understand your comment and the solution. – Decrux May 17 '23 at 19:49
  • I don't see how not mounting #app will help me in this case, neither Vue suspense. Can you please elaborate more or show me with code? – Decrux May 17 '23 at 19:56
  • The app is initialized when app.mount('#app') is called. Since you call it before the point that you expect, it works like it works. Move app.mount('#app') to the place when you expect it to be intialized. Inside then. Check https://vuejs.org/guide/built-ins/suspense.html on suspense – Estus Flask May 17 '23 at 20:04
  • Ah, ok. Now I think I am starting to understand. Sorry, backend developer here so frontend technologies are really far away from me. Still trying to learn. So let me get this straight when using .then() I receive a Promise and then() is asynchronous, meaning that the order of execution is not guaranteed? Regarding the Vue suspense I skimmed through the documentation but that seems to me it works only for components in Vue, not in my specific case main.ts <-> vue component, am I right? – Decrux May 17 '23 at 20:15
  • That's correct. It's not only just not guaranteed, it's the opposite, app.mount('#app') (and so `this.userToken =`) will always be called earlier than `authStore.storedToken =`. Check https://stackoverflow.com/questions/14220321 and other general info on promises. Things will look more straightforward with async/await instead of raw promises with .then but they are totally equivalent. As for suspense, async init code belongs to App instead of main and will naturally block the rendering of nested comps, this is Vue-specific way of doing what you're trying to do – Estus Flask May 17 '23 at 20:23

0 Answers0