Tutorial: Synchronize time

Tips and Tricks Synchronize time

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, you just have to listen to the virtual data node at the "/.info/connection/serverTimeOffsetMs" path (replace “<your-app>” with your actual application identifier):

const offsetRef = new Webcom("<your-app>").child(".info/connection/serverTimeOffsetMs");
let serverTime;
offsetRef.on("value", snap => {
  const timeOffset = snap.val(); // value in milliseconds since Unix Epoch
  console.log("server time offset has changed", timeOffset);
  serverTime = new Date(Date.now() + timeOffset);
  console.log("new server time is", serverTime);
});

In Kotlin, the datasync service maintains a DatasyncState in realtime, which may be subscribed using the subscribeToStateChange() method:

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()

var serverTime = Date()
manager.subscribeToStateChange { // it: DatasyncState
  it.connectionState.backendTimeOffsetMilliseconds?.let { offsetMilliseconds ->
    println("server time offset has changed: $offsetMilliseconds ms")
    serverTime = Date(System.currentTimeMillis() + offsetMilliseconds)
    println("new server time is: $serverTime")
  }
}
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 dataRef = new Webcom("<your-app>").child("mypath");
dataRef.set({
    value: "test",
    time: Webcom.ServerValue.TIMESTAMP
});
dataRef.get().then(snapshot => {
    const data = snapshot.val();
    console.log(`Value '${data.value}' changed at ${Date.new(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 Webcom.ServerValue.TIMESTAMP to store to 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 messagesRef = new Webcom('<your-app>');

const offsetRef = messagesRef.child('.info/connection/serverTimeOffsetMs');
let timeOffset=0;
offsetRef.on("value", snap => {
  timeOffset = snap.val();
  console.log("server time offset changed:", timeOffset);
});

// Function called to push message into Webcom
function addMessage(username, message) {
    messagesRef.push({
      text: message,
      name: username,
      time: Webcom.ServerValue.TIMESTAMP
    });
}

messagesRef.on('child_added', snapshot => {
  const data = snapshot.val();
  const message = data.text;
  const username = data.name || "anonymous";
  // Retrieve the timestamp of the message and add the current time offset
  const date = new Date(data.time - timeOffset);

  // Now you just have to append the new message in you page...
});
import com.orange.webcom.dessert.TimeStampDate
import com.orange.webcom.sdkv2.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

var timeOffset = 0L
manager.subscribeToStateChange { // it: DatasyncState
    it.connectionState.backendTimeOffsetMilliseconds?.let { offset ->
        timeOffset = offset
        println("server time offset changed: $offset")
    }
}

// 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 - timeOffset)) }
            . 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...
}