Nativescript Vue - async operations in main.js before rendering - vue.js

i would like to add a login page to my app with Firebase Authentication:
https://github.com/EddyVerbruggen/nativescript-plugin-firebase/blob/master/docs/AUTHENTICATION.md
Following the guide i've added the "onAuthStateChanged" function inside the init firebase.
Now i would like to pass to the render function in the Vue instance creation the correct page, based on the value returned by the Firebase function.
If the user it's authenticated, will be rendered the "home.vue" page, otherwise the "login.vue" page.
The problem it's that the firebase function return the state of the user after the Vue instance creation.
Here my code:
import Vue from 'nativescript-vue'
import store from './store'
import Home from './components/Page/home.vue'
import Login from './components/Page/login.vue'
import VueDevtools from 'nativescript-vue-devtools'
var firebase = require("#nativescript/firebase").firebase;
var pageToRender;
firebase.init({
onAuthStateChanged: function(data) {
if (data.loggedIn) {
pageToRender = Home;
}else{
pageToRender = Login;
}
}
}).then(
function () {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
if(TNS_ENV !== 'production') {
Vue.use(VueDevtools)
}
// Prints Vue logs when --env.production is *NOT* set while building
Vue.config.silent = (TNS_ENV === 'production')
new Vue({
store,
render: h => h('frame', [h(pageToRender)])
}).$start()
I already tried to move all the code inside an async function in order to await the firebase response before the Vue instance creation but i receive the error:
System.err: Error: Main entry is missing. App cannot be started.
Verify app bootstrap.
In this way:
async function start(){
var loggedIn = await firebase_start();
var pageToRender;
if (loggedIn) {
pageToRender = Home;
}else{
pageToRender = Login;
}
if(TNS_ENV !== 'production') {
Vue.use(VueDevtools)
}
// Prints Vue logs when --env.production is *NOT* set while building
Vue.config.silent = (TNS_ENV === 'production')
new Vue({
store,
render: h => h('frame', [h(pageToRender)])
}).$start()
}
Thanks for the help!

I would approach it differently.
When the user logs in, you can save this on the device with the https://github.com/nativescript-community/preferences plugin as follows:
function successLoginUser(){
const prefs = new Preferences();
prefs.setValue("isUserLogin", true);
}
And then when starting the application you can do something like this:
import Vue from 'nativescript-vue'
import store from './store'
import Home from './components/Page/home.vue'
import Login from './components/Page/login.vue'
import VueDevtools from 'nativescript-vue-devtools'
//This
const prefs = new Preferences();
const pageToRender = prefs.getValue("isUserLogin") ? Home: Login ;
if(TNS_ENV !== 'production') {
Vue.use(VueDevtools)
}
// Prints Vue logs when --env.production is *NOT* set while building
Vue.config.silent = (TNS_ENV === 'production')
new Vue({
store,
render: h => h('frame', [h(pageToRender)])
}).$start()
When the user logs out:
const prefs = new Preferences();
prefs.setValue("isUserLogin", false);
I haven't tried it but it should work

//for me here firebase is imported from a diffrent file where i already initialized it.
let app;
firebase.auth().onAuthStateChanged((user, error) => {
//decide which page to render
//also make sure both components you are trying to render are imported
if (!app) {
app = new Vue({
router,
store,
render: (h) => h(PageToRender),
}).$start()
}
});

Related

How to import a utility function that sets cookies into multiple Vue components

I have several components that I need to check if the user logged on/has valid access token
I currently do check this inside a Vue component method using the contents of isLoggedOut function below. I am thinking that I might need to create an external js file and import this js everywhere that I need to check of credentials. So js function will look sthg like below. However this function also resets the cookies in the component. see this.$cookies. I don't think this is possible due to scoping.
So how can I import functions (like from a utility js file) that also changes this objects? Or is there a better way of what avoiding code duplication in Vue/check for log out in multiple components using same code
import axios from "axios";
function isLoggedOut() {
axios.defaults.withCredentials = true;
const isLoggedOut = True;
const path = `/user_authentication/protected`;
axios
.get(path, { withCredentials: true })
.then((response) => {
message = response.data["user"];
isLoggedOut = false;
return isLoggedOut;
})
.catch((error) => {
console.error(error);
this.$cookies.remove("csrf_access_token");
isLoggedOut = true;
return isLoggedOut;
});
}
Create an index.ts file in a folder named utils and export the funtion isLoggedOut.
Pass the Vue app to the function isLoggedOut as a prop and call the vue methods.
import Vue from 'vue'
import axios from "axios";
export function isLoggedOut(app: Vue) {
axios.defaults.withCredentials = true;
const isLoggedOut = True;
const path = `/user_authentication/protected`;
axios
.get(path, { withCredentials: true })
.then((response) => {
message = response.data["user"];
isLoggedOut = false;
return isLoggedOut;
})
.catch((error) => {
console.error(error);
app.$cookies.remove("csrf_access_token");
isLoggedOut = true;
return isLoggedOut;
});
}
Component
import { isLoggedOut } from '~/utils'
export default {
methods: {
logOut() {
// Passing the Vue app
isLoggedOut(this)
}
}
}

How to re-direct to login page from vuex action in vue

Hi i have a existing project where in all action.js we are dispatching logoutFromServer upon 401 Unauthorized
here is how it will look like this
users.action.js
async getAllUsers(context,payload={}){
try{
let resp = await axios.get(...);
}catch(error){
if(error.response.status == 401)
context.dispatch('logoutFromServer');
}
}
customers.action.js
async getAllCustomers(context,payload={}){
try{
let resp = await axios.get(...);
}catch(error){
if(error.response.status == 401)
context.dispatch('logoutFromServer');
}
}
there above code is repeated in almost every GET,POST,PUT,DELETE at least more than 1000 places(so i cannot change them now). upon dispatch of logoutFromServer i'm getting
TypeError: Cannot read properties of undefined (reading 'push')
for below code
authentication.action.js
async logoutFromServer(context){
try{
let resp = await axios.delete(...);
}catch(e){
console.log('must be already deleted');
}
//clear cookie
this.$router.push({name:"login"}) <-- here the above error occurs i,e `TypeError: Cannot read properties of undefined (reading 'push')`
}
Question: how to re-direct from vuex actions to /login route
Please help me thanks in advance !!
you can explicitly import the router and use it.
==> router.js
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
export const router = new VueRouter({
mode: 'hash',
base: "./",
routes: [
{ path: "/", component: home},
...
]
})
===> actions.js
import {router} from "../router.js"
export const someAction = ({commit}) => {
router.push("/");
}

switch between root component based on different url parameter

i am developing a vujs based museum installation with several clients and one server. I would like to develop the two apps, client and server, in one application.
when calling the url I want to read out the parameters.
https://example.com/mode=client&id=1 or mode=server
then I want to load different root components with creatapp
if server .. const app = Vue.createApp(serverComponent)
if client ... const app = Vue.createApp(serverComponent)
is that a good way?
if so, how can I pass the clientID directly into the root component
EDITED
its simple to pass props to the root component with Vue.createApp(clientComponent, {id:id,...})
but currently I fail to choose between 2 different root components.
following setup
import App from './App.vue'
import AppServer from './AppServer.vue'
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const mode = urlParams.get('mode')
if (mode == "server"){
let app = createApp(AppServer);
} else {
let id = urlParams.get('id')
app = createApp(App, { id: parseInt(id) } );
}
but let app = createApp(AppServer); throws an error. app never initialised
I've implemented and tested the functionality that you need.
import Vue from 'vue'
import App from './App'
import AppServer from './AppServer'
Vue.config.productionTip = false
const NotFound = {
template: '<p>Page not found</p>'
}
/* eslint-disable no-new */
new Vue({
el: '#app',
data: {
currentRoute: window.location.pathname
},
methods: {
RequestDispatcher (url) {
let urlParams = new URLSearchParams(url)
if (url.includes('mode=server')) {
return AppServer // createApp(AppServer)
} else if (url.includes('mode=client')) {
let id = urlParams.get('id')
console.log(id)
return App // createApp(App, { id: parseInt(id) })
}
}
},
computed: {
ViewComponent () {
return this.RequestDispatcher(this.currentRoute) || NotFound
}
},
render (h) {
return h(this.ViewComponent)
}
})

Implement login command and access vuex store

I have a login process where after sending a request to the server and getting a response, I do this:
this.$auth.setToken(response.data.token);
this.$store.dispatch("setLoggedUser", {
username: this.form.username
});
Now I'd like to emulate this behavior when testing with cypress, so i don't need to actually login each time I run a test.
So I've created a command:
Cypress.Commands.add("login", () => {
cy
.request({
method: "POST",
url: "http://localhost:8081/api/v1/login",
body: {},
headers: {
Authorization: "Basic " + btoa("administrator:12345678")
}
})
.then(resp => {
window.localStorage.setItem("aq-username", "administrator");
});
});
But I don't know how to emulate the "setLoggedUser" actions, any idea?
In your app code where you create the vuex store, you can conditionally expose it to Cypress:
const store = new Vuex.Store({...})
// Cypress automatically sets window.Cypress by default
if (window.Cypress) {
window.__store__ = store
}
then in your Cypress test code:
cy.visit()
// wait for the store to initialize
cy.window().should('have.property', '__store__')
cy.window().then( win => {
win.__store__.dispatch('myaction')
})
You can add that as another custom command, but ensure you have visited your app first since that vuex store won't exist otherwise.
Step 1: Inside main.js provide the store to Cypress:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
if (window.Cypress) {
// Add `store` to the window object only when testing with Cypress
window.store = store
}
Step 2: Inside cypress/support/commands.js add a new command:
Cypress.Commands.add('login', function() {
cy.visit('/login') // Load the app in order `cy.window` to work
cy.window().then(window => { // .then() to make cypress wait until window is available
cy.wrap(window.store).as('store') // alias the store (can be accessed like this.store)
cy.request({
method: 'POST',
url: 'https://my-app/api/auth/login',
body: {
email: 'user#gmail.com',
password: 'passowrd'
}
}).then(res => {
// You can access store here
console.log(this.store)
})
})
})
Step 4: Inside cypress/integration create a new test
describe('Test', () => {
beforeEach(function() {
cy.login() // we run our custom command
})
it('passes', function() { // pass function to keep 'this' context
cy.visit('/')
// we have access to this.store in our test
cy.wrap(this.store.state.user).should('be.an', 'object')
})
})

Vue I18n - TypeError: Cannot redefine property: $i18n

So I'm getting kind of crazy with this. I really don't understand.
This is a minimal version of my app.js file:
import Vue from 'vue'
import VueI18n from 'vue-i18n'
console.log("vue.prototype", Vue.prototype.$i18n)
Vue.use(VueI18n)
console.log("vue.prototype", Vue.prototype.$i18n)
const createApp = function() {
// create store and router instances
const store = createStore()
const router = createRouter()
if(process.browser) {
if(window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
}
// sync the router with the vuex store.
// this registers `store.state.route`
sync(store, router)
// create the app instance.
// here we inject the router, store and ssr context to all child components,
// making them available everywhere as `this.$router` and `this.$store`.
//
const app = new Vue({
router,
store,
render: h => h(Application)
})
// expose the app, the router and the store.
// note we are not mounting the app here, since bootstrapping will be
// different depending on whether we are in a browser or on the server.
return { app, router, store }
}
export { createApp }
As you can see I did nothing but adding Vue.use(VueI18n) to the code.
I'm using:
{
"vue-i18n": "^7.6.0"
}
Now I'm getting this error:
TypeError: Cannot redefine property: $i18n
The line where this errors appear is this function in the source code:
function install (_Vue) {
Vue = _Vue;
var version = (Vue.version && Number(Vue.version.split('.')[0])) || -1;
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && install.installed) {
warn('already installed.');
return
}
install.installed = true;
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && version < 2) {
warn(("vue-i18n (" + (install.version) + ") need to use Vue 2.0 or later (Vue: " + (Vue.version) + ")."));
return
}
console.log("VUE:PROTOTYPE", Vue.prototype.$i18n)
Object.defineProperty(Vue.prototype, '$i18n', {
get: function get () { return this._i18n }
});
console.log("VUE:PROTOTYPE", Vue.prototype.$i18n)
extend(Vue);
Vue.mixin(mixin);
Vue.directive('t', { bind: bind, update: update });
Vue.component(component.name, component);
// use object-based merge strategy
var strats = Vue.config.optionMergeStrategies;
strats.i18n = strats.methods;
}
Both console.log("VUE:PROTOTYPE") where added by me, and surprise, the first one returns "undefined" and the second one is never reached because of the error.
What is happening? Anybody got a clue?