@itwin/core-mobile 4.0.0-dev.8 → 4.0.0-dev.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/README.md +1 -1
  3. package/lib/cjs/MobileBackend.d.ts +7 -7
  4. package/lib/cjs/MobileBackend.js +28 -24
  5. package/lib/cjs/MobileBackend.js.map +1 -1
  6. package/lib/cjs/MobileFrontend.d.ts +5 -5
  7. package/lib/cjs/MobileFrontend.js +25 -21
  8. package/lib/cjs/MobileFrontend.js.map +1 -1
  9. package/lib/cjs/__DOC_ONLY__.d.ts +33 -33
  10. package/lib/cjs/__DOC_ONLY__.js +56 -52
  11. package/lib/cjs/__DOC_ONLY__.js.map +1 -1
  12. package/lib/cjs/backend/MobileAuthorizationBackend.d.ts +17 -17
  13. package/lib/cjs/backend/MobileAuthorizationBackend.js +46 -46
  14. package/lib/cjs/backend/MobileAuthorizationBackend.js.map +1 -1
  15. package/lib/cjs/backend/MobileFileHandler.d.ts +112 -112
  16. package/lib/cjs/backend/MobileFileHandler.d.ts.map +1 -1
  17. package/lib/cjs/backend/MobileFileHandler.js +263 -263
  18. package/lib/cjs/backend/MobileFileHandler.js.map +1 -1
  19. package/lib/cjs/backend/MobileHost.d.ts +72 -72
  20. package/lib/cjs/backend/MobileHost.d.ts.map +1 -1
  21. package/lib/cjs/backend/MobileHost.js +149 -156
  22. package/lib/cjs/backend/MobileHost.js.map +1 -1
  23. package/lib/cjs/backend/MobileRpcServer.d.ts +19 -19
  24. package/lib/cjs/backend/MobileRpcServer.js +156 -156
  25. package/lib/cjs/backend/MobileRpcServer.js.map +1 -1
  26. package/lib/cjs/backend/Request.d.ts +117 -149
  27. package/lib/cjs/backend/Request.d.ts.map +1 -1
  28. package/lib/cjs/backend/Request.js +255 -267
  29. package/lib/cjs/backend/Request.js.map +1 -1
  30. package/lib/cjs/common/MobileAppChannel.d.ts +4 -4
  31. package/lib/cjs/common/MobileAppChannel.js +11 -11
  32. package/lib/cjs/common/MobileAppChannel.js.map +1 -1
  33. package/lib/cjs/common/MobileAppProps.d.ts +35 -35
  34. package/lib/cjs/common/MobileAppProps.d.ts.map +1 -1
  35. package/lib/cjs/common/MobileAppProps.js +26 -26
  36. package/lib/cjs/common/MobileAppProps.js.map +1 -1
  37. package/lib/cjs/common/MobileEventLoop.d.ts +11 -11
  38. package/lib/cjs/common/MobileEventLoop.js +30 -30
  39. package/lib/cjs/common/MobileEventLoop.js.map +1 -1
  40. package/lib/cjs/common/MobileIpc.d.ts +17 -17
  41. package/lib/cjs/common/MobileIpc.js +70 -70
  42. package/lib/cjs/common/MobileIpc.js.map +1 -1
  43. package/lib/cjs/common/MobilePush.d.ts +20 -20
  44. package/lib/cjs/common/MobilePush.js +53 -53
  45. package/lib/cjs/common/MobilePush.js.map +1 -1
  46. package/lib/cjs/common/MobileRpcManager.d.ts +40 -40
  47. package/lib/cjs/common/MobileRpcManager.js +110 -110
  48. package/lib/cjs/common/MobileRpcManager.js.map +1 -1
  49. package/lib/cjs/common/MobileRpcProtocol.d.ts +58 -58
  50. package/lib/cjs/common/MobileRpcProtocol.d.ts.map +1 -1
  51. package/lib/cjs/common/MobileRpcProtocol.js +277 -277
  52. package/lib/cjs/common/MobileRpcProtocol.js.map +1 -1
  53. package/lib/cjs/common/MobileRpcRequest.d.ts +20 -20
  54. package/lib/cjs/common/MobileRpcRequest.js +50 -50
  55. package/lib/cjs/common/MobileRpcRequest.js.map +1 -1
  56. package/lib/cjs/frontend/MobileApp.d.ts +23 -23
  57. package/lib/cjs/frontend/MobileApp.d.ts.map +1 -1
  58. package/lib/cjs/frontend/MobileApp.js +79 -80
  59. package/lib/cjs/frontend/MobileApp.js.map +1 -1
  60. package/lib/cjs/frontend/MobileAuthorizationFrontend.d.ts +17 -17
  61. package/lib/cjs/frontend/MobileAuthorizationFrontend.d.ts.map +1 -1
  62. package/lib/cjs/frontend/MobileAuthorizationFrontend.js +54 -46
  63. package/lib/cjs/frontend/MobileAuthorizationFrontend.js.map +1 -1
  64. package/lib/cjs/test/ios/MobilePlatform.test.d.ts +1 -1
  65. package/lib/cjs/test/ios/MobilePlatform.test.js +84 -84
  66. package/lib/cjs/test/ios/MobilePlatform.test.js.map +1 -1
  67. package/package.json +18 -26
