-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
Copy pathSilentHandler.ts
159 lines (136 loc) · 5.75 KB
/
SilentHandler.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
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { UrlString, StringUtils, CommonAuthorizationCodeRequest, AuthorizationCodeClient, Constants, Logger } from "@azure/msal-common";
import { InteractionHandler } from "./InteractionHandler";
import { BrowserConstants } from "../utils/BrowserConstants";
import { BrowserAuthError } from "../error/BrowserAuthError";
import { BrowserCacheManager } from "../cache/BrowserCacheManager";
import { DEFAULT_IFRAME_TIMEOUT_MS } from "../config/Configuration";
export class SilentHandler extends InteractionHandler {
private navigateFrameWait: number;
constructor(authCodeModule: AuthorizationCodeClient, storageImpl: BrowserCacheManager, authCodeRequest: CommonAuthorizationCodeRequest, browserRequestLogger: Logger, navigateFrameWait: number) {
super(authCodeModule, storageImpl, authCodeRequest, browserRequestLogger);
this.navigateFrameWait = navigateFrameWait;
}
/**
* Creates a hidden iframe to given URL using user-requested scopes as an id.
* @param urlNavigate
* @param userRequestScopes
*/
async initiateAuthRequest(requestUrl: string): Promise<HTMLIFrameElement> {
if (StringUtils.isEmpty(requestUrl)) {
// Throw error if request URL is empty.
this.browserRequestLogger.info("Navigate url is empty");
throw BrowserAuthError.createEmptyNavigationUriError();
}
return this.navigateFrameWait ? await this.loadFrame(requestUrl) : this.loadFrameSync(requestUrl);
}
/**
* Monitors an iframe content window until it loads a url with a known hash, or hits a specified timeout.
* @param iframe
* @param timeout
*/
monitorIframeForHash(iframe: HTMLIFrameElement, timeout: number): Promise<string> {
return new Promise((resolve, reject) => {
if (timeout < DEFAULT_IFRAME_TIMEOUT_MS) {
this.browserRequestLogger.warning(`system.loadFrameTimeout or system.iframeHashTimeout set to lower (${timeout}ms) than the default (${DEFAULT_IFRAME_TIMEOUT_MS}ms). This may result in timeouts.`);
}
/*
* Polling for iframes can be purely timing based,
* since we don't need to account for interaction.
*/
const nowMark = window.performance.now();
const timeoutMark = nowMark + timeout;
const intervalId = setInterval(() => {
if (window.performance.now() > timeoutMark) {
this.removeHiddenIframe(iframe);
clearInterval(intervalId);
reject(BrowserAuthError.createMonitorIframeTimeoutError());
return;
}
let href: string = Constants.EMPTY_STRING;
const contentWindow = iframe.contentWindow;
try {
/*
* Will throw if cross origin,
* which should be caught and ignored
* since we need the interval to keep running while on STS UI.
*/
href = contentWindow ? contentWindow.location.href : Constants.EMPTY_STRING;
} catch (e) {}
if (StringUtils.isEmpty(href)) {
return;
}
const contentHash = contentWindow ? contentWindow.location.hash: Constants.EMPTY_STRING;
if (UrlString.hashContainsKnownProperties(contentHash)) {
// Success case
this.removeHiddenIframe(iframe);
clearInterval(intervalId);
resolve(contentHash);
return;
}
}, BrowserConstants.POLL_INTERVAL_MS);
});
}
/**
* @hidden
* Loads iframe with authorization endpoint URL
* @ignore
*/
private loadFrame(urlNavigate: string): Promise<HTMLIFrameElement> {
/*
* This trick overcomes iframe navigation in IE
* IE does not load the page consistently in iframe
*/
return new Promise((resolve, reject) => {
const frameHandle = this.createHiddenIframe();
setTimeout(() => {
if (!frameHandle) {
reject("Unable to load iframe");
return;
}
frameHandle.src = urlNavigate;
resolve(frameHandle);
}, this.navigateFrameWait);
});
}
/**
* @hidden
* Loads the iframe synchronously when the navigateTimeFrame is set to `0`
* @param urlNavigate
* @param frameName
* @param logger
*/
private loadFrameSync(urlNavigate: string): HTMLIFrameElement{
const frameHandle = this.createHiddenIframe();
frameHandle.src = urlNavigate;
return frameHandle;
}
/**
* @hidden
* Creates a new hidden iframe or gets an existing one for silent token renewal.
* @ignore
*/
private createHiddenIframe(): HTMLIFrameElement {
const authFrame = document.createElement("iframe");
authFrame.style.visibility = "hidden";
authFrame.style.position = "absolute";
authFrame.style.width = authFrame.style.height = "0";
authFrame.style.border = "0";
authFrame.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms");
document.getElementsByTagName("body")[0].appendChild(authFrame);
return authFrame;
}
/**
* @hidden
* Removes a hidden iframe from the page.
* @ignore
*/
private removeHiddenIframe(iframe: HTMLIFrameElement): void {
if (document.body === iframe.parentNode) {
document.body.removeChild(iframe);
}
}
}