1/** 2 * Router 3 * 4 * @since 10.1.0 5 * 6 * @copyright (c) 2017 Hightail Inc. All Rights Reserved 7 */ 8'use strict'; 9 10wilson.router(['$window', '$q', '$location', '$rootScope', '$route', '$timeout', 'ObjectStoreService', 'AuthService', 'TrackingService', 'UrlUtility', 'InterruptService', 'localStorageService', 'EarlyAccessService', 'ChatWidgetService', 'CookieService', 'SubscriptionResource', 11 function($window, $q, $location, $rootScope, $route, $timeout, ObjectStoreService, AuthService, TrackingService, UrlUtility, InterruptService, localStorageService, EarlyAccessService, ChatWidgetService, CookieService, SubscriptionResource) { 12 13 var DEFAULT_TRANSFER_ROUTE = '/dashboard'; 14 var MIGRATED_TRANSFER_ROUTE = '/send'; 15 16 var previousParams = {}; 17 var previousPath = null; 18 var libraryCache = {}; 19 20 21 // ____ _ _ __ __ _ _ _ 22 // | _ \ _ __(_)_ ____ _| |_ ___ | \/ | ___| |_| |__ ___ __| |___ 23 // | |_) | '__| \ \ / / _` | __/ _ \ | |\/| |/ _ \ __| '_ \ / _ \ / _` / __| 24 // | __/| | | |\ V / (_| | || __/ | | | | __/ |_| | | | (_) | (_| \__ \ 25 // |_| |_| |_| \_/ \__,_|\__\___| |_| |_|\___|\__|_| |_|\___/ \__,_|___/ 26 // 27 // region private methods 28 29 30 /** 31 * Handle any prerequisite logic prior to processing a route. 32 * 33 * @returns {angular.IPromise<T>} 34 */ 35 function handleRoutePrereqs() {
36 // Facilitate possible early access, promise is resolved if no redirect is necessary 37 return AuthService.reconcileSessionUser().then(EarlyAccessService.facilitateEarlyAccess); 38 } 39 40 41 /** 42 * Method to process a given route change and handle any necessary redirect/storage/setup 43 * required to fulfill the route. Also handles any actions necessary to fulfill component-level routing. 44 * 45 * @param currentRoute 46 * @param routeOptions 47 * @param routeInfo 48 * @returns {angular.IPromise<T>} 49 */ 50 function processRoute(currentRoute, routeOptions, routeInfo) { 51 var routeLocal = true; 52 53 wilson.log.info('Handling Route Change - ' + currentRoute); 54 $rootScope.$broadcast('showLeftNavigationPannel', routeInfo); 55 56 57 // Handle any possible redirect that is required 58 var redirectInfo = getRouteRedirect(currentRoute, routeOptions); 59 60 if (redirectInfo) { 61 62 // Set Original Route to localStorage if it exists 63 if (redirectInfo.originalRoute) { localStorageService.set('originalRoute', redirectInfo.originalRoute); } 64 65 if (redirectInfo.hardRedirect) { 66 $window.location.href = redirectInfo.url; 67 } else { 68 $location.replace(); 69 $location.path(redirectInfo.url); 70 } 71 72 return $q.reject(); 73 } 74 75 // If we are ignoringLocalRoutes and we already have the component loaded, we can continue 76 if (routeOptions.ignoreLocalRouting && wilson.getActivePage() === routeInfo.component) { 77 78 // Check if our new route matches our current one 79 var routeMatchesCurrent = _.every(previousParams, function every(value, key) { 80 return $route.current.params[key] === value; 81 }); 82 var samePath = previousPath && $location.path() === previousPath; 83 84 // If our unique route characteristics match up, then we know its time to ignore local routing and handle 85 if (routeMatchesCurrent) { 86 routeLocal = false;
87 88 // In the next process tick, broadcast a navigation event down the rootScope for handling 89 if (!samePath) { 90 $timeout(function() { 91 $rootScope.$broadcast('navigate', wilson.routeInfo); 92 93 // Fire artificial page tracking events 94 TrackingService.page($location.path(), routeInfo.component).track(); 95 }); 96 } 97 } 98 99 } 100 101 if (wilson.config.chatWidgetEnabled) { 102 // Hide chat for all routes except /update-billing /dashboard and /all-spaces 103 if (_.startsWith(currentRoute, '/update-billing')) { 104 ChatWidgetService.showChat(); 105 } else if (!_.startsWith(currentRoute, '/dashboard') && !_.startsWith(currentRoute, '/all-spaces')) { 106 ChatWidgetService.hideChat(); 107 } else if ($rootScope.authedUser.loggedIn && 108 ($rootScope.authedUser.isTrialParticipant || 109 ($rootScope.authedUser.subscription && 110 ($rootScope.authedUser.subscription.planId === 'pl-00000000-0000-0000-0000-000000000003' || 111 $rootScope.authedUser.subscription.planId === 'pl-00000000-0000-0000-0000-000000000004' || 112 $rootScope.authedUser.subscription.planId === 'pl-00000000-0000-0000-0000-000000000005' || 113 $rootScope.authedUser.subscription.planId === 'pl-00000000-0000-0000-0000-000000000006')))) { 114 ChatWidgetService.showChat(); 115 } else { 116 ChatWidgetService.hideChat(); 117 } 118 } 119 120 // Update our previous component reference and keep a reference to our previous params/path 121 previousParams = _.pick($route.current.params, routeInfo.options.uniqueParams || {}); 122 previousPath = $location.path(); 123 124 // If we are routing locally and there are any open interrupts, we should close them 125 if (routeLocal && $rootScope.interruptActive) { InterruptService.clearAll(); } 126 127 // Now update our page title if we are not going to route locally (as this step will be skipped if we don't!) 128 if (!routeLocal) { $rootScope.page.title = _.template(routeInfo.title)($route.current.params); } 129 130 return routeLocal ? $q.when() : $q.reject(); 131 } 132 133 /** 134 * Populate a redirect with the param values from the current route 135 * @param redirect 136 * @returns {String} - The redirect with param values inserted 137 */ 138 function getRedirectWithParams(redirect) { 139 return _.map(redirect.split('/'), function(path) { 140 var paramRegex = /:\w+$/; 141 var param = path.match(paramRegex); 142 143 if (param) { param = param[0].substring(1); } 144 145 if (wilson.routeInfo[param]) { 146 return path.replace(paramRegex, wilson.routeInfo[param]); 147 } else { 148 return path; 149 } 150 }).join('/'); 151 } 152 153 /** 154 * Get any supported redirect that is required from the given routeOptions 155 * @param currentRoute 156 * @param routeOptions 157 * @returns {Object} - An object defining the supported redirect (null if no redirect required) 158 */ 159 function getRouteRedirect(currentRoute, routeOptions) { 160 var user = AuthService.bindUser(); 161 if (user.authType && 'none' !== user.authType.toLowerCase() && false === user.verified && !routeOptions.allowUnverified) { 162 return { 163 url: '/verification', 164 hardRedirect: true 165 }; 166 } 167 168 // Bad standing redirect 169 if (wilson.config.badstandingInterruptEnabled && !routeOptions.overrideBadStandingRedirect 170 && user.subscription && user.subscription.goodStanding === false 171 && (user.subscription.planId !== SubscriptionResource.supportedPlans.LITE.id 172 && user.subscription.planId !== SubscriptionResource.supportedPlans.CORE.id)) { 173 return { 174 url: '/update-billing', 175 hardRedirect: true 176 }; 177 } 178 179 var hasClosedSeatLimitWarning = localStorageService.get('hasClosedSeatLimitWarning'); 180 if (hasClosedSeatLimitWarning && (Number(hasClosedSeatLimitWarning) < (Date.now() - 24 * 60 * 60 * 1000))) { 181 localStorageService.remove('hasClosedSeatLimitWarning'); 182 } 183 184 // retention over seat limit redirect 185 if (user.subscription && user.subscription.overSeatsLimitStatus === 'WARN' && 186 !user.subscription.isPendingCancellation() && 187 !routeOptions.overrideSeatLimitRedirect && 188 !hasClosedSeatLimitWarning) { 189 return { 190 url: '/seat-limit', 191 hardRedirect: true 192 }; 193 } else if (user.subscription && user.subscription.overSeatsLimitStatus === 'ERROR' && 194 !routeOptions.overrideSeatLimitRedirect) { 195 return { 196 url: '/seat-limit', 197 hardRedirect: true 198 }; 199 } 200 201 // 2FA Notification redirect if force-enable is on 202 if (!user.isSAMLUser && !user.oAuthProvider && !user.twoFactorEnabled && !routeOptions.overrideTwoFactorRedirect && user.getOrgPolicyValue('twoFactorAuthType') === 'ENABLED') { 203 if (user.getOrgPolicyValue('twoFactorAuthEffectiveDate') && moment(user.getOrgPolicyValue('twoFactorAuthEffectiveDate')).isSameOrBefore()) { 204 return { 205 url: '/two-factor-notification', 206 hardRedirect: true 207 }; 208 } else if (!CookieService.get('2FAReminderDate') || moment(CookieService.get('2FAReminderDate')).isSameOrBefore()) { 209 $timeout(function() { 210 $rootScope.$broadcast('interrupt:two-factor-notification'); 211 }); 212 } 213 } 214 215 // Check if there is any pattern matching needed on this route 216 if (routeOptions.invalidPatternRedirect && !checkParamPatternsOnRoute($route.current.params, routeOptions)) { 217 var invalidRedirectTpl = _.template(routeOptions.invalidPatternRedirect); 218 return { url: invalidRedirectTpl($route.current.params) }; 219 } 220 221 // Forwarding 222 if (routeOptions.forward) { 223 // Template forward with route params 224 var forwardTpl = _.template(routeOptions.forward); 225 return { url: forwardTpl($route.current.params) }; 226 } 227 228 // Hard Redirect 229 if (routeOptions.hardRedirect) { return { url: routeOptions.hardRedirect, hardRedirect: true }; } 230 231 // Logged in redirect 232 if (routeOptions.loggedInRedirect && AuthService.hasUserSession()) { 233 return { url: getRedirectWithParams(routeOptions.loggedInRedirect) }; 234 } 235 236 // Logged in hard redirect 237 if (routeOptions.loggedInHardRedirect && AuthService.hasUserSession()) { return { url: getRedirectWithParams(routeOptions.loggedInHardRedirect), hardRedirect: true }; } 238 239 // Logged out redirect 240 if (routeOptions.loggedOutRedirect && !AuthService.hasUserSession()) { 241 return { url: getRedirectWithParams(routeOptions.loggedOutRedirect), originalRoute: (routeOptions.storeOriginalRoute ? $location.url() : null) }; 242 } 243 244 // Logged out corporate site redirect 245 if (routeOptions.loggedOutCorporateSiteRedirect && !AuthService.hasUserSession()) { 246 return { 247 url: (wilson.config.corporateWebBaseUrl + routeOptions.loggedOutCorporateSiteRedirect), 248 originalRoute: (routeOptions.storeOriginalRoute ? $location.path() : null),
249 hardRedirect: true 250 }; 251 } 252 253 // Corporate Site Redirect 254 if (routeOptions.corporateSiteRedirect) { 255 return { 256 url: (wilson.config.corporateWebBaseUrl + routeOptions.corporateSiteRedirect), 257 originalRoute: (routeOptions.storeOriginalRoute ? $location.path() : null), 258 hardRedirect: true 259 }; 260 } 261 262 // SignUp redirects 263 if (routeOptions.loggedOutSignUpRedirect && AuthService.hasAnonymousSession()) { 264 if (wilson.config.signupsInMarketing) { 265 return { 266 url: (wilson.config.corporateWebBaseUrl + (routeOptions.marketingPromoUrl? getRedirectWithParams(routeOptions.marketingPromoUrl): getRedirectWithParams(routeOptions.loggedOutSignUpRedirect))), 267 hardRedirect: true 268 }; 269 } else if (currentRoute !== routeOptions.loggedOutSignUpRedirect) { 270 return { 271 url: getRedirectWithParams(routeOptions.loggedOutSignUpRedirect), 272 hardRedirect: true 273 }; 274 } 275 } 276 277 if (routeOptions.loggedInSignUpRedirect && AuthService.hasUserSession()) { 278 return { 279 url: getRedirectWithParams(routeOptions.loggedInSignUpRedirect), 280 hardRedirect: true 281 }; 282 } else if (routeOptions.loggedInSignUpRedirect && !AuthService.hasUserSession() && wilson.config.signupsInMarketing) { 283 return { 284 url: '/404', 285 hardRedirect: true 286 }; 287 } 288 289 // User property redirects 290 if (routeOptions.userPropertyRedirects) { 291 var redirect = null; 292 _.forEach(routeOptions.userPropertyRedirects, function(userPropertyRedirect) { 293 if (redirect || (userPropertyRedirect.excludeAdmin && user.isOrgAdmin())) { 294 return; 295 } 296 if (userPropertyRedirect.not && _.first(_.at(user, userPropertyRedirect.path)) !== userPropertyRedirect.value) { 297 redirect = { 298 url: userPropertyRedirect.url, 299 hardRedirect: true 300 } 301 } else if (!userPropertyRedirect.not && _.first(_.at(user, userPropertyRedirect.path)) === userPropertyRedirect.value) { 302 redirect = { 303 url: userPropertyRedirect.url, 304 hardRedirect: true 305 } 306 } 307 }); 308 if (redirect) { 309 return redirect; 310 } 311 } 312 313 // Privilege Check 314 if (routeOptions.privilegeRequired) { 315 var req = routeOptions.privilegeRequired; 316 317 // Check for org privilege to go into admin console. If he does NOT have organization AND the required privilege 318 if (!AuthService.hasUserSession() || !(user.subscription.organization && _.intersection(user.subscription.organization.privileges, req.privilege).length > 0) ) { 319 return { url: req.redirect }; 320 } 321 } 322 323 if (routeOptions.teamsPrivilegeRequired) { 324 var req = routeOptions.teamsPrivilegeRequired; 325 326 // Check for org privilege to go into Teams tab. If the user does NOT have organization AND the required privilege 327 if (!AuthService.hasUserSession() || !user.hasTeamAccess()) { 328 return { url: req.redirect }; 329 } 330 } 331 332 // Required Inlet Type check 333 if (routeOptions.requiredInletType && AuthService.hasUserSession()) { 334 if (routeOptions.requiredInletType !== user.inletType) { return { url: routeOptions.inletTypeRedirect || '/' }; } 335 } 336 337 // Required entitlement check 338 if (routeOptions.requiredEntitlement && AuthService.hasUserSession()) { 339 if (!user.entitlements[routeOptions.requiredEntitlement]) { return { url: routeOptions.entitlementRedirect || '/' }; } 340 } 341 342 // Hide nav bar for routes that don't support it. 343 if (!routeOptions.hasNavigation) { 344 $('.static-nav-bar').css('display', 'none'); 345 } 346 347 return null; 348 } 349 350 function isParamPatternValid(paramValue, pattern) { 351 if (!_.isString(paramValue)) { return false; } 352 353 var regex = new RegExp(pattern); 354 355 return regex.test(paramValue); 356 } 357 358 function checkParamPatternsOnRoute(params, routeOptions) { 359 // If no regex patterns need to be enforced, there's no issue :) 360 if (!routeOptions.paramRegexPatterns) { return true; } 361 362 return _.every(routeOptions.paramRegexPatterns, function(pattern, routeParam) { 363 return isParamPatternValid(params[routeParam], pattern); 364 }); 365 } 366 367 // endregion 368 369 370 // ____ _ __ __ _ _ _ 371 // / ___| ___ _ ____ _(_) ___ ___ | \/ | ___| |_| |__ ___ __| |___ 372 // \___ \ / _ \ '__\ \ / / |/ __/ _ \ | |\/| |/ _ \ __| '_ \ / _ \ / _` / __| 373 // ___) | __/ | \ V /| | (_| __/ | | | | __/ |_| | | | (_) | (_| \__ \ 374 // |____/ \___|_| \_/ |_|\___\___| |_| |_|\___|\__|_| |_|\___/ \__,_|___/ 375 // 376 // region service methods 377 378 379 /** 380 * Handle top level route change in the application (i.e. URL navigation) 381 * 382 * Performs any application-specific logic necessary for fulfilling an angular route. 383 * 384 * Returns a promise that is resolve if angular should continue its routing, and rejected 385 * if it should stop routing. 386 * 387 * @param currentRoute 388 * @param routeOptions 389 * @param routeInfo 390 * @returns {*|Promise} 391 */ 392 function handleRouteChange(currentRoute, routeOptions, routeInfo) { 393 return handleRoutePrereqs().then(function() { 394 return processRoute(currentRoute, routeOptions, routeInfo); 395 }, $q.reject); 396 } 397 398 399 /** 400 * Method to Asynchronously resolve any js dependencies that are needed for this client-side route 401 * to be resolved by angular. 402 * 403 * @param routeInfo 404 */ 405 function loadDependencies(routeInfo) { 406 // Load any needed javascript dependencies 407 var config = wilson.config; 408 var host = config.cdn ? (config.cdn.protocol + '://' + config.cdn.host) : ''; 409 var versionedAssets = config.app.useVersionedAssets; 410 var options = routeInfo.options || {}; 411 var promises = []; 412 413 _.each(options.jsDependencies, function(scriptPath) { 414 var preparedScriptPath = scriptPath; 415 416 if (versionedAssets) { preparedScriptPath = scriptPath.replace(config.app.assetPath, config.app.versionedAssetPath); } 417 418 if (!libraryCache[preparedScriptPath]) { 419 var scriptPromise = $q.defer(); 420 421 $.getScript(host + preparedScriptPath).done(function() { 422 libraryCache[preparedScriptPath] = true; 423 scriptPromise.resolve(); 424 }).fail(scriptPromise.reject); 425 426 promises.push(scriptPromise.promise); 427 } 428 }); 429 430 // Resolve after all script dependencies are loaded 431 return $q.all(promises); 432 } 433 434 435 /** 436 * Attempts to provision a new session if we do not have one yet. If we do have a session, 437 * check if that session is valid. 438 * 439 * @method loadSession 440 * 441 * @return {Promise} - A Promise which will resolve when we have a session or our current session is deemed valid 442 */ 443 function loadSession() { 444 return $q.when().then(function() { 445 // If there is no session, then let's provision one 446 if (!AuthService.hasSession()) { 447 return AuthService.provisionNewSession().then(function() { // Mark tracking first touch 448 $timeout(function() { 449 var firstTouch = localStorageService.get('firstTouch'); 450 localStorageService.set('firstTouch', 1); 451 if (!firstTouch) { $rootScope.$broadcast('firstTouch'); } 452 }, 500); 453 }); 454 } else {
455 // Ok, we have a session, but is it valid? 456 AuthService.checkSession(); 457 } 458 }); 459 } 460 461 462 /** 463 * Translate and template the route title. 464 * 465 * This method should apply any appropriate templating and/or tranform to the route.title. 466 * 467 * @param routeTitle 468 * @returns {*} 469 */ 470 function getTitleText(routeTitle) { 471 // Update page title 472 if (routeTitle) { 473 var content = 'Hightail Spaces'; 474 475 try { 476 content = _.template(routeTitle)($route.current.params); 477 } catch (err) { } 478 479 return content; 480 } 481 482 return false; 483 } 484 485 // endregion 486 487 488 /************************************/ 489 /******** SERVICE INTERFACE *********/ 490 /************************************/ 491 var service = { 492 handleRouteChange: handleRouteChange, 493 494 loadDependencies: loadDependencies, 495 loadSession: loadSession, 496 497 getTitleText: getTitleText 498 }; 499 500 return service; 501 } 502]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.