setLocale on the server side with node, koa, and i18next - koa

I'm not able to have setLocale work on the server side with node, koa, and i18next. as i18n library
I've followed this issue (https://github.com/jquense/yup/issues/758) proposed solution but no luck...
I'm setting the locale in a middleware which detects the client accept-language header.
This is the koa middleware which sets the language:
import i18next from "i18next";
import { setLocale } from 'yup';
export default async (ctx, next) => {
if (ctx.req.method != 'OPTIONS' && ctx.headers['accept-language']) {
let acceptLanguages = []
ctx.headers['accept-language'].split(',').forEach(x => acceptLanguages.push(x.substring(0,2)))
if (acceptLanguages.some(x => x == acceptLanguages[0]) && i18next.language != acceptLanguages[0]) {
i18next.changeLanguage(acceptLanguages[0])
buildYupLocale(i18next.t)
}
}
await next()
}
const buildYupLocale = (t) => {
setLocale({
// use constant translation keys for messages without values
mixed: {
required: t('field_required'),
},
// use functions to generate an error object that includes the value from the schema
string: {
max: ({ max }) => t('field_too_long', { max }),
},
});
}
Any suggestions or examples will be much appreciated.

Related

Making multiple socket instances in React with socket.io, each in a namespace

I have a nestjs application which has Websockets integrated with socket.io. Some of the gateways need authentication. So connecting to them without authenticating logs you out. The problem is, I need some of them without authentication, so I managed to figure out that I could use "namespaces" to connect only to specific Gateways.
I specified in the gateways the namespaces like this:
#WebSocketGateway({
namespace: 'tourneys',
...ConfigConstants.WsConfig,
})
export class AuxiliaryGateway
and in gateways that need authentication, I made it like this:
#UseGuards(SocketSessionGuard)
#WebSocketGateway({
namespace: 'matches',
...ConfigConstants.WsConfig,
})
The problem doesn't seem to be on the back-end however. In the front-end, I tried connecting the websockets like this:
import React, { useEffect, useMemo } from "react";
import { io, ManagerOptions, Socket, SocketOptions } from "socket.io-client";
import { SocketContext } from "#lib/context/SocketContext";
import {
ServerToClientEvents,
ClientToServerEvents,
} from "#lib/types/socket/instance";
import { getAuthToken } from "#lib/services/storage/authToken";
export const SocketProvider: React.FC = ({ children }) => {
const options = {
auth: {
token: getAuthToken(),
},
transports: ["websocket"],
timeout: 20000,
reconnectionAttempts: 10,
reconnectionDelay: 1500,
reconnectionDelayMax: 5000,
} as Partial<ManagerOptions & SocketOptions>;
const tourneysSocket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
`${process.env.NEXT_PUBLIC_WSS_HOST}/tourneys `,
options
);
const matchesSocket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
`${process.env.NEXT_PUBLIC_WSS_HOST}/matches `,
options
);
useEffect(() => {
tourneysSocket.on("connect", () => {
console.log("conectado");
});
tourneysSocket.on("disconnect", e => {
console.warn(`- desconectado "disconnect", ${e}`);
});
tourneysSocket.on("exception", e => {
console.error(e);
});
matchesSocket.on("connect", () => {
console.log("conectado");
});
matchesSocket.on("disconnect", e => {
console.warn(`- desconectado "disconnect", ${e}`);
});
matchesSocket.on("exception", e => {
console.error(e);
});
}, [tourneysSocket, matchesSocket]);
const value = useMemo(
() => ({
tourneysSocket,
matchesSocket,
}),
[tourneysSocket, matchesSocket]
);
return (
<SocketContext.Provider value={value}>{children}</SocketContext.Provider>
);
};
I make two instances, one for each namespace. However, these instances they stop emitting to the correct subscribes after some testing. What might be causing this issue? I can't figure out and I believe it's happening in the front-end. React somehow seems to not use the sockets I'm instatiating after some emits.

Laravel Sanctum/Vuex Uncaught Error Vue Router Navigation Guard

Can anyone advise on this issue?
I've got a Laravel app with a Vue front-end, connecting to the API using Laravel Sanctum. (within the same application) I'm trying to set up the authentication guards so routes can only be accessed after authentication with the API.
I'm connecting through the state action like so:
async getAuthenticatedUser({ commit }, params) {
await axios.get('api/auth-user', { params })
.then(response => {
commit('SET_AUTHENTICATED', true)
commit('SET_AUTHENTICATED_USER', response.data)
localStorage.setItem('is-authenticated', 'true')
return Promise.resolve(response.data)
})
.catch(error => {
commit('SET_AUTHENTICATED', false)
commit('SET_AUTHENTICATED_USER', [])
localStorage.removeItem('is-authenticated')
return Promise.reject(error.response.data)
})
},
The state authenticated property is set as follows:
const state = () => ({
authenticated: localStorage.getItem('is-authenticated') || false,
authUser: [],
})
I have the following guard checking the auth. If I'm not signed in the app correctly redirects me back to the login screen when I access a route with the requiresAuth attribute.
However when I attempt to log in, I get Redirected when going from "/login" to "/dashboard" via a navigation guard.
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (store.getters["Auth/isAuthenticated"]) {
next()
return
}
next('/login')
}
if (to.matched.some((record) => record.meta.requiresVisitor)) {
if (! store.getters["Auth/isAuthenticated"]) {
next()
return
}
next('/dashboard')
}
next()
})
If it's safe to ignore, and you are using vue-router ^3.4.0, you can do:
import VueRouter from 'vue-router'
const { isNavigationFailure, NavigationFailureType } = VueRouter
...
this.$router.push(fullPath).catch(error => {
if (!isNavigationFailure(error, NavigationFailureType.redirected)) {
throw Error(error)
}
})

