Redux-offline : commit action never called - react-native

I'am trying to implement Redux-offline in my react-native app for this i have installed the module and i have added offline to my createStore method :
const store = createStore(
myReducer,
compose(
offline(offlineConfig),
applyMiddleware(thunk, promiseMiddleware())
)
);
this is the action that uses redux-offline :
export const addResources = resource => ({
type: "ADD_RESOURCES",
payload: resource,
meta: {
offline: {
// the network action to execute:
effect: {
url: "https://reqres.in/api/users",
method: "POST",
json: {
body: { name: "paul rudd", movies: ["Fake data", "Fake text"] }
}
},
// action to dispatch when effect succeeds:
commit: { type: "FOLLOW_ADD_RESOURCE", meta: { resource } },
// action to dispatch if network action fails permanently:
rollback: { type: "ADD_RESOURCE_ROLLBACK", meta: { resource } }
}
}
});
for the sake of explanation i'am using a sample dummy API that accepts the creation of new users and return an id as a response.
My problem is that the commit action never gets called after dispatching my ADD_RESOURCES action, on the other hand therollback gets called if i'am sending a bad request.
This is my reducer:
let tempList = state.list.concat();
switch (action.type) {
case ADD_RESOURCES:
console.log("add resources action");
return Object.assign({}, state, {
list: tempList
});
case FOLLOW_ADD_RESOURCE:
console.log(" *** FOLLOW_ADD_RESOURCE **", res);
return Object.assign({}, state, {
list: tempList
});
case ADD_RESOURCE_ROLLBACK:
console.log("ADD_RESOURCE_ROLLBACK");
return Object.assign({}, state, {
list: tempList
});
default:
return state;
}
PS: i'am testing this on Pixel 2 xl API 27 emulator, with and without wifi and 3G internet connection.
As i said the commit action never get dispatched, does anyone know what did i get wrong ?

Related

Store Ability in Express Session?

