-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
Copy pathBrowserUtils.ts
215 lines (193 loc) · 6.52 KB
/
BrowserUtils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
UrlString,
invoke,
invokeAsync,
UrlUtils,
} from "@azure/msal-common/browser";
import {
createBrowserAuthError,
BrowserAuthErrorCodes,
} from "../error/BrowserAuthError.js";
import { BrowserConstants, BrowserCacheLocation } from "./BrowserConstants.js";
import * as BrowserCrypto from "../crypto/BrowserCrypto.js";
import {
BrowserConfigurationAuthErrorCodes,
createBrowserConfigurationAuthError,
} from "../error/BrowserConfigurationAuthError.js";
import { BrowserConfiguration } from "../config/Configuration.js";
/**
* Clears hash from window url.
*/
export function clearHash(contentWindow: Window): void {
// Office.js sets history.replaceState to null
contentWindow.location.hash = "";
if (typeof contentWindow.history.replaceState === "function") {
// Full removes "#" from url
contentWindow.history.replaceState(
null,
"",
`${contentWindow.location.origin}${contentWindow.location.pathname}${contentWindow.location.search}`
);
}
}
/**
* Replaces current hash with hash from provided url
*/
export function replaceHash(url: string): void {
const urlParts = url.split("#");
urlParts.shift(); // Remove part before the hash
window.location.hash = urlParts.length > 0 ? urlParts.join("#") : "";
}
/**
* Returns boolean of whether the current window is in an iframe or not.
*/
export function isInIframe(): boolean {
return window.parent !== window;
}
/**
* Returns boolean of whether or not the current window is a popup opened by msal
*/
export function isInPopup(): boolean {
return (
typeof window !== "undefined" &&
!!window.opener &&
window.opener !== window &&
typeof window.name === "string" &&
window.name.indexOf(`${BrowserConstants.POPUP_NAME_PREFIX}.`) === 0
);
}
// #endregion
/**
* Returns current window URL as redirect uri
*/
export function getCurrentUri(): string {
return typeof window !== "undefined" && window.location
? window.location.href.split("?")[0].split("#")[0]
: "";
}
/**
* Gets the homepage url for the current window location.
*/
export function getHomepage(): string {
const currentUrl = new UrlString(window.location.href);
const urlComponents = currentUrl.getUrlComponents();
return `${urlComponents.Protocol}//${urlComponents.HostNameAndPort}/`;
}
/**
* Throws error if we have completed an auth and are
* attempting another auth request inside an iframe.
*/
export function blockReloadInHiddenIframes(): void {
const isResponseHash = UrlUtils.getDeserializedResponse(
window.location.hash
);
// return an error if called from the hidden iframe created by the msal js silent calls
if (isResponseHash && isInIframe()) {
throw createBrowserAuthError(BrowserAuthErrorCodes.blockIframeReload);
}
}
/**
* Block redirect operations in iframes unless explicitly allowed
* @param interactionType Interaction type for the request
* @param allowRedirectInIframe Config value to allow redirects when app is inside an iframe
*/
export function blockRedirectInIframe(allowRedirectInIframe: boolean): void {
if (isInIframe() && !allowRedirectInIframe) {
// If we are not in top frame, we shouldn't redirect. This is also handled by the service.
throw createBrowserAuthError(BrowserAuthErrorCodes.redirectInIframe);
}
}
/**
* Block redirectUri loaded in popup from calling AcquireToken APIs
*/
export function blockAcquireTokenInPopups(): void {
// Popups opened by msal popup APIs are given a name that starts with "msal."
if (isInPopup()) {
throw createBrowserAuthError(BrowserAuthErrorCodes.blockNestedPopups);
}
}
/**
* Throws error if token requests are made in non-browser environment
* @param isBrowserEnvironment Flag indicating if environment is a browser.
*/
export function blockNonBrowserEnvironment(): void {
if (typeof window === "undefined") {
throw createBrowserAuthError(
BrowserAuthErrorCodes.nonBrowserEnvironment
);
}
}
/**
* Throws error if initialize hasn't been called
* @param initialized
*/
export function blockAPICallsBeforeInitialize(initialized: boolean): void {
if (!initialized) {
throw createBrowserAuthError(
BrowserAuthErrorCodes.uninitializedPublicClientApplication
);
}
}
/**
* Helper to validate app environment before making an auth request
* @param initialized
*/
export function preflightCheck(initialized: boolean): void {
// Block request if not in browser environment
blockNonBrowserEnvironment();
// Block auth requests inside a hidden iframe
blockReloadInHiddenIframes();
// Block redirectUri opened in a popup from calling MSAL APIs
blockAcquireTokenInPopups();
// Block token acquisition before initialize has been called
blockAPICallsBeforeInitialize(initialized);
}
/**
* Helper to validate app enviornment before making redirect request
* @param initialized
* @param config
*/
export function redirectPreflightCheck(
initialized: boolean,
config: BrowserConfiguration
): void {
preflightCheck(initialized);
blockRedirectInIframe(config.system.allowRedirectInIframe);
// Block redirects if memory storage is enabled
if (config.cache.cacheLocation === BrowserCacheLocation.MemoryStorage) {
throw createBrowserConfigurationAuthError(
BrowserConfigurationAuthErrorCodes.inMemRedirectUnavailable
);
}
}
/**
* Adds a preconnect link element to the header which begins DNS resolution and SSL connection in anticipation of the /token request
* @param loginDomain Authority domain, including https protocol e.g. https://login.microsoftonline.com
* @returns
*/
export function preconnect(authority: string): void {
const link = document.createElement("link");
link.rel = "preconnect";
link.href = new URL(authority).origin;
link.crossOrigin = "anonymous";
document.head.appendChild(link);
// The browser will close connection if not used within a few seconds, remove element from the header after 10s
window.setTimeout(() => {
try {
document.head.removeChild(link);
} catch {}
}, 10000); // 10s Timeout
}
/**
* Wrapper function that creates a UUID v7 from the current timestamp.
* @returns {string}
*/
export function createGuid(): string {
return BrowserCrypto.createNewGuid();
}
export { invoke };
export { invokeAsync };