admin-on-rest / restClient : call a resource with no auth

I made a register page that use restClient to send a POST to /users api.
But my problem is that the only way to send a POST is to be logged first as I receive this error log from the restClient :
'Could not find stored JWT and no authentication strategy was given'
Is there a way to desactivate the authentication middleware for a specific api call ?
// registerActions.js
import { CREATE } from 'admin-on-rest'
export const USER_REGISTER = 'AOR/USER_REGISTER'
export const USER_REGISTER_LOADING = 'AOR/USER_REGISTER_LOADING'
export const USER_REGISTER_FAILURE = 'AOR/USER_REGISTER_FAILURE'
export const USER_REGISTER_SUCCESS = 'AOR/USER_REGISTER_SUCCESS'
export const userRegister = (data, basePath) => ({
type: USER_REGISTER,
payload: { data: { email: data.username, ...data } },
meta: { resource: 'users', fetch: CREATE, auth: true },
})
//registerSaga.js
import { put, takeEvery, all } from 'redux-saga/effects'
import { push } from 'react-router-redux'
import { showNotification } from 'admin-on-rest'
import {
USER_REGISTER,
USER_REGISTER_LOADING,
USER_REGISTER_SUCCESS,
USER_REGISTER_FAILURE
} from './registerActions'
function* registerSuccess() {
yield put(showNotification('Register approved'))
yield put(push('/'))
}
function* registerFailure({ error }) {
yield put(showNotification('Error: register not approved', 'warning'))
console.error(error)
}
export default function* commentSaga() {
yield all([
takeEvery(USER_REGISTER_SUCCESS, registerSuccess),
takeEvery(USER_REGISTER_FAILURE, registerFailure),
])
}
You'll probably have to make your own feathers client and explicitly bypass the call to authenticate for this specific request
You can also write a rest wrappper this will intercept the call for this particular case and bypass auth
https://marmelab.com/admin-on-rest/RestClients.html#decorating-your-rest-client-example-of-file-upload
So something like below
const restWrapper = requestHandler => (type, resource, params) => {
import { fetchUtils } from 'admin-on-rest';
if (type === 'CREATE' && resource === 'users') {
return fetchUtils.fetchJson(url, params)
.then((response) => {
const {json} = response;
return { data: json };
})
}
Eliminates the need of rewriting an entire Rest Client when you only want to override the default behaviour for a single case

RxJs How to set default request headers?

Not sure is there any way to set default request headers in rxjs like we do with axios js as-
axios.defaults.headers.common['Authorization'] = 'c7b9392955ce63b38cf0901b7e523efbf7613001526117c79376122b7be2a9519d49c5ff5de1e217db93beae2f2033e9';
Here is my epic code where i want to set request headers -
export default function epicFetchProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.mergeMap(action =>
ajax.get(`http://localhost/products?${action.q}`)
.map(response => doFetchProductsFulfilled(response))
);
}
Please help.
It's not possible to set default headers for all ajax requests using RxJS's ajax utilities.
You can however provide headers in each call, or create your own simple wrapper that provides them by default.
utils/ajax.js
const defaultHeaders = {
Authorization: 'c7b9392955ce63b38cf090...etc'
};
export const get = (url, headers) =>
ajax.get(url, Object.assign({}, defaultHeaders, headers));
my-example.js
import * as ajax from './utils/ajax';
// Usage is the same, but now with defaults
ajax.get(`http://localhost/products?${action.q}`;)
I'm using redux-observable but this applies to rxjs; maybe the next answer its too over-engineered, but I needed to get dinamically the headers depending of certain factors, without affecting the unit testing (something decoupled from my epics too), and without changing the sintax of ajax.get/ajax.post etc, this is what I found:
ES6 has proxies support, and after reading this and improving the solution here, I'm using a High Order Function to create a Proxy in the original rxjs/ajax object, and return the proxified object; below is my code:
Note: I'm using typescript, but you can port it to plain ES6.
AjaxUtils.ts
export interface AjaxGetHeadersFn {
(): Object;
}
// the function names we will proxy
const getHeadersPos = (ajaxMethod: string): number => {
switch (ajaxMethod) {
case 'get':
case 'getJSON':
case 'delete':
return 1;
case 'patch':
case 'post':
case 'put':
return 2;
default:
return -1;
}
};
export const ajaxProxy = (getHeadersFn: AjaxGetHeadersFn) =>
<TObject extends object>(obj: TObject): TObject => {
return new Proxy(obj, {
get(target: TObject, propKey: PropertyKey) {
const origProp = target[propKey];
const headersPos = getHeadersPos(propKey as string);
if (headersPos === -1 || typeof origProp !== 'function') {
return origProp;
}
return function (...args: Array<object>) {
args[headersPos] = { ...args[headersPos], ...getHeadersFn() };
// #ts-ignore
return origProp.apply(this, args);
};
}
});
};
You use it this way:
ConfigureAjax.ts
import { ajax as Ajax } from 'rxjs/ajax'; // you rename it
// this is the function to get the headers dynamically
// anything, a function, a service etc.
const getHeadersFn: AjaxGetHeadersFn = () => ({ 'Bearer': 'BLABLABLA' });
const ajax = ajaxProxy(getHeadersFn)(Ajax); // proxified object
export default ajax;
Anywhere in you application you import ajax from ConfigureAjax.ts and use it as normal.
If you are using redux-observable you configure epics this way (injecting ajax object as a dependency more info here):
ConfigureStore.ts
import ajax from './ConfigureAjax.ts'
const rootEpic = combineEpics(
fetchUserEpic
)({ ajax });
UserEpics.ts
// the same sintax ajax.getJSON, decoupled and
// under the covers with dynamically injected headers
const fetchUserEpic = (action$, state$, { ajax }) => action$.pipe(
ofType('FETCH_USER'),
mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe(
map(response => ({
type: 'FETCH_USER_FULFILLED',
payload: response
}))
)
);
Hope it helps people looking for the same :D

