Unregister clients who leave a channel so that resources are cleaned up.
Prerequisites
This tutorial requires the Hello World! app you have created earlier.
Reregister a Client
If a client is unable to register because of external factors, like a network connection failure, we determine whether the client has decided to leave and must be unregistered. If not, we reregister the client according to the reregister backoff period.
The reregister backoff is an incremental interval time between reregistrations. For example, if the reregister backoff time is 10 seconds, then the first time the application tries to reregister is after 10 seconds. The second reregister attempt happens after 20 seconds. The third reregister attempt happens after 30 seconds and so on until it reaches a set maximum backoff time. If you set the maximum backoff to 1 minute, then once 1 minute has elapsed, the backoff interval doesn't increase.
To reregister a client:
Set the reregister backoff and maximum backoff period in milliseconds.
Set unregistering to false.
Create an event on the client to handle the state changes. If the client isn't unregistering, register the client with a token into the channel again.
Update the joinAsync function in the HelloWorldLogic class to the following:
// Registering and reregistering a client
private int _ReregisterBackoff = 200;
private const int _MaxRegisterBackoff = 60000;
private bool _Unregistering = false;
public async Task JoinAsync()
{
Client = new Client(GatewayURL, ApplicationId);
ChannelClaim[] channelClaims = new[] { new ChannelClaim(ChannelId) };
string token = Token.GenerateClientRegisterToken(
ApplicationId,
Client.UserId,
Client.DeviceId,
Client.Id,
null,
channelClaims,
SharedSecret
);
_Unregistering = false;
Client.OnStateChange += async (client) =>
{
if (client.State == ClientState.Unregistered && !_Unregistering)
{
Log.Debug($"Registering with backoff = {_ReregisterBackoff}.");
// Incrementally increase register backoff to prevent runaway process
System.Threading.Thread.Sleep(_ReregisterBackoff);
if (_ReregisterBackoff <= _MaxRegisterBackoff)
{
_ReregisterBackoff += _ReregisterBackoff;
}
DisplayMessage("Client unregistered unexpectedly, trying to re-register.");
var reregisterChannels = await Client.Register(token).AsTask();
_ReregisterBackoff = 200; // Reset registering backoff
OnClientRegistered(reregisterChannels);
}
};
var channels = await Client.Register(token).AsTask();
DisplayMessage("Client is registered!");
OnClientRegistered(channels);
}
int reRegisterBackoff = 200;
int maxRegisterBackoff = 60000;
boolean unregistering = false;
// Make a registration request.
public Future<Channel[]> joinAsync() {
// Create a client.
client = new Client(gatewayUrl, applicationId);
// Create a token (do this on the server to avoid exposing your shared secret).
String token = Token.generateClientRegisterToken(applicationId, client.getUserId(), client.getDeviceId(), client.getId(), null, new ChannelClaim[]{new ChannelClaim(channelId)}, sharedSecret);
// Allow re-register.
unregistering = false;
client.addOnStateChange(client -> {
if (client.getState() == ClientState.Unregistered) {
Log.debug("Client has been unregistered.");
if (!unregistering) {
Log.debug(String.format(Locale.US, "Registering with backoff = %d.", reRegisterBackoff));
// Incrementally increase register backoff to prevent runaway process.
ManagedThread.sleep(reRegisterBackoff);
if (reRegisterBackoff < maxRegisterBackoff) {
reRegisterBackoff += reRegisterBackoff;
}
client.register(token).then(channels -> {
// Reset re-register backoff after successful registration.
reRegisterBackoff = 200;
onClientRegistered(channels);
}, ex -> Log.error("ERROR: Client unable to register with the gateway.", ex));
}
}
});
// Register client with token.
return client.register(token).then(this::onClientRegistered, ex -> Log.error("ERROR: Client unable to register with the gateway.", ex));
}
var _unRegistering: Bool = false
var _reRegisteringBackoff: Int32 = 200
var _maxReRegisteringBackoff: Int32 = 60000
// Make a registration request
func joinAsync() -> FMLiveSwitchFuture? {
// Instantiate the client
self._client = FMLiveSwitchClient.init(
gatewayUrl: _gatewayUrl,
applicationId: _applicationId)
let claim: FMLiveSwitchChannelClaim = FMLiveSwitchChannelClaim.init()
claim.setId(_channelId)
let claims: NSMutableArray = []
claims.add(claim)
let token: String = generateToken(claims: claims)
// We attempt to reregister..
_client?.addOnStateChange({[weak self] (obj: Any?) in
let c = obj as! FMLiveSwitchClient
if (c.state() == FMLiveSwitchClientState.unregistered) {
FMLiveSwitchLog.error(withMessage: "Client unexpectedly unregistered.")
// Client has failed for some reason.
if (!self!._unRegistering) {
FMLiveSwitchManagedThread.dispatch(with: FMLiveSwitchAction0.init(block: {
FMLiveSwitchManagedThread.sleep(withMillisecondsTimeout: self!._reRegisteringBackoff)
FMLiveSwitchLog.debug(withMessage: "Reregistering with backoff of \(String(describing: self!._reRegisteringBackoff)).")
// Increment reRegistering backoff to prevent runaway process
if (self!._reRegisteringBackoff < self!._maxReRegisteringBackoff) {
self!._reRegisteringBackoff += self!._reRegisteringBackoff
}
c.register(withToken: token)?.then(resolveActionBlock: { [weak self] (obj: Any!) -> Void in
self?._reRegisteringBackoff = 200 // Reset reregistering back off
self!.onClientRegistered(obj: obj)
})?.fail(rejectActionBlock: { (e: NSException?) in
FMLiveSwitchLog.error(withMessage: "Client failed to reregister.", ex: e)
})
}))
}
}
})
return self._client?.register(withToken: token)?.then(resolveActionBlock: { [weak self] (obj: Any!) -> Void in
self!.onClientRegistered(obj: obj)
})?.fail(rejectActionBlock: { (e: NSException?) in
FMLiveSwitchLog.error(withMessage: "Client failed to register.", ex: e)
})
}
private reRegisterBackoff = 200;
private readonly maxRegisterBackoff = 60000;
private unregistering = false;
public joinAsync(): fm.liveswitch.Future<Object> {
const promise = new fm.liveswitch.Promise<Object>();
// Create a client.
this.client = new fm.liveswitch.Client(this.gatewayUrl, this.applicationId);
// Generate a token (do this on the server to avoid exposing your shared secret).
const token: string = fm.liveswitch.Token.generateClientRegisterToken(this.applicationId, this.client.getUserId(), this.client.getDeviceId(), this.client.getId(), null, [new fm.liveswitch.ChannelClaim(this.channelId)], this.sharedSecret);
// Allow re-register.
this.unregistering = false;
this.client.addOnStateChange(() => {
// Write registration state to log.
fm.liveswitch.Log.debug(`Client is ${new fm.liveswitch.ClientStateWrapper(this.client.getState())}.`);
if (this.client.getState() === fm.liveswitch.ClientState.Unregistered && !this.unregistering) {
fm.liveswitch.Log.debug(`Registering with backoff = ${this.reRegisterBackoff}.`);
this.displayMessage("Client unregistered unexpectedly, trying to re-register.");
// Re-register after a backoff.
setTimeout(() => {
// Incrementally increase register backoff to prevent runaway process.
if (this.reRegisterBackoff <= this.maxRegisterBackoff) {
this.reRegisterBackoff += this.reRegisterBackoff;
}
// Register client with token.
this.client.register(token)
.then(channels => {
// Reset re-register backoff after successful registration.
this.reRegisterBackoff = 200;
this.onClientRegistered(channels);
promise.resolve(null);
})
.fail(ex => {
fm.liveswitch.Log.error("Failed to register with Gateway.");
promise.reject(ex)
});
}, this.reRegisterBackoff);
}
});
// Register client with token.
this.client.register(token)
.then(channels => {
this.onClientRegistered(channels);
promise.resolve(null);
})
.fail(ex => {
fm.liveswitch.Log.error("Failed to register with Gateway.");
promise.reject(ex)
});
return promise;
}
Unregister a Client
Unregistering clients when they leave is optional because the LiveSwitch Gateway automatically unregisters clients that are inactive for a period of time. However, unregistering clients cleans up resources immediately. To learn more about unregistering clients, refer to the Unregister section.
To unregister a client, invoke the unregister method of the client instance. In this code example, we set unregistering to true. This is only used for reregistering a client.
Paste the following code into the HelloWorldLogic class:
In MainWindow.xaml.cs, uncomment the code between the <Unregistered> and </Unregistered> tags.
In app/src/main/java/com/example/helloworld/StartingFragment.java, uncomment the code between the <Unregistered> and </Unregistered> tags.
Note
There are multiple instances of these tags. Uncomment all the code between those instances.
In ViewModel.swift, uncomment the code between the <Unregister> and </Unregister> tags.
Note
There are multiple instances of these tags. Uncomment all the code between those instances.
In index.ts, uncomment the code between the <Unregister> and </Unregister> tags.
Run Your App
Run your app in your project IDE.
Click Join.
Disable your device's Internet connection. In your project console, there is an error message describing a failure to connect because of a network issue.
Note
For TypeScript, the log is in the browser console.
Enable your device's Internet connection. In your project console, there is a message that the client has successfully joined.
Go to your LiveSwitch Console's homepage, and select the Hello World Application. There is only one client and one channel connected to your Hello World Application.
Go to your LiveSwitch Cloud's Dashboard page, and select the Hello World Application. There is only one client and one channel connected to your Hello World Application.
Click Leave.
Go to your LiveSwitch Console's homepage, and select the Hello World Application. There are no clients or channels connected to your Hello World Application.
Go to your LiveSwitch Cloud's Dashboard page, and select the Hello World Application. There are no clients or channels connected to your Hello World Application.
Congratulations, you've built your app to handle reregistering and unregistering.