In order to add specific header, I need to check what the api/query url is. There isn't any param based on redux toolkit documentation.
export const gatewayApi = createApi({
reducerPath: 'gatewayApi',
tagTypes: [
'Country',
'Users',
],
baseQuery: fetchBaseQuery({
baseUrl: '/api/',
prepareHeaders: headers => {
headers.set('content-type', 'application/json');
const { auth } = JSON.parse(
localStorage.getItem('user'),
);
const { accessToken } = JSON.parse(
localStorage.getItem('token'),
);
const queryUrl = '**** TODO ****';
if (isTokenAuth(queryUrl)) {
headers.set('authorization', `${accessToken}`);
} else if (auth) {
headers.set('authorization', `${auth}`);
}
return headers;
},
}),
endpoints: () => ({}),
});
No, there isn't. You can also just set headers from your endpoint's query function though:
myEndpoint: build.query({
query(arg) {
return {
url: ...,
headers: { foo: "bar" }
}
}
})
Related
I have the multilanguege app in next.js with ISR. Data fetch using graphQL. The API return data for a given language after getting origin from header request. Unfortunately, If I do it in function getStaticProps() then origin is empty. How Can I submit origin which domain is request?
In example below use apollo client.
The origin header is not visible in the API.
Where should I type something?
How can I submit this origin?
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined
const createApolloClient = (headers: IncomingHttpHeaders | null = null) => {
const enhancedFetch = (url: RequestInfo, init: RequestInit) => {
return fetch(url, {
...init,
headers: {
...init.headers,
Cookie: headers?.cookie ?? '',
},
}).then((response) => {
return response
})
}
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: ApolloLink.from([
onError(({ graphQLErrors, networkError, operation, forward, response }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path, }) =>
console.error(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
)
}
if (networkError)
console.error(
`[Network error]: ${networkError}. Backend is unreachable. Is it running?`
)
}),
createUploadLink({
uri: "https://api.url_site/api/graphql",
fetchOptions: {
mode: 'cors',
},
credentials: 'same-origin',
fetch: enhancedFetch,
}),
]),
cache: new InMemoryCache(),
})
type InitialState = NormalizedCacheObject | undefined
interface IInitializeApollo {
headers?: IncomingHttpHeaders | null
initialState?: InitialState | null
}
export const initializeApollo = (
{ headers, initialState }: IInitializeApollo = {
headers: {
// 'Access-Control-Allow-Origin': '*'
},
initialState: null,
}
) => {
const _apolloClient = apolloClient ?? createApolloClient(headers)
if (initialState) {
const existingCache = _apolloClient.extract()
const data = merge(existingCache, initialState, {
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
...destinationArray.filter((d) =>
sourceArray.every((s) => !isEqual(d, s))
),
],
})
_apolloClient.cache.restore(data)
}
if (typeof window === 'undefined') return _apolloClient
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function addApolloState(
client: ApolloClient<NormalizedCacheObject>,
pageProps: AppProps['pageProps']
) {
// #ts-ignore
if (pageProps?.props) {
// #ts-ignore
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract()
}
return pageProps
}
export function useApollo(pageProps: AppProps['pageProps']) {
// #ts-ignore
const state = pageProps[APOLLO_STATE_PROP_NAME]
const store = useMemo(() => initializeApollo(state), [state])
return store
}
}
I have this code:
import { useGetPostQuery, useGetPostFnQuery } from "./store/postApi";
export function App() {
const { data, isLoading } = useGetPostQuery(1);
const result = useGetPostFnQuery(1);
console.log("result", result);
return (
<div>
{isLoading ? <div>loading...</div> : <div>{data.post}</div>}
{result.isLoading ? <div>loading...</div> : <div>{result.data.post}</div>}
</div>
);
}
where ./store/postApi.js is as follows:
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
export const postApi = createApi({
reducerPath: "postApi",
baseQuery: fetchBaseQuery({ baseUrl: "https://webcodingcenter.com/shared/" }),
endpoints: (builder) => ({
getPost: builder.query({
query: (id) => `get_post.php?id=${id}`, // expects a JSON response
transformResponse: (response, meta, arg) => {
// not applicable to queryFn
console.log("meta", meta);
return { post: response.post.toLowerCase() };
}
}),
getPostFn: builder.query({
queryFn(arg, queryApi, extraOptions, baseQuery) {
console.log("queryApi", queryApi);
if (Math.random() < 0.9) {
return fetch(
`https://webcodingcenter.com/shared/${extraOptions.file}?id=${arg}`
)
.then((r) => r.json())
.then((j) => ({ data: j }));
} else
return {
error: {
status: 500,
statusText: "Internal Server Error",
data: "Just a simulated error!"
}
};
},
extraOptions: { file: "get_post.php" }
})
})
});
// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useGetPostQuery, useGetPostFnQuery } = postApi;
I expect the two lines ('hello world' and 'HELLO WORLD') to be loaded at the same time. However, the second line ('HELLO WORLD') is always loaded before the first ('hello world'), as you can see from the Code Sanbox here.
Here is the file get_post.php:
<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS, post, get');
header("Access-Control-Max-Age", "3600");
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');
header("Access-Control-Allow-Credentials", "true");
sleep(1);
echo json_encode(array("post"=>file_get_contents("data".$_GET["id"].".txt")));
?>
How can I make the two lines load together as soon as possible?
A noticed that onUploadProgress from axios are not being called at all on my Nuxt.js Project. After some debugging I found out it is related to the "#nuxtjs/pwa" and "#nuxtjs/firebase" modules. I use firebase auth, and the PWA module uses a service worker to take care of SSR auth and inject the auth token on outgoing requests.
This modules are interfering somehow on the axios onUploadProgress. I use axios to upload files to other Apis.
Once I remove the "#nuxtjs/pwa" module the onUploadProgress from axios gets called normally.
Does anyone have an idea how to fix that?
The versions of the modules:
"#nuxtjs/axios": "^5.13.6",
"#nuxtjs/firebase": "^7.6.1",
"#nuxtjs/pwa": "^3.3.5",
nuxt.config.js
firebase: {
....
services: {
auth: {
ssr: true,
persistence: 'local',
initialize: {
onAuthStateChangedAction: 'auth/onAuthStateChangedAction',
subscribeManually: false,
},
},
firestore: true,
functions: true,
},
}
pwa: {
meta: false,
icon: false,
workbox: {
importScripts: ['/firebase-auth-sw.js'],
dev: process.env.NODE_ENV === 'development',
},
},
Axios upload
const asset = await $axios.post(uploadUrl, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
console.log('onUploadProgress');
const prog = parseInt(
Math.round((progressEvent.loaded / progressEvent.total) * 100)
);
progress(prog);
},
});
the console.log isn't called at all.
firebase-auth-sw
const ignorePaths = ["\u002F__webpack_hmr","\u002F_loading","\u002F_nuxt\u002F"]
importScripts(
'https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js'
)
importScripts(
'https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js'
)
firebase.initializeApp({"apiKey":"AIzaSyDUjfwaCRNG72CaPznknOfbNLySkFQvfrs","authDomain":"j-a-developer-web-site.firebaseapp.com","projectId":"j-a-developer-web-site","storageBucket":"j-a-developer-web-site.appspot.com","messagingSenderId":"393360816421","appId":"1:393360816421:web:75c43cac27032d924502cc"})
// Initialize authService
const authService = firebase.auth()
/**
* Returns a promise that resolves with an ID token if available.
* #return {!Promise<?string>} The promise that resolves with an ID token if
* available. Otherwise, the promise resolves with null.
*/
const getIdToken = () => {
return new Promise((resolve) => {
const unsubscribe = authService.onAuthStateChanged((user) => {
unsubscribe()
if (user) {
// force token refresh as it might be used to sign in server side
user.getIdToken(true).then((idToken) => {
resolve(idToken)
}, () => {
resolve(null)
})
} else {
resolve(null)
}
})
})
}
const fetchWithAuthorization = async (original, idToken) => {
// Clone headers as request headers are immutable.
const headers = new Headers()
for (let entry of original.headers.entries()) {
headers.append(entry[0], entry[1])
}
// Add ID token to header.
headers.append('Authorization', 'Bearer ' + idToken)
// Create authorized request
const { url, ...props } = original.clone()
const authorized = new Request(url, {
...props,
mode: 'same-origin',
redirect: 'manual',
headers
})
return fetch(authorized)
}
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
const expectsHTML = event.request.headers.get('accept').includes('text/html')
const isSameOrigin = self.location.origin === url.origin
const isHttps = (self.location.protocol === 'https:' || self.location.hostname === 'localhost' || self.location.hostname === '127.0.0.1')
const isIgnored = ignorePaths.some(path => {
if (typeof path === 'string') {
return url.pathname.startsWith(path)
}
return path.test(url.pathname.slice(1))
})
// https://github.com/nuxt-community/firebase-module/issues/465
if (!expectsHTML || !isSameOrigin || !isHttps || isIgnored) {
event.respondWith(fetch(event.request))
return
}
// Fetch the resource after checking for the ID token.
// This can also be integrated with existing logic to serve cached files
// in offline mode.
event.respondWith(
getIdToken().then(
idToken => idToken
// if the token was retrieved we attempt an authorized fetch
// if anything goes wrong we fall back to the original request
? fetchWithAuthorization(event.request, idToken).catch(() => fetch(event.request))
// otherwise we return a fetch of the original request directly
: fetch(event.request)
)
)
})
// In service worker script.
self.addEventListener('activate', event => {
event.waitUntil(clients.claim())
})
sw
const options = {"workboxURL":"https://cdn.jsdelivr.net/npm/workbox-cdn#5.1.4/workbox/workbox-sw.js","importScripts":["/firebase-auth-sw.js"],"config":{"debug":true},"cacheOptions":{"cacheId":"J.A-Developer-Web-Site-dev","directoryIndex":"/","revision":"qIA7lTEhJ6Mk"},"clientsClaim":true,"skipWaiting":true,"cleanupOutdatedCaches":true,"offlineAnalytics":false,"preCaching":[{"revision":"qIA7lTEhJ6Mk","url":"/?standalone=true"}],"runtimeCaching":[{"urlPattern":"/_nuxt/","handler":"NetworkFirst","method":"GET","strategyPlugins":[]},{"urlPattern":"/","handler":"NetworkFirst","method":"GET","strategyPlugins":[]}],"offlinePage":null,"pagesURLPattern":"/","offlineStrategy":"NetworkFirst"}
importScripts(...[options.workboxURL, ...options.importScripts])
initWorkbox(workbox, options)
workboxExtensions(workbox, options)
precacheAssets(workbox, options)
cachingExtensions(workbox, options)
runtimeCaching(workbox, options)
offlinePage(workbox, options)
routingExtensions(workbox, options)
function getProp(obj, prop) {
return prop.split('.').reduce((p, c) => p[c], obj)
}
function initWorkbox(workbox, options) {
if (options.config) {
// Set workbox config
workbox.setConfig(options.config)
}
if (options.cacheNames) {
// Set workbox cache names
workbox.core.setCacheNameDetails(options.cacheNames)
}
if (options.clientsClaim) {
// Start controlling any existing clients as soon as it activates
workbox.core.clientsClaim()
}
if (options.skipWaiting) {
workbox.core.skipWaiting()
}
if (options.cleanupOutdatedCaches) {
workbox.precaching.cleanupOutdatedCaches()
}
if (options.offlineAnalytics) {
// Enable offline Google Analytics tracking
workbox.googleAnalytics.initialize()
}
}
function precacheAssets(workbox, options) {
if (options.preCaching.length) {
workbox.precaching.precacheAndRoute(options.preCaching, options.cacheOptions)
}
}
function runtimeCaching(workbox, options) {
const requestInterceptor = {
requestWillFetch({ request }) {
if (request.cache === 'only-if-cached' && request.mode === 'no-cors') {
return new Request(request.url, { ...request, cache: 'default', mode: 'no-cors' })
}
return request
},
fetchDidFail(ctx) {
ctx.error.message =
'[workbox] Network request for ' + ctx.request.url + ' threw an error: ' + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
},
handlerDidError(ctx) {
ctx.error.message =
`[workbox] Network handler threw an error: ` + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
return null
}
}
for (const entry of options.runtimeCaching) {
const urlPattern = new RegExp(entry.urlPattern)
const method = entry.method || 'GET'
const plugins = (entry.strategyPlugins || [])
.map(p => new (getProp(workbox, p.use))(...p.config))
plugins.unshift(requestInterceptor)
const strategyOptions = { ...entry.strategyOptions, plugins }
const strategy = new workbox.strategies[entry.handler](strategyOptions)
workbox.routing.registerRoute(urlPattern, strategy, method)
}
}
function offlinePage(workbox, options) {
if (options.offlinePage) {
// Register router handler for offlinePage
workbox.routing.registerRoute(new RegExp(options.pagesURLPattern), ({ request, event }) => {
const strategy = new workbox.strategies[options.offlineStrategy]
return strategy
.handle({ request, event })
.catch(() => caches.match(options.offlinePage))
})
}
}
function workboxExtensions(workbox, options) {
}
function cachingExtensions(workbox, options) {
}
function routingExtensions(workbox, options) {
}
store's index.js
export const state = () => ({});
export const actions = {
async nuxtServerInit({ dispatch, commit }, { res }) {
// initialize the store with user if already authenticated
if (res && res.locals && res.locals.user) {
const {
allClaims: claims,
idToken: token,
...authUser
} = res.locals.user;
await dispatch('auth/onAuthStateChangedAction', {
authUser,
claims,
token,
});
}
},
};
I am trying to pass data to my redux store with the help of redux toolkit, but I'm unable to do so. I double checked that there is no problem with my data. Here is my code:
//USER DETAILS
export const userDetailsSlice = createSlice({
name: 'userDetails',
initialState: { loading: 'idle', user: {} },
reducers: {
userDetailsLoading(state) {
if (state.loading === 'idle') {
state.loading = 'pending';
}
},
userDetailsReceived(state, action) {
if (state.loading === 'pending') {
state.loading = 'idle';
state.user = action.payload;
}
},
userDetailsError(state, action) {
if (state.loading === 'pending') {
state.loading = 'idle';
state.error = action.payload;
}
},
},
});
export const {
userDetailsLoading,
userDetailsReceived,
userDetailsError,
} = userDetailsSlice.actions;
export const getUserDetails = (id) => async (dispatch, getState) => {
dispatch(userRegisterLoading());
try {
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/users/${id}`, config);
console.log(data);
dispatch(userDetailsReceived(data));
} catch (error) {
dispatch(userDetailsError(error.toString()));
}
};
I console logged my data right before the dispatch action: so the data is definitely there. Therefore I think there's something wrong with my code. Here is my code for my redux store:
import { configureStore } from '#reduxjs/toolkit';
import { photoListSlice, photoCreateSlice } from './photo';
import { userRegisterSlice, userLoginSlice, userDetailsSlice } from './user';
const reducer = {
userRegister: userRegisterSlice.reducer,
userLogin: userLoginSlice.reducer,
userDetails: userDetailsSlice.reducer,
};
const store = configureStore({ reducer });
export default store;
As seen from this photo: the action userDetailsReceived was called but the state for user did not change.
Any help or hint would be greatly appreciated!!
I made a typo on the first dispatch call of getUserDetails function, I dispatched userRegisterLoading instead of userDetailsLoading... Now it works fine after the change.
export const getUserDetails = (id) => async (dispatch, getState) => {
dispatch(userDetailsLoading());
try {
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/users/${id}`, config);
console.log(data);
dispatch(userDetailsReceived(data));
} catch (error) {
dispatch(userDetailsError(error.toString()));
}
};
Suppose I have an API that return user detail:
/api/get_user/1
{
"status": 200,
"data": {
"username": "username1",
"email": "username#email.com"
}
}
And a "main function" like this:
function main (sources) {
const request$ = sources.ACTIONS
.filter(action => action.type === 'GET_USER_REQUEST')
.map(action => action.payload)
.map(payload => ({
category: 'GET_USER_REQUEST',
url: `${BASE_URL}/api/get_user/${payload.userId}`,
method: 'GET'
}))
const action$ = sources.HTTP
.select('GET_USER_REQUEST')
.flatten()
.map(response => response.data)
const sinks = {
HTTP: request$,
LOG: action$
}
return sinks
}
For testing the "ACTION" source, I can simply made an xstream observable
test.cb('Test main function', t => {
const actionStream$ = xs.of({
type: 'GET_USER_REQUEST',
payload: { userId: 1 }
})
const sources = { ACTION: actionStream$ }
const expectedResult = {
category: 'GET_USER_REQUEST',
url: `${BASE_URL}/api/get_user/${payload.userId}`,
method: 'GET'
}
main(sources).HTTP.addEventListener({
next: (data) => {
t.deepEqual(data, expectedResult)
},
error: (error) => {
t.fail(error)
},
complete: () => {
t.end()
}
})
})
The question is. Is it possible to do the same thing (using plan xstream observable)
to test cycle-http driver without a helper from something like nock?
Or is there a better way to test something like this?
You can mock out the HTTP source like so:
test.cb('Test main function', t => {
const actionStream$ = xs.of({
type: 'GET_USER_REQUEST',
payload: { userId: 1 }
})
const response$ = xs.of({
data: {
status: 200,
data: {
username: "username1",
email: "username#email.com"
}
}
});
const HTTP = {
select (category) {
// if you have multiple categories you could return different streams depending on the category
return xs.of(response$);
}
}
const sources = { ACTION: actionStream$, HTTP }
const expectedResult = {
category: 'GET_USER_REQUEST',
url: `${BASE_URL}/api/get_user/${payload.userId}`,
method: 'GET'
}
main(sources).HTTP.addEventListener({
next: (data) => {
t.deepEqual(data, expectedResult)
},
error: (error) => {
t.fail(error)
},
complete: () => {
t.end()
}
})
})
Really, we should have a mockHTTPSource helper to make this a bit easier. I have opened an issue to that effect. https://github.com/cyclejs/cyclejs/issues/567
If you want to test that certain things happen at the correct time, you could use this pattern in conjunction with #cycle/time.
http://github.com/cyclejs/time