Skip to content

Blazor Server - Auto reconnect on mobile browsers #32122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1 change: 1 addition & 0 deletions src/Components/Web.JS/src/Boot.Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
const reconnection = existingConnection || await initializeConnection(options, logger, circuit);
if (!(await circuit.reconnect(reconnection))) {
logger.log(LogLevel.Information, 'Reconnection attempt to the circuit was rejected by the server. This may indicate that the associated state is no longer available on the server.');
options.reconnectionHandler!.onConnectionRejected(options.reconnectionOptions);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect we should be checking whether reconnectionHandler or onConnectionRejected are unset, and skipping the call if so. Or is there some way we know for sure that both of them are set?

Example (untested):

Suggested change
options.reconnectionHandler!.onConnectionRejected(options.reconnectionOptions);
options.reconnectionHandler?.onConnectionRejected?(options.reconnectionOptions);

return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ export interface ReconnectionOptions {
maxRetries: number;
retryIntervalMilliseconds: number;
dialogId: string;
reloadOnCircuitRejected: boolean;
}

export interface ReconnectionHandler {
onConnectionDown(options: ReconnectionOptions, error?: Error): void;
onConnectionUp(): void;
onConnectionRejected(options: ReconnectionOptions): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a pretty good idea to me. I'm not 100% sold on reloading automatically by default (we need to at least reason about why that wasn't already our default) but the ability to configure a handler for this scenario seems valuable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For back-compatibility, this new property needs to be added as optional

}

const defaultOptions: CircuitStartOptions = {
Expand All @@ -36,5 +38,6 @@ const defaultOptions: CircuitStartOptions = {
maxRetries: 8,
retryIntervalMilliseconds: 20000,
dialogId: 'components-reconnect-modal',
reloadOnCircuitRejected: true,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ export class DefaultReconnectionHandler implements ReconnectionHandler {
this._currentReconnectionProcess = null;
}
}

onConnectionRejected(options: ReconnectionOptions) {
if (options.reloadOnCircuitRejected) {
location.reload();
}
}
};

class ReconnectionProcess {
static readonly MaximumFirstRetryInterval = 3000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anyone remember why we implemented this originally?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely because after connection goes down it would try to attempt the reconnection only after 20s, which is quite a long time.
So workaround that originally is there is to shorten the first reconnection attempt interval to 3s.


readonly reconnectDisplay: ReconnectDisplay;
isDisposed = false;

Expand All @@ -57,12 +61,6 @@ class ReconnectionProcess {
async attemptPeriodicReconnection(options: ReconnectionOptions) {
for (let i = 0; i < options.maxRetries; i++) {
this.reconnectDisplay.update(i + 1);

const delayDuration = i == 0 && options.retryIntervalMilliseconds > ReconnectionProcess.MaximumFirstRetryInterval
? ReconnectionProcess.MaximumFirstRetryInterval
: options.retryIntervalMilliseconds;
await this.delay(delayDuration);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since retryIntervalMilliseconds was put back to 20000ms, I think it's also necessary to bring back this logic.


if (this.isDisposed) {
break;
}
Expand All @@ -83,6 +81,7 @@ class ReconnectionProcess {
// We got an exception so will try again momentarily
this.logger.log(LogLevel.Error, err);
}
await this.delay(options.retryIntervalMilliseconds);
}

this.reconnectDisplay.failed();
Expand Down
12 changes: 8 additions & 4 deletions src/Components/Web.JS/tests/DefaultReconnectionHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ describe('DefaultReconnectionHandler', () => {
handler.onConnectionDown({
maxRetries: 1000,
retryIntervalMilliseconds: 100,
dialogId: 'ignored'
dialogId: 'ignored',
reloadOnCircuitRejected: true
});
handler.onConnectionUp();

Expand All @@ -48,7 +49,8 @@ describe('DefaultReconnectionHandler', () => {
handler.onConnectionDown({
maxRetries: 1000,
retryIntervalMilliseconds: 100,
dialogId: 'ignored'
dialogId: 'ignored',
reloadOnCircuitRejected: true
});
expect(testDisplay.show).toHaveBeenCalled();
expect(testDisplay.failed).not.toHaveBeenCalled();
Expand All @@ -67,7 +69,8 @@ describe('DefaultReconnectionHandler', () => {
handler.onConnectionDown({
maxRetries: 2,
retryIntervalMilliseconds: 5,
dialogId: 'ignored'
dialogId: 'ignored',
reloadOnCircuitRejected: true
});

await delay(500);
Expand All @@ -85,7 +88,8 @@ describe('DefaultReconnectionHandler', () => {
handler.onConnectionDown({
maxRetries: maxRetries,
retryIntervalMilliseconds: 5,
dialogId: 'ignored'
dialogId: 'ignored',
reloadOnCircuitRejected: true
});

await delay(500);
Expand Down