Watermelon Sync With Firebase - javascript

I'm wanting to use watermelon sync with Firestore but I'm not getting it. I do not even know where to begin with. I'm not using API. I want to do it only in React Native. I want to sync my app offline and online. Can someone help me??
Im using React Native to do That...
import { synchronize } from '#nozbe/watermelondb/sync'
async function mySync() {
await synchronize({
database,
pullChanges: async ({ lastPulledAt, schemaVersion, migration }) => {
const urlParams = `last_pulled_at=${lastPulledAt}&schema_version=${schemaVersion}&migration=${encodeURIComponent(JSON.stringify(migration))}`
const response = await fetch(`https://my.backend/sync?${urlParams}`)
if (!response.ok) {
throw new Error(await response.text())
}
const { changes, timestamp } = await response.json()
return { changes, timestamp }
},
pushChanges: async ({ changes, lastPulledAt }) => {
const response = await fetch(`https://my.backend/sync?last_pulled_at=${lastPulledAt}`, {
method: 'POST',
body: JSON.stringify(changes)
})
if (!response.ok) {
throw new Error(await response.text())
}
},
migrationsEnabledAtVersion: 1,
})
}
the example above is the code shown on the Watermelon website. But I want to do it without using API! Only with React Native and Firestore/firebase. How could I do this in react Native, and whenever there is any change in the application it automatically saves it in the database when the user is connected to the internet? My app is Offline Frist

There are some packages that helps with syncing react native to firebase and you won't need to build your own backend server and apis. To quote from the watermelondb docs.
MelonFire, a React Native library to sync your database to Firestore. MelonFire overcomes common bugs in implementations (e.g. timestamp jitter, multiple writers, Firestore's 500-write transaction limit, retries) to guarantee database consistency.
Firemelon, an alternative implementation to sync your database to Firestore. It relies on changes being smaller than Firestore's 500-write transaction limit, and doesn't handle server timestamp intricacies, but supports ignoring certain tables when backing up.
Hope this helps.

Related

onError Authentication with refresh token

In the Apollographql documentation it states:
The onError link can retry a failed operation based on the type of GraphQL error that's returned. For example, when using token-based authentication, you might want to automatically handle re-authentication when the token expires.
This is followed up by their sample code:
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
// Apollo Server sets code to UNAUTHENTICATED
// when an AuthenticationError is thrown in a resolver
case "UNAUTHENTICATED":
// Modify the operation context with a new token
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: getNewToken(),
},
});
// Retry the request, returning the new observable
return forward(operation);
}
}
}
// To retry on network errors, we recommend the RetryLink
// instead of the onError link. This just logs the error.
if (networkError) {
console.log(`[Network error]: ${networkError}`);
}
});
My question is in regards to the getNewToken(), as no code was provided for this function, I want to know (assuming this is another request to the backend and I am not sure how it could not be), if you are able to and or supposed to use query/mutation in graphql or make the request through axios for example.
One problem, if it can/should be a graphql query or mutation, is to get the new token, the onError code is defined in the same file as the ApolloClient as ApolloClient needs access to onError, thus when trying to implement this as retrieving a new token through a graphql mutation I got the following error:
React Hook "useApolloClient" is called in function "refresh" that is
neither a React function component nor a custom React Hook function.
After trying to useQuery/useMutation hook and realizing I cannot outside of a react component and at the top level I found this post whose answers suggested you can use useApolloClient.mutate instead but I still ran into issues. My code was (and tried multiple iterations of this same code like useApolloClient() outside of the function and inside etc.):
const refresh = () => {
const client = useApolloClient();
const refreshFunc = () => {
client
.mutate({ mutation: GET_NEW_TOKEN })
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
};
refreshFunc();
};
I could capitalize Refresh but this still would not work and would break the rules of hooks.
And to clarify all the above would do is I would replace the console.logs with setting session storage to the retrieved new token and then re trying the original request with onError.
Now in another post I found when looking into this, the users getNewToken request was a rest request using axios:
const getNewToken = async () => {
try {
const { data } = await axios.post(
"https://xxx/api/v2/refresh",
{ token: localStorage.getItem("refreshToken") }
);
localStorage.setItem("refreshToken", data.refresh_token);
return data.access_token;
} catch (error) {
console.log(error);
}
};
Now from my understanding, if I wanted to implement it this way I would have to change my backend to include express as I am only using apolloserver. Now I could definitely be wrong about that as my backend knowledge is quite limited and would love to be corrected their.
So my question is, what is the best way to do this, whether natively using graphql queries/mutations (if possible), doing it with axios, or maybe their is another best practice for this seemingly common task I am unaware of.

Is there a better way of implementing this without multiple fetch request?

