React rails authentication - Get user state without page reload - ruby-on-rails

I'm building a SPA with rails API on the back-end and React on the front-end. The user authentication works as desired and I'm getting the user state from the rails. The problem is that when a user signs in to the app, I have to manually refresh the whole browser to update the state.
// App.js
function userReducer(state, action) {
switch (action.type) {
case "success": {
return {
...state,
id: action.id,
username: action.username,
email: action.email,
logged_in: action.status,
error: ""
};
}
case "fail": {
return {
...state,
id: "",
username: "",
email: "",
logged_in: false,
error: action.error
};
}
default:
return state;
}
}
// set initial state of the logged in user
const intialUserState = {
id: "",
username: "",
email: "",
logged_in: false,
error: ""
};
export const UserStatus = React.createContext(intialUserState);
function App() {
const [userState, dispatch] = useReducer(userReducer, intialUserState);
// fetch the user's data
function fetchLoggedInUserData() {
axios
.get("http://localhost:3000/api/v1/logged_in", { withCredential: true })
.then(response => {
const { id, username, email, logged_in } = response.data;
if (response.status === 200) {
dispatch({
type: "success",
id: id,
username: username,
email: email,
status: logged_in
});
}
})
.catch(error => {
dispatch({ type: "fail", error: error });
});
}
// when the app loads, check if the user is signed in or not.
// if the user is signed in, then store the user's data into the state
useEffect(() => {
fetchLoggedInUserData();
}, []);
return (
<UserStatus.Provider value={{ userState, dispatch }}>
<Router>
<>
<Switch>
<Route path="/admin" component={Admin} />
<Route path="/" render={props => <Public {...props} />} />
</Switch>
</>
</Router>
</UserStatus.Provider>
);
}
The above code works as expected and I can store the user state as you see from the code. The problem is when a user clicks on the submit button I want to automatically store the user without having to refresh the page. The code below is from Login.js
// Login.js
function signinReducer(state, action) {
switch (action.type) {
case "field": {
return {
...state,
[action.field]: action.value
};
}
case "signin": {
return {
...state,
error: "",
isLoading: true
};
}
case "success": {
return {
...state,
error: "",
isLoading: false
};
}
case "error": {
return {
...state,
isLoading: false,
error: action.error
};
}
}
}
const initialState = {
username: "",
password: "",
isLoading: false,
error: ""
};
function FormMain() {
const [signinState, dispatch] = useReducer(signinReducer, initialState);
const { username, password, isLoading, error } = signinState;
const handleSubmit = async e => {
e.preventDefault();
dispatch({ type: "signin" });
await postUserData();
};
function postUserData() {
axios
.post(
"http://localhost:3000/api/v1/sessions",
{
user: {
username: username,
password: password
}
},
{ withCredentials: true }
)
.then(response => {
if (response.status === 200) {
dispatch({ type: "success" });
}
})
.catch(error => {
// dispatch({ type: "error", error: error.response.data[0] });
});
}
}
I have removed the sign-in form from the code above as it was getting too lengthy.
The solution can be to somehow transfer the state from the Login.js to App.js or directly update the state of the App.js from the Login.js and make use of the useEffect in the App.js, and update the state without having to manually refresh the browser but I do not know how to do that.

The problem is fixed by creating a new case to App.js and updating that case from Login.js.
// App.js
function userReducer(state, action) {
switch (action.type) {
case "success": {
return {
...state,
id: action.id,
username: action.username,
email: action.email,
admin: action.admin,
logged_in: action.status,
error: ""
};
}
case "fail": {
return {
...state,
id: "",
username: "",
email: "",
admin: false,
logged_in: false,
error: action.error
};
}
case "globalFetch": {
return {
...state,
id: action.id,
username: action.username,
email: action.email,
admin: action.admin,
logged_in: action.status,
error: ""
};
}
default:
return state;
}
}
As you can see the globalFetch case can be easily passed down to the Login.js via useContext and then update the case as per your requirement.
// Login.js
function postUserData() {
axios
.post(
"http://localhost:3000/api/v1/sessions",
{
user: {
username: username,
password: password
}
},
{ withCredentials: true }
)
.then(response => {
if (response.status === 200) {
const { id, username, email, logged_in, admin } = response.data;
dispatch({ type: "success" });
state.dispatch({
type: "globalFetch",
id: id,
username: username,
email: email,
admin: admin,
status: logged_in
});
}
})
.catch(error => {
// dispatch({ type: "error", error: error.response.data[0] });
});
}
// More code...
}

