├── .gitignore ├── Controllers ├── DataManagementController.cs ├── OAuthController.cs └── UserController.cs ├── Hubs └── ContentsHub.cs ├── LICENSE ├── Models └── APSService.cs ├── Program.cs ├── README.md ├── Startup.cs ├── appsettings.Development.json ├── appsettings.json ├── flowthumbnail.gif ├── hubsRecursiveExtraction.csproj ├── libman.json ├── thumbnail.png └── wwwroot ├── css └── main.css ├── img └── readme │ ├── visual_code_restore.png │ └── visual_studio_settings.png ├── index.html └── js ├── APSTree.js ├── ContentsHub.js ├── ItemsTable.js └── signalr └── dist └── browser ├── signalr.js └── signalr.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .vs/fileInfoExtract/DesignTimeBuild/.dtbcache.v2 3 | .vs/fileInfoExtract/FileContentIndex/b05a77ae-dee8-47cb-8faa-263ea9c38b17.vsidx 4 | .vs/fileInfoExtract/FileContentIndex/dc8908cf-0abd-4f49-a51e-c63e2f9e6956.vsidx 5 | .vs/fileInfoExtract/FileContentIndex/read.lock 6 | .vs/fileInfoExtract/v17/.suo 7 | .vs/ProjectEvaluation/fileinfoextract.metadata.v5.2 8 | .vs/ProjectEvaluation/fileinfoextract.projects.v5.2 9 | obj/Debug/netcoreapp3.1/apphost.exe 10 | obj/Debug/netcoreapp3.1/fileInfoExtract.assets.cache 11 | obj/Debug/netcoreapp3.1/fileInfoExtract.csproj.AssemblyReference.cache 12 | obj/Debug/netcoreapp3.1/fileInfoExtract.GeneratedMSBuildEditorConfig.editorconfig 13 | obj/fileInfoExtract.csproj.nuget.dgspec.json 14 | obj/fileInfoExtract.csproj.nuget.g.props 15 | obj/fileInfoExtract.csproj.nuget.g.targets 16 | obj/project.assets.json 17 | obj/project.nuget.cache 18 | .vs/fileInfoExtract/FileContentIndex/447ba62b-9ff7-450c-8eb9-823a730aad09.vsidx 19 | obj/Debug/netcoreapp3.1/apphost.exe 20 | obj/Debug/netcoreapp3.1/fileInfoExtract.assets.cache 21 | obj/Debug/netcoreapp3.1/fileInfoExtract.csproj.AssemblyReference.cache 22 | obj/fileInfoExtract.csproj.nuget.dgspec.json 23 | obj/fileInfoExtract.csproj.nuget.g.props 24 | obj/fileInfoExtract.csproj.nuget.g.targets 25 | obj/project.assets.json 26 | obj/project.nuget.cache 27 | .vs 28 | obj 29 | /bin 30 | -------------------------------------------------------------------------------- /Controllers/DataManagementController.cs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | using Hangfire; 20 | using Microsoft.AspNetCore.Mvc; 21 | using System; 22 | using System.Collections.Generic; 23 | using System.Threading.Tasks; 24 | using Microsoft.AspNetCore.SignalR; 25 | using System.Linq; 26 | using Autodesk.DataManagement.Model; 27 | using Newtonsoft.Json; 28 | 29 | 30 | public class DataManagementController : ControllerBase 31 | { 32 | public readonly IHubContext _contentsHub; 33 | public readonly APSService _apsService; 34 | 35 | public DataManagementController(IHubContext contentsHub, APSService apsService) 36 | { 37 | _contentsHub = contentsHub; 38 | _apsService = apsService; 39 | GC.KeepAlive(_contentsHub); 40 | } 41 | 42 | /// 43 | /// GET TreeNode passing the ID 44 | /// 45 | [HttpGet] 46 | [Route("api/aps/datamanagement")] 47 | public async Task> GetTreeNodeAsync(string id) 48 | { 49 | Tokens tokens = await OAuthController.PrepareTokens(Request, Response, _apsService); 50 | if (tokens == null) 51 | { 52 | return null; 53 | } 54 | 55 | IList nodes = new List(); 56 | 57 | if (id == "#") // root 58 | return await GetHubsAsync(tokens); 59 | else 60 | { 61 | string[] idParams = id.Split('/'); 62 | string resource = idParams[idParams.Length - 2]; 63 | switch (resource) 64 | { 65 | case "hubs": // hubs node selected/expanded, show projects 66 | return await GetProjectsAsync(id, tokens); 67 | } 68 | } 69 | 70 | return nodes; 71 | } 72 | 73 | [HttpGet] 74 | [Route("api/aps/resource/info")] 75 | public object GetResourceInfo() 76 | { 77 | string connectionId = base.Request.Query["connectionId"]; 78 | string hubId = base.Request.Query["hubId"]; 79 | string projectId = base.Request.Query["projectId"]; 80 | string currentFolderId = base.Request.Query["folderId"]; 81 | string dataType = base.Request.Query["dataType"]; 82 | string projectGuid = base.Request.Query["guid"]; 83 | 84 | Tokens tokens = OAuthController.PrepareTokens(Request, Response, _apsService).GetAwaiter().GetResult(); 85 | if (tokens == null) 86 | { 87 | return null; 88 | } 89 | 90 | string jobId = BackgroundJob.Enqueue(() => 91 | // the API SDK 92 | GatherData(connectionId, hubId, projectId, currentFolderId, dataType, projectGuid, tokens) 93 | ); 94 | 95 | return new { Success = true }; 96 | } 97 | 98 | public async Task GatherData(string connectionId, string hubId, string projectId, string currentFolderId, string dataType, string projectGuid, Tokens tokens) 99 | { 100 | await GetProjectContents(hubId, projectId, connectionId, dataType, projectGuid, tokens); 101 | } 102 | 103 | private async Task> GetHubsAsync(Tokens tokens) 104 | { 105 | IList nodes = new List(); 106 | 107 | List hubs = (List)await _apsService.GetHubsDataAsync(tokens); 108 | foreach (HubsData hubData in hubs) 109 | { 110 | string nodeType = "hubs"; 111 | switch (hubData.Attributes.Extension.Type) 112 | { 113 | case "hubs:autodesk.core:Hub": 114 | nodeType = "hubs"; // if showing only BIM 360, mark this as 'unsupported' 115 | break; 116 | case "hubs:autodesk.a360:PersonalHub": 117 | nodeType = "personalHub"; // if showing only BIM 360, mark this as 'unsupported' 118 | break; 119 | case "hubs:autodesk.bim360:Account": 120 | nodeType = "bim360Hubs"; 121 | break; 122 | } 123 | 124 | jsTreeNode hubNode = new jsTreeNode(hubData.Links.Self.Href, hubData.Attributes.Name, nodeType, !(nodeType == "unsupported")); 125 | nodes.Add(hubNode); 126 | } 127 | return nodes; 128 | } 129 | 130 | private async Task> GetProjectsAsync(string href, Tokens tokens) 131 | { 132 | IList nodes = new List(); 133 | 134 | // extract the hubId from the href 135 | string[] idParams = href.Split('/'); 136 | string hubId = idParams[idParams.Length - 1]; 137 | 138 | List projects = (List)await _apsService.GetProjectsDatasAsync(hubId, tokens); 139 | foreach (ProjectsData project in projects) 140 | { 141 | // check the type of the project to show an icon 142 | string nodeType = "projects"; 143 | switch (project.Attributes.Extension.Type) 144 | { 145 | case "projects:autodesk.core:Project": 146 | nodeType = "a360projects"; 147 | break; 148 | case "projects:autodesk.bim360:Project": 149 | if (project.Attributes.Extension.Data.ProjectType == "ACC") 150 | { 151 | nodeType = "accprojects"; 152 | } 153 | else 154 | { 155 | nodeType = "bim360projects"; 156 | } 157 | break; 158 | } 159 | 160 | jsTreeNode projectNode = new jsTreeNode(project.Links.Self.Href, project.Attributes.Name, nodeType, false); 161 | nodes.Add(projectNode); 162 | } 163 | 164 | return nodes; 165 | } 166 | 167 | public async Task GetProjectContents(string hubId, string projectId, string connectionId, string dataType, string projectGuid, Tokens tokens) 168 | { 169 | List folderContentsDatas = (List)await _apsService.GetTopFoldersDatasAsync(hubId, projectId, tokens); 170 | foreach (TopFoldersData folderContentsData in folderContentsDatas) 171 | { 172 | dynamic newFolder = getNewObject(folderContentsData); 173 | string newFolderString = JsonConvert.SerializeObject(newFolder); 174 | ContentsHub.SendData(_contentsHub, connectionId, dataType, newFolderString, null); 175 | ContentsHub.SendUpdate(_contentsHub, connectionId, 0, 1); 176 | GetFolderContents(projectId, folderContentsData.Id, connectionId, "folder", projectGuid, tokens); 177 | } 178 | } 179 | 180 | public async Task GetFolderContents(string projectId, string folderId, string connectionId, string dataType, string projectGuid, Tokens tokens) 181 | { 182 | string jobId = ""; 183 | try 184 | { 185 | jobId = BackgroundJob.Enqueue(() => 186 | // the API SDK 187 | GetAllFolderContents(projectId, folderId, connectionId, dataType, projectGuid, tokens) 188 | ); 189 | } 190 | catch (Exception ex) 191 | { 192 | BackgroundJob.Requeue(jobId); 193 | } 194 | 195 | } 196 | 197 | public async Task GetAllFolderContents(string projectId, string folderId, string connectionId, string dataType, string projectGuid, Tokens tokens) 198 | { 199 | FolderContents folderContents = await _apsService.GetFolderContentsDatasAsync(projectId, folderId, tokens); 200 | 201 | List items = AddItems(folderContents); 202 | 203 | int pageNumber = 0; 204 | try 205 | { 206 | while (folderContents.Links.Next != null) 207 | { 208 | pageNumber++; 209 | folderContents = await _apsService.GetFolderContentsDatasAsync(projectId, folderId, pageNumber, tokens); 210 | 211 | List newItems = AddItems(folderContents); 212 | items.AddRange(newItems); 213 | } 214 | } 215 | catch (Exception) 216 | { } 217 | 218 | foreach (dynamic item in items) 219 | { 220 | string newItemString = JsonConvert.SerializeObject(item); 221 | ContentsHub.SendData(_contentsHub, connectionId, dataType, newItemString, folderId); 222 | if (item.type == "folder") 223 | { 224 | ContentsHub.SendUpdate(_contentsHub, connectionId, 0, 1); 225 | GetFolderContents(projectId, item.id, connectionId, "folder", projectGuid, tokens); 226 | } 227 | } 228 | ContentsHub.SendUpdate(_contentsHub, connectionId, 1, 0); 229 | } 230 | 231 | public static List AddItems(FolderContents folderContents) 232 | { 233 | List items = new List(); 234 | 235 | // let's start iterating the FOLDER DATA 236 | foreach (FolderContentsData folderContentsData in folderContents.Data) 237 | { 238 | dynamic newItem = getNewObject(folderContentsData); 239 | if (newItem.type == "file") 240 | { 241 | try 242 | { 243 | dynamic itemLastVersion = getFileVersion(folderContents.Included, folderContentsData.Id); 244 | newItem.version = itemLastVersion.version; 245 | newItem.size = itemLastVersion.size; 246 | } 247 | catch (Exception) 248 | { 249 | 250 | } 251 | } 252 | items.Add(newItem); 253 | } 254 | return items; 255 | } 256 | 257 | public static dynamic getNewObject(TopFoldersData folderContentItem) 258 | { 259 | dynamic newItem = new System.Dynamic.ExpandoObject(); 260 | newItem.createTime = folderContentItem.Attributes.CreateTime; 261 | newItem.createUserId = folderContentItem.Attributes.CreateUserId; 262 | newItem.createUserName = folderContentItem.Attributes.CreateUserName; 263 | newItem.lastModifiedTime = folderContentItem.Attributes.LastModifiedTime; 264 | newItem.lastModifiedUserId = folderContentItem.Attributes.LastModifiedUserId; 265 | newItem.lastModifiedUserName = folderContentItem.Attributes.LastModifiedUserName; 266 | newItem.hidden = folderContentItem.Attributes.Hidden; 267 | newItem.id = folderContentItem.Id; 268 | newItem.timestamp = DateTime.UtcNow.ToLongDateString(); 269 | 270 | string extension = folderContentItem.Attributes.Extension.Type; 271 | switch (extension) 272 | { 273 | case "folders:autodesk.bim360:Folder": 274 | newItem.name = folderContentItem.Attributes.Name; 275 | newItem.filesInside = 0; 276 | newItem.foldersInside = 0; 277 | newItem.type = "folder"; 278 | break; 279 | case "items:autodesk.bim360:File": 280 | newItem.name = folderContentItem.Attributes.DisplayName; 281 | newItem.type = "file"; 282 | break; 283 | case "items:autodesk.bim360:Document": 284 | newItem.name = folderContentItem.Attributes.DisplayName; 285 | newItem.type = "file"; 286 | break; 287 | default: 288 | newItem.name = folderContentItem.Attributes.DisplayName; 289 | newItem.type = "file"; 290 | break; 291 | } 292 | 293 | return newItem; 294 | } 295 | 296 | public static dynamic getNewObject(FolderContentsData folderContentItem) 297 | { 298 | dynamic newItem = new System.Dynamic.ExpandoObject(); 299 | newItem.createTime = folderContentItem.Attributes.CreateTime; 300 | newItem.createUserId = folderContentItem.Attributes.CreateUserId; 301 | newItem.createUserName = folderContentItem.Attributes.CreateUserName; 302 | newItem.lastModifiedTime = folderContentItem.Attributes.LastModifiedTime; 303 | newItem.lastModifiedUserId = folderContentItem.Attributes.LastModifiedUserId; 304 | newItem.lastModifiedUserName = folderContentItem.Attributes.LastModifiedUserName; 305 | newItem.hidden = folderContentItem.Attributes.Hidden; 306 | newItem.id = folderContentItem.Id; 307 | newItem.timestamp = DateTime.UtcNow.ToLongDateString(); 308 | 309 | string extension = folderContentItem.Attributes.Extension.Type; 310 | switch (extension) 311 | { 312 | case "folders:autodesk.bim360:Folder": 313 | newItem.name = folderContentItem.Attributes.Name; 314 | newItem.filesInside = 0; 315 | newItem.foldersInside = 0; 316 | newItem.type = "folder"; 317 | break; 318 | case "items:autodesk.bim360:File": 319 | newItem.name = folderContentItem.Attributes.DisplayName; 320 | newItem.type = "file"; 321 | break; 322 | case "items:autodesk.bim360:Document": 323 | newItem.name = folderContentItem.Attributes.DisplayName; 324 | newItem.type = "file"; 325 | break; 326 | default: 327 | newItem.name = folderContentItem.Attributes.DisplayName; 328 | newItem.type = "file"; 329 | break; 330 | } 331 | 332 | return newItem; 333 | } 334 | 335 | public static dynamic getFileVersion(List included, string itemId) 336 | { 337 | List folderIncluded = included.Where(x => x.Relationships.Item.Data.Id == itemId).ToList(); 338 | FolderContentsIncluded folderIncludedMaxVersion = folderIncluded.MaxBy(x => x.Attributes.VersionNumber); 339 | try 340 | { 341 | dynamic dynamicAux = new System.Dynamic.ExpandoObject(); 342 | dynamicAux.version = folderIncludedMaxVersion.Attributes.VersionNumber; 343 | dynamicAux.size = folderIncludedMaxVersion.Attributes.StorageSize; 344 | return dynamicAux; 345 | } 346 | catch (Exception ex) 347 | { 348 | dynamic dynamicAuxMissing = new System.Dynamic.ExpandoObject(); 349 | dynamicAuxMissing.version = ""; 350 | dynamicAuxMissing.size = ""; 351 | return dynamicAuxMissing; 352 | } 353 | } 354 | 355 | public class jsTreeNode 356 | { 357 | public jsTreeNode(string id, string text, string type, bool children) 358 | { 359 | this.id = id; 360 | this.text = text; 361 | this.type = type; 362 | this.children = children; 363 | } 364 | 365 | public string id { get; set; } 366 | public string text { get; set; } 367 | public string type { get; set; } 368 | public bool children { get; set; } 369 | } 370 | 371 | } 372 | -------------------------------------------------------------------------------- /Controllers/OAuthController.cs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | using Microsoft.AspNetCore.Http; 20 | using Microsoft.AspNetCore.Mvc; 21 | using Microsoft.AspNetCore.SignalR; 22 | using System; 23 | using System.Net; 24 | using System.Threading.Tasks; 25 | 26 | 27 | [ApiController] 28 | [Route("api/aps/[controller]")] 29 | public class OAuthController : ControllerBase 30 | { 31 | public readonly APSService _apsService; 32 | public readonly IHubContext _contentsHub; 33 | 34 | public OAuthController(IHubContext contentsHub, APSService apsService) 35 | { 36 | _contentsHub = contentsHub; 37 | _apsService = apsService; 38 | GC.KeepAlive(_contentsHub); 39 | } 40 | 41 | public static async Task PrepareTokens(HttpRequest request, HttpResponse response, APSService forgeService) 42 | { 43 | if (!request.Cookies.ContainsKey("internal_token")) 44 | { 45 | return null; 46 | } 47 | var tokens = new Tokens 48 | { 49 | PublicToken = request.Cookies["public_token"], 50 | InternalToken = request.Cookies["internal_token"], 51 | RefreshToken = request.Cookies["refresh_token"], 52 | ExpiresAt = DateTime.Parse(request.Cookies["expires_at"]) 53 | }; 54 | if (tokens.ExpiresAt < DateTime.Now.ToUniversalTime()) 55 | { 56 | tokens = await forgeService.RefreshTokens(tokens); 57 | response.Cookies.Append("public_token", tokens.PublicToken); 58 | response.Cookies.Append("internal_token", tokens.InternalToken); 59 | response.Cookies.Append("refresh_token", tokens.RefreshToken); 60 | response.Cookies.Append("expires_at", tokens.ExpiresAt.ToString()); 61 | } 62 | return tokens; 63 | } 64 | 65 | [HttpGet("clientid")] 66 | public async Task ClientId() 67 | { 68 | var clientid = await _apsService.GetClientId(); 69 | return new { id = clientid }; 70 | } 71 | 72 | [HttpGet("signin")] 73 | public ActionResult Signin() 74 | { 75 | var redirectUri = _apsService.GetAuthorizationURL(); 76 | return Redirect(redirectUri); 77 | } 78 | 79 | [HttpGet("signout")] 80 | public ActionResult Signout() 81 | { 82 | Response.Cookies.Delete("public_token"); 83 | Response.Cookies.Delete("internal_token"); 84 | Response.Cookies.Delete("refresh_token"); 85 | Response.Cookies.Delete("expires_at"); 86 | return Redirect("/"); 87 | } 88 | 89 | [HttpGet("callback")] 90 | public async Task Callback(string code) 91 | { 92 | var tokens = await _apsService.GenerateTokens(code); 93 | Response.Cookies.Append("public_token", tokens.PublicToken); 94 | Response.Cookies.Append("internal_token", tokens.InternalToken); 95 | Response.Cookies.Append("refresh_token", tokens.RefreshToken); 96 | Response.Cookies.Append("expires_at", tokens.ExpiresAt.ToString()); 97 | return Redirect("/"); 98 | } 99 | 100 | [HttpGet("profile")] 101 | public async Task GetProfile() 102 | { 103 | var tokens = await PrepareTokens(Request, Response, _apsService); 104 | if (tokens == null) 105 | { 106 | return Unauthorized(); 107 | } 108 | dynamic profile = await _apsService.GetUserProfile(tokens); 109 | return new 110 | { 111 | name = string.Format("{0} {1}", profile.firstName, profile.lastName) 112 | }; 113 | } 114 | 115 | [HttpGet("token")] 116 | public async Task GetPublicToken() 117 | { 118 | var tokens = await PrepareTokens(Request, Response, _apsService); 119 | if (tokens == null) 120 | { 121 | return Unauthorized(); 122 | } 123 | return new 124 | { 125 | access_token = tokens.PublicToken, 126 | token_type = "Bearer", 127 | expires_in = Math.Floor((tokens.ExpiresAt - DateTime.Now.ToUniversalTime()).TotalSeconds) 128 | }; 129 | } 130 | } -------------------------------------------------------------------------------- /Controllers/UserController.cs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | using Autodesk.Authentication.Model; 20 | using Microsoft.AspNetCore.Mvc; 21 | using Newtonsoft.Json.Linq; 22 | using System.Threading.Tasks; 23 | 24 | 25 | public class UserController : ControllerBase 26 | { 27 | public readonly APSService _apsService; 28 | 29 | public UserController(APSService apsService) 30 | { 31 | _apsService = apsService; 32 | } 33 | [HttpGet] 34 | [Route("api/aps/user/profile")] 35 | public async Task GetUserProfileAsync() 36 | { 37 | Tokens tokens = OAuthController.PrepareTokens(Request, Response, _apsService).GetAwaiter().GetResult(); 38 | if (tokens == null) 39 | { 40 | return null; 41 | } 42 | UserInfo userInfo = await _apsService.GetUserProfile(tokens); 43 | return new { name = userInfo.Name, picture = userInfo.Picture }; 44 | 45 | } 46 | } -------------------------------------------------------------------------------- /Hubs/ContentsHub.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR; 2 | using System.Collections.Generic; 3 | using System.Dynamic; 4 | using System.Threading.Tasks; 5 | 6 | public class ContentsHub : Microsoft.AspNetCore.SignalR.Hub 7 | { 8 | public async static Task SendData(IHubContext hub, string connectionId, string dataType, string folderData, string parentFolderId) 9 | { 10 | await hub.Clients.Client(connectionId).SendAsync("ReceiveData", dataType, folderData, parentFolderId); 11 | } 12 | public async static Task SendUpdate(IHubContext hub, string connectionId, int completedJobs, int pendingJobs) 13 | { 14 | await hub.Clients.Client(connectionId).SendAsync("ReceiveUpdate", completedJobs, pendingJobs); 15 | } 16 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Autodesk Platform Services 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Models/APSService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Autodesk.Authentication; 5 | using Autodesk.Authentication.Model; 6 | using Autodesk.DataManagement; 7 | using Autodesk.SDKManager; 8 | using Autodesk.DataManagement.Model; 9 | 10 | public class Tokens 11 | { 12 | public string InternalToken; 13 | public string PublicToken; 14 | public string RefreshToken; 15 | public DateTime ExpiresAt; 16 | } 17 | 18 | public partial class APSService 19 | { 20 | private readonly string _clientId; 21 | private readonly string _clientSecret; 22 | private readonly string _callbackUri; 23 | private readonly AuthenticationClient _authClient; 24 | private readonly DataManagementClient _dataManagementClient; 25 | private readonly List InternalTokenScopes = new List { Scopes.DataRead, Scopes.ViewablesRead }; 26 | private readonly List PublicTokenScopes = new List { Scopes.DataRead, Scopes.ViewablesRead }; 27 | 28 | public APSService(string clientId, string clientSecret, string callbackUri) 29 | { 30 | _clientId = clientId; 31 | _clientSecret = clientSecret; 32 | _callbackUri = callbackUri; 33 | SDKManager sdkManager = SdkManagerBuilder 34 | .Create() // Creates SDK Manager Builder itself. 35 | .Build(); 36 | 37 | _authClient = new AuthenticationClient(sdkManager); 38 | _dataManagementClient = new DataManagementClient(sdkManager); 39 | 40 | } 41 | 42 | public string GetAuthorizationURL() 43 | { 44 | return _authClient.Authorize(_clientId, ResponseType.Code, _callbackUri, InternalTokenScopes); 45 | } 46 | 47 | public async Task GenerateTokens(string code) 48 | { 49 | ThreeLeggedToken internalAuth = await _authClient.GetThreeLeggedTokenAsync(_clientId, _clientSecret, code, _callbackUri); 50 | RefreshToken publicAuth = await _authClient.GetRefreshTokenAsync(_clientId, _clientSecret, internalAuth.RefreshToken, PublicTokenScopes); 51 | return new Tokens 52 | { 53 | PublicToken = publicAuth.AccessToken, 54 | InternalToken = internalAuth.AccessToken, 55 | RefreshToken = publicAuth._RefreshToken, 56 | ExpiresAt = DateTime.Now.ToUniversalTime().AddSeconds((double)internalAuth.ExpiresIn) 57 | }; 58 | } 59 | 60 | public async Task RefreshTokens(Tokens tokens) 61 | { 62 | RefreshToken internalAuth = await _authClient.GetRefreshTokenAsync(_clientId, _clientSecret, tokens.RefreshToken, InternalTokenScopes); 63 | RefreshToken publicAuth = await _authClient.GetRefreshTokenAsync(_clientId, _clientSecret, internalAuth._RefreshToken, PublicTokenScopes); 64 | return new Tokens 65 | { 66 | PublicToken = publicAuth.AccessToken, 67 | InternalToken = internalAuth.AccessToken, 68 | RefreshToken = publicAuth._RefreshToken, 69 | ExpiresAt = DateTime.Now.ToUniversalTime().AddSeconds((double)internalAuth.ExpiresIn).AddSeconds(-1700) 70 | }; 71 | } 72 | 73 | public async Task GetUserProfile(Tokens tokens) 74 | { 75 | UserInfo userInfo = await _authClient.GetUserInfoAsync(tokens.InternalToken); 76 | return userInfo; 77 | } 78 | 79 | public async Task> GetVersions(string projectId, string itemId, Tokens tokens) 80 | { 81 | Versions versions = await _dataManagementClient.GetItemVersionsAsync(projectId, itemId); 82 | return versions.Data; 83 | } 84 | 85 | public async Task> GetHubsDataAsync(Tokens tokens) 86 | { 87 | Hubs hubs = await _dataManagementClient.GetHubsAsync(accessToken: tokens.InternalToken); 88 | return hubs.Data; 89 | } 90 | 91 | public async Task> GetProjectsDatasAsync(string hubId, Tokens tokens) 92 | { 93 | Projects projects = await _dataManagementClient.GetHubProjectsAsync(hubId, accessToken: tokens.InternalToken); 94 | return projects.Data; 95 | } 96 | 97 | public async Task> GetTopFoldersDatasAsync(string hubId, string projectId, Tokens tokens) 98 | { 99 | TopFolders topFolders = await _dataManagementClient.GetProjectTopFoldersAsync(hubId, projectId, accessToken: tokens.InternalToken); 100 | return topFolders.Data; 101 | } 102 | 103 | public async Task GetFolderContentsDatasAsync(string projectId, string folderUrn, Tokens tokens) 104 | { 105 | FolderContents folderContents = await _dataManagementClient.GetFolderContentsAsync(projectId, folderUrn, accessToken: tokens.InternalToken); 106 | return folderContents; 107 | } 108 | 109 | internal async Task GetFolderContentsDatasAsync(string projectId, string folderId, int pageNumber, Tokens tokens) 110 | { 111 | FolderContents folderContents = await _dataManagementClient.GetFolderContentsAsync(projectId, folderId, pageNumber: pageNumber, accessToken: tokens.InternalToken); 112 | return folderContents; 113 | } 114 | 115 | internal async Task GetClientId() 116 | { 117 | return _clientId; 118 | } 119 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.AspNetCore.SignalR; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.Hosting; 5 | using Microsoft.Extensions.Logging; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | 11 | public class Program 12 | { 13 | public static void Main(string[] args) 14 | { 15 | var host = CreateHostBuilder(args).Build(); 16 | var hubContext = host.Services.GetService(typeof(IHubContext)); 17 | host.Run(); 18 | } 19 | 20 | public static IHostBuilder CreateHostBuilder(string[] args) => 21 | Host.CreateDefaultBuilder(args) 22 | .ConfigureWebHostDefaults(webBuilder => 23 | { 24 | webBuilder.UseStartup(); 25 | }); 26 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Files and folders extraction sample 2 | 3 | ![Platforms](https://img.shields.io/badge/platform-Windows|MacOS-lightgray.svg) 4 | ![.NET](https://img.shields.io/badge/.NET-6-blue.svg) 5 | [![License](http://img.shields.io/:license-MIT-blue.svg)](http://opensource.org/licenses/MIT) 6 | 7 | [![oAuth2](https://img.shields.io/badge/oAuth2-v1-green.svg)](http://developer.autodesk.com/) 8 | [![Data-Management](https://img.shields.io/badge/Data%20Management-v2-green.svg)](http://developer.autodesk.com/) 9 | [![BIM360](https://img.shields.io/badge/BIM360-v1-green.svg)](http://developer.autodesk.com/) 10 | [![ACC](https://img.shields.io/badge/ACC-v1-green.svg)](http://developer.autodesk.com/) 11 | 12 | ![Advanced](https://img.shields.io/badge/Level-Advanced-red.svg) 13 | 14 | # Description 15 | 16 | This sample demonstrate how to retrieve data of all the folders and files on a specific project to render on a table and export it as csv. This sample recursively iterate through all folders of the selected project. It works by traversing the entire folder structure and sending the individual contents to the client one by one. Every file and folder under the selected project are rendered on the user table and can be exported as csv. 17 | 18 | ## Thumbnail 19 | 20 | ![thumbnail](thumbnail.png) 21 | 22 | ## Live version 23 | 24 | Try it at https://aps-hubs-recursive-extraction.autodesk.io/ 25 | 26 | ![](flowthumbnail.gif) 27 | 28 | ## DEMO VIDEO 29 | 30 | [![thumbnail](flowthumbnail.gif)](https://www.youtube.com/watch?v=VDFbMNllJPU) 31 | 32 | # Setup 33 | 34 | ## Prerequisites 35 | 36 | 1. **APS Account**: Learn how to create a APS Account, activate subscription and create an app at [this tutorial](http://aps.autodesk.com/tutorials/#/account/). 37 | 2. **Visual Studio**: Either Community (Windows) or Code (Windows, MacOS). 38 | 3. **.NET Core** basic knowledge with C# 39 | 4. **HangFire**: Library for dealing with queueing. [learn more] (https://www.hangfire.io). 40 | 41 | ## Running locally 42 | 43 | Clone this project or download it. It's recommended to install [GitHub desktop](https://desktop.github.com/). To clone it via command line, use the following (**Terminal** on MacOSX/Linux, **Git Shell** on Windows): 44 | 45 | git clone https://github.com/autodesk-platform-services/aps-hubs-recursive-extraction 46 | 47 | **Visual Studio** (Windows): 48 | 49 | Right-click on the project, then go to **Debug**. Adjust the settings as shown below. For environment variable, define the following: 50 | 51 | - ASPNETCORE_ENVIRONMENT: `Development` 52 | - APS_CLIENT_ID: `your id here` 53 | - APS_CLIENT_SECRET: `your secret here` 54 | - APS_CALLBACK_URL: `http://localhost:8080/api/aps/oauth/callback` 55 | 56 | **Visual Sutdio Code** (Windows, MacOS): 57 | 58 | Open the folder, at the bottom-right, select **Yes** and **Restore**. This restores the packages (e.g. Autodesk.Forge) and creates the launch.json file. See _Tips & Tricks_ for .NET Core on MacOS. 59 | 60 | At the `.vscode\launch.json`, find the env vars and add your APS Client ID, Secret and callback URL. Also define the `ASPNETCORE_URLS` variable. The end result should be as shown below: 61 | 62 | ```json 63 | "env": { 64 | "ASPNETCORE_ENVIRONMENT": "Development", 65 | "ASPNETCORE_URLS" : "http://localhost:8080", 66 | "APS_CALLBACK_URL": "http://localhost:8080/api/aps/oauth/callback", 67 | "APS_CLIENT_SECRET": "your client secret here", 68 | "APS_CLIENT_ID": "your client Id here" 69 | }, 70 | ``` 71 | 72 | Open `http://localhost:3000` to start the app. Select **Index my BIM 360 Account** before using (this process may take a while). Check the `http://localhost:3000/dashboard` to see the jobs running (Hangfire dashboard). 73 | 74 | # Further Reading 75 | 76 | Documentation: 77 | 78 | - [BIM 360 API](https://developer.autodesk.com/en/docs/bim360/v1/overview/) 79 | - [Data Management API](https://developer.autodesk.com/en/docs/data/v2/overview/) 80 | 81 | Other APIs: 82 | 83 | - [Hangfire](https://www.hangfire.io/) queueing library for .NET 84 | 85 | ### Tips & Tricks 86 | 87 | This sample uses .NET Core and works fine on both Windows and MacOS, see [this tutorial for MacOS](https://github.com/augustogoncalves/dotnetcoreheroku). 88 | 89 | ### Troubleshooting 90 | 91 | 1. **Cannot see my BIM 360/ACC projects**: Make sure to provision the APS App Client ID within the BIM 360 Account, [learn more here](https://aps.autodesk.com/blog/bim-360-docs-provisioning-forge-apps). This requires the Account Admin permission. 92 | 93 | 2. **error setting certificate verify locations** error: may happen on Windows, use the following: `git config --global http.sslverify "false"` 94 | 95 | ## License 96 | 97 | This sample is licensed under the terms of the [MIT License](http://opensource.org/licenses/MIT). Please see the [LICENSE](LICENSE) file for full details. 98 | 99 | ## Written by 100 | 101 | João Martins [in/jpornelas](https://www.linkedin.com/in/jpornelas), [APS Partner Development](http://aps.autodesk.com) 102 | -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | using System; 6 | using Hangfire; 7 | using Microsoft.Extensions.Configuration; 8 | using Hangfire.MemoryStorage; 9 | 10 | 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | //https://stackoverflow.com/questions/58340247/how-to-use-hangfire-in-net-core-with-mongodb 20 | 21 | // This method gets called by the runtime. Use this method to add services to the container. 22 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 23 | public void ConfigureServices(IServiceCollection services) 24 | { 25 | services.AddHangfire(config => 26 | { 27 | config.SetDataCompatibilityLevel(CompatibilityLevel.Version_170); 28 | config.UseSimpleAssemblyNameTypeSerializer(); 29 | config.UseRecommendedSerializerSettings(); 30 | config.UseMemoryStorage(); 31 | 32 | }); 33 | services.AddHangfireServer(); 34 | 35 | services.AddControllers(); 36 | services.AddSignalR(o => 37 | { 38 | o.EnableDetailedErrors = true; 39 | o.MaximumReceiveMessageSize = 10240; // bytes 40 | }); 41 | 42 | var APSClientID = Configuration["APS_CLIENT_ID"]; 43 | var APSClientSecret = Configuration["APS_CLIENT_SECRET"]; 44 | var APSCallbackURL = Configuration["APS_CALLBACK_URL"]; 45 | if (string.IsNullOrEmpty(APSClientID) || string.IsNullOrEmpty(APSClientSecret) || string.IsNullOrEmpty(APSCallbackURL)) 46 | { 47 | throw new ApplicationException("Missing required environment variables APS_CLIENT_ID, APS_CLIENT_SECRET, or APS_CALLBACK_URL."); 48 | } 49 | services.AddSingleton(new APSService(APSClientID, APSClientSecret, APSCallbackURL)); 50 | } 51 | 52 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 53 | public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env) 54 | { 55 | if (env.IsDevelopment()) 56 | { 57 | app.UseDeveloperExceptionPage(); 58 | } 59 | 60 | app.UseDefaultFiles(); 61 | app.UseStaticFiles(); 62 | app.UseHangfireDashboard(); 63 | app.UseRouting(); 64 | app.UseEndpoints(endpoints => 65 | { 66 | endpoints.MapControllers(); 67 | endpoints.MapHub("/contentshub"); 68 | }); 69 | } 70 | } -------------------------------------------------------------------------------- /appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /flowthumbnail.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-recursive-extraction/ce669f58187c2ed87b4f8071bb7aa51263a671f9/flowthumbnail.gif -------------------------------------------------------------------------------- /hubsRecursiveExtraction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | false 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /libman.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "defaultProvider": "unpkg", 4 | "libraries": [ 5 | { 6 | "library": "@microsoft/signalr@latest", 7 | "destination": "wwwroot/js/signalr", 8 | "files": [ 9 | "dist/browser/signalr.js", 10 | "dist/browser/signalr.min.js" 11 | ] 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-recursive-extraction/ce669f58187c2ed87b4f8071bb7aa51263a671f9/thumbnail.png -------------------------------------------------------------------------------- /wwwroot/css/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | min-height: 100%; 3 | height: 100%; 4 | } 5 | 6 | .fill { 7 | height: calc(100vh - 100px); 8 | } 9 | 10 | body { 11 | padding-top: 60px; /* space for the top nav bar */ 12 | margin-right: 30px; 13 | } 14 | 15 | #userHubs { 16 | overflow: auto; 17 | width: 100%; 18 | height: calc(100vh - 150px); 19 | } 20 | 21 | .jstree-themeicon-custom { 22 | background-size: 14px !important; 23 | } 24 | -------------------------------------------------------------------------------- /wwwroot/img/readme/visual_code_restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-recursive-extraction/ce669f58187c2ed87b4f8071bb7aa51263a671f9/wwwroot/img/readme/visual_code_restore.png -------------------------------------------------------------------------------- /wwwroot/img/readme/visual_studio_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-recursive-extraction/ce669f58187c2ed87b4f8071bb7aa51263a671f9/wwwroot/img/readme/visual_studio_settings.png -------------------------------------------------------------------------------- /wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Files and Folders Extraction - Autodesk Platform Services 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 | 47 | 48 |
49 |
50 |
51 |
52 |
53 | 54 | 55 | 56 |
57 |
58 |
59 | 63 |
64 |
65 |
You may also need to provision your
BIM 360 Docs account for this app.
66 | Learn more. 67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |

TABLE

75 |
76 |
77 | 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 |

Export Data

121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | 130 |
131 |
132 | 133 | 134 | 135 | 136 |
137 |
138 |
139 |
140 |
141 |
142 | 145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | 154 |
155 |
156 | 157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | 168 | 207 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /wwwroot/js/APSTree.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | $(document).ready(function () { 20 | // first, check if current visitor is signed in 21 | jQuery.ajax({ 22 | url: '/api/aps/oauth/token', 23 | success: function (res) { 24 | // yes, it is signed in... 25 | $('#signOut').show(); 26 | $('#refreshHubs').show(); 27 | 28 | // prepare sign out 29 | $('#signOut').click(function () { 30 | $('#hiddenFrame').on('load', function (event) { 31 | location.href = '/api/aps/oauth/signout'; 32 | }); 33 | $('#hiddenFrame').attr('src', 'https://accounts.autodesk.com/Authentication/LogOut'); 34 | // learn more about this signout iframe at 35 | // https://aps.autodesk.com/blog/log-out-forge 36 | }) 37 | 38 | // and refresh button 39 | $('#refreshHubs').click(function () { 40 | $('#userHubs').jstree(true).refresh(); 41 | }); 42 | 43 | // finally: 44 | prepareUserHubsTree(); 45 | showUser(); 46 | } 47 | }); 48 | 49 | $('#autodeskSigninButton').click(function () { 50 | window.location.replace('/api/aps/oauth/signin'); 51 | }); 52 | 53 | $('input[type=radio][name=filter_by]').change(function () { 54 | itemsTable.refreshTable(); 55 | }); 56 | 57 | $('#btnRefresh').click(function () { 58 | itemsTable.reset(); 59 | itemsTable.getReport(); 60 | }); 61 | 62 | $('#executeCSV').click(function () { 63 | (!!itemsTable ? itemsTable.exportData() : alert("Please, click on a project and wait for the conclusion of the steps before extracting the data!")); 64 | }); 65 | 66 | $.getJSON("/api/aps/oauth/clientid", function (res) { 67 | $("#ClientID").val(res.id); 68 | $("#provisionAccountSave").click(function () { 69 | $('#provisionAccountModal').modal('toggle'); 70 | $('#userHubs').jstree(true).refresh(); 71 | }); 72 | }); 73 | 74 | }); 75 | 76 | function prepareUserHubsTree() { 77 | $('#userHubs').jstree({ 78 | 'core': { 79 | 'themes': { "icons": true }, 80 | 'multiple': false, 81 | 'data': { 82 | "url": '/api/aps/datamanagement', 83 | "dataType": "json", 84 | 'cache': false, 85 | 'data': function (node) { 86 | $('#userHubs').jstree(true).toggle_node(node); 87 | return { "id": node.id }; 88 | } 89 | } 90 | }, 91 | 'types': { 92 | 'default': { 'icon': 'glyphicon glyphicon-question-sign' }, 93 | '#': { 'icon': 'glyphicon glyphicon-user' }, 94 | 'hubs': { 'icon': 'https://cdn.autodesk.io/dm/a360hub.png' }, 95 | 'personalHub': { 'icon': 'https://cdn.autodesk.io/dm/a360hub.png' }, 96 | 'bim360Hubs': { 'icon': 'https://cdn.autodesk.io/dm/xs/bim360hub.png' }, 97 | 'bim360projects': { 'icon': 'https://cdn.autodesk.io/dm/bim360project.png' }, 98 | 'a360projects': { 'icon': 'https://cdn.autodesk.io/dm/a360project.png' }, 99 | 'accprojects': { 100 | 'icon': 'https://cdn.autodesk.io/dm/accproject.png' 101 | }, 102 | 103 | 'unsupported': { 'icon': 'glyphicon glyphicon-ban-circle' } 104 | }, 105 | "sort": function (a, b) { 106 | var a1 = this.get_node(a); 107 | var b1 = this.get_node(b); 108 | var parent = this.get_node(a1.parent); 109 | if (parent.type === 'items') { // sort by version number 110 | var id1 = Number.parseInt(a1.text.substring(a1.text.indexOf('v') + 1, a1.text.indexOf(':'))) 111 | var id2 = Number.parseInt(b1.text.substring(b1.text.indexOf('v') + 1, b1.text.indexOf(':'))); 112 | return id1 > id2 ? 1 : -1; 113 | } 114 | else if (a1.type !== b1.type) return a1.icon < b1.icon ? 1 : -1; // types are different inside folder, so sort by icon (files/folders) 115 | else return a1.text > b1.text ? 1 : -1; // basic name/text sort 116 | }, 117 | "plugins": ["types", "state", "sort"], 118 | "state": { "key": "autodeskHubs" }// key restore tree state 119 | }).bind("activate_node.jstree", function (evt, data) { 120 | if (data != null && data.node != null && (data.node.type == 'accprojects' || data.node.type == 'bim360projects')) { 121 | $('#statusLabel').empty(); 122 | $('#statusLabel').append(''); 123 | itemsTable = new ItemsTable("itemsTable", data.node.id.split('/')[6], data.node.id.split('/')[8]); 124 | itemsTable.getReport(); 125 | itemsTable.drawTable(); 126 | } 127 | }); 128 | } 129 | 130 | function showUser() { 131 | jQuery.ajax({ 132 | url: '/api/aps/user/profile', 133 | success: function (profile) { 134 | var img = ''; 135 | $('#userInfo').html(img + profile.name); 136 | } 137 | }); 138 | } 139 | -------------------------------------------------------------------------------- /wwwroot/js/ContentsHub.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | var connection = new signalR.HubConnectionBuilder().withUrl("/contentshub").build(); 20 | 21 | connection.on("ReceiveData", function(dataType, folderData, parentFolderId) { 22 | itemsTable.addItem(dataType, folderData, parentFolderId); 23 | }); 24 | 25 | connection.on("ReceiveUpdate", function(completedJobs, pendingJobs){ 26 | itemsTable.updateStatus(completedJobs, pendingJobs); 27 | }); 28 | 29 | connection.start().then(function () { 30 | //No function for now 31 | }).catch(function (err) { 32 | return console.error(err.toString()); 33 | }); 34 | -------------------------------------------------------------------------------- /wwwroot/js/ItemsTable.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | var itemsTable = null; 20 | 21 | const humanReadableTitles = { 22 | name: 'ITEM NAME', 23 | createTime: 'CREATION DATE', 24 | createUserId: 'CREATOR ID', 25 | createUserName: 'CREATOR NAME', 26 | lastModifiedTime: 'LAST CHANGE TIME', 27 | lastModifiedUserId: 'LAST CHANGED BY (ID)', 28 | lastModifiedUserName: 'LAST CHANGED BY (NAME)', 29 | fullPath: 'FULL PATH', 30 | timestamp: 'EXTRACTION DATE' 31 | } 32 | 33 | const excludedFromTable = [ 34 | 'id', 35 | 'type', 36 | 'hidden' 37 | ] 38 | 39 | const folderSpecificFields = { 40 | filesInside: 'FILES INSIDE', 41 | foldersInside: 'FOLDERS INSIDE' 42 | } 43 | 44 | const fileSpecificFields = { 45 | version: 'VERSION', 46 | size: 'SIZE' 47 | } 48 | 49 | class ItemsTable { 50 | constructor(tableId, hubId, projectId) { 51 | this.tableId = tableId; 52 | this.hubId = hubId; 53 | this.projectId = projectId; 54 | this.items = []; 55 | this.dataSet = []; 56 | this.fullPaths = {}; 57 | this.guid = createUUID(); 58 | this.requests = 0; 59 | this.responses = 0; 60 | } 61 | 62 | getTableData() { 63 | return $("#itemsTable").bootstrapTable('getData'); 64 | } 65 | 66 | exportData() { 67 | switch (this.checkExportType()) { 68 | case 'csv': 69 | this.exportTableAsCSV(); 70 | break; 71 | } 72 | } 73 | 74 | exportTableAsCSV() { 75 | let csvData = this.getTableData(); 76 | let csvDataCleared = this.cleanForCommas(csvData); 77 | let csvString = csvDataCleared.join("%0A"); 78 | let a = document.createElement('a'); 79 | a.href = 'data:attachment/csv,' + csvString; 80 | a.target = '_blank'; 81 | a.download = 'ExportedData' + (new Date()).getTime() + '.csv'; 82 | document.body.appendChild(a); 83 | a.click(); 84 | } 85 | 86 | getVisibleColumns() { 87 | let visibleColumnsKeys = []; 88 | for (const columnObjcet of $("#itemsTable").bootstrapTable('getVisibleColumns')) { 89 | visibleColumnsKeys.push(columnObjcet.field); 90 | } 91 | return visibleColumnsKeys; 92 | } 93 | 94 | cleanForCommas(csvData) { 95 | let clearedCsvData = []; 96 | let visibleColumns = this.getVisibleColumns(); 97 | let mergedTitles = {...humanReadableTitles, ...folderSpecificFields, ...fileSpecificFields}; 98 | clearedCsvData.push(visibleColumns.map((columnName) => mergedTitles[columnName])); 99 | for (const rowObject of csvData) { 100 | let auxRow = []; 101 | for(const columnTitle of visibleColumns){ 102 | auxRow.push(typeof rowObject[columnTitle] === "string" ? rowObject[columnTitle].replaceAll(',', ' ') : rowObject[columnTitle]); 103 | } 104 | clearedCsvData.push(auxRow); 105 | } 106 | return clearedCsvData; 107 | } 108 | 109 | checkExportType() { 110 | return $('input[name=export]:checked', '#datasets').val(); 111 | } 112 | 113 | reset() { 114 | this.items = []; 115 | this.dataSet = []; 116 | this.fullPaths = {}; 117 | } 118 | 119 | checkHumanReadable() { 120 | return $('input[name=dataTypeToDisplay]:checked', '#datasets').val() === 'humanReadable'; 121 | } 122 | 123 | getTableLevel() { 124 | return $('input[name=filter_by]:checked', '#statsView').val(); 125 | } 126 | 127 | prepareDataset() { 128 | let filteredObjects; 129 | switch (this.getTableLevel()) { 130 | case "folderlevel": 131 | filteredObjects = this.items.filter(item => item.type === 'folder'); 132 | break; 133 | case "filelevel": 134 | filteredObjects = this.items.filter(item => item.type === 'file'); 135 | break; 136 | } 137 | 138 | for (const filteredItem of filteredObjects) { 139 | if (!filteredItem.fullPath) { 140 | filteredItem.fullPath = this.fullPaths[filteredItem.id]; 141 | } 142 | } 143 | 144 | this.dataSet = filteredObjects; 145 | } 146 | 147 | getHumanReadableColumns() { 148 | let excludedColumns = excludedFromTable; 149 | 150 | this.prepareDataset(); 151 | let tableColumns = []; 152 | for (const elementKey in humanReadableTitles) { 153 | if (excludedColumns.findIndex(key => key === elementKey) === -1) { 154 | tableColumns.push({ 155 | field: elementKey, 156 | title: humanReadableTitles[elementKey] 157 | }) 158 | } 159 | } 160 | switch (this.getTableLevel()) { 161 | case "folderlevel": 162 | for (const elementKey in folderSpecificFields) { 163 | if (excludedColumns.findIndex(key => key === elementKey) === -1) { 164 | tableColumns.push({ 165 | field: elementKey, 166 | title: folderSpecificFields[elementKey] 167 | }) 168 | } 169 | } 170 | break; 171 | case "filelevel": 172 | for (const elementKey in fileSpecificFields) { 173 | if (excludedColumns.findIndex(key => key === elementKey) === -1) { 174 | tableColumns.push({ 175 | field: elementKey, 176 | title: fileSpecificFields[elementKey] 177 | }) 178 | } 179 | } 180 | break; 181 | } 182 | 183 | return tableColumns; 184 | } 185 | 186 | async drawTable() { 187 | $("#itemsTable").empty(); 188 | 189 | this.prepareDataset(); 190 | let tableColumns = this.getHumanReadableColumns(); 191 | 192 | $("#itemsTable").bootstrapTable({ 193 | data: this.dataSet, 194 | pagination: true, 195 | search: true, 196 | sortable: true, 197 | columns: tableColumns 198 | }); 199 | } 200 | 201 | refreshTable() { 202 | this.prepareDataset(); 203 | 204 | let tableColumns = this.getHumanReadableColumns(); 205 | 206 | $('#itemsTable').bootstrapTable('refreshOptions', { 207 | data: this.dataSet, 208 | columns: tableColumns 209 | }); 210 | } 211 | 212 | async fetchDataAsync( currentFolderId = null, dataType ) { 213 | // this.updateStatus(0,0); 214 | try { 215 | const requestUrl = '/api/aps/resource/info'; 216 | const requestData = { 217 | 'hubId': this.hubId, 218 | 'projectId': this.projectId, 219 | 'folderId': currentFolderId, 220 | 'dataType': dataType, 221 | 'connectionId': connection.connection.connectionId, 222 | 'guid': this.guid 223 | }; 224 | apiClientAsync(requestUrl, requestData); 225 | 226 | } 227 | catch (err) { 228 | console.log(err); 229 | } 230 | } 231 | 232 | async addItem(dataType, folderData, parentFolderId){ 233 | let jsonFolderData = JSON.parse(folderData); 234 | this.items.push(jsonFolderData); 235 | 236 | switch (dataType) { 237 | case 'topFolders': { 238 | this.fullPaths[jsonFolderData.id] = jsonFolderData.name; 239 | break; 240 | } 241 | case 'folder': { 242 | let parentFullPath = this.fullPaths[parentFolderId]; 243 | this.fullPaths[jsonFolderData.id] = `${parentFullPath}/${jsonFolderData.name}`; 244 | let parentFolder = this.items.filter(f => f.id === parentFolderId)[0]; 245 | jsonFolderData.type=='folder'? parentFolder.foldersInside ++ : parentFolder.filesInside ++; 246 | }; 247 | }; 248 | this.refreshTable(); 249 | 250 | } 251 | 252 | async updateStatus(completedJobs, pendingJobs){ 253 | this.responses += completedJobs; 254 | this.requests += pendingJobs; 255 | $('#statusLabel').empty(); 256 | $('#statusLabel').append(''); 257 | } 258 | 259 | async getReport() { 260 | this.fetchDataAsync(null, 'topFolders'); 261 | } 262 | 263 | async getFolderContents(folder) { 264 | this.fetchDataAsync(folder.id, 'folder'); 265 | } 266 | } 267 | 268 | function createUUID() { 269 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 270 | var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); 271 | return v.toString(16); 272 | }); 273 | } 274 | 275 | // helper function for Request 276 | function apiClientAsync(requestUrl, requestData = null, requestMethod = 'get') { 277 | let def = $.Deferred(); 278 | 279 | if (requestMethod == 'post') { 280 | requestData = JSON.stringify(requestData); 281 | } 282 | 283 | jQuery.ajax({ 284 | url: requestUrl, 285 | contentType: 'application/json', 286 | type: requestMethod, 287 | dataType: 'json', 288 | data: requestData, 289 | success: function (res) { 290 | def.resolve(res); 291 | }, 292 | error: function (err) { 293 | console.error('request failed:'); 294 | def.reject(err) 295 | } 296 | }); 297 | return def.promise(); 298 | } -------------------------------------------------------------------------------- /wwwroot/js/signalr/dist/browser/signalr.min.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root,factory){if(typeof exports==="object"&&typeof module==="object")module.exports=factory();else if(typeof define==="function"&&define.amd)define([],factory);else if(typeof exports==="object")exports["signalR"]=factory();else root["signalR"]=factory()})(window,function(){return function(modules){var installedModules={};function __webpack_require__(moduleId){if(installedModules[moduleId]){return installedModules[moduleId].exports}var module=installedModules[moduleId]={i:moduleId,l:false,exports:{}};modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);module.l=true;return module.exports}__webpack_require__.m=modules;__webpack_require__.c=installedModules;__webpack_require__.d=function(exports,name,getter){if(!__webpack_require__.o(exports,name)){Object.defineProperty(exports,name,{enumerable:true,get:getter})}};__webpack_require__.r=function(exports){if(typeof Symbol!=="undefined"&&Symbol.toStringTag){Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"})}Object.defineProperty(exports,"__esModule",{value:true})};__webpack_require__.t=function(value,mode){if(mode&1)value=__webpack_require__(value);if(mode&8)return value;if(mode&4&&typeof value==="object"&&value&&value.__esModule)return value;var ns=Object.create(null);__webpack_require__.r(ns);Object.defineProperty(ns,"default",{enumerable:true,value:value});if(mode&2&&typeof value!="string")for(var key in value)__webpack_require__.d(ns,key,function(key){return value[key]}.bind(null,key));return ns};__webpack_require__.n=function(module){var getter=module&&module.__esModule?function getDefault(){return module["default"]}:function getModuleExports(){return module};__webpack_require__.d(getter,"a",getter);return getter};__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)};__webpack_require__.p="";return __webpack_require__(__webpack_require__.s=0)}([function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);var es6_promise_dist_es6_promise_auto_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(1);var es6_promise_dist_es6_promise_auto_js__WEBPACK_IMPORTED_MODULE_0___default=__webpack_require__.n(es6_promise_dist_es6_promise_auto_js__WEBPACK_IMPORTED_MODULE_0__);var _index__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(3);__webpack_require__.d(__webpack_exports__,"AbortError",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["AbortError"]});__webpack_require__.d(__webpack_exports__,"HttpError",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HttpError"]});__webpack_require__.d(__webpack_exports__,"TimeoutError",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["TimeoutError"]});__webpack_require__.d(__webpack_exports__,"HttpClient",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HttpClient"]});__webpack_require__.d(__webpack_exports__,"HttpResponse",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HttpResponse"]});__webpack_require__.d(__webpack_exports__,"DefaultHttpClient",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["DefaultHttpClient"]});__webpack_require__.d(__webpack_exports__,"HubConnection",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HubConnection"]});__webpack_require__.d(__webpack_exports__,"HubConnectionState",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HubConnectionState"]});__webpack_require__.d(__webpack_exports__,"HubConnectionBuilder",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HubConnectionBuilder"]});__webpack_require__.d(__webpack_exports__,"MessageType",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["MessageType"]});__webpack_require__.d(__webpack_exports__,"LogLevel",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["LogLevel"]});__webpack_require__.d(__webpack_exports__,"HttpTransportType",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HttpTransportType"]});__webpack_require__.d(__webpack_exports__,"TransferFormat",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["TransferFormat"]});__webpack_require__.d(__webpack_exports__,"NullLogger",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["NullLogger"]});__webpack_require__.d(__webpack_exports__,"JsonHubProtocol",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["JsonHubProtocol"]});__webpack_require__.d(__webpack_exports__,"Subject",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["Subject"]});__webpack_require__.d(__webpack_exports__,"VERSION",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["VERSION"]});if(!Uint8Array.prototype.indexOf){Object.defineProperty(Uint8Array.prototype,"indexOf",{value:Array.prototype.indexOf,writable:true})}if(!Uint8Array.prototype.slice){Object.defineProperty(Uint8Array.prototype,"slice",{value:function(start,end){return new Uint8Array(Array.prototype.slice.call(this,start,end))},writable:true})}if(!Uint8Array.prototype.forEach){Object.defineProperty(Uint8Array.prototype,"forEach",{value:Array.prototype.forEach,writable:true})}},function(module,exports,__webpack_require__){(function(global){var require; 2 | /*! 3 | * @overview es6-promise - a tiny implementation of Promises/A+. 4 | * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) 5 | * @license Licensed under MIT license 6 | * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE 7 | * @version v4.2.2+97478eb6 8 | */ 9 | /*! 10 | * @overview es6-promise - a tiny implementation of Promises/A+. 11 | * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) 12 | * @license Licensed under MIT license 13 | * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE 14 | * @version v4.2.2+97478eb6 15 | */ 16 | (function(global,factory){true?module.exports=factory():undefined})(this,function(){"use strict";function objectOrFunction(x){var type=typeof x;return x!==null&&(type==="object"||type==="function")}function isFunction(x){return typeof x==="function"}var _isArray=void 0;if(Array.isArray){_isArray=Array.isArray}else{_isArray=function(x){return Object.prototype.toString.call(x)==="[object Array]"}}var isArray=_isArray;var len=0;var vertxNext=void 0;var customSchedulerFn=void 0;var asap=function asap(callback,arg){queue[len]=callback;queue[len+1]=arg;len+=2;if(len===2){if(customSchedulerFn){customSchedulerFn(flush)}else{scheduleFlush()}}};function setScheduler(scheduleFn){customSchedulerFn=scheduleFn}function setAsap(asapFn){asap=asapFn}var browserWindow=typeof window!=="undefined"?window:undefined;var browserGlobal=browserWindow||{};var BrowserMutationObserver=browserGlobal.MutationObserver||browserGlobal.WebKitMutationObserver;var isNode=typeof self==="undefined"&&typeof process!=="undefined"&&{}.toString.call(process)==="[object process]";var isWorker=typeof Uint8ClampedArray!=="undefined"&&typeof importScripts!=="undefined"&&typeof MessageChannel!=="undefined";function useNextTick(){return function(){return process.nextTick(flush)}}function useVertxTimer(){if(typeof vertxNext!=="undefined"){return function(){vertxNext(flush)}}return useSetTimeout()}function useMutationObserver(){var iterations=0;var observer=new BrowserMutationObserver(flush);var node=document.createTextNode("");observer.observe(node,{characterData:true});return function(){node.data=iterations=++iterations%2}}function useMessageChannel(){var channel=new MessageChannel;channel.port1.onmessage=flush;return function(){return channel.port2.postMessage(0)}}function useSetTimeout(){var globalSetTimeout=setTimeout;return function(){return globalSetTimeout(flush,1)}}var queue=new Array(1e3);function flush(){for(var i=0;i0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]-1){this.subject.observers.splice(index,1)}if(this.subject.observers.length===0&&this.subject.cancelCallback){this.subject.cancelCallback().catch(function(_){})}};return SubjectSubscription}();var ConsoleLogger=function(){function ConsoleLogger(minimumLogLevel){this.minimumLogLevel=minimumLogLevel;this.outputConsole=console}ConsoleLogger.prototype.log=function(logLevel,message){if(logLevel>=this.minimumLogLevel){switch(logLevel){case _ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"].Critical:case _ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"].Error:this.outputConsole.error("["+(new Date).toISOString()+"] "+_ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"][logLevel]+": "+message);break;case _ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"].Warning:this.outputConsole.warn("["+(new Date).toISOString()+"] "+_ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"][logLevel]+": "+message);break;case _ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"].Information:this.outputConsole.info("["+(new Date).toISOString()+"] "+_ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"][logLevel]+": "+message);break;default:this.outputConsole.log("["+(new Date).toISOString()+"] "+_ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"][logLevel]+": "+message);break}}};return ConsoleLogger}();function getUserAgentHeader(){var userAgentHeaderName="X-SignalR-User-Agent";if(Platform.isNode){userAgentHeaderName="User-Agent"}return[userAgentHeaderName,constructUserAgent(VERSION,getOsName(),getRuntime(),getRuntimeVersion())]}function constructUserAgent(version,os,runtime,runtimeVersion){var userAgent="Microsoft SignalR/";var majorAndMinor=version.split(".");userAgent+=majorAndMinor[0]+"."+majorAndMinor[1];userAgent+=" ("+version+"; ";if(os&&os!==""){userAgent+=os+"; "}else{userAgent+="Unknown OS; "}userAgent+=""+runtime;if(runtimeVersion){userAgent+="; "+runtimeVersion}else{userAgent+="; Unknown Runtime Version"}userAgent+=")";return userAgent}function getOsName(){if(Platform.isNode){switch(process.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return process.platform}}else{return""}}function getRuntimeVersion(){if(Platform.isNode){return process.versions.node}return undefined}function getRuntime(){if(Platform.isNode){return"NodeJS"}else{return"Browser"}}},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"NullLogger",function(){return NullLogger});var NullLogger=function(){function NullLogger(){}NullLogger.prototype.log=function(_logLevel,_message){};NullLogger.instance=new NullLogger;return NullLogger}()},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"XhrHttpClient",function(){return XhrHttpClient});var _Errors__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(4);var _HttpClient__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(5);var _ILogger__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(8);var __extends=undefined&&undefined.__extends||function(){var extendStatics=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(d,b){d.__proto__=b}||function(d,b){for(var p in b)if(b.hasOwnProperty(p))d[p]=b[p]};return function(d,b){extendStatics(d,b);function __(){this.constructor=d}d.prototype=b===null?Object.create(b):(__.prototype=b.prototype,new __)}}();var XhrHttpClient=function(_super){__extends(XhrHttpClient,_super);function XhrHttpClient(logger){var _this=_super.call(this)||this;_this.logger=logger;return _this}XhrHttpClient.prototype.send=function(request){var _this=this;if(request.abortSignal&&request.abortSignal.aborted){return Promise.reject(new _Errors__WEBPACK_IMPORTED_MODULE_0__["AbortError"])}if(!request.method){return Promise.reject(new Error("No method defined."))}if(!request.url){return Promise.reject(new Error("No url defined."))}return new Promise(function(resolve,reject){var xhr=new XMLHttpRequest;xhr.open(request.method,request.url,true);xhr.withCredentials=request.withCredentials===undefined?true:request.withCredentials;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Content-Type","text/plain;charset=UTF-8");var headers=request.headers;if(headers){Object.keys(headers).forEach(function(header){xhr.setRequestHeader(header,headers[header])})}if(request.responseType){xhr.responseType=request.responseType}if(request.abortSignal){request.abortSignal.onabort=function(){xhr.abort();reject(new _Errors__WEBPACK_IMPORTED_MODULE_0__["AbortError"])}}if(request.timeout){xhr.timeout=request.timeout}xhr.onload=function(){if(request.abortSignal){request.abortSignal.onabort=null}if(xhr.status>=200&&xhr.status<300){resolve(new _HttpClient__WEBPACK_IMPORTED_MODULE_1__["HttpResponse"](xhr.status,xhr.statusText,xhr.response||xhr.responseText))}else{reject(new _Errors__WEBPACK_IMPORTED_MODULE_0__["HttpError"](xhr.statusText,xhr.status))}};xhr.onerror=function(){_this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_2__["LogLevel"].Warning,"Error from HTTP request. "+xhr.status+": "+xhr.statusText+".");reject(new _Errors__WEBPACK_IMPORTED_MODULE_0__["HttpError"](xhr.statusText,xhr.status))};xhr.ontimeout=function(){_this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_2__["LogLevel"].Warning,"Timeout from HTTP request.");reject(new _Errors__WEBPACK_IMPORTED_MODULE_0__["TimeoutError"])};xhr.send(request.content||"")})};return XhrHttpClient}(_HttpClient__WEBPACK_IMPORTED_MODULE_1__["HttpClient"])},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"HubConnectionState",function(){return HubConnectionState});__webpack_require__.d(__webpack_exports__,"HubConnection",function(){return HubConnection});var _HandshakeProtocol__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(13);var _IHubProtocol__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(15);var _ILogger__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(8);var _Subject__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(16);var _Utils__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__(9);var __awaiter=undefined&&undefined.__awaiter||function(thisArg,_arguments,P,generator){return new(P||(P=Promise))(function(resolve,reject){function fulfilled(value){try{step(generator.next(value))}catch(e){reject(e)}}function rejected(value){try{step(generator["throw"](value))}catch(e){reject(e)}}function step(result){result.done?resolve(result.value):new P(function(resolve){resolve(result.value)}).then(fulfilled,rejected)}step((generator=generator.apply(thisArg,_arguments||[])).next())})};var __generator=undefined&&undefined.__generator||function(thisArg,body){var _={label:0,sent:function(){if(t[0]&1)throw t[1];return t[1]},trys:[],ops:[]},f,y,t,g;return g={next:verb(0),throw:verb(1),return:verb(2)},typeof Symbol==="function"&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return step([n,v])}}function step(op){if(f)throw new TypeError("Generator is already executing.");while(_)try{if(f=1,y&&(t=op[0]&2?y["return"]:op[0]?y["throw"]||((t=y["return"])&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;if(y=0,t)op=[op[0]&2,t.value];switch(op[0]){case 0:case 1:t=op;break;case 4:_.label++;return{value:op[1],done:false};case 5:_.label++;y=op[1];op=[0];continue;case 7:op=_.ops.pop();_.trys.pop();continue;default:if(!(t=_.trys,t=t.length>0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]responseLength?binaryData.slice(responseLength).buffer:null}else{var textData=data;var separatorIndex=textData.indexOf(_TextMessageFormat__WEBPACK_IMPORTED_MODULE_0__["TextMessageFormat"].RecordSeparator);if(separatorIndex===-1){throw new Error("Message is incomplete.")}var responseLength=separatorIndex+1;messageData=textData.substring(0,responseLength);remainingData=textData.length>responseLength?textData.substring(responseLength):null}var messages=_TextMessageFormat__WEBPACK_IMPORTED_MODULE_0__["TextMessageFormat"].parse(messageData);var response=JSON.parse(messages[0]);if(response.type){throw new Error("Expected a handshake response from the server.")}responseMessage=response;return[remainingData,responseMessage]};return HandshakeProtocol}()},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"TextMessageFormat",function(){return TextMessageFormat});var TextMessageFormat=function(){function TextMessageFormat(){}TextMessageFormat.write=function(output){return""+output+TextMessageFormat.RecordSeparator};TextMessageFormat.parse=function(input){if(input[input.length-1]!==TextMessageFormat.RecordSeparator){throw new Error("Message is incomplete.")}var messages=input.split(TextMessageFormat.RecordSeparator);messages.pop();return messages};TextMessageFormat.RecordSeparatorCode=30;TextMessageFormat.RecordSeparator=String.fromCharCode(TextMessageFormat.RecordSeparatorCode);return TextMessageFormat}()},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"MessageType",function(){return MessageType});var MessageType;(function(MessageType){MessageType[MessageType["Invocation"]=1]="Invocation";MessageType[MessageType["StreamItem"]=2]="StreamItem";MessageType[MessageType["Completion"]=3]="Completion";MessageType[MessageType["StreamInvocation"]=4]="StreamInvocation";MessageType[MessageType["CancelInvocation"]=5]="CancelInvocation";MessageType[MessageType["Ping"]=6]="Ping";MessageType[MessageType["Close"]=7]="Close"})(MessageType||(MessageType={}))},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"Subject",function(){return Subject});var _Utils__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(9);var Subject=function(){function Subject(){this.observers=[]}Subject.prototype.next=function(item){for(var _i=0,_a=this.observers;_i<_a.length;_i++){var observer=_a[_i];observer.next(item)}};Subject.prototype.error=function(err){for(var _i=0,_a=this.observers;_i<_a.length;_i++){var observer=_a[_i];if(observer.error){observer.error(err)}}};Subject.prototype.complete=function(){for(var _i=0,_a=this.observers;_i<_a.length;_i++){var observer=_a[_i];if(observer.complete){observer.complete()}}};Subject.prototype.subscribe=function(observer){this.observers.push(observer);return new _Utils__WEBPACK_IMPORTED_MODULE_0__["SubjectSubscription"](this,observer)};return Subject}()},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"HubConnectionBuilder",function(){return HubConnectionBuilder});var _DefaultReconnectPolicy__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(18);var _HttpConnection__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(19);var _HubConnection__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(12);var _ILogger__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(8);var _JsonHubProtocol__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__(25);var _Loggers__WEBPACK_IMPORTED_MODULE_5__=__webpack_require__(10);var _Utils__WEBPACK_IMPORTED_MODULE_6__=__webpack_require__(9);var __assign=undefined&&undefined.__assign||Object.assign||function(t){for(var s,i=1,n=arguments.length;i0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]0){return[2,Promise.reject(new Error("Unable to connect to the server with any of the available transports. "+transportExceptions.join(" ")))]}return[2,Promise.reject(new Error("None of the transports supported by the client are supported by the server."))]}})})};HttpConnection.prototype.constructTransport=function(transport){switch(transport){case _ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"].WebSockets:if(!this.options.WebSocket){throw new Error("'WebSocket' is not supported in your environment.")}return new _WebSocketTransport__WEBPACK_IMPORTED_MODULE_6__["WebSocketTransport"](this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||false,this.options.WebSocket,this.options.headers||{});case _ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"].ServerSentEvents:if(!this.options.EventSource){throw new Error("'EventSource' is not supported in your environment.")}return new _ServerSentEventsTransport__WEBPACK_IMPORTED_MODULE_4__["ServerSentEventsTransport"](this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||false,this.options.EventSource,this.options.withCredentials,this.options.headers||{});case _ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"].LongPolling:return new _LongPollingTransport__WEBPACK_IMPORTED_MODULE_3__["LongPollingTransport"](this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||false,this.options.withCredentials,this.options.headers||{});default:throw new Error("Unknown transport: "+transport+".")}};HttpConnection.prototype.startTransport=function(url,transferFormat){var _this=this;this.transport.onreceive=this.onreceive;this.transport.onclose=function(e){return _this.stopConnection(e)};return this.transport.connect(url,transferFormat)};HttpConnection.prototype.resolveTransportOrError=function(endpoint,requestedTransport,requestedTransferFormat){var transport=_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][endpoint.transport];if(transport===null||transport===undefined){this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Debug,"Skipping transport '"+endpoint.transport+"' because it is not supported by this client.");return new Error("Skipping transport '"+endpoint.transport+"' because it is not supported by this client.")}else{if(transportMatches(requestedTransport,transport)){var transferFormats=endpoint.transferFormats.map(function(s){return _ITransport__WEBPACK_IMPORTED_MODULE_2__["TransferFormat"][s]});if(transferFormats.indexOf(requestedTransferFormat)>=0){if(transport===_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"].WebSockets&&!this.options.WebSocket||transport===_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"].ServerSentEvents&&!this.options.EventSource){this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Debug,"Skipping transport '"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][transport]+"' because it is not supported in your environment.'");return new Error("'"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][transport]+"' is not supported in your environment.")}else{this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Debug,"Selecting transport '"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][transport]+"'.");try{return this.constructTransport(transport)}catch(ex){return ex}}}else{this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Debug,"Skipping transport '"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][transport]+"' because it does not support the requested transfer format '"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["TransferFormat"][requestedTransferFormat]+"'.");return new Error("'"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][transport]+"' does not support "+_ITransport__WEBPACK_IMPORTED_MODULE_2__["TransferFormat"][requestedTransferFormat]+".")}}else{this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Debug,"Skipping transport '"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][transport]+"' because it was disabled by the client.");return new Error("'"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][transport]+"' is disabled by the client.")}}};HttpConnection.prototype.isITransport=function(transport){return transport&&typeof transport==="object"&&"connect"in transport};HttpConnection.prototype.stopConnection=function(error){var _this=this;this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Debug,"HttpConnection.stopConnection("+error+") called while in state "+this.connectionState+".");this.transport=undefined;error=this.stopError||error;this.stopError=undefined;if(this.connectionState==="Disconnected"){this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Debug,"Call to HttpConnection.stopConnection("+error+") was ignored because the connection is already in the disconnected state.");return}if(this.connectionState==="Connecting"){this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Warning,"Call to HttpConnection.stopConnection("+error+") was ignored because the connection is still in the connecting state.");throw new Error("HttpConnection.stopConnection("+error+") was called while the connection is still in the connecting state.")}if(this.connectionState==="Disconnecting"){this.stopPromiseResolver()}if(error){this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Error,"Connection disconnected with error '"+error+"'.")}else{this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Information,"Connection disconnected.")}if(this.sendQueue){this.sendQueue.stop().catch(function(e){_this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Error,"TransportSendQueue.stop() threw error '"+e+"'.")});this.sendQueue=undefined}this.connectionId=undefined;this.connectionState="Disconnected";if(this.connectionStarted){this.connectionStarted=false;try{if(this.onclose){this.onclose(error)}}catch(e){this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Error,"HttpConnection.onclose("+error+") threw error '"+e+"'.")}}};HttpConnection.prototype.resolveUrl=function(url){if(url.lastIndexOf("https://",0)===0||url.lastIndexOf("http://",0)===0){return url}if(!_Utils__WEBPACK_IMPORTED_MODULE_5__["Platform"].isBrowser||!window.document){throw new Error("Cannot resolve '"+url+"'.")}var aTag=window.document.createElement("a");aTag.href=url;this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Information,"Normalizing '"+url+"' to '"+aTag.href+"'.");return aTag.href};HttpConnection.prototype.resolveNegotiateUrl=function(url){var index=url.indexOf("?");var negotiateUrl=url.substring(0,index===-1?url.length:index);if(negotiateUrl[negotiateUrl.length-1]!=="/"){negotiateUrl+="/"}negotiateUrl+="negotiate";negotiateUrl+=index===-1?"":url.substring(index);if(negotiateUrl.indexOf("negotiateVersion")===-1){negotiateUrl+=index===-1?"?":"&";negotiateUrl+="negotiateVersion="+this.negotiateVersion}return negotiateUrl};return HttpConnection}();function transportMatches(requestedTransport,actualTransport){return!requestedTransport||(actualTransport&requestedTransport)!==0}var TransportSendQueue=function(){function TransportSendQueue(transport){this.transport=transport;this.buffer=[];this.executing=true;this.sendBufferedData=new PromiseSource;this.transportResult=new PromiseSource;this.sendLoopPromise=this.sendLoop()}TransportSendQueue.prototype.send=function(data){this.bufferData(data);if(!this.transportResult){this.transportResult=new PromiseSource}return this.transportResult.promise};TransportSendQueue.prototype.stop=function(){this.executing=false;this.sendBufferedData.resolve();return this.sendLoopPromise};TransportSendQueue.prototype.bufferData=function(data){if(this.buffer.length&&typeof this.buffer[0]!==typeof data){throw new Error("Expected data to be of type "+typeof this.buffer+" but was of type "+typeof data)}this.buffer.push(data);this.sendBufferedData.resolve()};TransportSendQueue.prototype.sendLoop=function(){return __awaiter(this,void 0,void 0,function(){var transportResult,data,error_1;return __generator(this,function(_a){switch(_a.label){case 0:if(false){}return[4,this.sendBufferedData.promise];case 1:_a.sent();if(!this.executing){if(this.transportResult){this.transportResult.reject("Connection stopped.")}return[3,6]}this.sendBufferedData=new PromiseSource;transportResult=this.transportResult;this.transportResult=undefined;data=typeof this.buffer[0]==="string"?this.buffer.join(""):TransportSendQueue.concatBuffers(this.buffer);this.buffer.length=0;_a.label=2;case 2:_a.trys.push([2,4,,5]);return[4,this.transport.send(data)];case 3:_a.sent();transportResult.resolve();return[3,5];case 4:error_1=_a.sent();transportResult.reject(error_1);return[3,5];case 5:return[3,0];case 6:return[2]}})})};TransportSendQueue.concatBuffers=function(arrayBuffers){var totalLength=arrayBuffers.map(function(b){return b.byteLength}).reduce(function(a,b){return a+b});var result=new Uint8Array(totalLength);var offset=0;for(var _i=0,arrayBuffers_1=arrayBuffers;_i0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]