This is login component from a react application and I am trying to do a simple authentication login with Firebase as my real time database.
My first approach is to execute a fetch request (GET) to see if there is any existing user. Afterwhich, if the password do match with the user, I want to update the "isLoggedIn" field to true. In order to achieve the following, another fetch request (PATCH) was made.
Therefore, I am wondering if it is a bad practice to have multiple fetch request in one function and would like to know if there is a more efficient or better way of implementing this simple application?
const loginHandler = async (userName, password) => {
const url = `insert_url_link_here/users/${userName}.json`;
try {
const response = await fetch(url);
const user = await response.json();
if (user.password === password) {
await fetch(url, {
method: "PATCH",
body: JSON.stringify({ isLoggedIn: true }),
});
}
} catch (error) {
console.log(error);
}
This is the schema of my database.
--users:
eric:
isLoggedIn: false
password: "321"
test:
isLoggedIn: false
password: "222"
You're accessing the Firebase Realtime Database through its REST API, which implements simple CRUD operations, and there's no way to combine a read and a write operation into a single operation there.
The only other relevant operation is the conditional write, which you can use to only write data if it hasn't been modified since you read it. You can use this to implement an optimistic compare-and-set like transaction mechanism, but given that you're toggling a boolean it doesn't seem to apply here.
If you're on an environment that supports it, you may want to consider using the Firebase SDK for that platform, as the SDKs typically use a web socket to communicate with the server, which often ends up being a lot more efficient than performing multiple individual HTTP requests.
I suggest using a Firebase client implementation and looking at the Firebase documentation. There are well described and explicit examples in various languages:
https://firebase.google.com/docs/auth/web/start
Besides, I don't see the point of storing passwords or login state on your side since Firebase Auth already does this for you in a secure way.

Create multiple Firebase Instances for the same project in Node.js

I have a Node.js server, inside which I want to have two firebase instances.
One instance should use the JavaScript SDK and will be used to provide authentication - login/register. The other instance should use the Admin SDK and will be used to read/write from the Realtime Database. I want to use this approach, so that I don't have to authenticate the user before each request to the Realtime DB.
I've read how we're supposed to initialize Firebase instances for multiple projects, but I'm not sure if my issue isn't coming from the fact that both instances are for the same project.
My issue is that I can use the JS SDK without any issue and I can login/register the user, but for some reason I can't get the Admin SDK to work.
Here's how I'm instantiating the apps:
const admin = require("firebase-admin");
const { applicationDefault } = require('firebase-admin/app');
admin.initializeApp({
credential: applicationDefault(),
databaseURL: 'my-database-url'
}, 'adminApp');
const firebase = require("firebase/app");
firebase.initializeApp(my-config);
Now I can use the JS SDK without an issue, but not the Admin SDK. I've created a test endpoint to just get data from my Realtime DB:
app.get("/api/test", (req, res) => {
const uid = 'my-user-UID';
admin.database().ref(`users/${uid}`)
.once('value', (snapshot) => {
if(snapshot) {
console.log('data');
} else {
console.log('no data');
}
});
});
Now here as an approach to getting the data from the Realtime DB, I tried all possible described approaches. Using get with child and all sorts of possible combinations. Here's an example of another approach I used:
get(child(ref(admin.database()), `users/${uid}`)).then((snapshot) => {
if (snapshot.exists()) {
// retrieved data
} else {
// No data
}
}).catch((error) => {
console.error(error);
});
For the first approach I wasn't getting any response at all, like the once wasn't executing. For the second one I think I was getting - typeerror: pathstring.replace is not a function firebase. At some point I was getting a no firebase app '[default]' has been created . These errors don't worry me as much, but since I saw the last error I moved my focus to the initialization of the apps, but still to no avail.
I just need a direction of where my issue might be coming from.
Update:
The solution is to not pass a second argument (app name) to any of the Firebase initializations. Looks like it's not needed in case you're referencing the same project.

Adyen Drop-in - how to pass unique order ID?

I have been trying to use the Adyen Drop-in component to make payments on the Razor pages site I am developing. I have got a test version running that makes a payment for a hard-coded amount but I have yet to figure out how to pass a unique order ID to my API endpoint making the payment request.
Taking the examples from https://docs.adyen.com/online-payments/drop-in-web, the drop-in component is mounted via JavaScript using
const checkout = new AdyenCheckout(configuration);
const dropin = checkout.create('dropin').mount('#dropin-container');
where the configuration object is created with something like
const configuration = {
paymentMethodsResponse: paymentMethodsResponse, // The `/paymentMethods` response from the server.
clientKey: "YOUR_CLIENT_KEY", // Web Drop-in versions before 3.10.1 use originKey instead of clientKey.
locale: "en-US",
environment: "test",
onSubmit: (state, dropin) => {
// Your function calling your server to make the `/payments` request
makePayment(state.data)
.then(response => {
if (response.action) {
// Drop-in handles the action object from the /payments response
dropin.handleAction(response.action);
} else {
// Your function to show the final result to the shopper
showFinalResult(response);
}
})
.catch(error => {
throw Error(error);
});
},
onAdditionalDetails: (state, dropin) => {
// Your function calling your server to make a `/payments/details` request
makeDetailsCall(state.data)
.then(response => {
if (response.action) {
// Drop-in handles the action object from the /payments response
dropin.handleAction(response.action);
} else {
// Your function to show the final result to the shopper
showFinalResult(response);
}
})
.catch(error => {
throw Error(error);
});
}
};
Adyen's own JavaScript then supplies the state object for the onSubmit method, so that my API endpoint gets called with a PaymentRequest object created (somehow) from the state.data.
However, without being able to get a unique order ID into this PaymentRequest object, my server-side code does not know what amount to set. Note that one can set an Amount object in the configuration object but this is just used to display the value on the Drop-in component - the value is not passed to the server.
So how does one pass a unique order ID via the Drop-in component?
The Adyen docs don't explicitly provide an example here, but the makePayment() and makeDetailsCall() presume that you will take the state.data and post back to your server. You need to implement your own code here. At that point, you could add additional information like any identifiers.
Here is an example implementation as a reference:
async function makePayment(state_data) {
const order_id = ""; // You need to provide this however your client stores it.
const json_data = {
order_id,
state_data,
};
const res = await fetch("[url to your server's endpoint]", {
method: "POST",
body: JSON.stringify(json_data),
headers: {
"Content-Type": "application/json",
},
});
return await res.json();
}
Another helpful resource could be the Adyen node.js/express tutorial. It is more explicit on implementation details so might help remove some ambiguity.

Firestore not reflecting changes in firestore after deployed

I have a web app written in NUXT that makes use of Firebase's Hosting, Firestore, Authentication and Storage.
Its a simple blog layout that has all the usual CRUD functions for its blog posts. It is loosely bases on Quick Nuxt.js SSR prototyping with Firebase Cloud Functions and Nuxt.js Firebase Auth.
In the development environment it runs perfectly but when I deploy it, the Firestore specifically, behaves unexpectedly.
So after the project has been deployed I can CRUD documents that reflect as expected in the Firebase Console Firestore viewer, but when I read the data again it will load the same data. In other words if I delete a document it will disappear in the Firestore viewer but when I refresh my NUXT website it loads that document again even though it's no longer present in the Firebase console. I get the same result on different computers/devices, so not a local caching issue.
I noticed that the changes in the Firestore viewer will only reflect in my website after I re-deploy my project. But any changes I make will not show after I refresh the website even though they have changed permanently in the Firestore viewer.
When in development it works perfectly, I can manipulate the database, refresh and it will load exactly what’s reflected in Firestore viewer.
Sorry for repeating it so much but I’m having an existential crisis here, lol.
So below is a sample of the NUXT's Store's index.js file, where you would have all your data stored for your app. It works perfectly at manipulating the data on Firestore but once in production the website gets served the same data over and over.
import { firestore } from '~/plugins/fireinit.js' // the part where `firebase.initializeApp` happens
Decare my array state: posts.
export const state = () => ({
posts: []
})
Mutations for manipulating the posts array.
export const mutations = {
addP (state, payload) { // Gets run for each documents from collection on first load.
state.posts.push(payload);
},
delP (state, payload) { // Deletes a post from the posts state.
state.posts = state.posts.filter(p => {
return p.id != payload.id;
});
},
}
The nuxtServerInit() runs on the server to make it Server Side Rendered when the website first loads.
export const actions = {
async nuxtServerInit({commit}, context) {
await firestore.collection('posts').get().then((querySnapshot) => {
querySnapshot.forEach(function(doc) {
var obj = doc.data()
obj.id = doc.id;
commit('posts/addP', obj)
})
})
},
The deletePost() action deletes a file on Firebase Storage then deletes the document on Firestore. Then finally removes the item from the posts state.
deletePost ({commit}, payload) {
storage.ref().child(payload.fullPath).delete().then(function() {
firestore.collection('posts').doc(payload.id).delete().then(()=>{
commit('delP', payload);
})
.catch((error)=>{
console.error(error);
});
})
}
}
This is what my Firestore Rules look like
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
allow write: if request.auth != null;
}
}
}
What am I doing wrong :/
So after losing some hair I finally figured it out!
So in NUXT you have two options for deploying your project, nuxt build or nuxt generate.
The generate option reads the database and then builds your static files from the firestore, which is then deployed. This is why when I reloaded my page it had all the old entried in the DB.
After switching to the build option and deploying that instead it all works perfectly.

Categories

Resources