UNPKG

@azure/msal-browser

Version:
247 lines (244 loc) 12.5 kB
/*! @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