-
Notifications
You must be signed in to change notification settings - Fork 208
/
NativeApp.ts
281 lines (243 loc) · 11.3 KB
/
NativeApp.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module NativeApp
*/
import { AsyncMethodsOf, BeEvent, GuidString, Logger, PromiseReturnType } from "@itwin/core-bentley";
import {
BriefcaseDownloader, BriefcaseProps, IModelVersion, InternetConnectivityStatus, IpcSocketFrontend, LocalBriefcaseProps,
NativeAppFunctions, nativeAppIpcStrings, NativeAppNotifications, OverriddenBy,
RemoveFunction, RequestNewBriefcaseProps, StorageValue, SyncMode,
} from "@itwin/core-common";
import { ProgressCallback } from "./request/Request";
import { FrontendLoggerCategory } from "./common/FrontendLoggerCategory";
import { IpcApp, IpcAppOptions, NotificationHandler } from "./IpcApp";
import { NativeAppLogger } from "./NativeAppLogger";
import { OnDownloadProgress } from "./BriefcaseConnection";
/** Properties for specifying the BriefcaseId for downloading. May either specify a BriefcaseId directly (preferable) or, for
* backwards compatibility, a [SyncMode]($common). If [SyncMode.PullAndPush]($common) is supplied, a new briefcaseId will be acquired.
* @public
*/
export type DownloadBriefcaseId =
{ syncMode?: SyncMode, briefcaseId?: never } |
{ briefcaseId: number, syncMode?: never };
/**
* Options to download a briefcase
* @public
*/
export type DownloadBriefcaseOptions = DownloadBriefcaseId & {
/** the full path for the briefcase file */
fileName?: string;
/** Function called regularly to report progress of download. */
progressCallback?: OnDownloadProgress;
/** interval for calling progress function, in milliseconds */
progressInterval?: number;
};
/** NativeApp notifications from backend */
class NativeAppNotifyHandler extends NotificationHandler implements NativeAppNotifications {
public get channelName() { return nativeAppIpcStrings.notifyChannel; }
public notifyInternetConnectivityChanged(status: InternetConnectivityStatus) {
Logger.logInfo(FrontendLoggerCategory.NativeApp, "Internet connectivity changed");
NativeApp.onInternetConnectivityChanged.raiseEvent(status);
}
}
/**
* Options for [[NativeApp.startup]]
* @public
*/
export interface NativeAppOpts extends IpcAppOptions {
nativeApp?: {};
}
/**
* The frontend of a native application
* @see [Native Applications]($docs/learning/NativeApps.md)
* @public
*/
export class NativeApp {
private static _removeAppNotify?: RemoveFunction;
/** @deprecated in 3.x. use nativeAppIpc */
public static async callNativeHost<T extends AsyncMethodsOf<NativeAppFunctions>>(methodName: T, ...args: Parameters<NativeAppFunctions[T]>) {
return IpcApp.callIpcChannel(nativeAppIpcStrings.channelName, methodName, ...args) as PromiseReturnType<NativeAppFunctions[T]>;
}
/** A Proxy to call one of the [NativeAppFunctions]($common) functions via IPC. */
public static nativeAppIpc = IpcApp.makeIpcProxy<NativeAppFunctions>(nativeAppIpcStrings.channelName);
private static _storages = new Map<string, Storage>();
private static _onOnline = async () => {
await NativeApp.setConnectivity(OverriddenBy.Browser, InternetConnectivityStatus.Online);
};
private static _onOffline = async () => {
await NativeApp.setConnectivity(OverriddenBy.Browser, InternetConnectivityStatus.Offline);
};
private static async setConnectivity(by: OverriddenBy, status: InternetConnectivityStatus) {
await this.nativeAppIpc.overrideInternetConnectivity(by, status);
}
private static hookBrowserConnectivityEvents() {
if (typeof window === "object" && window.ononline && window.onoffline) {
window.addEventListener("online", this._onOnline);
window.addEventListener("offline", this._onOffline);
}
}
private static unhookBrowserConnectivityEvents() {
if (typeof window === "object" && window.ononline && window.onoffline) {
window.removeEventListener("online", this._onOnline);
window.removeEventListener("offline", this._onOffline);
}
}
/** event called when internet connectivity changes, if known */
public static onInternetConnectivityChanged = new BeEvent<(status: InternetConnectivityStatus) => void>();
/** determine whether the app currently has internet connectivity, if known */
public static async checkInternetConnectivity(): Promise<InternetConnectivityStatus> {
return this.nativeAppIpc.checkInternetConnectivity();
}
/** @internal */
public static async overrideInternetConnectivity(status: InternetConnectivityStatus): Promise<void> {
return this.nativeAppIpc.overrideInternetConnectivity(OverriddenBy.User, status);
}
private static _isValid = false;
public static get isValid(): boolean { return this._isValid; }
/**
* This is called by either ElectronApp.startup or MobileApp.startup - it should not be called directly
* @internal
*/
public static async startup(ipc: IpcSocketFrontend, opts?: NativeAppOpts) {
await IpcApp.startup(ipc, opts);
if (this._isValid)
return;
this._isValid = true;
this._removeAppNotify = NativeAppNotifyHandler.register(); // receives notifications from backend
NativeApp.hookBrowserConnectivityEvents();
// initialize current online state.
if (window.navigator.onLine) {
await this.setConnectivity(OverriddenBy.Browser, window.navigator.onLine ? InternetConnectivityStatus.Online : InternetConnectivityStatus.Offline);
}
}
/** @internal */
public static async shutdown() {
this._removeAppNotify?.();
NativeApp.unhookBrowserConnectivityEvents();
await NativeAppLogger.flush();
await IpcApp.shutdown();
this._isValid = false;
}
public static async requestDownloadBriefcase(iTwinId: string, iModelId: string, downloadOptions: DownloadBriefcaseOptions,
asOf?: IModelVersion): Promise<BriefcaseDownloader>;
/**
* @deprecated in 3.6. `progress` argument is now deprecated, use [[DownloadBriefcaseOptions.progressCallback]] instead.
*/
public static async requestDownloadBriefcase(iTwinId: string, iModelId: string, downloadOptions: DownloadBriefcaseOptions,
// eslint-disable-next-line @typescript-eslint/unified-signatures, deprecation/deprecation
asOf?: IModelVersion, progress?: ProgressCallback): Promise<BriefcaseDownloader>;
public static async requestDownloadBriefcase(
iTwinId: string,
iModelId: string,
downloadOptions: DownloadBriefcaseOptions,
asOf: IModelVersion = IModelVersion.latest(),
progress?: ProgressCallback, // eslint-disable-line deprecation/deprecation
): Promise<BriefcaseDownloader> {
const shouldReportProgress = !!progress || !!downloadOptions.progressCallback;
let stopProgressEvents = () => { };
if (shouldReportProgress) {
const handleProgress = (_evt: Event, data: { loaded: number, total: number }) => {
progress?.(data);
downloadOptions.progressCallback?.(data);
};
stopProgressEvents = IpcApp.addListener(`nativeApp.progress-${iModelId}`, handleProgress);
}
const briefcaseId = (undefined !== downloadOptions.briefcaseId) ? downloadOptions.briefcaseId :
(downloadOptions.syncMode === SyncMode.PullOnly ? 0 : await this.nativeAppIpc.acquireNewBriefcaseId(iModelId));
const fileName = downloadOptions.fileName ?? await this.getBriefcaseFileName({ briefcaseId, iModelId });
const requestProps: RequestNewBriefcaseProps = { iModelId, briefcaseId, iTwinId, asOf: asOf.toJSON(), fileName };
const doDownload = async (): Promise<void> => {
try {
await this.nativeAppIpc.downloadBriefcase(requestProps, shouldReportProgress, downloadOptions.progressInterval);
} finally {
stopProgressEvents();
}
};
const requestCancel = async (): Promise<boolean> => {
const status = await this.nativeAppIpc.requestCancelDownloadBriefcase(fileName);
if (status)
stopProgressEvents();
return status;
};
return { briefcaseId, fileName, downloadPromise: doDownload(), requestCancel };
}
/** Get the full path filename for a briefcase within the briefcase cache */
public static async getBriefcaseFileName(props: BriefcaseProps): Promise<string> {
return this.nativeAppIpc.getBriefcaseFileName(props);
}
/** Delete an existing briefcase
* @param fileName the briefcase fileName
*/
public static async deleteBriefcase(fileName: string): Promise<void> {
await this.nativeAppIpc.deleteBriefcaseFiles(fileName);
}
/** Get a list of all briefcase files held in the local briefcase cache directory */
public static async getCachedBriefcases(iModelId?: GuidString): Promise<LocalBriefcaseProps[]> {
return this.nativeAppIpc.getCachedBriefcases(iModelId);
}
/**
* Open a [[Storage]]. Creates a new Storage with that name if it does not already exist.
* @param name Should be a local filename without an extension.
* @returns a Promise for the [[Storage]].
*/
public static async openStorage(name: string): Promise<Storage> {
if (this._storages.has(name))
return this._storages.get(name)!;
const storage = new Storage(await this.nativeAppIpc.storageMgrOpen(name));
this._storages.set(storage.id, storage);
return storage;
}
/**
* Close a Storage and optionally delete it.
* @param storage normally not call directly instead use Storage.close()
* @param deleteStorage if true, delete the storage from disk after closing it.
*/
public static async closeStorage(storage: Storage, deleteStorage: boolean = false): Promise<void> {
if (!this._storages.has(storage.id))
throw new Error(`Storage [Id=${storage.id}] not open`);
await this.nativeAppIpc.storageMgrClose(storage.id, deleteStorage);
this._storages.delete(storage.id);
}
/** Get the list of existing Storages on the local disk. */
public static async getStorageNames(): Promise<string[]> {
return NativeApp.nativeAppIpc.storageMgrNames();
}
}
/**
* A local disk-based cache for key value pairs for NativeApps.
* @note This should be used only for local caching, since its not guaranteed to exist permanently.
* @public
*/
export class Storage {
constructor(public readonly id: string) { }
/** get the type of a value for a key, or undefined if not present. */
public async getValueType(key: string): Promise<"number" | "string" | "boolean" | "Uint8Array" | "null" | undefined> {
return NativeApp.nativeAppIpc.storageGetValueType(this.id, key);
}
/** Get the value for a key */
public async getData(key: string): Promise<StorageValue> {
return NativeApp.nativeAppIpc.storageGet(this.id, key);
}
/** Set value for a key */
public async setData(key: string, value: StorageValue): Promise<void> {
return NativeApp.nativeAppIpc.storageSet(this.id, key, value);
}
/**
* Return an array of all keys in this Storage.
* @note This can be expensive, depending on the number of keys present.
*/
public async getKeys(): Promise<string[]> {
return NativeApp.nativeAppIpc.storageKeys(this.id);
}
/** Remove a key and its data. */
public async removeData(key: string): Promise<void> {
return NativeApp.nativeAppIpc.storageRemove(this.id, key);
}
/** Remove all keys and their data. */
public async removeAll(): Promise<void> {
return NativeApp.nativeAppIpc.storageRemoveAll(this.id);
}
}