diff --git a/common/api/core-mobile.api.md b/common/api/core-mobile.api.md index dfe4ba5c9f08..5138b30db0cf 100644 --- a/common/api/core-mobile.api.md +++ b/common/api/core-mobile.api.md @@ -41,7 +41,7 @@ export interface CancelRequest { } // @beta (undocumented) -export type DeviceEvents = "memoryWarning" | "orientationChanged" | "enterForeground" | "enterBackground" | "willTerminate" | "authAccessTokenChanged"; +export type DeviceEvents = "memoryWarning" | "orientationChanged" | "enterForeground" | "enterBackground" | "willTerminate" | "authAccessTokenChanged" | "online" | "offline"; // @internal export class DownloadFailed extends BentleyError { diff --git a/common/changes/@itwin/core-backend/tcobbs-reachability_2025-11-19-19-50.json b/common/changes/@itwin/core-backend/tcobbs-reachability_2025-11-19-19-50.json new file mode 100644 index 000000000000..009c48fb80d4 --- /dev/null +++ b/common/changes/@itwin/core-backend/tcobbs-reachability_2025-11-19-19-50.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/core-backend", + "comment": "Mobile: don't do workspace update checks when offline", + "type": "none" + } + ], + "packageName": "@itwin/core-backend" +} \ No newline at end of file diff --git a/common/changes/@itwin/core-mobile/tcobbs-reachability_2025-11-19-19-50.json b/common/changes/@itwin/core-mobile/tcobbs-reachability_2025-11-19-19-50.json new file mode 100644 index 000000000000..dc13eedcfeec --- /dev/null +++ b/common/changes/@itwin/core-mobile/tcobbs-reachability_2025-11-19-19-50.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/core-mobile", + "comment": "Added online and offline device events for network reachability monitoring.", + "type": "none" + } + ], + "packageName": "@itwin/core-mobile" +} \ No newline at end of file diff --git a/core/backend/src/NativeHost.ts b/core/backend/src/NativeHost.ts index e21b368d5264..e2e935e34c46 100644 --- a/core/backend/src/NativeHost.ts +++ b/core/backend/src/NativeHost.ts @@ -7,7 +7,7 @@ */ import { join } from "path"; -import { AccessToken, assert, BeEvent, GuidString } from "@itwin/core-bentley"; +import { AccessToken, assert, BeEvent, GuidString, ProcessDetector } from "@itwin/core-bentley"; import { BriefcaseProps, InternetConnectivityStatus, LocalBriefcaseProps, NativeAppFunctions, nativeAppIpcStrings, NativeAppNotifications, OverriddenBy, RequestNewBriefcaseProps, StorageValue, @@ -18,6 +18,7 @@ import { IModelHost } from "./IModelHost"; import { IpcHandler, IpcHost, IpcHostOpts, throttleProgressCallback } from "./IpcHost"; import { NativeAppStorage } from "./NativeAppStorage"; import { CatalogIModelHandler } from "./CatalogDb"; +import { setOnlineStatus } from "./internal/OnlineStatus"; /** * Implementation of NativeAppFunctions @@ -199,6 +200,12 @@ export class NativeHost { if (this._reachability !== status) { this._reachability = status; this.onInternetConnectivityChanged.raiseEvent(status); + if (ProcessDetector.isMobileAppBackend) { + // Merely referencing NativeHost from a non-native backend causes a runtime exception (even + // inside an if statement that checks that the backend is a mobile backend). This allows code + // that needs to check connectivity to do so without referencing NativeHost directly. + setOnlineStatus(status === InternetConnectivityStatus.Online); + } } } } diff --git a/core/backend/src/internal/OnlineStatus.ts b/core/backend/src/internal/OnlineStatus.ts new file mode 100644 index 000000000000..c9a83f747ff1 --- /dev/null +++ b/core/backend/src/internal/OnlineStatus.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Bentley Systems, Incorporated. All rights reserved. +* See LICENSE.md in the project root for license terms and full copyright notice. +*--------------------------------------------------------------------------------------------*/ + +let isOnline = true; + +/** + * Sets the online status of the backend that will be returned by `getOnlineStatus`. + * @param online The new online status. + * @internal + */ +export function setOnlineStatus(online: boolean) { + isOnline = online; +} + +/** + * Determine whether the backend is currently considered online. + * @note This only works if `setOnlineStatus` has been called by something to update the status. + * `NativeHost` does this whenever the connectivity changes. If `setOnlineStatus` has never been + * called, this will return `true`. + * @internal + */ +export function getOnlineStatus(): boolean { + return isOnline; +} diff --git a/core/backend/src/internal/workspace/WorkspaceImpl.ts b/core/backend/src/internal/workspace/WorkspaceImpl.ts index 4594df59f57e..15be855e48ae 100644 --- a/core/backend/src/internal/workspace/WorkspaceImpl.ts +++ b/core/backend/src/internal/workspace/WorkspaceImpl.ts @@ -9,7 +9,7 @@ import { createHash } from "crypto"; import * as fs from "fs-extra"; import { dirname, extname, join } from "path"; -import { AccessToken, assert, BeEvent, DbResult, Mutable, OpenMode } from "@itwin/core-bentley"; +import { AccessToken, assert, BeEvent, DbResult, Mutable, OpenMode, ProcessDetector } from "@itwin/core-bentley"; import { CloudSqliteError, FilePropertyProps, LocalDirName, LocalFileName, WorkspaceError } from "@itwin/core-common"; import { CloudSqlite } from "../../CloudSqlite"; import { IModelHost, KnownLocations } from "../../IModelHost"; @@ -27,6 +27,7 @@ import { CreateNewWorkspaceContainerArgs, CreateNewWorkspaceDbVersionArgs, Edita import { WorkspaceSqliteDb } from "./WorkspaceSqliteDb"; import { SettingsImpl } from "./SettingsImpl"; import { _implementationProhibited, _nativeDb } from "../Symbols"; +import { getOnlineStatus } from "../OnlineStatus"; function workspaceDbNameWithDefault(dbName?: WorkspaceDbName): WorkspaceDbName { return dbName ?? "workspace-db"; @@ -119,6 +120,17 @@ class WorkspaceContainerImpl implements WorkspaceContainer { // sharedConnect returns true if we just connected (if the container is shared, it may have already been connected) if (cloudContainer.sharedConnect() && false !== props.syncOnConnect) { try { + if (ProcessDetector.isMobileAppBackend || ProcessDetector.isElectronAppBackend) { + // Even though we've already confirmed that we are running in a native app backend, + // having code here that references NativeHost causes a runtime exception. So we use + // getOnlineStatus to determine whether we're online, which NativeHost keeps up to date. + if (!getOnlineStatus()) { + // If running in a native app and we're offline, don't check for changes. + // Doing so will fail and be caught below, but it has to wait for the network + // timeout. + return; + } + } cloudContainer.checkForChanges(); } catch { // must be offline diff --git a/core/mobile/src/backend/MobileHost.ts b/core/mobile/src/backend/MobileHost.ts index cbd267b7c78d..138ab3926918 100644 --- a/core/mobile/src/backend/MobileHost.ts +++ b/core/mobile/src/backend/MobileHost.ts @@ -5,7 +5,7 @@ import { AccessToken, BeEvent, BriefcaseStatus } from "@itwin/core-bentley"; import { IpcHandler, IpcHost, NativeHost, NativeHostOpts } from "@itwin/core-backend"; -import { IpcWebSocketBackend, RpcInterfaceDefinition } from "@itwin/core-common"; +import { InternetConnectivityStatus, IpcWebSocketBackend, OverriddenBy, RpcInterfaceDefinition } from "@itwin/core-common"; import { CancelRequest, DownloadFailed, UserCancelledError } from "./MobileFileHandler"; import { ProgressCallback } from "./Request"; import { mobileAppStrings } from "../common/MobileAppChannel"; @@ -58,6 +58,12 @@ export abstract class MobileDevice { case "authAccessTokenChanged": MobileHost.onAuthAccessTokenChanged.raiseEvent(args[0], args[1]); break; + case "online": + NativeHost.overrideInternetConnectivity(OverriddenBy.Browser, InternetConnectivityStatus.Online); + break; + case "offline": + NativeHost.overrideInternetConnectivity(OverriddenBy.Browser, InternetConnectivityStatus.Offline); + break; } } diff --git a/core/mobile/src/common/MobileAppProps.ts b/core/mobile/src/common/MobileAppProps.ts index 5c529801ca00..1d3d3ab41f19 100644 --- a/core/mobile/src/common/MobileAppProps.ts +++ b/core/mobile/src/common/MobileAppProps.ts @@ -33,7 +33,7 @@ export interface MobileNotifications { } /** @beta */ -export type DeviceEvents = "memoryWarning" | "orientationChanged" | "enterForeground" | "enterBackground" | "willTerminate" | "authAccessTokenChanged"; +export type DeviceEvents = "memoryWarning" | "orientationChanged" | "enterForeground" | "enterBackground" | "willTerminate" | "authAccessTokenChanged" | "online" | "offline"; /** * The methods that may be invoked via Ipc from the frontend of a Mobile App that are implemented on its backend.