I am extremely excited about using contentful for my project, but I can't get the JS library to work in a react-native project.
I get the following error:
I have tried the following approaches:
import contentful from 'contentful';
// or
var contentful = require('contentful');
You can reproduce the bug by going to this repo and following the steps I have provided.
Help would be greatly appreciated.
I am maintaining the Contentful sdk. I've put together a simple example
that shows how to use the SDK in React Native, you can check it here
It is basically getting a list of items from one of our example spaces and display the names in a ListView.Check indes.ios.js.
It looks like there is something wrong with the caching in your machine or so.
Anyway I hope this helps.If you have more problems please feel free to create issues in our github page
[UPDATE]
you can now configure the axios instance used in the SDK to use a different adapter. You can pass that when calling createClient
adapter: config => {
config.adapter = null // this is important if it is passing to another axios instance
// an http client combatible with React Native
return fetch(config)
}
Best,
Khaled
I've tried every option and it will never work using the Contentful SDK.
However, you can get it with REST and transform the response using the types from the contentful lib.
import axios from 'axios';
import {EntryCollection} from 'contentful';
import Env from '../Env';
const contentfulApi = 'https://cdn.contentful.com';
/**
* Default locale used in contentful calls
*
* #see {#link getContentfulEntries}
* #constant
*/
export const ContentfulLocale = 'sv';
/**
* #typedef ContentfulProps
* #param locale optional locale to use. Default is {#link ContentfulLocale}
* #param entryType content type in contentful model
*/
type ContentfulProps = {
entryType: string;
locale?: string;
};
/**
* Get entries from Contentful content API
* #param props See {#link ContentfulProps}
*/
export const getContentfulEntries = async <T>(
props: ContentfulProps,
): Promise<EntryCollection<T>> => {
const client = axios.create({
baseURL: `${contentfulApi}/spaces/${Env.CONTENTFUL_SPACEID}/environments/master/entries?`,
timeout: 1000,
headers: {Authorization: `Bearer ${Env.CONTENTFUL_TOKEN}`},
});
const result = await client.get<EntryCollection<T>>(
'&content_type=' + props.entryType,
);
return result.data;
};
export default getContentfulEntries;
I think the best way to use Contentful APIs with React Native is to use Apollo client and graphQL packages.
I worked around the same and get it done.
Install Apollo client and GraphQL npm package
npm install #apollo/client graphql
Install react-native async storage to store cache
npm install #react-native-async-storage/async-storage
Install apollo cache persist to persist the cache
npm install apollo3-cache-persist
you can read apollographql official documents for implementation
Create ApolloClient in the app.tsx/.js file
import { ApolloClient, ApolloProvider, InMemoryCache } from '#apollo/client';
const cache = new InMemoryCache();
const client = new ApolloClient({
uri: 'https://graphql.contentful.com/content/v1/spaces/{SPACE_ID}',
cache,
credentials: 'same-origin',
headers: {
Authorization: `Bearer {CDA-Access-Token}`,
},
});
Wrap all components in ApolloProvider
const App = () => {
const [loadingCache, setLoadingCache] = useState(true);
useEffect(() => {
persistCache({
cache,
storage: AsyncStorage,
}).then(() => setLoadingCache(false));
}, []);
if (loadingCache) {
return <Loader />;
}
return (
<ApolloProvider client={client}>
<SafeAreaView style={{flex: 1}}>
<Posts />
</SafeAreaView>
</ApolloProvider>
);
};
export default App;
Import gql and useQuery to fetch data
import { gql, useQuery } from '#apollo/client';
Now, write GraphQL query
const QUERY_COLLECTION = gql`
{
postsCollection {
items {
title
author
publishDate
inshorts
featuredImage {
url
}
}
}
}
`;
Fetch data using useQuery function
const { data, loading } = useQuery(QUERY_COLLECTION);
That's all to fetch data from Contentful in React Native App.
To read this in detailed, have a look to this post
Related
I am using ghost, i made an integration and i would like to hide the api key from the front-end. I do not believe i can set restrictions on the ghost cms (that would also work). And i do believe so +page.js files are run on the browser also, so im a little confused on how to achieve this?
The interal sveltekit module $env/static/private (docs) is how you use secure API keys. Sveltekit will not allow you to import this module into client code so it provides an extra layer of safety. Vite automatically loads your enviroment variables from .env files and process.env on build and injects your key into your server side bundle.
import { API_KEY } from '$env/static/private';
// Use your secret
Sveltekit has 4 modules for accessing enviroment variables
$env/static/private (covered)
$env/static/public accessiable by server and client and injected at build (docs)
$env/dynamic/private provided by your runtime adapter; only includes variables with that do not start with the your public prefix which defaults to PUBLIC_ and can only be imported by server files (docs)
$env/dynamic/public provided by your runtime adapter; only includes variables with that do start with the your public prefix which defaults to PUBLIC_ (docs)
You don't need to hide the key.
Ghost Content API Docs:
These keys are safe for use in browsers and other insecure environments, as they only ever provide access to public data.
One common way to hide your third-party API key(s) from public view is to set up proxy API routes.
The general idea is to have your client (browser) query a proxy API route that you provide/host, have that proxy route query the third-party API using your credentials (API key), and pass on the results from the third-party API back to the client.
Because the query to the third-party API takes place exclusively on the back-end, your credentials are never exposed to the client (browser) and thus not visible to the public.
In your use case, you would have to create 3 dynamic endpoint routes to replicate the structure of Ghost's API:
src/routes/api/[resource]/+server.js to match /posts/, /authors/, /tags/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
src/routes/api/[resource]/[id]/+server.js to match /posts/{id}/, /authors/{id}/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource, id } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/${id}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
src/routes/api/[resource]/slug/[slug]/+server.js to match /posts/slug/{slug}/, /authors/slug/{slug}/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource, slug } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/slug/${slug}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
Then all you have to do is call your proxy routes in place of your original third-party API routes in your app:
// very barebones example
<script>
let uri;
let data;
async function get() {
const res = await fetch(`/api/${uri}`);
data = await res.json();
}
</script>
<input name="uri" bind:value={uri} />
<button on:click={get}>GET</button>
{data}
Note that using proxy API routes will also have the additional benefit of sidestepping potential CORS issues.
I have a nestjs app that has an AuthService which has these parts:
export class AuthService {
constructor(
#InjectRepository(User)
private readonly userRepo: Repository<User>,
) {}
async updateFromInternally () {
...
}
I have another file which is, crucially, outside of any module, which contains a number of helpful functions relating to Google oauth. For example, this file initiates Google's oauth2 client like so:
export const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
process.env.GOOGLE_CLIENT_REDIRECT
);
This file also has a listener function which I found in Google's documentation as a way to catch when my use of Google's oauth2 client automatically uses a refresh token to obtain a new access token:
oauth2Client.on('tokens', async (tokens) => {
[****]
})
At [****], I need to query in my database for a particular user and update them. Either of these conceptually work:
I somehow get userRepo into this file right here and use it to query + update
I somehow call updateFromInternally in AuthService from here
But I don't know how to interact with either TypeORM repositories or methods within services from outside of any module in nestjs! Can I do either of these?
The first question is why you're using nestjs?
You can access the internals of nestjs by using the instantiated app;
In your main.ts file you have something like this:
const app = await NestFactory.create(AppModule);
You can access the services or DataSource from the app by using the get method.
import {DataSource} from 'typeorm'
const dataSource = app.get<DataSource>(DataSource)
// or custom service
const someServices = app.get<SomeService>(SomeService)
you just need a way to export app from main.ts and import it in your outside world.
for example:
// main.ts
let app;
async bootstrap() {
app = await NestFactory.create(AppModule);
}
export const getApp = () => app;
bootstrap()
and in your other file
// outside.ts
import {getApp} from './main.ts'
const app = getApp()
I didn't write the whole logic, but I think it would give you an idea of what you need to do.
But in my opinion, it's the worst thing you can do. You're using 'nestjs` and try to respect its philosophy.
Just write a module that handles all the work you want.
I figured out #1:
import {getRepository} from "typeorm";
oauth2Client.on('tokens', async (tokens) => {
const gaRepo = getRepository(Googleauth);
// Can now use gaRepo like you do in a service
})
I am working on a project that creates a google chrome extension. I am trying to add redux-saga middleware for using saga debounce. However, I am getting an error that says: Argument type is not assignable of type 'Store<any, AnyAction>'. How can I fix that and How should I do that? There are not various example in the internet in web extension with middleware. Thanks for your time. Here is my code:
in background.ts
const middleware = [saga]
const store = createStore(reducer, initialState)
// a normal Redux store
const storeWithMiddleware = applyMiddleware(store, ...middleware)
wrapStore(storeWithMiddleware, { portName: 'bla' })
in popup.tsx
const store = new Store({ portName: 'bla' })
// wait for the store to connect to the background page
store
.ready()
.then(() => {
// The store implements the same interface as Redux's store
// so you can use tools like `react-redux` no problem!
const root = document.createElement('div')
document.body.appendChild(root)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
root
)
})
.catch((e) => console.log(e))
//});
export default store
applyMiddleware should contain only middlewares (not the store) and be passed as an argument to the createStore function:
const middleware = [saga]
const store = createStore(reducer, initialState, applyMiddleware(...middleware))
const storeWithMiddleware = wrapStore(store, { portName: 'bla' })
Edit:
Since you are using webext-redux, it seems you are actually mixing two ways of creating store together. You should use the applyMiddleware from webext-redux only if you are directly using the proxy Store from webext-redux as well. Since you are using createStore form redux package instead, you should also import the applyMiddleware function from the redux store.
import {createStore, applyMiddleware} from 'redux'
...
I have the following apolloClient
/**
* Initializes an ApolloClient instance. For configuration values refer to the following page
* https://www.apollographql.com/docs/react/api/core/ApolloClient/#the-apolloclient-constructor
*
* #returns ApolloClient
*/
const createApolloClient = (authToken: string | null) => {
const httpLinkHeaders = {
...(authToken && { Authorization: `Bearer ${authToken}` })
};
console.log('CREATING APOLLO CLIENT WITH HEADERS >>>>', httpLinkHeaders);
console.log(
'Graph Env Variable URL >>>>>',
publicRuntimeConfig.GRAPHQL_URL
);
return new ApolloClient({
name: 'client',
ssrMode: typeof window === 'undefined',
link: createHttpLink({
uri: publicRuntimeConfig.GRAPHQL_URL,
credentials: 'same-origin',
headers: httpLinkHeaders
}),
cache: new InMemoryCache()
});
};
/**
* Initializes the apollo client with data restored from the cache for pages that fetch data
* using either getStaticProps or getServerSideProps methods
*
* #param accessToken
* #param initialState
*
* #returns ApolloClient
*/
export const initializeApollo = (
accessToken: string,
initialState = null,
forceNewInstane = false
): ApolloClient<NormalizedCacheObject> => {
// Regenerate client?
if (forceNewInstane) {
apolloClient = null;
}
const _apolloClient = apolloClient || createApolloClient(accessToken);
// for pages that have Next.js data fetching methods that use Apollo Client,
// the initial state gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Restore the cache using the data passed from
// getStaticProps/getServerSideProps combined with the existing cached data
_apolloClient.cache.restore({ ...existingCache, ...initialState });
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
};
/**
* Hook to initialize the apollo client only when state has changed.
*
* #param initialState
*
* #returns
*/
export const useApollo = (
initialState: any
): ApolloClient<NormalizedCacheObject> => {
return useMemo(() => {
if (process.browser) {
const accessToken = extractCookie(document.cookie, 'access_token');
return initializeApollo(accessToken, initialState);
}
// document is not present and we can't retrieve the token but ApolloProvider requires to pass a client
return initializeApollo(null, initialState);
}, [initialState]);
};
That is initialized in the _app.tsx file like so
const updateApolloWithNewToken = useCallback(
(accessToken: string) => {
// Initialize apollo client with new access token
setClient(
initializeApollo(accessToken, pageProps.initialApolloState, true)
);
// Show the dashboard
router.replace('/dashboard');
},
[router]
);
With the following Next Config
const { PHASE_DEVELOPMENT_SERVER } = require('next/constants');
module.exports = (phase, { defaultConfig }) => {
console.log('Phase >>>>', phase);
if (phase === PHASE_DEVELOPMENT_SERVER) {
console.log('RETURNING DEVELOPMENT CONFIGURATION...');
return {
publicRuntimeConfig: {
GRAPHQL_URL: process.env.GRAPHQL_URL
}
};
}
console.log('RETURNING PRODUCTION CONFIGURATION...');
console.log('GRAPHQL_URL', process.env.GRAPHQL_URL);
return {
publicRuntimeConfig: {
GRAPHQL_URL: process.env.GRAPHQL_URL
}
};
};
This is my _app.tsx
function MyApp({ Component, pageProps }: AppProps) {
// Grab the apollo client instance with state hydration from the pageProps
const router = useRouter();
const apolloClient = useApollo(pageProps.initialApolloState);
const [client, setClient] = useState(apolloClient);
React.useEffect(() => {
// Refresh token on browser load or page regresh
handleAcquireTokenSilent();
// We also set up an interval of 5 mins to check if token needs to be refreshed
const refreshTokenInterval = setInterval(() => {
handleAcquireTokenSilent();
}, REFRESH_TOKEN_SILENTLY_INTERVAL);
return () => {
clearInterval(refreshTokenInterval);
};
}, []);
const updateApolloWithNewToken = useCallback(
(accessToken: string) => {
// Initialize apollo client with new access token
setClient(
initializeApollo(accessToken, pageProps.initialApolloState, true)
);
// Show the dashboard
router.replace('/dashboard');
},
[router]
);
return pageProps.isAuthenticated || pageProps.shouldPageHandleUnAuthorize ? (
<ApolloProvider client={client}>
<ThemeProvider theme={theme}>
<SCThemeProvider theme={theme}>
<StylesProvider injectFirst>
<Component
{...pageProps}
updateAuthToken={updateApolloWithNewToken}
/>
</StylesProvider>
</SCThemeProvider>
</ThemeProvider>
</ApolloProvider>
) : (
<UnAuthorize />
);
}
/**
* Fetches the Me query so that pages/components can grab it from the cache in the
* client.
*
* Note: This disables the ability to perform automatic static optimization, causing
* every page in the app to be server-side rendered.
*/
MyApp.getInitialProps = async (appContext: AppContext) => {
const appProps = await App.getInitialProps(appContext);
const req = appContext.ctx.req;
// Execute Me query and handle all scenarios including, unauthorized response, caching data so pages can grab
// data from the cache
return await handleMeQuery(appProps, req);
};
export default MyApp;
My problem is that when I run yarn build I get a server error generating 500 page. I know it is because when creating the Apollo Client it doesn't have access to the publicRuntimeConfig, it seems like Next is trying to build the ApolloClient when I run yarn build, I am using getInitialProps and getServerSideProps so I just want to access all the env variables on runtime not on build, because we want one build for our pipeline.
All other env variables in my app that are using publicRuntimeConfig are working, I tested by removing the env vars on build and adding them back on start and the app functioned as normal.
If there isn't a way to do this with apollo client, what would be reccomended to be able to pass different uri's as env variables on start of the app not on build for Apollo Client or alternate solution?
Thanks for any help ahead of time
So I don't know if I have explained the problem well enough.
Basically the graphql URL is different depending on the environment it is in in development, staging, and production however they are supposed to use the same build so I need to access the GRAPHQL_URL via a runtime variable, but in my current setup it is just undefined.
Generally you don't want to use publicRuntimeConfig because it adds overhead and is unnecessary for your use-case. It also disables automatic static optimization.
Traditionally, environment variables are the way to handle dynamic settings based on the environment. Next.js has three default environments – development, test, and production.
Your Next.js app will automatically grab (and merge) the correct variables depending on the environment.
.env.local
GRAPHQL_URL = localhost:8000/graphql
.env.test
GRAPHQL_URL = test.example.com/graphql
.env or .env.production
GRAPHQL_URL = production.example.com/graphql
Apollo config
new ApolloClient({
link: createHttpLink({
uri: process.env.GRAPHQL_URL,
})
});
Environment variable files
In your project root you can create files named
.env
.env.local
.env.test
.env.test.local
.env.production
I believe you can append .local to any environment to create a local only version .env.production.local - this has limited use in your case
Next.js Environment variables
All environments - .env
loaded in development, test, and production environments. This file is used for defaults across all environments. The variables in this file will be overroad if another environment variable file has a variable with the same name.
Development environment .env.local
loaded only when NODE_ENV = development (next dev)
Test environment .env.test
loaded only when NODE_ENV = test
Test environment .env.test.local
loaded only when NODE_ENV = test and local
Production environment .env.production
loaded only when NODE_ENV = production (next start)
Example production only variable
Create a .env.production and add the variable there. You can repeat the same for test and local only variables.
Notes
It's good practice to add your .env to your .gitignore so you don't accidently commit secrets to your repositories. At a minimum you should omit .env*.local files from git.
Depending on your CI/CD setup you can set your environment variables in the deployment platform like vercel, github actions, etc. This would allow you to set the test and production variables in the hosting platform and not in your code.
If you need an environment variable accessible in the browser you need to prefix the variabel with NEXT_PUBLIC_
First, there is redundant code and inefficiency. Primarily the hooks updateApolloWithNewToken and useApollo but also in the way you inject the accessToken.
I would recommend throwing the ApolloClient in it's own separate file and using it's configurable links for your use-case.
However, the real problem most likely lies in the initialization of the client and your attempt at memoizing the client.
First, try updating the following,
link: createHttpLink({
uri: publicRuntimeConfig.GRAPHQL_URL,
credentials: 'same-origin',
headers: httpLinkHeaders
}),
to
link: createHttpLink({
// ...your other stuff
uri: () => getConfig().publicRuntimeConfig.GRAPHQL_URL
})
If that doesn't work right away, I would recommend trying with the outmost minimal example.
Create a client you export, a provider and a component that uses it (without using state, useEffect or anything else). We can go from there if that still doesn't work.
I am new to contentful. I am trying to develop an UI extension on Contentful using Contentful SDK.
I followed all the steps mentioned in this article.
This is the code I have in index.js.
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import { TextInput , Button } from '#contentful/forma-36-react-components';
import { init } from 'contentful-ui-extensions-sdk';
import '#contentful/forma-36-react-components/dist/styles.css';
import './index.css';
export class App extends React.Component {
static propTypes = {
sdk: PropTypes.object.isRequired
};
detachExternalChangeHandler = null;
constructor(props) {
super(props);
this.state = {
value: props.sdk.field.getValue() || ''
};
}
componentDidMount() {
this.props.sdk.window.startAutoResizer();
// Handler for external field value changes (e.g. when multiple authors are working on the same entry).
this.detachExternalChangeHandler = this.props.sdk.field.onValueChanged(this.onExternalChange);
}
componentWillUnmount() {
if (this.detachExternalChangeHandler) {
this.detachExternalChangeHandler();
}
}
onExternalChange = value => {
this.setState({ value });
};
onChange = e => {
const value = e.currentTarget.value;
this.setState({ value });
if (value) {
this.props.sdk.field.setValue(value);
} else {
this.props.sdk.field.removeValue();
}
};
onButtonClick = async () => {
console.log('hurray');
};
render() {
return (
<Button buttonType="primary" isFullWidth={false}
onClick={this.onButtonClick}>
Add Content from AEM DAM
</Button>
);
}
}
Ideally i am trying to create an UI extension to be used in contentful space. I downloaded the contentful SDK and i have put in a button. But I receive this error on the console and it doesn't work
Screenshot:
https://github.com/contentful/create-contentful-extension
Go to the content of this Content Type and enable mixed content at
your browser so that development version that is served from your
local machine could be rendered within https://app.contentful.com.
Better yet:
I'm not the biggest fan of disabling the mixed content setting in browsers. Can I use HTTPS in development mode?
Yes, you can serve your extension using HTTPS. Add --https to start command.
"start": "contentful-extension-scripts start --https",
It uses Parcel HTTPS under the hood , which generates a self-signed certificate, you might have to configure your browser to allow self-signed certificates for localhost.
I think that will fix the 404 error and get things working.
Please follow the readme carefully and post a separate question if you still have problems.