mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-10-08 10:58:32 +00:00
337 lines
10 KiB
TypeScript
337 lines
10 KiB
TypeScript
import {
|
|
ConnectionStatus,
|
|
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution,
|
|
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
|
} from '@theia/core/lib/browser/connection-status-service';
|
|
import type { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
|
import { WebSocketConnectionSource } from '@theia/core/lib/browser/messaging/ws-connection-source';
|
|
import { StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-bar';
|
|
import { Disposable } from '@theia/core/lib/common/disposable';
|
|
import { Emitter, Event } from '@theia/core/lib/common/event';
|
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
|
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
|
|
import { nls } from '@theia/core/lib/common/nls';
|
|
import {
|
|
inject,
|
|
injectable,
|
|
postConstruct,
|
|
} from '@theia/core/shared/inversify';
|
|
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
|
import debounce from 'lodash.debounce';
|
|
import { ArduinoDaemon } from '../../../common/protocol';
|
|
import { assertUnreachable } from '../../../common/utils';
|
|
import { CreateFeatures } from '../../create/create-features';
|
|
import { NotificationCenter } from '../../notification-center';
|
|
|
|
@injectable()
|
|
export class IsOnline implements FrontendApplicationContribution {
|
|
private readonly onDidChangeOnlineEmitter = new Emitter<boolean>();
|
|
private _online = false;
|
|
private stopped = false;
|
|
|
|
onStart(): void {
|
|
import('is-online').then((module) => {
|
|
const checkOnline = async () => {
|
|
if (!this.stopped) {
|
|
try {
|
|
const online = await module.default();
|
|
this.setOnline(online);
|
|
} finally {
|
|
window.setTimeout(() => checkOnline(), 6_000); // 6 seconds poll interval
|
|
}
|
|
}
|
|
};
|
|
checkOnline();
|
|
});
|
|
}
|
|
|
|
onStop(): void {
|
|
this.stopped = true;
|
|
this.onDidChangeOnlineEmitter.dispose();
|
|
}
|
|
|
|
get online(): boolean {
|
|
return this._online;
|
|
}
|
|
|
|
get onDidChangeOnline(): Event<boolean> {
|
|
return this.onDidChangeOnlineEmitter.event;
|
|
}
|
|
|
|
private setOnline(online: boolean): void {
|
|
const oldOnline = this._online;
|
|
this._online = online;
|
|
if (!this.stopped && this._online !== oldOnline) {
|
|
this.onDidChangeOnlineEmitter.fire(this._online);
|
|
}
|
|
}
|
|
}
|
|
|
|
@injectable()
|
|
export class DaemonPort implements FrontendApplicationContribution {
|
|
@inject(ArduinoDaemon)
|
|
private readonly daemon: ArduinoDaemon;
|
|
@inject(NotificationCenter)
|
|
private readonly notificationCenter: NotificationCenter;
|
|
|
|
private readonly onPortDidChangeEmitter = new Emitter<number | undefined>();
|
|
private _port: number | undefined;
|
|
|
|
onStart(): void {
|
|
this.daemon.tryGetPort().then(
|
|
(port) => this.setPort(port),
|
|
(reason) =>
|
|
console.warn('Could not retrieve the CLI daemon port.', reason)
|
|
);
|
|
this.notificationCenter.onDaemonDidStart((port) => this.setPort(port));
|
|
this.notificationCenter.onDaemonDidStop(() => this.setPort(undefined));
|
|
}
|
|
|
|
onStop(): void {
|
|
this.onPortDidChangeEmitter.dispose();
|
|
}
|
|
|
|
get port(): number | undefined {
|
|
return this._port;
|
|
}
|
|
|
|
get onDidChangePort(): Event<number | undefined> {
|
|
return this.onPortDidChangeEmitter.event;
|
|
}
|
|
|
|
private setPort(port: number | undefined): void {
|
|
const oldPort = this._port;
|
|
this._port = port;
|
|
if (this._port !== oldPort) {
|
|
this.onPortDidChangeEmitter.fire(this._port);
|
|
}
|
|
}
|
|
}
|
|
|
|
@injectable()
|
|
export class FrontendConnectionStatusService extends TheiaFrontendConnectionStatusService {
|
|
@inject(DaemonPort)
|
|
private readonly daemonPort: DaemonPort;
|
|
@inject(IsOnline)
|
|
private readonly isOnline: IsOnline;
|
|
@inject(WebSocketConnectionSource)
|
|
private readonly connectionSource: WebSocketConnectionSource;
|
|
|
|
@postConstruct()
|
|
protected override init(): void {
|
|
this.schedulePing();
|
|
const refresh = debounce(() => {
|
|
this.updateStatus(Boolean(this.daemonPort.port) && this.isOnline.online);
|
|
this.schedulePing();
|
|
}, this.options.offlineTimeout - 10);
|
|
this.wsConnectionProvider.onIncomingMessageActivity(() => refresh());
|
|
}
|
|
|
|
protected override async performPingRequest(): Promise<void> {
|
|
if (!this.connectionSource['socket'].connected) {
|
|
this.updateStatus(false);
|
|
return;
|
|
}
|
|
try {
|
|
await this.pingService.ping();
|
|
this.updateStatus(this.isOnline.online);
|
|
} catch (e) {
|
|
this.updateStatus(false);
|
|
this.logger.error(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
const connectionStatusStatusBar = 'connection-status';
|
|
const theiaOffline = 'theia-mod-offline';
|
|
|
|
export type OfflineConnectionStatus =
|
|
/**
|
|
* There is no websocket connection between the frontend and the backend.
|
|
*/
|
|
| 'backend'
|
|
/**
|
|
* The CLI daemon port is not available. Could not establish the gRPC connection between the backend and the CLI.
|
|
*/
|
|
| 'daemon'
|
|
/**
|
|
* Cloud not connect to the Internet from the browser.
|
|
*/
|
|
| 'internet';
|
|
|
|
@injectable()
|
|
export class ApplicationConnectionStatusContribution extends TheiaApplicationConnectionStatusContribution {
|
|
@inject(DaemonPort)
|
|
private readonly daemonPort: DaemonPort;
|
|
@inject(IsOnline)
|
|
private readonly isOnline: IsOnline;
|
|
@inject(MessageService)
|
|
private readonly messageService: MessageService;
|
|
@inject(NotificationManager)
|
|
private readonly notificationManager: NotificationManager;
|
|
@inject(CreateFeatures)
|
|
private readonly createFeatures: CreateFeatures;
|
|
@inject(WebSocketConnectionSource)
|
|
private readonly connectionSource: WebSocketConnectionSource;
|
|
|
|
private readonly offlineStatusDidChangeEmitter = new Emitter<
|
|
OfflineConnectionStatus | undefined
|
|
>();
|
|
private noInternetConnectionNotificationId: string | undefined;
|
|
private _offlineStatus: OfflineConnectionStatus | undefined;
|
|
|
|
get offlineStatus(): OfflineConnectionStatus | undefined {
|
|
return this._offlineStatus;
|
|
}
|
|
|
|
get onOfflineStatusDidChange(): Event<OfflineConnectionStatus | undefined> {
|
|
return this.offlineStatusDidChangeEmitter.event;
|
|
}
|
|
|
|
protected override onStateChange(state: ConnectionStatus): void {
|
|
if (
|
|
(!Boolean(this.daemonPort.port) || !this.isOnline.online) &&
|
|
state === ConnectionStatus.ONLINE
|
|
) {
|
|
return;
|
|
}
|
|
super.onStateChange(state);
|
|
}
|
|
|
|
protected override handleOffline(): void {
|
|
const params = <OfflineMessageParams>{
|
|
port: this.daemonPort.port,
|
|
online: this.isOnline.online,
|
|
backendConnected: this.connectionSource['socket'].connected, // https://github.com/arduino/arduino-ide/issues/2081
|
|
};
|
|
this._offlineStatus = offlineConnectionStatusType(params);
|
|
const { text, tooltip } = offlineMessage(params);
|
|
this.statusBar.setElement(connectionStatusStatusBar, {
|
|
alignment: StatusBarAlignment.LEFT,
|
|
text,
|
|
tooltip,
|
|
priority: 5000,
|
|
});
|
|
document.body.classList.add(theiaOffline);
|
|
this.toDisposeOnOnline.pushAll([
|
|
Disposable.create(() =>
|
|
this.statusBar.removeElement(connectionStatusStatusBar)
|
|
),
|
|
Disposable.create(() => document.body.classList.remove(theiaOffline)),
|
|
Disposable.create(() => {
|
|
this._offlineStatus = undefined;
|
|
this.fireStatusDidChange();
|
|
}),
|
|
]);
|
|
if (!this.isOnline.online) {
|
|
const text = nls.localize(
|
|
'arduino/connectionStatus/connectionLost',
|
|
"Connection lost. Cloud sketch actions and updates won't be available."
|
|
);
|
|
this.noInternetConnectionNotificationId = this.notificationManager[
|
|
'getMessageId'
|
|
]({ text, type: MessageType.Warning });
|
|
if (this.createFeatures.enabled) {
|
|
this.messageService.warn(text);
|
|
}
|
|
this.toDisposeOnOnline.push(
|
|
Disposable.create(() => this.clearNoInternetConnectionNotification())
|
|
);
|
|
}
|
|
this.fireStatusDidChange();
|
|
}
|
|
|
|
private clearNoInternetConnectionNotification(): void {
|
|
if (this.noInternetConnectionNotificationId) {
|
|
this.notificationManager.clear(this.noInternetConnectionNotificationId);
|
|
this.noInternetConnectionNotificationId = undefined;
|
|
}
|
|
}
|
|
|
|
private fireStatusDidChange(): void {
|
|
if (this.createFeatures.enabled) {
|
|
return this.offlineStatusDidChangeEmitter.fire(this._offlineStatus);
|
|
}
|
|
}
|
|
}
|
|
|
|
interface OfflineMessageParams {
|
|
readonly port: string | undefined;
|
|
readonly online: boolean;
|
|
readonly backendConnected: boolean;
|
|
}
|
|
interface OfflineMessage {
|
|
readonly text: string;
|
|
readonly tooltip: string;
|
|
}
|
|
|
|
/**
|
|
* (non-API) exported for testing
|
|
*
|
|
* The precedence of the offline states are the following:
|
|
* - No connection to the Theia backend,
|
|
* - CLI daemon is offline, and
|
|
* - There is no Internet connection.
|
|
*/
|
|
export function offlineMessage(params: OfflineMessageParams): OfflineMessage {
|
|
const statusType = offlineConnectionStatusType(params);
|
|
const text = getOfflineText(statusType);
|
|
const tooltip = getOfflineTooltip(statusType);
|
|
return { text, tooltip };
|
|
}
|
|
|
|
function offlineConnectionStatusType(
|
|
params: OfflineMessageParams
|
|
): OfflineConnectionStatus {
|
|
const { port, online, backendConnected } = params;
|
|
if (!backendConnected || (port && online)) {
|
|
return 'backend';
|
|
}
|
|
if (!port) {
|
|
return 'daemon';
|
|
}
|
|
return 'internet';
|
|
}
|
|
|
|
export const backendOfflineText = nls.localize('theia/core/offline', 'Offline');
|
|
export const daemonOfflineText = nls.localize(
|
|
'theia/core/daemonOffline',
|
|
'CLI Daemon Offline'
|
|
);
|
|
export const offlineText = nls.localize('theia/core/offlineText', 'Offline');
|
|
export const backendOfflineTooltip = nls.localize(
|
|
'theia/core/cannotConnectBackend',
|
|
'Cannot connect to the backend.'
|
|
);
|
|
export const daemonOfflineTooltip = nls.localize(
|
|
'theia/core/cannotConnectDaemon',
|
|
'Cannot connect to the CLI daemon.'
|
|
);
|
|
export const offlineTooltip = offlineText;
|
|
|
|
function getOfflineText(statusType: OfflineConnectionStatus): string {
|
|
switch (statusType) {
|
|
case 'backend':
|
|
return backendOfflineText;
|
|
case 'daemon':
|
|
return '$(bolt) ' + daemonOfflineText;
|
|
case 'internet':
|
|
return '$(alert) ' + offlineText;
|
|
default:
|
|
assertUnreachable(statusType);
|
|
}
|
|
}
|
|
|
|
function getOfflineTooltip(statusType: OfflineConnectionStatus): string {
|
|
switch (statusType) {
|
|
case 'backend':
|
|
return backendOfflineTooltip;
|
|
case 'daemon':
|
|
return daemonOfflineTooltip;
|
|
case 'internet':
|
|
return offlineTooltip;
|
|
default:
|
|
assertUnreachable(statusType);
|
|
}
|
|
}
|