How do you dynamically control react apollo-client query initiation?

A react component wrapped with an apollo-client query will automatically initiate a call to the server for data.
I would like to fire off a request for data only on a specific user input.
You can pass the skip option in the query options - but this means the refetch() function is not provided as a prop to the component; and it appears that the value of skip is not assessed dynamically on prop update.
My use is case is a map component. I only want data for markers to be loaded when the user presses a button, but not on initial component mount or location change.
A code sample below:
// GraphQL wrapping
Explore = graphql(RoutesWithinQuery, {
options: ({ displayedMapRegion }) => ({
variables: {
scope: 'WITHIN',
targetRegion: mapRegionToGeoRegionInputType(displayedMapRegion)
},
skip: ({ targetResource, searchIsAllowedForMapArea }) => {
const skip = Boolean(!searchIsAllowedForMapArea || targetResource != 'ROUTE');
return skip;
},
}),
props: ({ ownProps, data: { loading, viewer, refetch }}) => ({
routes: viewer && viewer.routes ? viewer.routes : [],
refetch,
loading
})
})(Explore);
To include an HoC based on a condition affected by a props change, you could use branch from recompose.
branch(
test: (props: Object) => boolean,
left: HigherOrderComponent,
right: ?HigherOrderComponent
): HigherOrderComponent
check: https://github.com/acdlite/recompose/blob/master/docs/API.md#branch
For this specific example, would look something like:
const enhance = compose(
branch(
// evaluate condition
({ targetResource, searchIsAllowedForMapArea }) =>
Boolean(!searchIsAllowedForMapArea || targetResource != 'ROUTE'),
// HoC if condition is true
graphql(RoutesWithinQuery, {
options: ({ displayedMapRegion }) => ({
variables: {
scope: 'WITHIN',
targetRegion: mapRegionToGeoRegionInputType(displayedMapRegion)
},
}),
props: ({ ownProps, data: { loading, viewer, refetch } }) => ({
routes: viewer && viewer.routes ? viewer.routes : [],
refetch,
loading
})
})
)
);
Explore = enhance(Explore);
I have a similar use case, I wanted to load the data only when the user clicked.
I've not tried the withQuery suggestion given by pencilcheck above. But I've seen the same suggestion elsewhere. I will try it, but in the meantime this is how I got it working based off a discussion on github:
./loadQuery.js
Note: I'm using the skip directive:
const LOAD = `
query Load($ids:[String], $skip: Boolean = false) {
things(ids: $ids) #skip(if: $skip) {
title
}
`
LoadMoreButtonWithQuery.js
Here I use the withState higher-order function to add in a flag and a flag setter to control skip:
import { graphql, compose } from 'react-apollo';
import { withState } from 'recompose';
import LoadMoreButton from './LoadMoreButton';
import LOAD from './loadQuery';
export default compose(
withState('isSkipRequest', 'setSkipRequest', true),
graphql(
gql(LOAD),
{
name: 'handleLoad',
options: ({ids, isSkipRequest}) => ({
variables: {
ids,
skip: isSkipRequest
},
})
}
),
)(Button);
./LoadMoreButton.js
Here I have to manually "flip" the flag added using withState:
export default props => (
<Button onClick={
() => {
props.setSkipRequest(false); // setter added by withState
props.handleLoad.refetch();
}
}>+</Button>
);
Frankly I'm a little unhappy with this, as it is introduces a new set of wiring (composed in by "withState"). Its also not battle tested - I just got it working and I came to StackOverflow to check for better solutions.

Resources