Related

Apple Pay using #stripe/stripe-react-native is not working showing some hook call error

When i am trying to implement Apple Pay using #stripe/stripe-react-native then is not working showing some hook call , Code & Error showing below:
import { StripeProvider, useApplePay} from '#stripe/stripe-react-native';
const { presentApplePay, confirmApplePayPayment } = useApplePay();
export default class App (){
handlePayPress = async () => {
const {error, paymentMethod} = await presentApplePay({
cartItems: [
{
label: 'payment label',
amount: '50', // amount as string
type: 'final',
},
],
country: 'US', // enter any country code supported by stripe,
currency: 'USD', // enter any currency supported by stripe,
});
if (error) {
Alert.alert(error.code, error.message);
} else {
const {error: confirmApplePayError} = await confirmApplePayPayment(
clientSecret,
);
confirmApplePayPayment(clientSecret);
if (confirmApplePayError) {
Alert.alert(confirmApplePayError.code, confirmApplePayError.message);
} else {
Alert.alert('Success', 'The payment was confirmed successfully!');
}
}
};
...
...
}
You can create a seperate functional component for that and call in your class component like this
export default class App (){
render() {
return(
<ApplePayFunction data={'your data here'}/>
);
}
}
export default function ApplePayFunction(props) {
const { presentApplePay, confirmApplePayPayment } = useApplePay();
const handlePayPress = async () => {
const { error, paymentMethod } = await presentApplePay({
cartItems: [
{
label: "payment label",
amount: "50", // amount as string
type: "final",
},
],
country: "US", // enter any country code supported by stripe,
currency: "USD", // enter any currency supported by stripe,
});
if (error) {
Alert.alert(error.code, error.message);
} else {
const { error: confirmApplePayError } = await confirmApplePayPayment(
clientSecret
);
confirmApplePayPayment(clientSecret);
if (confirmApplePayError) {
Alert.alert(confirmApplePayError.code, confirmApplePayError.message);
} else {
Alert.alert("Success", "The payment was confirmed successfully!");
}
}
};
// Return UI Views below
return null;
}
Hooks must always be called inside functional components. Code refactor for reference.
import { StripeProvider, useApplePay } from "#stripe/stripe-react-native";
export default function App() {
const { presentApplePay, confirmApplePayPayment } = useApplePay();
const handlePayPress = async () => {
const { error, paymentMethod } = await presentApplePay({
cartItems: [
{
label: "payment label",
amount: "50", // amount as string
type: "final",
},
],
country: "US", // enter any country code supported by stripe,
currency: "USD", // enter any currency supported by stripe,
});
if (error) {
Alert.alert(error.code, error.message);
} else {
const { error: confirmApplePayError } = await confirmApplePayPayment(
clientSecret
);
confirmApplePayPayment(clientSecret);
if (confirmApplePayError) {
Alert.alert(confirmApplePayError.code, confirmApplePayError.message);
} else {
Alert.alert("Success", "The payment was confirmed successfully!");
}
}
};
// Return UI Views below
return null;
}

Voice Javascript SDK: using device.updateOptions() to change incoming sound to emulate a call waiting notification

