@azure/msal-browser
Version:
Microsoft Authentication Library for js
247 lines (244 loc) • 12.5 kB
JavaScript
/*! @azure/msal-browser v2.28.1 2022-08-01 */
'use strict';
import { __awaiter, __generator } from '../../_virtual/_tslib.js';
import { NativeConstants, NativeExtensionMethod } from '../../utils/BrowserConstants.js';
import { AuthError, AuthenticationScheme } from '@azure/msal-common';
import { NativeAuthError } from '../../error/NativeAuthError.js';
import { BrowserAuthError } from '../../error/BrowserAuthError.js';
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var NativeMessageHandler = /** @class */ (function () {
function NativeMessageHandler(logger, handshakeTimeoutMs, extensionId) {
this.logger = logger;
this.handshakeTimeoutMs = handshakeTimeoutMs;
this.extensionId = extensionId;
this.resolvers = new Map(); // Used for non-handshake messages
this.handshakeResolvers = new Map(); // Used for handshake messages
this.responseId = 0;
this.messageChannel = new MessageChannel();
this.windowListener = this.onWindowMessage.bind(this); // Window event callback doesn't have access to 'this' unless it's bound
}
/**
* Sends a given message to the extension and resolves with the extension response
* @param body
*/
NativeMessageHandler.prototype.sendMessage = function (body) {
return __awaiter(this, void 0, void 0, function () {
var req;
var _this = this;
return __generator(this, function (_a) {
this.logger.trace("NativeMessageHandler - sendMessage called.");
req = {
channel: NativeConstants.CHANNEL_ID,
extensionId: this.extensionId,
responseId: this.responseId++,
body: body
};
this.logger.trace("NativeMessageHandler - Sending request to browser extension");
this.logger.tracePii("NativeMessageHandler - Sending request to browser extension: " + JSON.stringify(req));
this.messageChannel.port1.postMessage(req);
return [2 /*return*/, new Promise(function (resolve, reject) {
_this.resolvers.set(req.responseId, { resolve: resolve, reject: reject });
})];
});
});
};
/**
* Returns an instance of the MessageHandler that has successfully established a connection with an extension
* @param logger
* @param handshakeTimeoutMs
*/
NativeMessageHandler.createProvider = function (logger, handshakeTimeoutMs) {
return __awaiter(this, void 0, void 0, function () {
var preferredProvider, backupProvider;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
logger.trace("NativeMessageHandler - createProvider called.");
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 5]);
preferredProvider = new NativeMessageHandler(logger, handshakeTimeoutMs, NativeConstants.PREFERRED_EXTENSION_ID);
return [4 /*yield*/, preferredProvider.sendHandshakeRequest()];
case 2:
_a.sent();
return [2 /*return*/, preferredProvider];
case 3:
_a.sent();
backupProvider = new NativeMessageHandler(logger, handshakeTimeoutMs);
return [4 /*yield*/, backupProvider.sendHandshakeRequest()];
case 4:
_a.sent();
return [2 /*return*/, backupProvider];
case 5: return [2 /*return*/];
}
});
});
};
/**
* Send handshake request helper.
*/
NativeMessageHandler.prototype.sendHandshakeRequest = function () {
return __awaiter(this, void 0, void 0, function () {
var req;
var _this = this;
return __generator(this, function (_a) {
this.logger.trace("NativeMessageHandler - sendHandshakeRequest called.");
// Register this event listener before sending handshake
window.addEventListener("message", this.windowListener, false); // false is important, because content script message processing should work first
req = {
channel: NativeConstants.CHANNEL_ID,
extensionId: this.extensionId,
responseId: this.responseId++,
body: {
method: NativeExtensionMethod.HandshakeRequest
}
};
this.messageChannel.port1.onmessage = function (event) {
_this.onChannelMessage(event);
};
window.postMessage(req, window.origin, [this.messageChannel.port2]);
return [2 /*return*/, new Promise(function (resolve, reject) {
_this.handshakeResolvers.set(req.responseId, { resolve: resolve, reject: reject });
_this.timeoutId = window.setTimeout(function () {
/*
* Throw an error if neither HandshakeResponse nor original Handshake request are received in a reasonable timeframe.
* This typically suggests an event handler stopped propagation of the Handshake request but did not respond to it on the MessageChannel port
*/
window.removeEventListener("message", _this.windowListener, false);
_this.messageChannel.port1.close();
_this.messageChannel.port2.close();
reject(BrowserAuthError.createNativeHandshakeTimeoutError());
_this.handshakeResolvers.delete(req.responseId);
}, _this.handshakeTimeoutMs); // Use a reasonable timeout in milliseconds here
})];
});
});
};
/**
* Invoked when a message is posted to the window. If a handshake request is received it means the extension is not installed.
* @param event
*/
NativeMessageHandler.prototype.onWindowMessage = function (event) {
this.logger.trace("NativeMessageHandler - onWindowMessage called");
// We only accept messages from ourselves
if (event.source !== window) {
return;
}
var request = event.data;
if (!request.channel || request.channel !== NativeConstants.CHANNEL_ID) {
return;
}
if (request.extensionId && request.extensionId !== this.extensionId) {
return;
}
if (request.body.method === NativeExtensionMethod.HandshakeRequest) {
// If we receive this message back it means no extension intercepted the request, meaning no extension supporting handshake protocol is installed
this.logger.verbose(request.extensionId ? "Extension with id: " + request.extensionId + " not installed" : "No extension installed");
clearTimeout(this.timeoutId);
this.messageChannel.port1.close();
this.messageChannel.port2.close();
window.removeEventListener("message", this.windowListener, false);
var handshakeResolver = this.handshakeResolvers.get(request.responseId);
if (handshakeResolver) {
handshakeResolver.reject(BrowserAuthError.createNativeExtensionNotInstalledError());
}
}
};
/**
* Invoked when a message is received from the extension on the MessageChannel port
* @param event
*/
NativeMessageHandler.prototype.onChannelMessage = function (event) {
this.logger.trace("NativeMessageHandler - onChannelMessage called.");
var request = event.data;
var resolver = this.resolvers.get(request.responseId);
var handshakeResolver = this.handshakeResolvers.get(request.responseId);
try {
var method = request.body.method;
if (method === NativeExtensionMethod.Response) {
if (!resolver) {
return;
}
var response = request.body.response;
this.logger.trace("NativeMessageHandler - Received response from browser extension");
this.logger.tracePii("NativeMessageHandler - Received response from browser extension: " + JSON.stringify(response));
if (response.status !== "Success") {
resolver.reject(NativeAuthError.createError(response.code, response.description, response.ext));
}
else if (response.result) {
if (response.result["code"] && response.result["description"]) {
resolver.reject(NativeAuthError.createError(response.result["code"], response.result["description"], response.result["ext"]));
}
else {
resolver.resolve(response.result);
}
}
else {
throw AuthError.createUnexpectedError("Event does not contain result.");
}
this.resolvers.delete(request.responseId);
}
else if (method === NativeExtensionMethod.HandshakeResponse) {
if (!handshakeResolver) {
return;
}
clearTimeout(this.timeoutId); // Clear setTimeout
window.removeEventListener("message", this.windowListener, false); // Remove 'No extension' listener
this.extensionId = request.extensionId;
this.logger.verbose("NativeMessageHandler - Received HandshakeResponse from extension: " + this.extensionId);
handshakeResolver.resolve();
this.handshakeResolvers.delete(request.responseId);
}
// Do nothing if method is not Response or HandshakeResponse
}
catch (err) {
this.logger.error("Error parsing response from WAM Extension");
this.logger.errorPii("Error parsing response from WAM Extension: " + err.toString());
this.logger.errorPii("Unable to parse " + event);
if (resolver) {
resolver.reject(err);
}
else if (handshakeResolver) {
handshakeResolver.reject(err);
}
}
};
/**
* Returns boolean indicating whether or not the request should attempt to use native broker
* @param logger
* @param config
* @param nativeExtensionProvider
* @param authenticationScheme
*/
NativeMessageHandler.isNativeAvailable = function (config, logger, nativeExtensionProvider, authenticationScheme) {
logger.trace("isNativeAvailable called");
if (!config.system.allowNativeBroker) {
logger.trace("isNativeAvailable: allowNativeBroker is not enabled, returning false");
// Developer disabled WAM
return false;
}
if (!nativeExtensionProvider) {
logger.trace("isNativeAvailable: WAM extension provider is not initialized, returning false");
// Extension is not available
return false;
}
if (authenticationScheme) {
switch (authenticationScheme) {
case AuthenticationScheme.BEARER:
case AuthenticationScheme.POP:
logger.trace("isNativeAvailable: authenticationScheme is supported, returning true");
return true;
default:
logger.trace("isNativeAvailable: authenticationScheme is not supported, returning false");
return false;
}
}
return true;
};
return NativeMessageHandler;
}());
export { NativeMessageHandler };
//# sourceMappingURL=NativeMessageHandler.js.map