I use keycloak on my React project as authentication and authorization tool and want to store some data of the user inside a React Context to access data like username etc. This context should update when I log in or log out.
My first approach was to use two events called onAuthSuccess and onAuthLogout but it seems that it will not be fired at all.
To test this approach I execute a console.log. Unfortunately nothing happens if I log in via keycloak.login() and logout via keycloak.logout().
import Keycloak from 'keycloak-js';
import configData from './config.json';
const keycloak = new Keycloak(configData);
keycloak.onAuthSuccess = () => {
console.log('log in!');
}
keycloak.onAuthLogout = () => {
console.log('log out');
}
export default keycloak
Any ideas what the problem could be?
Related
I am working on a webapp with the frameworks Vue 3 and firebase. As stroe I use Pinia. For authentication I use firebase, which works well so far, users can register and log in. As soon as a user logs in, his data is stored in the pinia store.
However, as soon as the user logs in and reloads the page, the data in the pinia store is lost. If I use the firebase function onAuthStateChanged() there is still a user logged in.
My first solution was to call the onAuthStateChanged() function every time the app is started and the pinia store is then filled with the required data. I already tried to call the onAuthStateChanged() function in my firebase config file, but the pinia store is not initialized here yet.
At which point in the Vue app must the onAuthStateChanged() function be called so that the user is automatically logged in after a refresh and I can write the user data to the Pinia store?
I am not sure what you have tried but I know this will work. You can, of course, move the onAuthStateChanged out of your store and it will still work. Keep in mind you will have to use a watcher or computed prop to track store.user and update the UI.
import { getAuth, onAuthStateChanged } from 'firebase/auth';
const auth = getAuth();
onAuthStateChanged(auth, async () => {
const store = useStore();
store.setUser();
});
const useStore = defineStore('store', {
state: () => ({
user: null
}),
actions: {
setUser() {
// Set user here
// this.user = ...
}
}
});
I created a simple ionic app that allows users to book services. The user would select a service category, choose a service(s) then navigate to a form to complete the booking.
I've setup an event bus using tiny emitter since the project uses vue 3 with the composition api. The data emits as expected however when navigating to the booking form the listener is not triggered.
The expected behaviour is to get the selected service(s) and send it along with the rest of the booking info to a REST api.
eventBus.js
import { TinyEmitter } from 'tiny-emitter';
const emitter = new TinyEmitter();
const eventBus = () => {
return { emitter };
};
export default eventBus;
Service.vue
// template
<ion-button routerDirection="forward" routerLink="/booking" #click="sendEvent">Book Now</ion-button>
// script
import eventBus from './eventBus';
export default {
...
setup() {
...
const sendEvent = () => {
eventBus().emitter.emit('selected-service', 100) // the real code emits an array
}
return { sendEvent }
}
}
Booking.vue - Nothing happens in the console log
<script>
...
onMounted(() => {
eventBus().emitter.on('selected-service', (payload) => {
console.log('listener', payload);
})
})
</script>
I know this works in a regular Vue 3 project but I'm not sure why it's not working with ionic.
Things I've tried
Using the native emitter via context as a setup param. https://v3.vuejs.org/guide/composition-api-setup.html#accessing-component-properties
Using the mitt package as described here: Vue 3 Event Bus with Composition API
Emitting the event when the user chooses a service rather than when they click "Book Now"
Calling the listener in setup() directly rather than onMounted.
UPDATE
I noticed the listener gets called when I navigate off the booking page then back to it. So if I go from service details -> booking -> back to service details -> booking it triggers the bus and the payload is captured.
This may be a framework level bug. I've spoken to the Ionic team via twitter and was advised to use query params instead so that's the route I took.
may be rxjs helps you
import { Subject } from 'rxjs';
private newProduct = new Subject<any>();
publishNewProduct() {
this.newProduct.next();
}
subscribeNewProduct(): Subject<any> {
return this.newProduct;
}
Just had the same problem, to make it worse, in my case the bug was intermittent and only present if the dev tools console was closed. Opening the dev tools or using alerts would result in the component being rendered in time to receive the event.. I almost lost my sanity over it.
In my case using a watcher on the a prop using immediate: true was the cleanest solution.
I think this bug is really nasty since global events support have been removed from Vue2 and the Vue3 upgrade docs explicitly suggest to use tiny emitter to achieve it.
This leads to weird behaviors and bugs that are almost impossible to trace back. For this reason, I think the global event pattern should be avoided as much as possible.
As a final note if it can help someone, this is how I ended up being able to use console logs to trace the problem back to the global event bus:
const logs = []
app.config.globalProperties.$log = function () { logs.push(arguments) }
window.viewlogs = () => {
for (let i = 0; i < logs.length; i++) {
console.log.apply(window, logs[i])
}
}
Once the bug occured I could open the dev tools and view logs using window.viewlogs()
I am making a react application where a user can create some data in their own profile and later view it. I am saving this data in IndexedDB using localForage. The problem is due to IndexedDB's design, an IndexedDB store saves data according to the domain. And I have multiple users logging in and creating data. I am using firebase and I can get user email by using onAuthStateChange method. I need to create a store for each account in IndexedDB. I tried doing it but I am stuck with running async and sync code in a correct way. Here's my code where I am creating an IndexedDb instance -
import localforage from 'localforage';
let totalCardsData = localforage.createInstance({
name: 'totalCardsData',
storeName: 'cards',
});
export { totalCardsData };
Using this I can create only 1 store called 'totalCardsData'. Here I tried making a dynamic name for the object.
import firebase from 'firebase/app';
import localforage from 'localforage';
import initFirebase from '../utils/auth/initFirebase';
initFirebase();
let userEmail = '';
let totalCardsData;
firebase.auth().onAuthStateChanged(user => {
if (user) {
userEmail = user.email;
}
});
totalCardsData = localforage.createInstance({
// name: 'totalCardsData',
name: `${userEmail}`,
storeName: 'cards',
});
export { totalCardsData };
onAuthStateChanged is an asynchronous method. So I am getting an empty string for ${userEmail} because onAuthStateChanged finishes it's execution after rest of the code have been finished executing.
I want to run totalCardsData after the onAuthStateChanged has finished execution. So that I can get userEmail string.
I suggest
Seperate firebase onAuthStateChange (ex: module1) and localstorage creation logic(Ex: module2) in two different modules, import both in parent file.
Write a method which takes userEmail as argument and retuns localstorage instance in module2 and export that method(ex: createLocalStorageInstance).
Generate a custom event and emit it from onAuthStateChange and listen to it in parent file and from there call createLocalStorageInstance.
This should work.
I've been really struggling to do something very simple. I have to write some tests for a Vue application.
The scenario I want to test is the following:
The user fills in a form
The user submits the form
The values from the form are sent to the backend in one object
The server responds with an object containing the fields of the object it has received in the request (plus some new fields).
The Vue app stores the result in the Vuex store
I want my test to check if, after the form has been validated, the request is made and the returned values are properly stored in the store.
This is super basic, but for some reason I can't get the test to work.
I have a globally registered component that makes axios accessible by using this.api.
So in my test, I have the following (the file is simplified for this post):
...
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
import flushPromises from 'flush-promises';
// This is the axios setup used in the components
import api from '../../src/mixins/api';
// Components
import MyComponent from '../component.vue';
describe('MyComponent', () => {
// Set up the local vue
...
const wrapper = mount(MyComponent, {
localVue,
...
});
beforeEach(() => {
const instance = axios.create({
baseURL: 'some/url/',
});
wrapper.vm.api = new AxiosMockAdapter(instance);
wrapper.vm.api.onPost('url/').replyOnce(201, { data: { foo: 'bar' } });
});
it('should retrieve the data', async () => {
wrapper.find('#submit').trigger('click');
await flushPromises();
expect(wrapper.vm.$store.state.foo !== undefined).toBe(true);
});
});
But the test isn't successful. I think the request is made properly, but by the time the test reaches its end the response hasn't be received yet. And this is despite using flushPromises(). I also tried to use setTimeout but with no success neither.
I'm new to unit testing (especially on front end apps) and I have no idea what else to try. Nothng works... It's very frustrating because what I'm tryng to do is pretty straight forward and basic.
Anyone has an idea what to do ?
I'm trying to set up my first React app using Redux, but I'm currently having some issues with dispatching actions. I have the following files for my actions so far:
constants/AuthActionTypes.js:
export const AUTH_PENDING = 'AUTH_PENDING';
export const AUTH_SUCCESS = 'AUTH_SUCCESS';
export const AUTH_FAIL = 'AUTH_FAIL';
actions/AuthActions.js:
import * as AuthActionTypes from '../constants/AuthActionTypes';
function authPending() {
return { type: AuthActionTypes.AUTH_PENDING };
}
function authSuccess(response) {
return { type: AuthActionTypes.AUTH_SUCCESS };
}
export function login(username, password) {
return dispatch => {
dispatch(authPending());
// nothing really happens yet
dispatch(authSuccess());
};
}
I've also got a login component containing an HTML form, and a login function that gets called when the form is submitted.
components/Login.js
...
login (e) {
e.preventDefault();
var username = ReactDOM.findDOMNode(this.refs.username).value;
var password = ReactDOM.findDOMNode(this.refs.password).value;
this.props.dispatch(AuthActions.login(username, password));
}
...
The function is triggered, but... something isn't right. I use redux-devtools, and it only dispatches a single action - surely, it should dispatch the actions from both authPending and authSuccess? In addition, the devtools pane doesn't display any action type, and I get the following warning in the console:
Warning: Failed propType: Invalid prop action of type function
supplied to LogMonitorEntry, expected object. Check the render
method of LogMonitor.
What am I doing wrong here? What have I missed? I've primarily looked at the examples at https://github.com/rackt/redux/tree/master/examples/real-world and https://github.com/yildizberkay/redux-example, and I can't see what I'm doing differently that makes my own app break.
... and a couple of minutes later, I've stumbled across the answer: I didn't have the redux-thunk middleware applied to my store. Applied that, and everything works as expected.
You can do w/o thunk.
change
this.props.dispatch(AuthActions.login(username, password));
to
this.props.dispatch(AuthActions.authPending());
this.props.dispatch(AuthActions.authSuccess());
Ofcourse, you have to import those two methods/action-creators.