I have seen the express example, where an ability is stored via middleware in the req object. It then uses the following method to evaluate the permissions:
ForbiddenError.from(req.ability).throwUnlessCan('read', article);
I want to achieve a similar thing. My idea is to save the ability inside an express session that is shared with socket io websockets. Through the sharing req.session = socket.handshake.session. My approach is the following, I make a request from the frontend application to get rules to update the ability on the frontend. The backend saves the ability inside the express session:
// abilities.js file
import { Ability } from '#casl/ability';
export const defineAbilitiesFor = (rules) => {
return new Ability(rules);
};
export default defineAbilitiesFor;
// handler for express route to get permissions from the frontend
export const getPermissions = async (req, res) => {
...
rules.push({
action: ['view'],
subject: views,
});
// manage all own processes
rules.push({
action: ['manage'],
subject: 'Process',
conditions: {
userId: req.kauth.grant.access_token.content.sub,
},
});
// store ability in session
req.session.rules = defineAbilitiesFor(rules);
const token = jwt.sign({ token: packRules(rules) }, 'secret');
if (token) {
return res.status(200).json(token);
} else {
return res.status(400).json('Error');
}
...
Then when a websocket request happens, I want to check in the backend if the user has the permissions to do that action:
ForbiddenError.from(socket.handshake.session.rules).throwUnlessCan('view', 'Process');
However, this throws the following error:
TypeError: this.ability.relevantRuleFor is not a function
at ForbiddenError.throwUnlessCan
The session object seems to have the correct ability object. When I console.log socket.handshake.session.rules, I get the following output:
{
h: false,
l: {},
p: {},
'$': [
{ action: [Array], subject: 'Process', conditions: [Object] },
{ action: [Array], subject: [Array] },
{ action: [Array], subject: 'Process', conditions: [Object] }
],
m: {}
}
Also the can function and everything else I tried wasn't working. I think storing the plain rules as an object inside the session and then updating the ability class before each request would work, but I don't want to do that. I want to store the ability right inside the session, so that I only have to execute the throwUnlessCan or can functions.
Is this even possible and if so, how would you do this?
Thanks so far.
Instead of storing the whole Ability instance, you need to store only its rules! rules is a plain js array of objects, so it can be easily serialized.So, change the code to this:
export const getPermissions = async (req, res) => {
...
rules.push({
action: ['view'],
subject: views,
});
// manage all own processes
rules.push({
action: ['manage'],
subject: 'Process',
conditions: {
userId: req.kauth.grant.access_token.content.sub,
},
});
// store ability RULES in session
req.session.rules = rules;
const token = jwt.sign({
token: packRules(rules) // packRules accepts an array of RawRule! not an Ability instance
}, 'secret');
if (token) {
return res.status(200).json(token);
} else {
return res.status(400).json('Error');
}
To use Ability in other handlers add a middleware:
function defineAbility(req, res, next) {
if (req.session.rules) {
req.ability = new Ability(req.session.rules);
next();
} else {
// handle case when there is no rules in session yet
}
}
// later
app.get('/api/users', defineAbility, (req, res) => {
req.ability.can(...);
// or
ForbiddenError.from(req.ability).throwUnlessCan(...);
})

Cancelling upload request before destroying object makes mobx-state-tree throw Cannot modify [dead] errors

I have a React Native app where I want to upload some files using Axios.
I've made a mobx-state-tree store for file uploads, and each file has its own CancelTokenSource, which is sent to the Axios network call.
When an upload is in progress, I try to cancel the upload, and then destroy the item.
The simplest way is like I show below, by destroying the item in the store, and then have an beforeDestroy() hook that cancels the upload. But that approach makes mobx-state-tree show the error in the screenshot.
I've also tried calling the file.cancelTokenSource.cancel() explicitly before destroying the item. Same error. I suspect that the operation is not fully cancelled when the cancel() returns, but since it's not an async function, I cannot await its completion.
When I just call the cancel() without destroying, it cancels just fine, so I'm pretty sure that it's a timing issue, where the destroy(file) is called too soon, before cancel() has cleaned up after itself.
What to do here?
file-upload-store.ts
import { destroy, flow, Instance, types } from 'mobx-state-tree'
import { FileUpload, IFileUpload } from '../entities/file-upload/file-upload'
import { getApi } from '../../store-environment'
/**
* Store for handling the FileUpload
*/
export const FileUploadStore = types
.model('FileUploadStore')
.props({
files: types.array(FileUpload),
})
.actions((self) => {
const api = getApi(self)
const add = (uri: string, name: string, type: string, size: number) => {
const file = FileUpload.create({
uri,
name,
type,
size,
})
self.files.push(file)
upload(file)
}
const remove = (file: IFileUpload) => {
destroy(file)
}
const cancel = (file: IFileUpload) => {
// also tried this - with no luck
// file.cancelTokenSource.cancel()
destroy(file)
}
const upload = flow(function* (file: IFileUpload) {
file.status = 'pending'
file.uploadedBytes = 0
const { uri, name, type } = file
try {
const id = yield api.uploadFile(uri, name, type, file.setProgress, file.cancelTokenSource.token)
file.status = 'completed'
file.fileUploadId = id
} catch (error) {
file.status = 'failed'
file.error = error.message
}
})
return {
afterCreate() {
// Avoid persistance
self.files.clear()
},
remove,
cancel,
retry: upload,
add,
}
})
export type IFileUploadStore = Instance<typeof FileUploadStore>
file-upload.ts
import { Instance, SnapshotIn, types } from 'mobx-state-tree'
import { CancelToken } from 'apisauce'
/**
* FileUpload contains the particular data of a file, and some flags describing its status.
*/
export const FileUpload = types
.model('FileUpload')
.props({
name: types.string,
type: types.string,
uri: types.string,
size: types.number,
// set if an arror occours
error: types.maybe(types.string),
status: types.optional(types.enumeration(['pending', 'completed', 'failed']), 'pending'),
// updated by progressCallback
uploadedBytes: types.optional(types.number, 0),
// assigned when response from backend is received
fileUploadId: types.maybe(types.string),
})
.volatile(() => ({
cancelTokenSource: CancelToken.source(),
}))
.actions((self) => ({
setProgress(event: ProgressEvent) {
self.uploadedBytes = event.loaded
},
beforeDestroy() {
self.cancelTokenSource?.cancel()
},
}))
export interface IFileUpload extends Instance<typeof FileUpload> {}
// SnapshotIn, used for creating input to store: {Model}.create({})
export interface IFileUploadSnapshotIn extends SnapshotIn<typeof FileUpload> {}
You are destroying the FileUpload node and cancelling the axios request nicely, but cancelling the request will throw an error, so you need to make sure that your FileUpload node is still alive before you try to update it in the catch.
import { destroy, flow, Instance, types, isAlive } from 'mobx-state-tree'
// ...
const upload = flow(function* (file: IFileUpload) {
const { uri, name, type } = file
file.status = "pending"
file.uploadedBytes = 0
try {
const id = yield api.uploadFile(
uri,
name,
type,
file.setProgress,
file.cancelTokenSource.token
)
file.status = "completed"
file.fileUploadId = id
} catch (error) {
if (isAlive(file)) {
file.status = "failed"
file.error = error.message
}
}
})

Receive WebSockets data from vuex and Vue-native-websocket plugin

I am currently using the Quasar V1 framework which includes Vue and Vuex.
Today I was looking at this plugin:
https://www.npmjs.com/package/vue-native-websocket/v/2.0.6
I am unsure on how to setup this plugin and make it work and would require a little bit of help to make sure I am doing this right as it will be the first time I use WebSockets with Vue.
I have first installed vue-native-websocket via npm and created a boot file called src\boot\websocket.js
via this command:
npm install vue-native-websocket --save
websocket.js
import VueNativeSock from 'vue-native-websocket';
export default async ({ Vue }) => {
Vue.use(VueNativeSock, 'wss://echo.websocket.org', {
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 3000
});
};
In Quasar v1, I have then created a module called "websockets" in:
src\store\websockets
This module has:
actions.js
getters.js
index.js
mutations.js
state.js
I need to use the websocket with format: 'json' enabled
My question is:
Let's say I have a page where I would like my websocket connection to be created and receive the live data, shall I do this?
Code for the module:
websockets/mutations.js:
export function SOCKET_ONOPEN (state, event) {
let vm = this;
vm.prototype.$socket = event.currentTarget;
state.socket.isConnected = true;
}
export function SOCKET_ONCLOSE (state, event) {
state.socket.isConnected = false;
}
export function SOCKET_ONERROR (state, event) {
console.error(state, event);
}
// default handler called for all methods
export function SOCKET_ONMESSAGE (state, message) {
state.socket.message = message;
}
// mutations for reconnect methods
export function SOCKET_RECONNECT (state, count) {
console.info(state, count);
}
export function SOCKET_RECONNECT_ERROR (state) {
state.socket.reconnectError = true;
}
Code for the module:
websockets/state.js
export default {
socket: {
isConnected: false,
message: '',
reconnectError: false
}
};
But the issue now is in my vue page.
Let's say I would like to show only the data from the websocket that has a specific event, how do I call this from the vue page itself please? I am very confused on this part of the plugin.
What is very important for me to understand if how to separate the receive and send data.
ie: I may want to receive the list of many users
or I may want to receive a list of all the news
or I may add a new user to the database.
I keep hearing about channels and events and subscriptions......
From what I understand, you have to first subscribe to a channel(ie: wss://mywebsite.com/news), then listen for events, in this case I believe the events is simply the data flow from this channel).
If I am correct with the above, how to subscribe to a channel and listen for events with this plugin please, any idea?
If you had a very quick example, it would be great, thank you.
I have developed a chat application using Vue-native-websocket plugin. Here i am showing how you can register the pulgin in the vuex store and how to call it from your vue component.
Step 1: Define these methods in your index.js file
const connectWS = () => {
vm.$connect()
}
const disconnectWS = () => {
vm.$disconnect()
}
const sendMessageWS = (data) => {
if (!Vue.prototype.$socket) {
return
}
Vue.prototype.$socket.send(JSON.stringify(data))
}
Step 2: Write the socket state and mutations
SOCKET_ONOPEN (state, event) {
if (!state.socket.isConnected) {
Vue.prototype.$socket = event.currentTarget
state.socket.isConnected = true
let phone = state.config.selectedChatTicket.phone
sendMessageWS({type: WSMessageTypes.HANDSHAKE, data: {id: window.ACCOUNT_INFO.accId, phone: phone, agentId: USER_NAME}})
}
},
SOCKET_ONCLOSE (state, event) {
console.log('SOCKET_ONCLOSE', state, event)
state.socket.isConnected = false
Vue.prototype.$socket = null
},
// NOTE: Here you are getting the message from the socket connection
SOCKET_ONMESSAGE (state, message) {
state.data.chatCollection = updateChatCollection(state.data.chatCollection,message)
},
STEP 3 : Write Action, you can call it from your vue component
NOTE:: socket actions to connect and disconnect
WSConnect ({commit, state}) {
connectWS()
},
WSDisconnect ({commit, state}) {
disconnectWS()
},
STEP 4: Register the plugin in the end as it requires the store object
Vue.use(VueNativeSock, `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://www.example.com/socketserver`,
{ store: store, format: 'json', connectManually: true })
STEP 5: call your action from your vue component
buttonClick (rowData) {
const tickCount = this.ticketClickCounter
if (tickCount === 0) {
this.$store.dispatch('WSConnect')
} else {
this.$store.dispatch('WSDisconnect')
setTimeout(() => {
this.$store.dispatch('WSConnect')
}, 1000)
}
this.ticketClickCounter = tickCount + 1
},
Now you are connected to the socket
STEP 6: write a action method in your vuex file
sendChatMessageAction ({commit, state}, data) {
// NOTE: Here, you are sending the message through the socket connection
sendMessageWS({
type: WSMessageTypes.MESSAGE,
data: {
param1: abc,
param2: xyz,
param3: 123,
param4: $$$
}
})
},
STEP 7: you can define a input text box and on-enter evenlisterner you can call the action method
onEnter (event) {
if (event.target.value !== '') {
let newValue = {
param1: Date.now(),
param2: xyz,
param3: 123,
}
this.$store.dispatch('sendChatMessageAction', newValue) // Action
}
},

Axios post request with multiple parameters in vuex action

Fetching API data with axios in vuex action:
actions: {
login ({commit}, payload) {
axios.post(globalConfig.TOKEN_URL, {
payload
})
.then((resp) => {
commit('auth_success', resp.data)
})
.catch((err) => {
console.log(err)
})
},
}
Component's method for sending the data:
methods: {
authChatClient () {
let payload = {
name: this.clientFio,
number: this.clientNumber
}
this.$store.dispatch('login', payload)
},
}
However it won't work, since payload is an object, wrapped in a payload object. Is it possible to send multiple parameters from component's method to a vuex action?
Post request is looking like this: payload: {name: "aaa", number: "111"}
Vuex only allows the use of 1 parameter to an action. However, if I understand your question correctly, you can send multiple parameters to a vuex action if they are wrapped in an object. Example:
login({commit}, {name, number /*, ...more here*/}) {
axios.post(globalConfig.TOKEN_URL, {
name: name,
number: number,
/* more parameters here */
})
/* ... */
}
And you can call it with:
methods: {
authChatClient () {
let payload = {
name: this.clientFio,
number: this.clientNumber,
/* more parameters */
}
this.$store.dispatch('login', payload)
},
}

Sencha Touch 2, before filter on the router, to check for user's auth state

I am developing a Sencha Touch 2 app with user authentication.
I use a token for authentication.
The logic.
Check is a token exists in local storage:
var tokenStore = Ext.getStore('TokenStore'),
token = tokenStore.getAt(0).get('token');
If there is a token, check if it's valid.
I am doing a read from a model which is connected to my API which, returns success or fail - depending on the token - if it's valid or not.
TestApp.model.CheckAuthModel.load(1, {
scope: this,
success: function(record) {
// Here, I know the token is valid
},
failure: function() {
console.log('failure');
},
callback: function(record) {
console.log('callback');
console.log();
}
});
And here is the router, which handles the logic for the views:
Ext.define("TestApp.controller.Router", {
extend: "Ext.app.Controller",
config: {
refs: {
HomeView: 'HomeView',
LoginView: 'LoginView',
ProductsView: 'ProductsView',
ProductsViewTwo: 'ProductsViewTwo'
},
routes: {
'': 'home',
'home' : 'home',
'login' : 'login',
'products' : 'products',
'testingtwo' : 'testingtwo'
}
},
home: function () {
console.log('TestApp.controller.Router home function');
var initialItem = Ext.Viewport.getActiveItem(),
comp = this.getHomeView();
if (comp === undefined) comp = Ext.create('TestApp.view.HomeView');
Ext.Viewport.animateActiveItem(comp, {
type: 'slide',
listeners: {
animationend: function() {
initialItem.destroy();
}
}
});
},
login: function () {
var initialItem = Ext.Viewport.getActiveItem(),
comp = this.getLoginView();
if (comp === undefined) comp = Ext.create('TestApp.view.LoginView');
Ext.Viewport.animateActiveItem(comp, {
type: 'slide',
listeners: {
animationend: function() {
initialItem.destroy();
}
}
});
},
products: function () {
var initialItem = Ext.Viewport.getActiveItem(),
comp = this.getProductsView();
if (comp === undefined) comp = Ext.create('TestApp.view.ProductsView');
Ext.Viewport.animateActiveItem(comp, {
type: 'slide',
listeners: {
animationend: function(){
initialItem.destroy();
}
}
});
},
testingtwo: function () {
var initialItem = Ext.Viewport.getActiveItem(),
comp = this.getProductsViewTwo();
if (comp === undefined) comp = Ext.create('TestApp.view.ProductsViewTwo');
Ext.Viewport.animateActiveItem(comp, {
type: 'slide',
listeners: {
animationend: function(){
initialItem.destroy();
}
}
});
},
launch: function() {
console.log('TestApp.controller.Router launch!');
}
});
Now, how can I link the router with the check auth model callback?
I want to know the auth state when the app reaches the router.
In other MVC frameworks, I could do a before filter, on the router, check for auth and handle the routes accordingly.
Can i do this in Sencha Touch 2?
Any ideas?
Hi I think this section in the documentation is exactly what you need:
before : Object
Provides a mapping of Controller functions to filter functions that are run before them when dispatched to from a route. These are usually used to run pre-processing functions like authentication before a certain function is executed. They are only called when dispatching from a route. Example usage:
Ext.define('MyApp.controller.Products', {
config: {
before: {
editProduct: 'authenticate'
},
routes: {
'product/edit/:id': 'editProduct'
}
},
//this is not directly because our before filter is called first
editProduct: function() {
//... performs the product editing logic
},
//this is run before editProduct
authenticate: function(action) {
MyApp.authenticate({
success: function() {
action.resume();
},
failure: function() {
Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");
}
});
}
});
http://docs.sencha.com/touch/2.3.1/#!/api/Ext.app.Controller-cfg-before
Of course, it's still up to you to decide whether you should check every time or should cache the auth result for sometime.
Updated to answer comment below
Honestly, i am not sure how they was going to declare that static method Authenticate in Sencha (you would be able to do it normally through Javascript i think, i.e.: prototype).
But there are other better options to solve just that Authenticate function:
Just create a singleton class that handle utility stuffs.
http://docs.sencha.com/touch/2.3.1/#!/api/Ext.Class-cfg-singleton
If you really want to use MyApp, you can declare within the Ext.app.Application (in app.js). Then call it from the global instance MyApp.app.some_function(). I wouldn't exactly recommend this method because you change app.js, that might bring problem if you upgrade sencha touch.
You could implemented auth check in application's launch function or in your auth controller's init function and based on the response redirect the to appropriate url. Something like this:
TestApp.model.CheckAuthModel.load(1, {
scope: this,
success: function(record) {
this.redirectTo("home/");
},
failure: function() {
this.redirectTo("login/");
console.log('failure');
},
callback: function(record) {
console.log('callback');
console.log();
}
});