Trying to implement simple call waiting with a different incoming sound for any calls that come in while the device is already on an active call.
Upon the answer event we are using device.updateOptions() to update the sound, and the same to revert it back on the disconnect event.
Out of the box we would get the standard incoming sound for that second call. But using the below we do not get any sound on the 2nd incoming call while there is an active call. I'm not sure what we are missing or if there is a better approach.
const appReducer = (state, action) => {
switch (action.type) {
case SET_DEVICE:
return {
...state,
device: action.payload,
};
case SET_ADMIN_DETAIL:
return {
...state,
adminDetail: action.payload,
};
case SET_INCOMING_CALL: {
// when incoming calls set to null (or after calls end)
if (!action.payload.call) {
/** if two calls in progress we need to check which one we have set to null so compare the outboundConnectionId ids with
saved incoming call **/
if (state.incomingCall && state.incomingCall.outboundConnectionId === action.payload.prevStatusCall.outboundConnectionId) {
return {
...state,
incomingCall: null,
};
} else if (state.secondIncomingCall && state.secondIncomingCall.outboundConnectionId === action.payload.prevStatusCall.outboundConnectionId) {
/** if two calls in progress we need to check which one we have set to null so compare the outboundConnectionId ids with
saved second incoming call **/
return {
...state,
secondIncomingCall: null,
};
}
}
// when incoming calls intialized or accept
else {
/** if there is not any call (incomingCall, enqueuedCall, outgoingCall, secondIncomingCall) saved in our state
then treated as first call or update if first call outboundConnectionId is equal to call passed in params **/
if (
(!state.incomingCall && !state.enqueuedCall && !state.outgoingCall && !state.secondIncomingCall) ||
(state.incomingCall && action.payload.call.outboundConnectionId === state.incomingCall.outboundConnectionId)
) {
if (action.payload.event === "accept") {
console.log("First Call Accepted>>>>>>>>>>>>>>>>>>>>>");
state.device.updateOptions({
sounds: {
incoming: "https://method-platform.s3.us-east-1.amazonaws.com/phone-configuration/audios/Short%20Marimba%20Notification%20Ding.mp3",
disconnect: "https://method-platform.s3.us-east-1.amazonaws.com/phone-configuration/audios/call%20connect.mp3",
outgoing: "https://method-platform.s3.us-east-1.amazonaws.com/phone-configuration/audios/call%20disconnect.mp3",
},
});
}
return {
...state,
incomingCall: action.payload.call,
};
} else {
/** if we cancel/end the first call then we treate the second call as first **/
if (!state.incomingCall) {
return {
...state,
incomingCall: action.payload.call,
secondIncomingCall: null,
};
} else {
/** else save the call obj in second call and use for the UI **/
return {
...state,
secondIncomingCall: action.payload.call,
};
}
}
}
// return {
// ...state,
// incomingCall: action.payload.call,
// };
}
case SET_MISSED_CALL_FLAG:
return {
...state,
missedCallFlag: action.payload,
};
case SET_OUT_GOING_CALL: {
if (action.payload.event === "accept") {
console.log("First Call Accepted>>>>>>>>>>>>>>>>>>>>>");
state.device.updateOptions({
sounds: {
incoming: "https://method-platform.s3.us-east-1.amazonaws.com/phone-configuration/audios/Short%20Marimba%20Notification%20Ding.mp3",
disconnect: "https://method-platform.s3.us-east-1.amazonaws.com/phone-configuration/audios/call%20connect.mp3",
outgoing: "https://method-platform.s3.us-east-1.amazonaws.com/phone-configuration/audios/call%20disconnect.mp3",
},
});
}
return {
...state,
outgoingCall: action.payload.call,
};
}
case SET_CALL_TIME:
return {
...state,
callTime: action.payload === 0 ? 0 : state.callTime + action.payload,
};
case SET_ACTIVE:
return {
...state,
isActive: action.payload,
};
case SET_DISPLAY_LIVE_CHAT_PANEL:
return {
...state,
displayLiveChatPanel: action.payload,
};
case SET_DISPLAY_ON_CALL_PANEL:
return {
...state,
displayOnCallPanel: action.payload,
};
case SET_MUTE:
return {
...state,
mute: action.payload,
};
case SET_ENQUEUED:
return {
...state,
enqueued: action.payload,
};
case SET_CALL_ENQUEUED:
return {
...state,
enqueuedCall: action.payload,
enqueued: action.payload ? true : false,
};
case SET_OUT_GOING_USER:
return {
...state,
selectedOutgoingUser: action.payload,
};
default:
return state;
}
};
export const AppContext = createContext(initialState);
export const AppContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(appReducer, initialState);
const location = useLocation();
const dispatchFn = useDispatch();
function setAdminDetail(payload) {
dispatch({ type: SET_ADMIN_DETAIL, payload: payload });
}
function setDevice(payload) {
dispatch({ type: SET_DEVICE, payload: payload });
}
function setIncomingCall(payload) {
dispatch({ type: SET_INCOMING_CALL, payload: payload });
}
function setMissedCallFlag(payload) {
dispatch({ type: SET_MISSED_CALL_FLAG, payload: payload });
}
function setOutgoingCall(payload) {
dispatch({ type: SET_OUT_GOING_CALL, payload: payload });
}
function setCallTime(payload) {
dispatch({ type: SET_CALL_TIME, payload: payload });
}
function setIsActive(payload) {
dispatch({ type: SET_ACTIVE, payload: payload });
}
function setDisplayOnCallPanel(payload) {
dispatch({ type: SET_DISPLAY_ON_CALL_PANEL, payload: payload });
}
function setDisplayLiveChatPanel(payload) {
dispatch({ type: SET_DISPLAY_LIVE_CHAT_PANEL, payload: payload });
}
function setMute(payload) {
dispatch({ type: SET_MUTE, payload: payload });
}
function setCallEnqueue(payload) {
dispatch({ type: SET_CALL_ENQUEUED, payload: payload });
}
function setEnqueue(payload) {
dispatch({ type: SET_ENQUEUED, payload: payload });
}
function setSelectedOutgoingUser(payload) {
dispatch({ type: SET_OUT_GOING_USER, payload: payload });
}
const setUpDevice = async (adminDetail) => {
const result = await getData(
`${process.env.REACT_APP_TWILIO_SERVER}token?extension_name=${adminDetail?.twilio_call_extension?.name}&extension_id=${adminDetail?.twilio_call_extension?.id}`
);
const newDevice = new Device(result.token, {
sounds: {
disconnect: "https://method-platform.s3.us-east-1.amazonaws.com/phone-configuration/audios/call%20connect.mp3",
outgoing: "https://method-platform.s3.us-east-1.amazonaws.com/phone-configuration/audios/call%20disconnect.mp3",
},
logLevel: 1,
allowIncomingWhileBusy: true,
});
newDevice.register();
newDevice.on("registered", (twilioError, call) => {
console.log("registered: ", twilioError);
});
newDevice.on("error", (twilioError, call) => {
console.log("An error has occurred: ", twilioError);
});
newDevice.audio.on("", (twilioError, call) => {
console.log("An error has occurred: ", twilioError, call);
});
newDevice.on("incoming", (call) => {
setMissedCallFlag(true);
setDisplayLiveChatPanel(false);
call.on("accept", (call) => {
setIncomingCall({ call, event: "accept" });
setIsActive(true);
});
call.on("cancel", () => {
setDisplayOnCallPanel(false);
setIncomingCall({ call: null, prevStatusCall: call });
setIsActive(false);
setCallTime(0);
dispatch(
getRecentCalls({
id: adminDetail.twilio_call_extension.id,
offset: 0,
})
);
});
call.on("error", (error) => {
console.log("An error has occurred: ", error);
});
call.on("disconnect", (call) => {
setDisplayOnCallPanel(false);
setIncomingCall({ call: null, prevStatusCall: call, event: "disconnect" });
setIsActive(false);
setCallTime(0);
dispatch(
getRecentCalls({
id: adminDetail.twilio_call_extension.id,
offset: 0,
})
);
});
call.on("reject", () => {
setDisplayOnCallPanel(false);
console.log("The call was rejected.");
setIncomingCall({ call: null, prevStatusCall: call });
setIsActive(false);
setCallTime(0);
dispatch(
getRecentCalls({
id: adminDetail.twilio_call_extension.id,
offset: 0,
})
);
});
call.on("reconnected", () => {
console.log("The call has regained connectivity.");
});
setIncomingCall({ call });
});
newDevice.on("tokenWillExpire", async () => {
console.log("acascas");
let updatedToken = await getData(
`${process.env.REACT_APP_TWILIO_SERVER}token?extension_name=${adminDetail?.twilio_call_extension?.name}&extension_id=${adminDetail?.twilio_call_extension?.id}`
);
newDevice.updateToken(updatedToken.token);
setDevice(newDevice);
});
setDevice(newDevice);
};
const setCallOnMute = async () => {
let { incomingCall, outgoingCall, mute } = state;
let muted = !mute;
if (incomingCall) {
incomingCall.mute(muted);
setMute(muted);
} else if (outgoingCall) {
outgoingCall.mute(muted);
setMute(muted);
}
};
const enqueueCall = async (call_id) => {
const result = await getData(`${process.env.REACT_APP_TWILIO_SERVER}enqueue-call/${call_id}`);
if (result.status === 200) {
console.log(result);
setCallEnqueue(result.data);
setEnqueue(true);
return result.data;
} else {
return null;
}
};
const dialNumber = async (number, id, name) => {
let { adminDetail, device } = state;
let From = adminDetail?.twilio_call_extension?.direct_number;
let params = {
To: number,
From: From,
outboundcall: "true",
extension_id: adminDetail?.twilio_call_extension?.id,
createdBy: adminDetail?.id,
};
if (id) {
params.userId = id;
}
if (name) {
params.tempName = name;
}
const call = await device.connect({
params: params,
});
setOutGoingCallState(call);
setDisplayLiveChatPanel(false);
};
const setOutGoingCallState = (call) => {
setOutgoingCall({ call: null });
setIsActive(true);
setMute(false);
setOutgoingCall({ call: call });
call.on("disconnect", (nest_call) => {
setDisplayOnCallPanel(false);
console.log("disconnect");
setOutgoingCall({ call: null });
setIsActive(false);
setMute(false);
setSelectedOutgoingUser(null);
setCallTime(0);
dispatch(
getRecentCalls({
id: state.adminDetail.twilio_call_extension.id,
offset: 0,
})
);
});
call.on("accept", (nest_call) => {
setOutgoingCall({ call: nest_call, event: "accept" });
});
call.on("cancel", (nest_call) => {
setDisplayOnCallPanel(false);
setOutgoingCall({ call: null });
setIsActive(false);
setMute(false);
setSelectedOutgoingUser(null);
setCallTime(0);
});
};
useEffect(() => {
let interval = null;
if (state.isActive) {
interval = setInterval(() => {
setCallTime(1000);
}, 1000);
} else {
clearInterval(interval);
}
return () => {
clearInterval(interval);
};
}, [state.isActive]);
useEffect(() => {
let interval = null;
if (state.isActive) {
interval = setInterval(() => {
setCallTime(1000);
}, 1000);
} else {
clearInterval(interval);
}
return () => {
clearInterval(interval);
};
}, [state.isActive]);
// reset optons when there is no active call
useEffect(() => {
if (state.device && !state.incomingCall && !state.secondIncomingCall && !state.enqueuedCall && !state.outgoingCall) {
console.log("Both Calls Ends >>>>>>>>>>>>>>>>>>>>>");
state.device.updateOptions({
sounds: {
disconnect: "https://method-platform.s3.us-east-1.amazonaws.com/phone-configuration/audios/call%20connect.mp3",
outgoing: "https://method-platform.s3.us-east-1.amazonaws.com/phone-configuration/audios/call%20disconnect.mp3",
},
});
}
},

IOS Notification Permission alert does not show

SDK Version: 39.0.0
Platforms(Android/iOS/web/all): All
I am not getting accept or decline notifications permissions alert when loading my app in production.
I have tried clearing certificates and keys and allowing expo to add everything from a clean slate, but still no luck. I am starting to think maybe it’s my code which is the reason why the alert doesn’t get fired.
import Constants from "expo-constants";
import * as Notifications from "expo-notifications";
import { Permissions } from "expo-permissions";
import { Notifications as Notifications2 } from "expo";
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false
})
});
export default class LoginScreen extends React.Component {
state = {
email: "",
password: "",
notification: {},
errorMessage: null
};
async componentDidMount() {
this.registerForPushNotificationsAsync();
//Notifications.addNotificationReceivedListener(this._handleNotification);
Notifications2.addListener(data => {
this.setState({ notification: data });
});
Notifications.addNotificationResponseReceivedListener(
this._handleNotificationResponse
);
}
_handleNotification = notification => {
this.setState({ notification: notification });
};
_handleNotificationResponse = response => {
console.log(response);
};
handleLogin = async () => {
try {
const { email, password } = this.state;
const expoPushToken = await Notifications.getExpoPushTokenAsync();
console.log(expoPushToken);
const userinfo = await firebase
.auth()
.signInWithEmailAndPassword(email, password);
console.log("user ID ", userinfo.user.uid);
await firebase
.firestore()
.collection("users")
.doc(userinfo.user.uid.toString())
.update({
expo_token: expoPushToken["data"]
})
.then(function() {
console.log("token successfully updated!");
})
.catch(function(error) {
// The document probably doesn't exist.
console.error("Error updating document: ", error);
});
} catch (error) {
console.log("=======Error in login", error);
this.setState({ errorMessage: error.message });
}
};
registerForPushNotificationsAsync = async () => {
if (Constants.isDevice) {
const { status: existingStatus } = await Permissions.getAsync(
Permissions.NOTIFICATIONS
);
let finalStatus = existingStatus;
if (existingStatus !== "granted") {
const { status } = await Permissions.askAsync(
Permissions.NOTIFICATIONS
);
finalStatus = status;
}
if (finalStatus !== "granted") {
alert("Failed to get push token for push notification!");
return;
}
const token = await Notifications.getExpoPushTokenAsync();
console.log(token);
//this.setState({ expoPushToken: token });
} else {
alert("Must use physical device for Push Notifications");
}
if (Platform.OS === "android") {
Notifications.setNotificationChannelAsync("default", {
name: "default",
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: "#FF231F7C"
});
}
};
import { Permissions } from "expo-permissions";
should of been
import * as Permissions from 'expo-permissions';
Sometimes we all make simple mistakes.

sessionConfig.perform not being called

I am trying to write a session authentication mechanism for my application, which goes like that:
import { ZObject, Bundle } from "zapier-platform-core";
import IAuthenticationScheme from "../interfaces/authentication/IAuthenticationScheme";
const getSessionKey = (z: ZObject, bundle: Bundle) => {
console.log('GET SESSION called');
const { username: auth_login, password: auth_password } = bundle.authData;
return z.request({
method: 'POST',
url: 'http://******/perl/auth/login',
body: { auth_login, auth_password }
}).then(response => {
z.console.log(response);
console.log(response);
if (response.status === 401) {
throw new Error('The username/password you supplied is invalid');
} else {
return {
sessionKey: z.JSON.parse(response.content).session_id
};
}
});
};
const includeSessionKeyHeader = (request: any, z: ZObject, bundle: Bundle) => {
console.log('includeSessionKeyHeader called');
if (bundle.authData.sessionKey) {
request.headers = Object.assign({}, request.headers);
let { Cookie: cookie = '' } = request.headers;
cookie = `${bundle.authData.sessionKey};${cookie}`;
request.headers['Cookie'] = cookie;
}
return request;
};
const sessionRefreshIf401 = (response: any, z: ZObject, bundle: Bundle) => {
console.warn('sessionRefreshIf401 called');
if (bundle.authData.sessionKey) {
if (response.status === 401) {
throw new z.errors.RefreshAuthError(); // ask for a refresh & retry
}
}
return response;
};
const test = (z: ZObject, bundle: Bundle) => {
console.log('test called');
return z.request({
url: 'http://******/ruby/features'
}).then((response) => {
z.console.log(response);
if (response.status === 401) {
throw new Error('The API Key you supplied is invalid');
}
return response
});
};
const authentication: IAuthenticationScheme<any> = {
type: 'session',
test,
fields: [
{
key: 'username',
type: 'string',
required: true,
helpText: 'Your login username.'
},
{
key: 'password',
type: 'string',
required: true,
helpText: 'Your login password.'
}
],
connectionLabel: (z, bundle) => {
return bundle.inputData.username;
},
sessionConfig: {
perform: getSessionKey
}
};
export default {
authentication,
beforeRequest: { includeSessionKeyHeader },
afterRequest: { sessionRefreshIf401 }
};
As you can see, I put console.log markers at the beginning of each function here so I can see in which order they are getting called.
Here is my test configuration:
import { should } from "should";
import { describe } from "mocha";
const { version } = require("../../package.json");
import { version as platformVersion } from "zapier-platform-core";
import { createAppTester } from "zapier-platform-core";
import PlackSession from "../authentication/PlackSession";
const App = {
version,
platformVersion,
authentication: PlackSession.authentication,
beforeRequest: [PlackSession.beforeRequest.includeSessionKeyHeader],
afterResponse: [PlackSession.afterRequest.sessionRefreshIf401],
};
const appTester = createAppTester(App);
export default () => {
describe('PlackSession authentication', () => {
it('should authenticate', done => {
console.log(`AUTHENTICATE!!`)
const bundle = {
authData: {
username: 'dev#******.com',
password: 'abc123'
}
};
appTester(App.authentication.test, bundle)
.then(response => {
console.log('BBBBBBBB')
done();
})
.catch(a => {
console.log('CCCCCC');
done(a)
});
});
});
};
And I can see the test output the logs in the following order:
authentication
PlackSession authentication
AUTHENTICATE!!
test called
includeSessionKeyHeader called
CCCCCC
1) should authenticate
That means sessionConfig.perform (getSessionKey) is never called, and this is where the credentials should be exchanged for authentication through the login API call, which I can also see in my server logs it never gets called, it skips straight to the test call and fails.
David here, from the Zapier Platform team. Great question!
I think the problem lies in your test. There should be two function. One should call App.authentication.sessionConfig.perform and tests exchanging username & password for a token. Another should call App.authentication.test, which tests fetching a protected resource with a valid key. Though these may be able to be chained together, they can also be written separately.
There's a more complete example here: https://github.com/zapier/zapier-platform-example-app-session-auth/blob/cc63ca67cbc8933439577b2362d026ba2a701e36/test/basic.js

