I have to encrypt a credit card using https://unpkg.com/browse/openpgp#5.4.0/dist/
Then I import it as a module, cc processors code not mine.
<script type="module">
import * as openpgp from './js/openpgp.min.mjs';
import { createMessage, encrypt, readKey } from 'openpgp'
// Object to be encrypted
interface CardDetails {
number?: string, // required when storing card details
cvv?: string // required when cardVerification is set to cvv
}
// Encrypted result
interface EncryptedValue {
encryptedData: string,
keyId: string
}
const pciEncryptionKey = await getPCIPublicKey()
/**
* Encrypt card data function
*/
return async function(dataToEncrypt: CardDetails): Promise<EncryptedValue> {
const decodedPublicKey = await readKey({ armoredKey: atob(publicKey) })
const message = await createMessage({ text: JSON.stringify(dataToEncrypt) })
return encrypt({
message,
encryptionKeys: decodedPublicKey,
}).then((ciphertext) => {
return {
encryptedMessage: btoa(ciphertext),
keyId,
}
})
}
</script>
This code needs to be invoked when the user click a submit button and then inject the encrypted card into a hidden field.
Any ideas on how to get this to work?
Related
I am working on a smart contract interaction, I started writing the contract called BuyCoffee:
contract BuyCoffee {
struct Author {
address wallet_address;
uint256 balance;
}
struct Coffee {
address author_wallet_address;
address sender_wallet_address;
uint256 value;
}
Coffee[] public coffee;
mapping (address => Author) public authors;
uint256 public total_contract_balance;
function getContractBalance() public view returns (uint256) {
return total_contract_balance;
}
...
function buymeacoffee(address author_wallet_address, address sender_wallet_address, uint256 value) public {
coffee.push(Coffee({
author_wallet_address: author_wallet_address,
sender_wallet_address: sender_wallet_address,
value: value
}));
authors[author_wallet_address].balance += value;
total_contract_balance += value;
}
}
For the frontend app, I am using the library Ether and building a metamask_controller.js
import { Controller } from "#hotwired/stimulus";
import { ethers } from "ethers";
import abi from "../contract.json" assert { type: "json" };
export default class extends Controller {
...
async buyCoffee() {
try {
const authorWalletAddress = "0x5FF0...f16F3D2";
const senderWalletAddress = userAddress;
const price = this.priceTarget.innerText;
console.log("price: ", price);
console.log("authorWalletAddress: ", authorWalletAddress);
console.log("senderWalletAddress: ", senderWalletAddress);
// execute transaction
const transaction = await contract.buymeacoffee(
authorWalletAddress,
senderWalletAddress,
{ value: ethers.utils.parseEther(price)}
);
// console.log("Processing...", transaction.hash);
await transaction.wait();
// reload the whole page
window.location.reload();
}
catch (error) {
console.log(error);
alert("Transaction failed!");
}
}
But look like that I am having an issue when calling the buyCoffee function, the console log show me the correct values:
price: 0.001
authorWalletAddress: 0x5FF....F3D2
senderWalletAddress: 0x7da....1721
Then I get the error invalid BigNumber value (argument="value", value={"value":{"type":"BigNumber","hex":"0x038d7ea4c68000"}}, code=INVALID_ARGUMENT, version=bignumber/5.7.0)
How could I solve that? Look like is something about the price that has to change from 0.001 to big number
We always pass values as string:
// you could use BN too
{ value: ethers.utils.parseEther(price.toString())}
I have been taking online course from udemy and playing around with the guard middleware
I also created the admin.guard auth.guard suggested by following the tutorial but I am thinking what if I want to add an isAuthor.guard that not only the admin can make changes to post or whatever but the original author is also able to make edits...
What would be a better way to create this? Should it be a guard? or middleware would be better?
P.S. I tried accessing services through guard with this post Inject service into guard in Nest.JS but didn't work for me.
Edit: Also, is it possible to have or guards?
For example isAdmin / isAuthor so it can be used flexible instead of having a isAdminOrAuthor
Thanks in advance for any suggestions / advices.
I do not know if it is the best way, but this one is one that seems practical (it is applicable to the larger scope than just isAdmin/isAuthor case). NOTE: If only isAdmin isAuthor case is needed, please move appropriate logic from PostRelationResolver up to RolesGuard and skip a whole generic approach.
A generic approach is provided here because it allows covering a far, far wider range of cases, that are of the same nature (there are users and any specific entity - relationship-based restriction needs to be applied).
So, to cover it.
Suppose that reading posts (just as an example) is restricted in a way that the Admin can see all of them and the author can see only their own posts.
It can be implemented like this:
#Get('read-post/:postId')
#UseGuards(RolesGuard)
#SetMetadata('general-roles', [GeneralRole.ADMIN])
#SetMetadata('relation-roles', [RelationRole.POST_AUTHOR])
readPostAsAuthor(
#Param('postId') postId: number,
) {
return this.postRepository.findPostById(postId);
}
And for a listing of posts, something like this:
#Get('read-all-posts')
async readAllPosts(
#Req() request
) {
const posts = await this.postRepository.findAll();
return this.rbacService.filterList(
request,
posts,
[GeneralRole.ADMIN],
[RelationRole.POST_AUTHOR]
);
}
Note for listing filter: One should make sure that implementation doesn't even respond with unallowed posts and this filter should be only utilized as backup (since the request doesn't contain enough information to restrict call).
For this to work, RolesGuard implementation is needed:
import { CanActivate, ExecutionContext, Injectable } from "#nestjs/common";
import { Reflector } from "#nestjs/core";
import { GeneralRole } from "../role/general-role";
import { RelationRole } from "../role/relation-role";
import { RbacService } from "../rbac.service";
#Injectable()
export class RolesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private rbacService: RbacService,
) {
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const contextHandler = context.getHandler();
const request = context.switchToHttp().getRequest();
const requestedGeneralRoles = this.reflector.get<GeneralRole[]>('general-roles', contextHandler);
const requestedRelationRoles = this.reflector.get<RelationRole[]>('relation-roles', contextHandler);
return this.rbacService.authorize(request, requestedGeneralRoles, requestedRelationRoles);
}
}
The logic for actual authorization is contained in rbacService, given here:
import { Injectable } from "#nestjs/common";
import { GeneralRole } from "./role/general-role";
import { RelationRole } from "./role/relation-role";
import { UserRepository } from "./repository/user.repository";
import { CoreRelationResolver } from "./relation-resolver/core.relation-resolver";
#Injectable()
export class RbacService {
constructor(
private userRepository: UserRepository,
private coreRelationResolver: CoreRelationResolver,
) {
}
// NOTE: This method should be implemented however token to user mapping is done - based on business requirement.
async getUserByToken(token: string) {
return await this.userRepository.findByToken(token);
}
async authorize(request: any, requestedGeneralRoles: GeneralRole[], requestedRelationRoles: RelationRole[]) {
const user = await this.getUserByToken(request.headers['token']);
if (!user) {
return false;
}
if (requestedGeneralRoles && requestedGeneralRoles.indexOf(user.role) !== -1) {
// If user is of general role, it is simply allowed - regardless of relationRoles.
return true;
}
// Relation roles handling (user is not ADMIN - for example - but is author of post)
if (requestedRelationRoles) {
const relationRoles = await this.coreRelationResolver.getRelationRoles(user, requestedRelationRoles, request);
return this.isAllowed(requestedRelationRoles, relationRoles);
}
return false;
}
isAllowed(requestedRelationRoles: RelationRole[], containingRelationRoles: RelationRole[]) {
const matches = containingRelationRoles.filter(sr => {
return !!requestedRelationRoles.find(rr => rr === sr);
});
return !!matches.length;
}
async filterList(
request: any,
entities: any[],
requestedGeneralRoles: GeneralRole[],
requestedRelationRoles: RelationRole[]
): Promise<any[]> {
const user = await this.getUserByToken(request.headers['token']);
if (!user) {
return [];
}
if (requestedGeneralRoles && requestedGeneralRoles.indexOf(user.role) !== -1) {
return entities;
}
const result = [];
const relationResolver = await this.coreRelationResolver.findRelationResolver(requestedRelationRoles);
for (const entity of entities) {
const singleEntityRelations = await relationResolver.getRelations(user, entity);
if (this.isAllowed(requestedRelationRoles, singleEntityRelations)) {
result.push(entity);
} else {
console.warn("WARNING: Check next entity and query that responds with it. It shouldn't be here!");
console.warn(entity);
}
}
return result;
}
}
Allow me to provide a small description here before proceeding with the rest of the logic.
Authorization logic stops in RbacService.
CoreRelationResolver service is all about recognizing relations between the User that utilizes the application (makes a request) and the entity that is an object of the given operation (upon which operation is executed).
Possible relations between Users and specific entities are described with RelationalRoles. With RelationalRoles restriction is defined as: "only AUTHOR and COLLABORATOR of given Post can see it".
CoreRelationResolver implementation is provided here:
import { Injectable } from "#nestjs/common";
import { RelationRole } from "../role/relation-role";
import { IRelationResolver } from "./i-relation-resolver";
import { PostRelationResolver } from "./post.relation-resolver";
import { UserEntity } from "../entity/user.entity";
import { ClientAppRelationResolver } from "./client-app.relation-resolver";
#Injectable()
export class CoreRelationResolver {
private relationResolvers: IRelationResolver<UserEntity, unknown>[];
constructor(
private postRelationAuthorization: PostRelationResolver,
private clientAppRelationResolver: ClientAppRelationResolver,
) {
this.relationResolvers = [
this.postRelationAuthorization,
this.clientAppRelationResolver,
];
}
async getRelationRoles(user: UserEntity, requiredRelations: RelationRole[], request: any): Promise<RelationRole[]> {
let relationRoles = [];
const relationResolver = await this.findRelationResolver(requiredRelations);
if (relationResolver) {
const relatedObject = await relationResolver.getRelatedObject(request);
if (relatedObject) {
relationRoles = await relationResolver.getRelations(user, relatedObject);
}
}
return relationRoles;
}
async findRelationResolver(requiredRelations: RelationRole[]): Promise<IRelationResolver<UserEntity, unknown>> {
let result = null;
for (const relationResolver of this.relationResolvers) {
const supportedRelations = await relationResolver.getSupportedRelations();
const matches = supportedRelations.filter(sr => {
return !!requiredRelations.find(rr => rr === sr);
});
if (matches.length) {
result = relationResolver;
break;
}
}
return result;
}
}
It is designed in a way that in its constructor any RelationResolver (IRelationResolver interface) should be registered and properly implemented.
IRelationResolver interface:
import { RelationRole } from "../role/relation-role";
/**
* T - Type of user
* U - Type of relatedObject
*/
export interface IRelationResolver<T, U> {
/**
* Return RelationRoles that this resolver is responsible to handle.
*/
getSupportedRelations(): Promise<RelationRole[]>;
/**
* Retrieve related object from the request data.
*/
getRelatedObject(request: any): Promise<U>;
/**
* Calculate and provide relation between user and related object.
*/
getRelations(user: T, relatedObject: U): Promise<RelationRole[]>;
}
And finally, retrieving the related object and recognizing the relation between the user and the given object, is implemented here:
import { IRelationResolver } from "./i-relation-resolver";
import { Injectable } from "#nestjs/common";
import { RelationRole } from "../role/relation-role";
import { UserEntity } from "../entity/user.entity";
import { PostEntity } from "../entity/post.entity";
import { PostRepository } from "../repository/post.repository";
#Injectable()
export class PostRelationResolver implements IRelationResolver<UserEntity, PostEntity> {
constructor(
private postRepository: PostRepository
) {
}
async getSupportedRelations(): Promise<RelationRole[]> {
return [RelationRole.POST_AUTHOR];
}
async getRelatedObject(request: any): Promise<PostEntity> {
const postId: string = request.params.postId;
return await this.postRepository.findPostById(parseInt(postId));
}
async getRelations(user: UserEntity, relatedObject: PostEntity): Promise<RelationRole[]> {
const relations = [];
if (relatedObject.authorId === user.id) {
relations.push(RelationRole.POST_AUTHOR);
}
return relations;
}
}
Obviously, freedom is to implement here whatever is needed and however the relationship is defined.
For all next RBAC cases (for different entity types), one should create RelationResolver, implement it, and register it in the constructor of CoreRelationResolver.
All in all, considering the usability range, this approach should be flexible enough to be applied to many RBAC scenarios (and please consider it conceptual - there are no robustness features added).
I have a js object in which I return my endpoint addresses from api. This is a very nice solution for me, it looks like this:
export const API_BASE_URL = 'http://localhost:3000';
export const USERS = '/Users';
export default {
users: {
checkEmail: (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`,
notifications: `${API_BASE_URL}${USERS}/notifications`,
messages: `${API_BASE_URL}${USERS}/messages`,
},
};
Now I can call this address in my redux-saga to execute the xhr query:
import { api } from 'utils';
const requestURL = api.users.notifications;
But I'm a bit stuck because now I have a problem - base path is missing here: '/users'.
Now when I call api.users, then I get a object. I would like to have a default value after calling the object like:
import { api } from 'utils';
const requestURL = api.users; // http://localhost:3000/Users
const requestURL2 = api.users.notifications; // http://localhost:3000/Users/notifications
I know that I could add a new string with the name 'base' to the object and add '/Users' there, but I don't like this solution and I think, there is a better solution.
You could do one of the following:
extend the String class
const API_BASE_URL = "http://localhost:3000"
const USERS = "/Users"
class UsersEndpoints extends String {
constructor(base) {
super(base)
}
// this is still a proposal at stage 3 to declare instance variables like this
// if u want a truly es6 way you can move them to the constructor
checkEmail = (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`
notifications = `${API_BASE_URL}${USERS}/notifications`
messages = `${API_BASE_URL}${USERS}/messages`
}
// you can use userEndpoints itself as a string everywhere a string is expected
const userEndpoints = new UsersEndpoints(API_BASE_URL)
export default {
users: userEndpoints
}
The previous is just actually equivalent to
...
const userEndpoints = new String(API_BASE_URL)
userEndpoints.notifications = `${API_BASE_URL}${USERS}/notifications`
...
Obviously this is not recommended: you should not extend native classes, there are many disadvantages to this approach.
An obvious example is that there could be a conflict between the properties you use and the properties that might be brought by the native class
override the toString method
...
export default {
users: {
checkEmail: (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`,
notifications: `${API_BASE_URL}${USERS}/notifications`,
messages: `${API_BASE_URL}${USERS}/messages`,
toString: () => API_BASE_URL
},
};
// this is actually not much different than the previous method, since a String is an objet with an overridden toString method.
// That said this method is also not recommended since toString is used in many places in native code, and overriding it just to substitute a string value will make information get lost in such places, error stacks for example
Achieve what u want using the language features intended for such a use case
What you are asking is to make the same variable to have different values in the same time, which is not possible in the language syntax, and it makes sense because it makes it hard to reason about code.
that being said i recommend something of the following nature
// it is also better to use named exports
export const getUsersEndpoint = ({
path = "",
dynamicEndpointPayload = {},
} = {}) => {
switch (path) {
case "notifications":
return `${API_BASE_URL}${USERS}/notifications`
case "messages":
return `${API_BASE_URL}${USERS}/messages`
case "checkEmail":
return `${API_BASE_URL}${USERS}/${dynamicEndpointPayload.email}/checkEmail`
// you still can do checkEmail like this, but the previous is more consistent
// case "checkEmail":
// return (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`
default:
return `${API_BASE_URL}`
}
}
// you can use it like this
getUsersEndpoint() // returns the base
getUsersEndpoint({path: 'notifications'})
You can extend prototype to achieve this behaviour:
export const API_BASE_URL = 'http://localhost:3000';
export const USERS = '/Users';
const users = `${API_BASE_URL}${USERS}`
const baseUrls = {
checkEmail: (email) => `${users}/${email}/checkEmail`,
notifications: `${users}/notifications`,
messages: `${users}/messages`,
}
Object.setPrototypeOf(users.__proto__, baseUrls);
export default {
users
};
Try having object will all user endpoint and a function that return a value of a end point
const user = {
default: '/users',
notification: '/notification',
profile: '/profile',
getEndPoint(prop) {
if(this[prop] === 'default' ){
return this[prop];
} else {
if(this[prop]) {
return this.default + this[prop];
}
}
}
}
So you can have more end points that come under user and you can simply call
const requestURL = api.user.getEndPoint('default'); // http://localhost:3000/Users
const requestURL2 = api.user.getEndPoint('notifications'); // http://localhost:3000/Users/notification
So I have major problem with importing from one class to another and what I have done is in my "main" class which I call
detailsPage.js
import { DetailsPage } from '../tests/detailsPageObj';
const utils = require("../utils/utils");
const assert = require('../node_modules/chai').assert;
const userData = require('../globalContent.json');
describe('Details page', function () {
const detailsPage = new DetailsPage();
// The details page is accessible by the specified URL
it(`Is defined by the URL: ${userData.url}`, async function () {
await detailsPage.navigate();
});
// Details page has a form and it can be filled out with user data
it('Has a form that can receive user data', async function () {
await detailsPage.fillFormWithUserData(); // If you want, make the user data passable to the method
await utils.click(detailsPage.form.buttons.nextStep);
});
if (detailsPage.hasStockConflict) {
// Details page allows the user to fix conflicts in stocks
it('Enables resolution of stock conflicts', async function () {
// Wait for stock to fully load
await browser.sleep(2000);
await detailsPage.clickAllRemoveButtons();
await detailsPage.clickAllDecreaseButtons();
});
}
// Details page allows the user to proceed to the next stage when all conflicts (if any) has been resolved
it('Allows the user to proceed to the next stage of purchasing', async function () {
const nextStepButton = detailsPage.form.buttons.nextStep;
await utils.elementToBeClickable(nextStepButton);
await utils.click(nextStepButton);
});
});
and what I am trying tod o is to get DetailsPage from another script which is called:
detailsPageObj
import { element, by } from 'protractor';
const utils = require("../utils/utils");
const userData = require('../globalContent.json');
export class DetailsPage {
get pageUtils() {
return {
qtyRegex: /^Sorry.*?(\d+)/
}
}
private get fields() {
return {
email: element(by.id('email')),
firstName: element(by.id('firstName')),
lastName: element(by.id('lastName')),
postalCode: element(by.id('postalCode')),
addressOne: element(by.id('addressOne')),
addressTwo: element(by.id('addressTwo')),
phone: element(by.id('phone')),
businessCustomerCB: element(by.id('isBusinessCustomer')),
company: element(by.id('company')),
GST: element(by.id('gst')),
}
}
private get groups() {
return {
address: element(by.css('div#addressGroup.input-container.showHiddenGroup'));
company: element(by.id('companyGroup')),
}
}
private get modals() {
return {
contactModalLink: element(by.id('contactModalLink')),
cross: element(by.className('modal-cross')),
}
}
private get formButtons() {
return {
nextStep: element(by.id('submitIdentityFormButton')),
mobile: this.mobileFormButtons
}
}
private get mobileFormButtons() {
return {
continue: element(by.id('stock-conflict-continue-button')),
removeOutOfStockItems: element(by.css('button[id="removeOutOfStockItems"]')), // I just assumed that this is a part of the form
}
}
private get productFrameMobileButtons() {
return {
stockControll: element.all(by.className('stock-controller mobile')),
remove: element.all(by.className('btn btn-remove btn-outlined mobile')),
}
}
private get productFrameDesktopButtons() {
return {
stockControll: element.all(by.className('stock-controller desktop')),
remove: element.all(by.className('btn btn-remove btn-outlined desktop')),
}
}
get form() {
return {
fields: this.fields,
groups: this.groups,
buttons: this.formButtons,
modals: this.modals
}
}
get productFrame() {
return {
buttons: {
decrease: element.all(by.className("btn left")).first(),
mobile: this.productFrameMobileButtons,
desktop: this.productFrameDesktopButtons
}
}
}
get errors() {
return {
stockConflict: element(by.className('generic-error-heading')),
}
}
}
and what I am trying to do is in detailsPage.js im trying to import detailsPageObj.js but whenever I am trying to do it I do get SyntaxError: Cannot use import statement outside a module.
What am I doing wrong
I don't know what is your environment like, but I experienced a similar problem where my environment used a full build step for creating the target JS code from my sources (e.g. from TypeScript or from ES6+) to a bundled/plain JS.
But then my test environment did not have any build step. So when I executed the tests, it only understood plain JS, which by default in a node.js environment does not recognize import but only require.
You can use import in your node.js code without a build step, but you need to follow some steps, e.g. rename your file from *.js to *.mjs. More details here.
I have been working on a chat app using Gifted-Chat and a Firebase RealTime database (and running it with Expo). At this point, the basic messaging works, but I am trying to enable to app to load earlier messages when the user scrolls up and hits the button that appears (I am aware of the GiftedChat prop for this). Unfortunately, I have been having trouble doing this and am a bit stumped.
There are two separate problems I have been running up against that I am aware of.
Clicking the loadEarlier button gives me an undefined is not a function (near '...this.setState...' runtime error (clearly, something is wrong with the skeleton function I put there).
The bigger issues is that I am still not clear on how to download the n number of messages before the oldest messages currently loaded. I have looked at the GiftedChat example and this post for help, but must confess that I am still lost (the best I can figure is that I need to sort the messages, possibly by timestamp, somehow get the right range, then parse them and prepend them to the messages array in state, but I cannot figure out how to do this, especially the later parts).
The relevant parts of the code for my chat screen are below, as is a screenshot of the structure of my firebase database. I would appreciate any help regarding both of these issues.
// Your run of the mill React-Native imports.
import React, { Component } from 'react';
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
import * as firebase from 'firebase';
// Our custom components.
import { Input } from '../components/Input';
import { Button } from '../components/Button';
import { BotButton } from '../components/BotButton';
// Array of potential bot responses. Might be a fancy schmancy Markov
// chain like thing in the future.
import {botResponses} from '../Constants.js';
// Gifted-chat import. The library takes care of fun stuff like
// rendering message bubbles and having a message composer.
import { GiftedChat } from 'react-native-gifted-chat';
// To keep keyboard from covering up text input.
import { KeyboardAvoidingView } from 'react-native';
// Because keyboard avoiding behavior is platform specific.
import {Platform} from 'react-native';
console.disableYellowBox = true;
class Chat extends Component {
state = {
messages: [],
isLoadingEarlier: false,
};
// Reference to where in Firebase DB messages will be stored.
get ref() {
return firebase.database().ref('messages');
}
onLoadEarlier() {
this.setState((previousState) => {
return {
isLoadingEarlier: true,
};
});
console.log(this.state.isLoadingEarlier)
this.setState((previousState) => {
return {
isLoadingEarlier: false,
};
});
}
// Get last 20 messages, any incoming messages, and send them to parse.
on = callback =>
this.ref
.limitToLast(20)
.on('child_added', snapshot => callback(this.parse(snapshot)));
parse = snapshot => {
// Return whatever is associated with snapshot.
const { timestamp: numberStamp, text, user } = snapshot.val();
const { key: _id } = snapshot;
// Convert timestamp to JS date object.
const timestamp = new Date(numberStamp);
// Create object for Gifted Chat. id is unique.
const message = {
_id,
timestamp,
text,
user,
};
return message;
};
// To unsubscribe from database
off() {
this.ref.off();
}
// Helper function to get user UID.
get uid() {
return (firebase.auth().currentUser || {}).uid;
}
// Get timestamp for saving messages.
get timestamp() {
return firebase.database.ServerValue.TIMESTAMP;
}
// Helper function that takes array of messages and prepares all of
// them to be sent.
send = messages => {
for (let i = 0; i < messages.length; i++) {
const { text, user } = messages[i];
const message = {
text,
user,
timestamp: this.timestamp,
};
this.append(message);
}
};
// Save message objects. Actually sends them to server.
append = message => this.ref.push(message);
// When we open the chat, start looking for messages.
componentDidMount() {
this.on(message =>
this.setState(previousState => ({
messages: GiftedChat.append(previousState.messages, message),
}))
);
}
get user() {
// Return name and UID for GiftedChat to parse
return {
name: this.props.navigation.state.params.name,
_id: this.uid,
};
}
// Unsubscribe when we close the chat screen.
componentWillUnmount() {
this.off();
}
render() {
return (
<View>
<GiftedChat
loadEarlier={true}
onLoadEarlier={this.onLoadEarlier}
isLoadingEarlier={this.state.isLoadingEarlier}
messages={this.state.messages}
onSend={this.send}
user={this.user}
/>
</View>
);
}
}
export default Chat;
For your first issue, you should declare your onLoadEarlier with => function so as to get the current instance this i.e. your code should look like below:
onLoadEarlier = () => {
this.setState((previousState) => {
return {
isLoadingEarlier: true,
};
}, () => {
console.log(this.state.isLoadingEarlier)
this.setState((previousState) => {
return {
isLoadingEarlier: false,
};
});
});
}
Also, setState is asynchronous in nature, so you should rather depend on the second parameter of the setState i.e. the callback to ensure that the next lines of code execute synchronously.
Lastly, if you are using class syntax then you should declare the state in constructor like below:
class Chat extends Component {
constructor (props) {
super (props);
state = {
messages: [],
isLoadingEarlier: false,
};
}
......
onLoadEarlier = () => {
this.setState((previousState) => {
return {
isLoadingEarlier: true,
};
}, () => {
console.log(this.state.isLoadingEarlier)
this.setState((previousState) => {
return {
isLoadingEarlier: false,
};
});
});
}
...
}
For loading the last messages from firebase , I recommend using limitToLast function on your reference. You should afterwards order the results by date before calling append in gifted chat.
For the second question, it should be the same with this question How Firebase on and once differ?
You can using filter feature in Firebase for example using createdAt field to compare with last loaded message to load more.