How to synchronize client with server time
The Webcom SDK makes it possible to retrieve the time difference (offset) between the user device and the Webcom server:
In JavaScript, the ServerlessDb Service maintains the currentState
property in realtime that returns an instance of ServerlessDb.State
.
// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
const timeOffsetMillis = database.currentState.connection.serverTimeOffsetMs;
const serverTime = new Date(Date.now() + timeOffsetMillis);
console.log("The server time is", serverTime);
If you need to be notified in realtime of the changes of the
currentState
, you can subscribe to the virtual".info"
data node!
In Kotlin, the Datasync Service maintains the state
property in realtime that returns an instance of
DatasyncState
.
val myApp = WebcomApplication.default // the app defined by the 'webcom.properties' asset file
val datasync = myApp.datasyncService // this service is the entry point for all datasync-related features
datasync.state.connectionState.backendTimeOffsetMilliseconds?.let { timeOffsetMilliseconds ->
val serverTime = Date(System.currentTimeMillis() + timeOffsetMilliseconds)
println("The server time is: $serverTime")
}
If you need to be notified in realtime of the changes of the Datasync Service
state
, you can subscribe to its value using thesubscribeToStateChange()
method!
let datasyncManager = Webcom.defaultApplication.datasyncService.createManager()
datasyncManager.subscribeToStateChange { state in
guard let backEndTimeOffsetMS = state.connectionState.backEndTimeOffsetMS else {
return
}
print("server time offset changed:", backEndTimeOffsetMS, "ms")
let backEndTimeOffsetSeconds: TimeInterval = Double(backEndTimeOffsetMS) / 1000
let backEndDate = Date(timeIntervalSinceNow: backEndTimeOffsetSeconds)
print("new server time is", backEndDate)
}
Write data with server timestamp
You can use the special variable "Webcom.ServerValue.TIMESTAMP" to store timestamp managed by the Webcom server. This is useful if you want to ensure consistent representation of time in your data. When you read (or listen) values written with this constant, you will receive an integer timestamp (representing the number of milliseconds elapsed since Unix Epoch). You can use the serverTimeOffset described above to display the local time of your client.
For example (replace “<your-app>” with your actual application identifier):
// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
const dataNode = database.rootNode.relativeNode("mypath");
dataNode
.set({
value: "test",
time: Webcom.ServerValue.TIMESTAMP
})
.then(() => dataNode.get())
.then(snapshot => {
const data = snapshot.val();
console.log(`Value '${data.value}' changed at ${new Date(data.time).toLocaleString()}`);
});
When written into the Webcom database, the special TimeStampDate.Server
object is substituted (on server side)
with the actual timestamp of the Webcom back end. Of course, when read from the database as a TimeStampDate
object, it is converted into the actual date time written by the server. This objet is useful when you need to
ensure consistent representation of time within your data. You can also use the
serverTimeOffset described above to compare it with the local time of
the client device.
import com.orange.webcom.dessert.TimeStampDate
@Serializable
data class MyData(val value: String, val time: TimeStampDate)
val myApp = WebcomApplication.default // the app defined by the 'webcom.properties' asset file
val datasync = myApp.datasyncService // this service is the entry point for all datasync-related features
val manager = datasync.createManager()
val node = manager / "mypath"
node.set(MyData("test", TimeStampDate.Server)) {
node.get {
it.getOrNull()?.asA(MyData::class)?.let { data ->
println("Value '${data.value}' changed at ${data.time}")
}
}
}
A WebcomDate
value encapsulates a standard Date
value.
The type also provides a special .backEndCurrentDate
instance that represents the Webcom server current date.
You can use the serverTimeOffset described above to display the local time of your client.
struct MyData: Codable {
let value: String
let time: WebcomDate
}
let datasyncManager = Webcom.defaultApplication.datasyncService.createManager()
let node = datasyncManager / "mypath"
node? <- MyData(value: "test", time: .backEndCurrentDate)
node?.subscribe(to: .valueChange) { event in
guard let data: MyData = event.value.decoded() else {
return
}
print("Value '\(data.value)' changed at '\(data.time.value)'")
}
Full example for a simple chat application
In the following code, we use a server timestamp to store the timestamp of new messages. When we display the message to the user we add the 'timeOffset', so that all messages seems to be timestamped from the local device clock (replace “<your-app>” with your actual application identifier):
// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;
const messagesNode = database.rootNode.relativeNode("messages");
// Function called to push message into Webcom
function addMessage(username, message) {
messagesNode.push({
text: message,
name: username,
time: Webcom.ServerValue.TIMESTAMP
});
}
messagesNode.subscribe(
Webcom.Event.Child.Addition,
Webcom.Callback(snapshot => {
const {text: message, name: username = "anonymous", time} = snapshot.val();
// Retrieve the device date corresponding to the message date
const date = new Date(time - database.currentState.connection.serverTimeOffsetMs);
// Now you just have to append the new message in you page...
})
);
import com.orange.webcom.dessert.TimeStampDate
import com.orange.webcom.sdk.datasync.subscription.SubscribableEvent.*
@Serializable
data class Message(val text: String, val name: String, val time: TimeStampDate)
val myApp = WebcomApplication.default // the app defined by the 'webcom.properties' asset file
val datasync = myApp.datasyncService // this service is the entry point for all datasync-related features
val manager = datasync.createManager()
val messagesNode = manager.rootNode
// Function called to push message into Webcom
fun addMessage(username: String, message: String) {
messagesNode.push(Message(message, username, TimeStampDate()))
}
messagesNode.subscribe(Child.Added::class) { // it: Notification<Child.Added>
if (it is Child.Added) {
it.value.asA(Message::class)
// Retrieve the device date corresponding to the message date
. run { copy(time = TimeStampDate(time - (datasync.state.connectionState.backendTimeOffsetMilliseconds ?: 0L))) }
. let { message ->
// Now you just have to append the new message in you page...
}
}
}
struct Message: Codable {
let username: String
let message: String
let date: WebcomDate
}
let app = Webcom.defaultApplication
let manager = app.datasyncService.createManager()
let node = manager.rootNode
// Function called to push message into Webcom
func addMessage(username: String, message: String) {
node.push(Message(username: username, message: message, date: .backEndCurrentDate))
}
node.subscribe(to: .childAddition) { event in
guard let message: Message = event.value.decoded() else {
return
}
// Retrieve the device date corresponding to the message date
let deviceDate = app.datasyncService.state.connectionState.deviceDate(for: message.date)
// Now you just have to append the new message in you page...
}