Angular 2 Reactive Forms Async Custom Validation throws "subscribe not a function"

I've already tried every permutation of the answers to [angular2 async validation this.subscribe exception? but i'm still getting the exception.
import {AsyncValidatorFn, AbstractControl } from '#angular/forms';
export function userNameShouldBeUnique(): AsyncValidatorFn {
return (control: AbstractControl): { [key: string]: any } => {
return new Promise(resolve => {
setTimeout(() => {
if (control.value == 'mosh')
resolve({ shouldBeUnique: true });
else
resolve(null);
}, 1000);
});
}
}
and in the component (the last attempt):
this.myForm = this.fb.group({
username: [
'',
Validators.compose([Validators.required, forbiddenNameValidator(/bob/)]),
Validators.composeAsync([userNameShouldBeUnique])
],
password: ['', Validators.required]
});
so what am I doing wrong? Thanks
The solution is:
import { AsyncValidatorFn, AbstractControl } from '#angular/forms';
export function UniqueValidator(): AsyncValidatorFn {
return (control: AbstractControl): Promise<any> => {
return new Promise<any>(resolve => {
setTimeout(() => {
if (control.value === 'mosh')
resolve({ shouldBeUnique: true });
else
resolve(null);
}, 1000);
});
};
};
Now you have return types well configured. To be added as custom validation:
username: ['', Validators.required, UniqueValidator()]
Just tested and it works ;)

Resources