@@ -1,264 +1,264 @@
1
- "use strict";
2
- /*---------------------------------------------------------------------------------------------
3
- * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
- * See LICENSE.md in the project root for license terms and full copyright notice.
5
- *--------------------------------------------------------------------------------------------*/
6
- /** @packageDocumentation
7
- * @module iModelHub
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.MobileFileHandler = exports.SasUrlExpired = exports.DownloadFailed = exports.UserCancelledError = void 0;
11
- const fs = require("fs");
12
- const path = require("path");
13
- const core_bentley_1 = require("@itwin/core-bentley");
14
- const Request_1 = require("./Request");
15
- const MobileHost_1 = require("./MobileHost");
16
- const js_base64_1 = require("js-base64");
17
- const loggerCategory = "mobile.filehandler";
18
- const defined = (argumentName, argument, allowEmpty = false) => {
19
- if (argument === undefined || argument === null || (argument === "" && !allowEmpty))
20
- throw Error(`Argument ${argumentName} is null or undefined`);
21
- };
22
- /** Error thrown when user cancelled operation
23
- * @internal
24
- */
25
- class UserCancelledError extends core_bentley_1.BentleyError {
26
- constructor(errorNumber, message, getMetaData) {
27
- super(errorNumber, message, getMetaData);
28
- this.name = "User cancelled operation";
29
- }
30
- }
31
- exports.UserCancelledError = UserCancelledError;
32
- /** Error thrown fail to download file. ErrorNumber will correspond to HTTP error code.
33
- * @internal
34
- */
35
- class DownloadFailed extends core_bentley_1.BentleyError {
36
- constructor(errorNumber, message, getMetaData) {
37
- super(errorNumber, message, getMetaData);
38
- this.name = "Fail to download file";
39
- }
40
- }
41
- exports.DownloadFailed = DownloadFailed;
42
- /** Error thrown when sas-url provided for download has expired
43
- * @internal
44
- */
45
- class SasUrlExpired extends core_bentley_1.BentleyError {
46
- constructor(errorNumber, message, getMetaData) {
47
- super(errorNumber, message, getMetaData);
48
- this.name = "SaS url has expired";
49
- }
50
- }
51
- exports.SasUrlExpired = SasUrlExpired;
52
- /**
53
- * Provides methods to work with the file system and azure storage. An instance of this class has to be provided to [[IModelClient]] for file upload/download methods to work.
54
- * @internal
55
- */
56
- class MobileFileHandler {
57
- /**
58
- * Constructor for MobileFileHandler.
59
- */
60
- constructor() {
61
- }
62
- /** Create a directory, recursively setting up the path as necessary. */
63
- static makeDirectoryRecursive(dirPath) {
64
- if (fs.existsSync(dirPath))
65
- return;
66
- MobileFileHandler.makeDirectoryRecursive(path.dirname(dirPath));
67
- fs.mkdirSync(dirPath);
68
- }
69
- /**
70
- * Make url safe for logging by removing sensitive information
71
- * @param url input url that will be strip of search and query parameters and replace them by ... for security reason
72
- */
73
- static getSafeUrlForLogging(url) {
74
- const safeToLogDownloadUrl = new URL(url);
75
- if (safeToLogDownloadUrl.search && safeToLogDownloadUrl.search.length > 0)
76
- safeToLogDownloadUrl.search = "...";
77
- if (safeToLogDownloadUrl.hash && safeToLogDownloadUrl.hash.length > 0)
78
- safeToLogDownloadUrl.hash = "...";
79
- return safeToLogDownloadUrl.toString();
80
- }
81
- /**
82
- * Check if sas url has expired
83
- * @param download sas url for download
84
- * @param futureSeconds should be valid in future for given seconds.
85
- */
86
- static isUrlExpired(downloadUrl, futureSeconds) {
87
- const sasUrl = new URL(downloadUrl);
88
- const se = sasUrl.searchParams.get("se");
89
- if (se) {
90
- const expiryUTC = new Date(se);
91
- const now = new Date();
92
- const currentUTC = new Date(now.toUTCString());
93
- if (futureSeconds) {
94
- currentUTC.setSeconds(futureSeconds + currentUTC.getSeconds());
95
- }
96
- return expiryUTC <= currentUTC;
97
- }
98
- return false;
99
- }
100
- /**
101
- * Download a file from AzureBlobStorage for iModelHub. Creates the directory containing the file if necessary. If there is an error in the operation, incomplete file is deleted from disk.
102
- * @param downloadUrl URL to download file from.
103
- * @param downloadToPathname Pathname to download the file to.
104
- * @param fileSize Size of the file that's being downloaded.
105
- * @param progressCallback Callback for tracking progress.
106
- * @throws [[IModelHubClientError]] with [IModelHubStatus.UndefinedArgumentError]($bentley) if one of the arguments is undefined or empty.
107
- */
108
- async downloadFile(_accessToken, downloadUrl, downloadToPathname, fileSize, progressCallback, cancelRequest) {
109
- // strip search and hash parameters from download Url for logging purpose
110
- const safeToLogUrl = MobileFileHandler.getSafeUrlForLogging(downloadUrl);
111
- core_bentley_1.Logger.logInfo(loggerCategory, `Downloading file from ${safeToLogUrl}`);
112
- defined("downloadUrl", downloadUrl);
113
- defined("downloadToPathname", downloadToPathname);
114
- if (MobileFileHandler.isUrlExpired(downloadUrl)) {
115
- core_bentley_1.Logger.logError(loggerCategory, `Sas url has expired ${safeToLogUrl}`);
116
- throw new SasUrlExpired(403, "Download URL has expired");
117
- }
118
- if (fs.existsSync(downloadToPathname))
119
- fs.unlinkSync(downloadToPathname);
120
- MobileFileHandler.makeDirectoryRecursive(path.dirname(downloadToPathname));
121
- try {
122
- await MobileHost_1.MobileHost.downloadFile(downloadUrl, downloadToPathname, progressCallback, cancelRequest);
123
- }
124
- catch (err) {
125
- if (fs.existsSync(downloadToPathname))
126
- fs.unlinkSync(downloadToPathname); // Just in case there was a partial download, delete the file
127
- if (!(err instanceof UserCancelledError))
128
- core_bentley_1.Logger.logError(loggerCategory, `Error downloading file`);
129
- throw err;
130
- }
131
- if (fileSize && fs.existsSync(downloadToPathname)) {
132
- if (fs.lstatSync(downloadToPathname).size !== fileSize) {
133
- fs.unlinkSync(downloadToPathname);
134
- core_bentley_1.Logger.logError(loggerCategory, `Downloaded file is of incorrect size ${safeToLogUrl}`);
135
- throw new DownloadFailed(403, "Download failed. Expected filesize does not match");
136
- }
137
- }
138
- core_bentley_1.Logger.logTrace(loggerCategory, `Downloaded file from ${safeToLogUrl}`);
139
- }
140
- /** Get encoded block id from its number. */
141
- getBlockId(blockId) {
142
- return js_base64_1.Base64.encode(blockId.toString(16).padStart(5, "0"));
143
- }
144
- async uploadChunk(_accessToken, uploadUrlString, fileDescriptor, blockId, callback) {
145
- const chunkSize = 4 * 1024 * 1024;
146
- let buffer = Buffer.alloc(chunkSize);
147
- const bytesRead = fs.readSync(fileDescriptor, buffer, 0, chunkSize, chunkSize * blockId);
148
- buffer = buffer.subarray(0, bytesRead);
149
- const options = {
150
- method: "PUT",
151
- headers: {
152
- "x-ms-blob-type": "BlockBlob",
153
- "Content-Type": "application/octet-stream",
154
- "Content-Length": buffer.length, // eslint-disable-line @typescript-eslint/naming-convention
155
- },
156
- body: buffer,
157
- progressCallback: callback,
158
- agent: this.agent,
159
- timeout: {
160
- deadline: 60000,
161
- response: 60000,
162
- },
163
- };
164
- const uploadUrl = `${uploadUrlString}&comp=block&blockid=${this.getBlockId(blockId)}`;
165
- await (0, Request_1.request)(uploadUrl, options);
166
- }
167
- /**
168
- * Upload a file to AzureBlobStorage for iModelHub.
169
- * @param uploadUrl URL to upload the file to.
170
- * @param uploadFromPathname Pathname to upload the file from.
171
- * @param progressCallback Callback for tracking progress.
172
- * @throws [[IModelHubClientError]] with [IModelHubStatus.UndefinedArgumentError]($bentley) if one of the arguments is undefined or empty.
173
- */
174
- async uploadFile(accessToken, uploadUrlString, uploadFromPathname, progressCallback) {
175
- const safeToLogUrl = MobileFileHandler.getSafeUrlForLogging(uploadUrlString);
176
- core_bentley_1.Logger.logTrace(loggerCategory, `Uploading file to ${safeToLogUrl}`);
177
- defined("uploadUrlString", uploadUrlString);
178
- defined("uploadFromPathname", uploadFromPathname);
179
- const fileSize = this.getFileSize(uploadFromPathname);
180
- const file = fs.openSync(uploadFromPathname, "r");
181
- const chunkSize = 4 * 1024 * 1024;
182
- try {
183
- let blockList = '<?xml version=\"1.0\" encoding=\"utf-8\"?><BlockList>';
184
- let i = 0;
185
- const callback = (progress) => {
186
- const uploaded = i * chunkSize + progress.loaded;
187
- if (progressCallback)
188
- progressCallback({ loaded: uploaded, percent: uploaded / fileSize, total: fileSize });
189
- };
190
- for (; i * chunkSize < fileSize; ++i) {
191
- await this.uploadChunk(accessToken, uploadUrlString, file, i, progressCallback ? callback : undefined);
192
- blockList += `<Latest>${this.getBlockId(i)}</Latest>`;
193
- }
194
- blockList += "</BlockList>";
195
- const options = {
196
- method: "PUT",
197
- headers: {
198
- "Content-Type": "application/xml",
199
- "Content-Length": blockList.length, // eslint-disable-line @typescript-eslint/naming-convention
200
- },
201
- body: blockList,
202
- agent: this.agent,
203
- timeout: {
204
- response: 5000,
205
- deadline: 60000,
206
- },
207
- };
208
- const uploadUrl = `${uploadUrlString}&comp=blocklist`;
209
- await (0, Request_1.request)(uploadUrl, options);
210
- }
211
- finally {
212
- fs.closeSync(file);
213
- }
214
- }
215
- /**
216
- * Get size of a file.
217
- * @param filePath Path of the file.
218
- * @returns Size of the file.
219
- */
220
- getFileSize(filePath) {
221
- return fs.statSync(filePath).size;
222
- }
223
- /**
224
- * Check if path is a directory.
225
- * @param filePath Path of the file.
226
- * @returns True if path is directory.
227
- */
228
- isDirectory(filePath) {
229
- return fs.statSync(filePath).isDirectory();
230
- }
231
- /**
232
- * Check if path exists.
233
- * @param filePath Path of the file.
234
- * @returns True if path exists.
235
- */
236
- exists(filePath) {
237
- return fs.existsSync(filePath);
238
- }
239
- /**
240
- * Deletes file.
241
- * @param filePath Path of the file.
242
- */
243
- unlink(filePath) {
244
- fs.unlinkSync(filePath);
245
- }
246
- /**
247
- * Get file name from the path.
248
- * @param filePath Path of the file.
249
- * @returns File name.
250
- */
251
- basename(filePath) {
252
- return path.basename(filePath);
253
- }
254
- /**
255
- * Join multiple strings into a single path.
256
- * @param paths Strings to join.
257
- * @returns Joined path.
258
- */
259
- join(...paths) {
260
- return path.join(...paths);
261
- }
262
- }
263
- exports.MobileFileHandler = MobileFileHandler;
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
+ * See LICENSE.md in the project root for license terms and full copyright notice.
5
+ *--------------------------------------------------------------------------------------------*/
6
+ /** @packageDocumentation
7
+ * @module iModelHub
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.MobileFileHandler = exports.SasUrlExpired = exports.DownloadFailed = exports.UserCancelledError = void 0;
11
+ const node_buffer_1 = require("node:buffer");
12
+ const fs = require("node:fs");
13
+ const path = require("node:path");
14
+ const core_bentley_1 = require("@itwin/core-bentley");
15
+ const Request_1 = require("./Request");
16
+ const MobileHost_1 = require("./MobileHost");
17
+ const loggerCategory = "mobile.filehandler";
18
+ const defined = (argumentName, argument, allowEmpty = false) => {
19
+ if (argument === undefined || argument === null || (argument === "" && !allowEmpty))
20
+ throw Error(`Argument ${argumentName} is null or undefined`);
21
+ };
22
+ /** Error thrown when user cancelled operation
23
+ * @internal
24
+ */
25
+ class UserCancelledError extends core_bentley_1.BentleyError {
26
+ constructor(errorNumber, message, getMetaData) {
27
+ super(errorNumber, message, getMetaData);
28
+ this.name = "User cancelled operation";
29
+ }
30
+ }
31
+ exports.UserCancelledError = UserCancelledError;
32
+ /** Error thrown fail to download file. ErrorNumber will correspond to HTTP error code.
33
+ * @internal
34
+ */
35
+ class DownloadFailed extends core_bentley_1.BentleyError {
36
+ constructor(errorNumber, message, getMetaData) {
37
+ super(errorNumber, message, getMetaData);
38
+ this.name = "Fail to download file";
39
+ }
40
+ }
41
+ exports.DownloadFailed = DownloadFailed;
42
+ /** Error thrown when sas-url provided for download has expired
43
+ * @internal
44
+ */
45
+ class SasUrlExpired extends core_bentley_1.BentleyError {
46
+ constructor(errorNumber, message, getMetaData) {
47
+ super(errorNumber, message, getMetaData);
48
+ this.name = "SaS url has expired";
49
+ }
50
+ }
51
+ exports.SasUrlExpired = SasUrlExpired;
52
+ /**
53
+ * Provides methods to work with the file system and azure storage. An instance of this class has to be provided to [[IModelClient]] for file upload/download methods to work.
54
+ * @internal
55
+ */
56
+ class MobileFileHandler {
57
+ /**
58
+ * Constructor for MobileFileHandler.
59
+ */
60
+ constructor() {
61
+ }
62
+ /** Create a directory, recursively setting up the path as necessary. */
63
+ static makeDirectoryRecursive(dirPath) {
64
+ if (fs.existsSync(dirPath))
65
+ return;
66
+ MobileFileHandler.makeDirectoryRecursive(path.dirname(dirPath));
67
+ fs.mkdirSync(dirPath);
68
+ }
69
+ /**
70
+ * Make url safe for logging by removing sensitive information
71
+ * @param url input url that will be strip of search and query parameters and replace them by ... for security reason
72
+ */
73
+ static getSafeUrlForLogging(url) {
74
+ const safeToLogDownloadUrl = new URL(url);
75
+ if (safeToLogDownloadUrl.search && safeToLogDownloadUrl.search.length > 0)
76
+ safeToLogDownloadUrl.search = "...";
77
+ if (safeToLogDownloadUrl.hash && safeToLogDownloadUrl.hash.length > 0)
78
+ safeToLogDownloadUrl.hash = "...";
79
+ return safeToLogDownloadUrl.toString();
80
+ }
81
+ /**
82
+ * Check if sas url has expired
83
+ * @param download sas url for download
84
+ * @param futureSeconds should be valid in future for given seconds.
85
+ */
86
+ static isUrlExpired(downloadUrl, futureSeconds) {
87
+ const sasUrl = new URL(downloadUrl);
88
+ const se = sasUrl.searchParams.get("se");
89
+ if (se) {
90
+ const expiryUTC = new Date(se);
91
+ const now = new Date();
92
+ const currentUTC = new Date(now.toUTCString());
93
+ if (futureSeconds) {
94
+ currentUTC.setSeconds(futureSeconds + currentUTC.getSeconds());
95
+ }
96
+ return expiryUTC <= currentUTC;
97
+ }
98
+ return false;
99
+ }
100
+ /**
101
+ * Download a file from AzureBlobStorage for iModelHub. Creates the directory containing the file if necessary. If there is an error in the operation, incomplete file is deleted from disk.
102
+ * @param downloadUrl URL to download file from.
103
+ * @param downloadToPathname Pathname to download the file to.
104
+ * @param fileSize Size of the file that's being downloaded.
105
+ * @param progressCallback Callback for tracking progress.
106
+ * @throws [[IModelHubClientError]] with [IModelHubStatus.UndefinedArgumentError]($bentley) if one of the arguments is undefined or empty.
107
+ */
108
+ async downloadFile(_accessToken, downloadUrl, downloadToPathname, fileSize, progressCallback, cancelRequest) {
109
+ // strip search and hash parameters from download Url for logging purpose
110
+ const safeToLogUrl = MobileFileHandler.getSafeUrlForLogging(downloadUrl);
111
+ core_bentley_1.Logger.logInfo(loggerCategory, `Downloading file from ${safeToLogUrl}`);
112
+ defined("downloadUrl", downloadUrl);
113
+ defined("downloadToPathname", downloadToPathname);
114
+ if (MobileFileHandler.isUrlExpired(downloadUrl)) {
115
+ core_bentley_1.Logger.logError(loggerCategory, `Sas url has expired ${safeToLogUrl}`);
116
+ throw new SasUrlExpired(403, "Download URL has expired");
117
+ }
118
+ if (fs.existsSync(downloadToPathname))
119
+ fs.unlinkSync(downloadToPathname);
120
+ MobileFileHandler.makeDirectoryRecursive(path.dirname(downloadToPathname));
121
+ try {
122
+ await MobileHost_1.MobileHost.downloadFile(downloadUrl, downloadToPathname, progressCallback, cancelRequest);
123
+ }
124
+ catch (err) {
125
+ if (fs.existsSync(downloadToPathname))
126
+ fs.unlinkSync(downloadToPathname); // Just in case there was a partial download, delete the file
127
+ if (!(err instanceof UserCancelledError))
128
+ core_bentley_1.Logger.logError(loggerCategory, `Error downloading file`);
129
+ throw err;
130
+ }
131
+ if (fileSize && fs.existsSync(downloadToPathname)) {
132
+ if (fs.lstatSync(downloadToPathname).size !== fileSize) {
133
+ fs.unlinkSync(downloadToPathname);
134
+ core_bentley_1.Logger.logError(loggerCategory, `Downloaded file is of incorrect size ${safeToLogUrl}`);
135
+ throw new DownloadFailed(403, "Download failed. Expected filesize does not match");
136
+ }
137
+ }
138
+ core_bentley_1.Logger.logTrace(loggerCategory, `Downloaded file from ${safeToLogUrl}`);
139
+ }
140
+ /** Get encoded block id from its number. */
141
+ getBlockId(blockId) {
142
+ return node_buffer_1.Buffer.from(blockId.toString(16).padStart(5, "0")).toString("base64");
143
+ }
144
+ async uploadChunk(_accessToken, uploadUrlString, fileDescriptor, blockId, callback) {
145
+ const chunkSize = 4 * 1024 * 1024;
146
+ let buffer = node_buffer_1.Buffer.alloc(chunkSize);
147
+ const bytesRead = fs.readSync(fileDescriptor, buffer, 0, chunkSize, chunkSize * blockId);
148
+ buffer = buffer.subarray(0, bytesRead);
149
+ const options = {
150
+ method: "PUT",
151
+ headers: {
152
+ "x-ms-blob-type": "BlockBlob",
153
+ "Content-Type": "application/octet-stream",
154
+ "Content-Length": buffer.length, // eslint-disable-line @typescript-eslint/naming-convention
155
+ },
156
+ body: buffer,
157
+ progressCallback: callback,
158
+ agent: this.agent,
159
+ timeout: {
160
+ deadline: 60000,
161
+ response: 60000,
162
+ },
163
+ };
164
+ const uploadUrl = `${uploadUrlString}&comp=block&blockid=${this.getBlockId(blockId)}`;
165
+ await (0, Request_1.request)(uploadUrl, options);
166
+ }
167
+ /**
168
+ * Upload a file to AzureBlobStorage for iModelHub.
169
+ * @param uploadUrl URL to upload the file to.
170
+ * @param uploadFromPathname Pathname to upload the file from.
171
+ * @param progressCallback Callback for tracking progress.
172
+ * @throws [[IModelHubClientError]] with [IModelHubStatus.UndefinedArgumentError]($bentley) if one of the arguments is undefined or empty.
173
+ */
174
+ async uploadFile(accessToken, uploadUrlString, uploadFromPathname, progressCallback) {
175
+ const safeToLogUrl = MobileFileHandler.getSafeUrlForLogging(uploadUrlString);
176
+ core_bentley_1.Logger.logTrace(loggerCategory, `Uploading file to ${safeToLogUrl}`);
177
+ defined("uploadUrlString", uploadUrlString);
178
+ defined("uploadFromPathname", uploadFromPathname);
179
+ const fileSize = this.getFileSize(uploadFromPathname);
180
+ const file = fs.openSync(uploadFromPathname, "r");
181
+ const chunkSize = 4 * 1024 * 1024;
182
+ try {
183
+ let blockList = '<?xml version=\"1.0\" encoding=\"utf-8\"?><BlockList>';
184
+ let i = 0;
185
+ const callback = (progress) => {
186
+ const uploaded = i * chunkSize + progress.loaded;
187
+ if (progressCallback)
188
+ progressCallback({ loaded: uploaded, percent: uploaded / fileSize, total: fileSize });
189
+ };
190
+ for (; i * chunkSize < fileSize; ++i) {
191
+ await this.uploadChunk(accessToken, uploadUrlString, file, i, progressCallback ? callback : undefined);
192
+ blockList += `<Latest>${this.getBlockId(i)}</Latest>`;
193
+ }
194
+ blockList += "</BlockList>";
195
+ const options = {
196
+ method: "PUT",
197
+ headers: {
198
+ "Content-Type": "application/xml",
199
+ "Content-Length": blockList.length, // eslint-disable-line @typescript-eslint/naming-convention
200
+ },
201
+ body: blockList,
202
+ agent: this.agent,
203
+ timeout: {
204
+ response: 5000,
205
+ deadline: 60000,
206
+ },
207
+ };
208
+ const uploadUrl = `${uploadUrlString}&comp=blocklist`;
209
+ await (0, Request_1.request)(uploadUrl, options);
210
+ }
211
+ finally {
212
+ fs.closeSync(file);
213
+ }
214
+ }
215
+ /**
216
+ * Get size of a file.
217
+ * @param filePath Path of the file.
218
+ * @returns Size of the file.
219
+ */
220
+ getFileSize(filePath) {
221
+ return fs.statSync(filePath).size;
222
+ }
223
+ /**
224
+ * Check if path is a directory.
225
+ * @param filePath Path of the file.
226
+ * @returns True if path is directory.
227
+ */
228
+ isDirectory(filePath) {
229
+ return fs.statSync(filePath).isDirectory();
230
+ }
231
+ /**
232
+ * Check if path exists.
233
+ * @param filePath Path of the file.
234
+ * @returns True if path exists.
235
+ */
236
+ exists(filePath) {
237
+ return fs.existsSync(filePath);
238
+ }
239
+ /**
240
+ * Deletes file.
241
+ * @param filePath Path of the file.
242
+ */
243
+ unlink(filePath) {
244
+ fs.unlinkSync(filePath);
245
+ }
246
+ /**
247
+ * Get file name from the path.
248
+ * @param filePath Path of the file.
249
+ * @returns File name.
250
+ */
251
+ basename(filePath) {
252
+ return path.basename(filePath);
253
+ }
254
+ /**
255
+ * Join multiple strings into a single path.
256
+ * @param paths Strings to join.
257
+ * @returns Joined path.
258
+ */
259
+ join(...paths) {
260
+ return path.join(...paths);
261
+ }
262
+ }
263
+ exports.MobileFileHandler = MobileFileHandler;
264
264
  //# sourceMappingURL=MobileFileHandler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MobileFileHandler.js","sourceRoot":"","sources":["../../../src/backend/MobileFileHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n/** @packageDocumentation\r\n * @module iModelHub\r\n */\r\n\r\nimport * as fs from \"fs\";\r\nimport * as https from \"https\";\r\nimport * as path from \"path\";\r\nimport { AccessToken, BentleyError, GetMetaDataFunction, Logger } from \"@itwin/core-bentley\";\r\nimport { ProgressCallback, ProgressInfo, request, RequestOptions } from \"./Request\";\r\nimport { MobileHost } from \"./MobileHost\";\r\nimport { Base64 } from \"js-base64\";\r\n\r\nconst loggerCategory: string = \"mobile.filehandler\";\r\n\r\nconst defined = (argumentName: string, argument?: any, allowEmpty: boolean = false) => {\r\n if (argument === undefined || argument === null || (argument === \"\" && !allowEmpty))\r\n throw Error(`Argument ${argumentName} is null or undefined`);\r\n};\r\n\r\n/** Interface to cancel a request\r\n * @beta\r\n */\r\nexport interface CancelRequest {\r\n /** Returns true if cancel request was acknowledged */\r\n cancel: () => boolean;\r\n}\r\n\r\n/** Error thrown when user cancelled operation\r\n * @internal\r\n */\r\nexport class UserCancelledError extends BentleyError {\r\n public constructor(errorNumber: number, message: string, getMetaData?: GetMetaDataFunction) {\r\n super(errorNumber, message, getMetaData);\r\n this.name = \"User cancelled operation\";\r\n }\r\n}\r\n\r\n/** Error thrown fail to download file. ErrorNumber will correspond to HTTP error code.\r\n * @internal\r\n */\r\nexport class DownloadFailed extends BentleyError {\r\n public constructor(errorNumber: number, message: string, getMetaData?: GetMetaDataFunction) {\r\n super(errorNumber, message, getMetaData);\r\n this.name = \"Fail to download file\";\r\n }\r\n}\r\n\r\n/** Error thrown when sas-url provided for download has expired\r\n * @internal\r\n */\r\nexport class SasUrlExpired extends BentleyError {\r\n public constructor(errorNumber: number, message: string, getMetaData?: GetMetaDataFunction) {\r\n super(errorNumber, message, getMetaData);\r\n this.name = \"SaS url has expired\";\r\n }\r\n}\r\n\r\n/**\r\n * Provides methods to work with the file system and azure storage. An instance of this class has to be provided to [[IModelClient]] for file upload/download methods to work.\r\n * @internal\r\n */\r\nexport class MobileFileHandler {\r\n /** @internal */\r\n public agent?: https.Agent;\r\n\r\n /**\r\n * Constructor for MobileFileHandler.\r\n */\r\n constructor() {\r\n }\r\n\r\n /** Create a directory, recursively setting up the path as necessary. */\r\n private static makeDirectoryRecursive(dirPath: string) {\r\n if (fs.existsSync(dirPath))\r\n return;\r\n\r\n MobileFileHandler.makeDirectoryRecursive(path.dirname(dirPath));\r\n fs.mkdirSync(dirPath);\r\n }\r\n\r\n /**\r\n * Make url safe for logging by removing sensitive information\r\n * @param url input url that will be strip of search and query parameters and replace them by ... for security reason\r\n */\r\n private static getSafeUrlForLogging(url: string): string {\r\n const safeToLogDownloadUrl = new URL(url);\r\n if (safeToLogDownloadUrl.search && safeToLogDownloadUrl.search.length > 0)\r\n safeToLogDownloadUrl.search = \"...\";\r\n if (safeToLogDownloadUrl.hash && safeToLogDownloadUrl.hash.length > 0)\r\n safeToLogDownloadUrl.hash = \"...\";\r\n return safeToLogDownloadUrl.toString();\r\n }\r\n\r\n /**\r\n * Check if sas url has expired\r\n * @param download sas url for download\r\n * @param futureSeconds should be valid in future for given seconds.\r\n */\r\n public static isUrlExpired(downloadUrl: string, futureSeconds?: number): boolean {\r\n const sasUrl = new URL(downloadUrl);\r\n const se = sasUrl.searchParams.get(\"se\");\r\n if (se) {\r\n const expiryUTC = new Date(se);\r\n const now = new Date();\r\n const currentUTC = new Date(now.toUTCString());\r\n if (futureSeconds) {\r\n currentUTC.setSeconds(futureSeconds + currentUTC.getSeconds());\r\n }\r\n return expiryUTC <= currentUTC;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Download a file from AzureBlobStorage for iModelHub. Creates the directory containing the file if necessary. If there is an error in the operation, incomplete file is deleted from disk.\r\n * @param downloadUrl URL to download file from.\r\n * @param downloadToPathname Pathname to download the file to.\r\n * @param fileSize Size of the file that's being downloaded.\r\n * @param progressCallback Callback for tracking progress.\r\n * @throws [[IModelHubClientError]] with [IModelHubStatus.UndefinedArgumentError]($bentley) if one of the arguments is undefined or empty.\r\n */\r\n public async downloadFile(_accessToken: AccessToken, downloadUrl: string, downloadToPathname: string, fileSize?: number, progressCallback?: ProgressCallback, cancelRequest?: CancelRequest): Promise<void> {\r\n // strip search and hash parameters from download Url for logging purpose\r\n const safeToLogUrl = MobileFileHandler.getSafeUrlForLogging(downloadUrl);\r\n Logger.logInfo(loggerCategory, `Downloading file from ${safeToLogUrl}`);\r\n\r\n defined(\"downloadUrl\", downloadUrl);\r\n defined(\"downloadToPathname\", downloadToPathname);\r\n if (MobileFileHandler.isUrlExpired(downloadUrl)) {\r\n Logger.logError(loggerCategory, `Sas url has expired ${safeToLogUrl}`);\r\n throw new SasUrlExpired(403, \"Download URL has expired\");\r\n }\r\n if (fs.existsSync(downloadToPathname))\r\n fs.unlinkSync(downloadToPathname);\r\n\r\n MobileFileHandler.makeDirectoryRecursive(path.dirname(downloadToPathname));\r\n try {\r\n await MobileHost.downloadFile(downloadUrl, downloadToPathname, progressCallback, cancelRequest);\r\n } catch (err) {\r\n if (fs.existsSync(downloadToPathname))\r\n fs.unlinkSync(downloadToPathname); // Just in case there was a partial download, delete the file\r\n\r\n if (!(err instanceof UserCancelledError))\r\n Logger.logError(loggerCategory, `Error downloading file`);\r\n throw err;\r\n }\r\n if (fileSize && fs.existsSync(downloadToPathname)) {\r\n if (fs.lstatSync(downloadToPathname).size !== fileSize) {\r\n fs.unlinkSync(downloadToPathname);\r\n Logger.logError(loggerCategory, `Downloaded file is of incorrect size ${safeToLogUrl}`);\r\n throw new DownloadFailed(403, \"Download failed. Expected filesize does not match\");\r\n }\r\n }\r\n Logger.logTrace(loggerCategory, `Downloaded file from ${safeToLogUrl}`);\r\n }\r\n /** Get encoded block id from its number. */\r\n private getBlockId(blockId: number) {\r\n return Base64.encode(blockId.toString(16).padStart(5, \"0\"));\r\n }\r\n\r\n private async uploadChunk(_accessToken: AccessToken, uploadUrlString: string, fileDescriptor: number, blockId: number, callback?: ProgressCallback) {\r\n const chunkSize = 4 * 1024 * 1024;\r\n let buffer = Buffer.alloc(chunkSize);\r\n const bytesRead = fs.readSync(fileDescriptor, buffer, 0, chunkSize, chunkSize * blockId);\r\n buffer = buffer.subarray(0, bytesRead);\r\n\r\n const options: RequestOptions = {\r\n method: \"PUT\",\r\n headers: {\r\n \"x-ms-blob-type\": \"BlockBlob\",\r\n \"Content-Type\": \"application/octet-stream\", // eslint-disable-line @typescript-eslint/naming-convention\r\n \"Content-Length\": buffer.length, // eslint-disable-line @typescript-eslint/naming-convention\r\n },\r\n body: buffer,\r\n progressCallback: callback,\r\n agent: this.agent,\r\n timeout: {\r\n deadline: 60000,\r\n response: 60000,\r\n },\r\n };\r\n\r\n const uploadUrl = `${uploadUrlString}&comp=block&blockid=${this.getBlockId(blockId)}`;\r\n await request(uploadUrl, options);\r\n }\r\n\r\n /**\r\n * Upload a file to AzureBlobStorage for iModelHub.\r\n * @param uploadUrl URL to upload the file to.\r\n * @param uploadFromPathname Pathname to upload the file from.\r\n * @param progressCallback Callback for tracking progress.\r\n * @throws [[IModelHubClientError]] with [IModelHubStatus.UndefinedArgumentError]($bentley) if one of the arguments is undefined or empty.\r\n */\r\n public async uploadFile(accessToken: AccessToken, uploadUrlString: string, uploadFromPathname: string, progressCallback?: ProgressCallback): Promise<void> {\r\n const safeToLogUrl = MobileFileHandler.getSafeUrlForLogging(uploadUrlString);\r\n Logger.logTrace(loggerCategory, `Uploading file to ${safeToLogUrl}`);\r\n defined(\"uploadUrlString\", uploadUrlString);\r\n defined(\"uploadFromPathname\", uploadFromPathname);\r\n\r\n const fileSize = this.getFileSize(uploadFromPathname);\r\n const file = fs.openSync(uploadFromPathname, \"r\");\r\n const chunkSize = 4 * 1024 * 1024;\r\n\r\n try {\r\n let blockList = '<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?><BlockList>';\r\n let i = 0;\r\n const callback: ProgressCallback = (progress: ProgressInfo) => {\r\n const uploaded = i * chunkSize + progress.loaded;\r\n if (progressCallback)\r\n progressCallback({ loaded: uploaded, percent: uploaded / fileSize, total: fileSize });\r\n };\r\n for (; i * chunkSize < fileSize; ++i) {\r\n await this.uploadChunk(accessToken, uploadUrlString, file, i, progressCallback ? callback : undefined);\r\n blockList += `<Latest>${this.getBlockId(i)}</Latest>`;\r\n }\r\n blockList += \"</BlockList>\";\r\n\r\n const options: RequestOptions = {\r\n method: \"PUT\",\r\n headers: {\r\n \"Content-Type\": \"application/xml\", // eslint-disable-line @typescript-eslint/naming-convention\r\n \"Content-Length\": blockList.length, // eslint-disable-line @typescript-eslint/naming-convention\r\n },\r\n body: blockList,\r\n agent: this.agent,\r\n timeout: {\r\n response: 5000,\r\n deadline: 60000,\r\n },\r\n };\r\n\r\n const uploadUrl = `${uploadUrlString}&comp=blocklist`;\r\n await request(uploadUrl, options);\r\n } finally {\r\n fs.closeSync(file);\r\n }\r\n }\r\n\r\n /**\r\n * Get size of a file.\r\n * @param filePath Path of the file.\r\n * @returns Size of the file.\r\n */\r\n public getFileSize(filePath: string): number {\r\n return fs.statSync(filePath).size;\r\n }\r\n\r\n /**\r\n * Check if path is a directory.\r\n * @param filePath Path of the file.\r\n * @returns True if path is directory.\r\n */\r\n public isDirectory(filePath: string): boolean {\r\n return fs.statSync(filePath).isDirectory();\r\n }\r\n\r\n /**\r\n * Check if path exists.\r\n * @param filePath Path of the file.\r\n * @returns True if path exists.\r\n */\r\n public exists(filePath: string): boolean {\r\n return fs.existsSync(filePath);\r\n }\r\n\r\n /**\r\n * Deletes file.\r\n * @param filePath Path of the file.\r\n */\r\n public unlink(filePath: string): void {\r\n fs.unlinkSync(filePath);\r\n }\r\n\r\n /**\r\n * Get file name from the path.\r\n * @param filePath Path of the file.\r\n * @returns File name.\r\n */\r\n public basename(filePath: string): string {\r\n return path.basename(filePath);\r\n }\r\n\r\n /**\r\n * Join multiple strings into a single path.\r\n * @param paths Strings to join.\r\n * @returns Joined path.\r\n */\r\n public join(...paths: string[]): string {\r\n return path.join(...paths);\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"MobileFileHandler.js","sourceRoot":"","sources":["../../../src/backend/MobileFileHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n* See LICENSE.md in the project root for license terms and full copyright notice.\n*--------------------------------------------------------------------------------------------*/\n/** @packageDocumentation\n * @module iModelHub\n */\n\nimport { Buffer } from \"node:buffer\";\nimport * as fs from \"node:fs\";\nimport * as https from \"node:https\";\nimport * as path from \"node:path\";\nimport { AccessToken, BentleyError, GetMetaDataFunction, Logger } from \"@itwin/core-bentley\";\nimport { ProgressCallback, ProgressInfo, request, RequestOptions } from \"./Request\";\nimport { MobileHost } from \"./MobileHost\";\n\nconst loggerCategory: string = \"mobile.filehandler\";\n\nconst defined = (argumentName: string, argument?: any, allowEmpty: boolean = false) => {\n if (argument === undefined || argument === null || (argument === \"\" && !allowEmpty))\n throw Error(`Argument ${argumentName} is null or undefined`);\n};\n\n/** Interface to cancel a request\n * @beta\n */\nexport interface CancelRequest {\n /** Returns true if cancel request was acknowledged */\n cancel: () => boolean;\n}\n\n/** Error thrown when user cancelled operation\n * @internal\n */\nexport class UserCancelledError extends BentleyError {\n public constructor(errorNumber: number, message: string, getMetaData?: GetMetaDataFunction) {\n super(errorNumber, message, getMetaData);\n this.name = \"User cancelled operation\";\n }\n}\n\n/** Error thrown fail to download file. ErrorNumber will correspond to HTTP error code.\n * @internal\n */\nexport class DownloadFailed extends BentleyError {\n public constructor(errorNumber: number, message: string, getMetaData?: GetMetaDataFunction) {\n super(errorNumber, message, getMetaData);\n this.name = \"Fail to download file\";\n }\n}\n\n/** Error thrown when sas-url provided for download has expired\n * @internal\n */\nexport class SasUrlExpired extends BentleyError {\n public constructor(errorNumber: number, message: string, getMetaData?: GetMetaDataFunction) {\n super(errorNumber, message, getMetaData);\n this.name = \"SaS url has expired\";\n }\n}\n\n/**\n * Provides methods to work with the file system and azure storage. An instance of this class has to be provided to [[IModelClient]] for file upload/download methods to work.\n * @internal\n */\nexport class MobileFileHandler {\n /** @internal */\n public agent?: https.Agent;\n\n /**\n * Constructor for MobileFileHandler.\n */\n constructor() {\n }\n\n /** Create a directory, recursively setting up the path as necessary. */\n private static makeDirectoryRecursive(dirPath: string) {\n if (fs.existsSync(dirPath))\n return;\n\n MobileFileHandler.makeDirectoryRecursive(path.dirname(dirPath));\n fs.mkdirSync(dirPath);\n }\n\n /**\n * Make url safe for logging by removing sensitive information\n * @param url input url that will be strip of search and query parameters and replace them by ... for security reason\n */\n private static getSafeUrlForLogging(url: string): string {\n const safeToLogDownloadUrl = new URL(url);\n if (safeToLogDownloadUrl.search && safeToLogDownloadUrl.search.length > 0)\n safeToLogDownloadUrl.search = \"...\";\n if (safeToLogDownloadUrl.hash && safeToLogDownloadUrl.hash.length > 0)\n safeToLogDownloadUrl.hash = \"...\";\n return safeToLogDownloadUrl.toString();\n }\n\n /**\n * Check if sas url has expired\n * @param download sas url for download\n * @param futureSeconds should be valid in future for given seconds.\n */\n public static isUrlExpired(downloadUrl: string, futureSeconds?: number): boolean {\n const sasUrl = new URL(downloadUrl);\n const se = sasUrl.searchParams.get(\"se\");\n if (se) {\n const expiryUTC = new Date(se);\n const now = new Date();\n const currentUTC = new Date(now.toUTCString());\n if (futureSeconds) {\n currentUTC.setSeconds(futureSeconds + currentUTC.getSeconds());\n }\n return expiryUTC <= currentUTC;\n }\n return false;\n }\n\n /**\n * Download a file from AzureBlobStorage for iModelHub. Creates the directory containing the file if necessary. If there is an error in the operation, incomplete file is deleted from disk.\n * @param downloadUrl URL to download file from.\n * @param downloadToPathname Pathname to download the file to.\n * @param fileSize Size of the file that's being downloaded.\n * @param progressCallback Callback for tracking progress.\n * @throws [[IModelHubClientError]] with [IModelHubStatus.UndefinedArgumentError]($bentley) if one of the arguments is undefined or empty.\n */\n public async downloadFile(_accessToken: AccessToken, downloadUrl: string, downloadToPathname: string, fileSize?: number, progressCallback?: ProgressCallback, cancelRequest?: CancelRequest): Promise<void> {\n // strip search and hash parameters from download Url for logging purpose\n const safeToLogUrl = MobileFileHandler.getSafeUrlForLogging(downloadUrl);\n Logger.logInfo(loggerCategory, `Downloading file from ${safeToLogUrl}`);\n\n defined(\"downloadUrl\", downloadUrl);\n defined(\"downloadToPathname\", downloadToPathname);\n if (MobileFileHandler.isUrlExpired(downloadUrl)) {\n Logger.logError(loggerCategory, `Sas url has expired ${safeToLogUrl}`);\n throw new SasUrlExpired(403, \"Download URL has expired\");\n }\n if (fs.existsSync(downloadToPathname))\n fs.unlinkSync(downloadToPathname);\n\n MobileFileHandler.makeDirectoryRecursive(path.dirname(downloadToPathname));\n try {\n await MobileHost.downloadFile(downloadUrl, downloadToPathname, progressCallback, cancelRequest);\n } catch (err) {\n if (fs.existsSync(downloadToPathname))\n fs.unlinkSync(downloadToPathname); // Just in case there was a partial download, delete the file\n\n if (!(err instanceof UserCancelledError))\n Logger.logError(loggerCategory, `Error downloading file`);\n throw err;\n }\n if (fileSize && fs.existsSync(downloadToPathname)) {\n if (fs.lstatSync(downloadToPathname).size !== fileSize) {\n fs.unlinkSync(downloadToPathname);\n Logger.logError(loggerCategory, `Downloaded file is of incorrect size ${safeToLogUrl}`);\n throw new DownloadFailed(403, \"Download failed. Expected filesize does not match\");\n }\n }\n Logger.logTrace(loggerCategory, `Downloaded file from ${safeToLogUrl}`);\n }\n /** Get encoded block id from its number. */\n private getBlockId(blockId: number) {\n return Buffer.from(blockId.toString(16).padStart(5, \"0\")).toString(\"base64\");\n }\n\n private async uploadChunk(_accessToken: AccessToken, uploadUrlString: string, fileDescriptor: number, blockId: number, callback?: ProgressCallback) {\n const chunkSize = 4 * 1024 * 1024;\n let buffer = Buffer.alloc(chunkSize);\n const bytesRead = fs.readSync(fileDescriptor, buffer, 0, chunkSize, chunkSize * blockId);\n buffer = buffer.subarray(0, bytesRead);\n\n const options: RequestOptions = {\n method: \"PUT\",\n headers: {\n \"x-ms-blob-type\": \"BlockBlob\",\n \"Content-Type\": \"application/octet-stream\", // eslint-disable-line @typescript-eslint/naming-convention\n \"Content-Length\": buffer.length, // eslint-disable-line @typescript-eslint/naming-convention\n },\n body: buffer,\n progressCallback: callback,\n agent: this.agent,\n timeout: {\n deadline: 60000,\n response: 60000,\n },\n };\n\n const uploadUrl = `${uploadUrlString}&comp=block&blockid=${this.getBlockId(blockId)}`;\n await request(uploadUrl, options);\n }\n\n /**\n * Upload a file to AzureBlobStorage for iModelHub.\n * @param uploadUrl URL to upload the file to.\n * @param uploadFromPathname Pathname to upload the file from.\n * @param progressCallback Callback for tracking progress.\n * @throws [[IModelHubClientError]] with [IModelHubStatus.UndefinedArgumentError]($bentley) if one of the arguments is undefined or empty.\n */\n public async uploadFile(accessToken: AccessToken, uploadUrlString: string, uploadFromPathname: string, progressCallback?: ProgressCallback): Promise<void> {\n const safeToLogUrl = MobileFileHandler.getSafeUrlForLogging(uploadUrlString);\n Logger.logTrace(loggerCategory, `Uploading file to ${safeToLogUrl}`);\n defined(\"uploadUrlString\", uploadUrlString);\n defined(\"uploadFromPathname\", uploadFromPathname);\n\n const fileSize = this.getFileSize(uploadFromPathname);\n const file = fs.openSync(uploadFromPathname, \"r\");\n const chunkSize = 4 * 1024 * 1024;\n\n try {\n let blockList = '<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?><BlockList>';\n let i = 0;\n const callback: ProgressCallback = (progress: ProgressInfo) => {\n const uploaded = i * chunkSize + progress.loaded;\n if (progressCallback)\n progressCallback({ loaded: uploaded, percent: uploaded / fileSize, total: fileSize });\n };\n for (; i * chunkSize < fileSize; ++i) {\n await this.uploadChunk(accessToken, uploadUrlString, file, i, progressCallback ? callback : undefined);\n blockList += `<Latest>${this.getBlockId(i)}</Latest>`;\n }\n blockList += \"</BlockList>\";\n\n const options: RequestOptions = {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/xml\", // eslint-disable-line @typescript-eslint/naming-convention\n \"Content-Length\": blockList.length, // eslint-disable-line @typescript-eslint/naming-convention\n },\n body: blockList,\n agent: this.agent,\n timeout: {\n response: 5000,\n deadline: 60000,\n },\n };\n\n const uploadUrl = `${uploadUrlString}&comp=blocklist`;\n await request(uploadUrl, options);\n } finally {\n fs.closeSync(file);\n }\n }\n\n /**\n * Get size of a file.\n * @param filePath Path of the file.\n * @returns Size of the file.\n */\n public getFileSize(filePath: string): number {\n return fs.statSync(filePath).size;\n }\n\n /**\n * Check if path is a directory.\n * @param filePath Path of the file.\n * @returns True if path is directory.\n */\n public isDirectory(filePath: string): boolean {\n return fs.statSync(filePath).isDirectory();\n }\n\n /**\n * Check if path exists.\n * @param filePath Path of the file.\n * @returns True if path exists.\n */\n public exists(filePath: string): boolean {\n return fs.existsSync(filePath);\n }\n\n /**\n * Deletes file.\n * @param filePath Path of the file.\n */\n public unlink(filePath: string): void {\n fs.unlinkSync(filePath);\n }\n\n /**\n * Get file name from the path.\n * @param filePath Path of the file.\n * @returns File name.\n */\n public basename(filePath: string): string {\n return path.basename(filePath);\n }\n\n /**\n * Join multiple strings into a single path.\n * @param paths Strings to join.\n * @returns Joined path.\n */\n public join(...paths: string[]): string {\n return path.join(...paths);\n }\n}\n"]}