Robust Permissions, for the Power User
With Partial Sync, it's probable that your entire user base needs to access one realm. This brings the concept of Object Level Permissions and "Async Server Side Permissioning". This is an advanced power user feature that allows you to inject logical permissions to operations on objects. This is a great way to put in custom, advanced, or 3rd party logic for specific objects.
Async Serverside Permissions
Sometimes we want permissions to exist on the server instead. Similar to the login functionality, Async Server Side Permissions allow you to reject synced changes inflight.
const ros = new ROS({
... other config values
permissions: {
'realmPath/2:Car': function(incoming, current, done){
// call done() without any parameters, and the change is accepted
// call done(err) with an error parameter, the change is reject
})
}
})
The permissions
config value takes in an dictionary of regex:ObjectTypeName
- The regex string is the name of the realmPath (without the host or port)
- The ObjectName is the name of the Realm Object
What is the incoming object?
The incoming object describes both the userId, the operation, and object as it would look if the application accepted
function(incoming, current, done) {
console.log(incoming.userId) // the userId that is attempting to make the change
console.log(incoming.operation) // various information about what the client is attempting to change
// ^ also contains information about the Operational-Transformation if your interested
console.log(incoming.objects) // this is what the object would look like if you accepted it
}
Example of using Async Server Side Permissions
const BankApi = require('bank-api')
function(incoming, current, done) {
const userId = incoming.userId
const bankAccount = incoming.object
const operation = incoming.operation
const increaseAmount = operation.type === 'INCR'
const amount = operation.value === '200000'
bankApi(userId).canIncrease(amount)
.then((canIncrease) => {
done() // incoming changes are now accepted
})
.catch(err => {
done(err) // incoming changes are rejected
// changes will be reverted
})
}
Client Side Optimistic vs Pessimistic Writes
When you have an Async
Serverside Permission, we are bound to the latency of our network. There are instances where you'd like to react to it optimistically vs pessmistically.
Optimistically
If you are attempting to mutate data that will be rejected by the server. You will see a much quieter error handling. This keeps the app much more "responsive". This doesn't mean that error or the correct model reversion isn't correct.
This is best shown in the code below as the notification block is fired twice in the event that the change is rejected. We call this optimistically because the code assumes more likely that the app will succeed with the write operation.
Upon failure, the server will "revert" the change. Depending on your latency you will probably see a quick blip in your UI. This is similar behavior on apps like Facebook or Instagram
let mySyncedRealm = ...
mySyncedRealm.filter(Car.self).addNotificationBlock({ changeEvent in
print("\(changeEvent.remote?.isServer)") // this will fire TWICE!
pinrt("\(changeEvent.remote?.error)") // this will contain the specific error from the server
})
//let attempt to write something that will fail on the server side
try! mySyncedRealm.write {
bankAccount.amount = bankAccount.amount + 200000
}
Pessimistically
With this, we depend on an asynchronous write. This is a good place to ensure that you check if the server has acknowledged the change.
mySyncedRealm.writeRemoteAsync {
bankAccount.amount = bankAccount.amount + 200000
} { (err: Error?) in
guard let error = err else { return }
print("Uh oh the server likely rejected your change! \(err.localizedDescription)")
}
This is a great place to show a progress indicator. before and after the block is fired. In the pessimistic way, the object is not mutated until the completion block fails or succeeds.
Important Considerations
With great power comes great responsibility! Even though our Robust Serverside Permissioning is powerful, take care to understand that it can become a bottleneck for real-time events if your custom function resolves slowly.
If modification events are being fired extremely frequently and requires a check, a queue of objects checks can build up. Take care to use Robust Permissions on objects that do not aggressively change.
No other clients will sync data that has not passed a permission checkpoint.