oidc-lib
Version:
A library for creating OIDC Service Providers
1,434 lines (1,210 loc) • 71.5 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["credentialHandlerPolyfill"] = factory();
else
root["credentialHandlerPolyfill"] = factory();
})(window, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./index.js");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./CredentialHandler.js":
/*!******************************!*\
!*** ./CredentialHandler.js ***!
\******************************/
/*! exports provided: CredentialHandler */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialHandler", function() { return CredentialHandler; });
/* harmony import */ var web_request_rpc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! web-request-rpc */ "./node_modules/web-request-rpc/index.js");
/* harmony import */ var _CredentialHandlerService_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./CredentialHandlerService.js */ "./CredentialHandlerService.js");
/*!
* The core CredentialHandler class.
*
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
/* global DOMException */
const EVENT_TYPES = ['credentialrequest', 'credentialstore'];
class CredentialHandler extends web_request_rpc__WEBPACK_IMPORTED_MODULE_0__["WebApp"] {
constructor(mediatorOrigin) {
if(typeof mediatorOrigin !== 'string') {
throw new TypeError('"mediatorOrigin" must be a string.');
}
super(mediatorOrigin);
this._emitter = new web_request_rpc__WEBPACK_IMPORTED_MODULE_0__["EventEmitter"]({
async waitUntil(event) {
// TODO: may need to do `this.hide()` after this promise resolves
// to handle case where e.openWindow() was called
return event._promise || Promise.reject(
new DOMException(
'No "credentialrequest" event handler found.', 'NotFoundError'));
}
});
}
async connect() {
const injector = await super.connect();
// define API that CredentialMediator can call on this credential handler
this.server.define('credentialHandler', new _CredentialHandlerService_js__WEBPACK_IMPORTED_MODULE_1__["CredentialHandlerService"](this));
// auto-call `ready`
await this.ready();
return injector;
}
addEventListener(eventType, fn) {
if(!EVENT_TYPES.includes(eventType)) {
throw new DOMException(
`Unsupported event type "${eventType}"`, 'NotSupportedError');
}
return this._emitter.addEventListener(eventType, fn);
}
removeEventListener(eventType, fn) {
if(!EVENT_TYPES.includes(eventType)) {
throw new DOMException(
`Unsupported event type "${eventType}"`, 'NotSupportedError');
}
return this._emitter.removeEventListener(eventType, fn);
}
}
/***/ }),
/***/ "./CredentialHandlerRegistration.js":
/*!******************************************!*\
!*** ./CredentialHandlerRegistration.js ***!
\******************************************/
/*! exports provided: CredentialHandlerRegistration */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialHandlerRegistration", function() { return CredentialHandlerRegistration; });
/* harmony import */ var _CredentialManager_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./CredentialManager.js */ "./CredentialManager.js");
/*!
* A CredentialHandlerRegistration provides a CredentialManager to enable Web
* apps to register Profiles that can be presented to websites.
*
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
class CredentialHandlerRegistration {
constructor(url, injector) {
if(!(url && typeof url === 'string')) {
throw new TypeError('"url" must be a non-empty string.');
}
this.credentialManager = new _CredentialManager_js__WEBPACK_IMPORTED_MODULE_0__["CredentialManager"](url, injector);
}
}
/***/ }),
/***/ "./CredentialHandlerService.js":
/*!*************************************!*\
!*** ./CredentialHandlerService.js ***!
\*************************************/
/*! exports provided: CredentialHandlerService */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialHandlerService", function() { return CredentialHandlerService; });
/* harmony import */ var _CredentialRequestEvent_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./CredentialRequestEvent.js */ "./CredentialRequestEvent.js");
/* harmony import */ var _CredentialStoreEvent_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./CredentialStoreEvent.js */ "./CredentialStoreEvent.js");
/*!
* A CredentialHandlerService handles remote calls to a CredentialHandler.
*
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
class CredentialHandlerService {
constructor(credentialHandler) {
this._credentialHandler = credentialHandler;
}
async request(credentialRequestEvent) {
// TODO: validate credentialRequestEvent
return await this._credentialHandler._emitter.emit(
new _CredentialRequestEvent_js__WEBPACK_IMPORTED_MODULE_0__["CredentialRequestEvent"](Object.assign(
{credentialHandler: this._credentialHandler}, credentialRequestEvent)));
}
async store(credentialStoreEvent) {
// TODO: validate credentialStoreEvent
return await this._credentialHandler._emitter.emit(
new _CredentialStoreEvent_js__WEBPACK_IMPORTED_MODULE_1__["CredentialStoreEvent"](Object.assign(
{credentialHandler: this._credentialHandler}, credentialStoreEvent)));
}
}
/***/ }),
/***/ "./CredentialHandlers.js":
/*!*******************************!*\
!*** ./CredentialHandlers.js ***!
\*******************************/
/*! exports provided: CredentialHandlers */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialHandlers", function() { return CredentialHandlers; });
/* harmony import */ var _CredentialHandlerRegistration_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./CredentialHandlerRegistration.js */ "./CredentialHandlerRegistration.js");
/*!
* Provides an API for working with CredentialHandlerRegistrations.
*
* Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved.
*/
class CredentialHandlers {
constructor(injector) {
this._init = (async () => {
this._injector = await injector;
this._remote = this._injector.get('credentialHandlers', {
functions: [
'register', 'unregister', 'getRegistration', 'hasRegistration']
});
})();
}
/**
* Creates a credential handler registration.
*
* @param url the unique URL for the credential handler.
*
* @return a Promise that resolves to the CredentialHandlerRegistration.
*/
async register(url) {
await this._init;
// register with credential mediator
url = await this._remote.register('credential', url);
return new _CredentialHandlerRegistration_js__WEBPACK_IMPORTED_MODULE_0__["CredentialHandlerRegistration"](url, this._injector);
}
/**
* Unregisters a credential handler, destroying its registration.
*
* @param url the unique URL for the credential handler.
*
* @return a Promise that resolves to `true` if the handler was registered
* and `false` if not.
*/
async unregister(url) {
await this._init;
// unregister with credential mediator
return this._remote.unregister('credential', url);
}
/**
* Gets an existing credential handler registration.
*
* @param url the URL for the credential handler.
*
* @return a Promise that resolves to the CredentialHandlerRegistration or
* `null` if no such registration exists.
*/
async getRegistration(url) {
await this._init;
url = await this._remote.getRegistration('credential', url);
if(!url) {
return null;
}
return new _CredentialHandlerRegistration_js__WEBPACK_IMPORTED_MODULE_0__["CredentialHandlerRegistration"](url, this._injector);
}
/**
* Returns true if the given credential handler has been registered and
* false if not.
*
* @param url the URL for the credential handler.
*
* @return a Promise that resolves to `true` if the registration exists and
* `false` if not.
*/
async hasRegistration(url) {
await this._init;
return await this._remote.hasRegistration('credential', url);
}
}
/***/ }),
/***/ "./CredentialHints.js":
/*!****************************!*\
!*** ./CredentialHints.js ***!
\****************************/
/*! exports provided: CredentialHints */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialHints", function() { return CredentialHints; });
/*!
* API for managing CredentialHints.
*
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
/* global Image */
class CredentialHints {
constructor(url, injector) {
const remote = injector.get('credentialHints', {
functions: ['delete', 'get', 'keys', 'has', 'set', 'clear']
});
for(let methodName in remote) {
if(methodName !== 'set') {
this[methodName] = remote[methodName].bind(this, url);
}
}
this._remoteSet = remote.set.bind(this, url);
}
async set(hintKey, credentialHint) {
// TODO: validate credential hint
// ensure images are prefetched so that they will not leak information
// when fetched later
credentialHint.icons = credentialHint.icons || [];
const promises = credentialHint.icons.map(icon =>
imageToDataUrl(icon.src).then(fetchedImage => {
icon.fetchedImage = fetchedImage;
}));
await Promise.all(promises);
return this._remoteSet(hintKey, credentialHint);
}
}
function imageToDataUrl(url) {
return new Promise(resolve => {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = () => {
let canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img, 0, 0);
const dataUrl = canvas.toDataURL();
resolve(dataUrl);
canvas = null;
};
// TODO: `reject` as an error and fail `.set`?
img.onerror = () => resolve(null);
img.src = url;
});
}
/***/ }),
/***/ "./CredentialManager.js":
/*!******************************!*\
!*** ./CredentialManager.js ***!
\******************************/
/*! exports provided: CredentialManager */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialManager", function() { return CredentialManager; });
/* harmony import */ var _CredentialHints_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./CredentialHints.js */ "./CredentialHints.js");
/*!
* A CredentialManager for a Web Credential Mediator.
*
* Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved.
*/
/* global navigator */
class CredentialManager {
constructor(url, injector) {
if(!(url && typeof url === 'string')) {
throw new TypeError('"url" must be a non-empty string.');
}
this.hints = new _CredentialHints_js__WEBPACK_IMPORTED_MODULE_0__["CredentialHints"](url, injector);
}
/**
* Requests that the user grant 'credentialhandler' permission to the current
* origin.
*
* @return a Promise that resolves to the new PermissionState of the
* permission (e.g. 'granted'/'denied').
*/
static async requestPermission() {
const status = await navigator.credentialsPolyfill.permissions.request(
{name: 'credentialhandler'});
return status.state;
}
}
/***/ }),
/***/ "./CredentialRequestEvent.js":
/*!***********************************!*\
!*** ./CredentialRequestEvent.js ***!
\***********************************/
/*! exports provided: CredentialRequestEvent */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialRequestEvent", function() { return CredentialRequestEvent; });
/* harmony import */ var web_request_rpc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! web-request-rpc */ "./node_modules/web-request-rpc/index.js");
/*!
* A CredentialRequestEvent is emitted when a request has been made for
* credentials.
*
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
/* global Event */
// can't use "ExtendableEvent"; only accessible from Workers
// TODO: may not be able to even extend `Event` here; could produce "incorrect"
// core attributes
class CredentialRequestEvent /*extends Event*/ {
constructor({
credentialHandler,
credentialRequestOrigin,
credentialRequestOptions,
hintKey
}) {
//super('credentialrequest');
this.type = 'credentialrequest';
this._credentialHandler = credentialHandler;
this.credentialRequestOrigin = credentialRequestOrigin;
this.credentialRequestOptions = credentialRequestOptions;
this.hintKey = hintKey;
}
async openWindow(url) {
// TODO: disallow more than one call
// TODO: ensure `url` is to the same origin
await this._credentialHandler.show();
const appWindow = new web_request_rpc__WEBPACK_IMPORTED_MODULE_0__["WebAppWindow"](url, {
className: 'credential-handler'
});
appWindow.ready();
appWindow.show();
// TODO: note that `appWindow.handle` is not a ServiceWorker
// `WindowClient` polyfill... could be confusing here, should we
// implement one to wrap it? -- there is, for example, a
// `navigate` call on `WindowClient` that enforces same origin, would
// need to attempt to add or approximate that
return appWindow.handle;
}
respondWith(handlerResponse) {
// TODO: throw exception if `_promise` is already set
// TODO: validate handlerResponse
this._promise = handlerResponse;
}
}
/***/ }),
/***/ "./CredentialStoreEvent.js":
/*!*********************************!*\
!*** ./CredentialStoreEvent.js ***!
\*********************************/
/*! exports provided: CredentialStoreEvent */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialStoreEvent", function() { return CredentialStoreEvent; });
/* harmony import */ var web_request_rpc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! web-request-rpc */ "./node_modules/web-request-rpc/index.js");
/*!
* A CredentialStoreEvent is emitted when a request has been made to
* store a credential.
*
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
/* global Event */
// can't use "ExtendableEvent"; only accessible from Workers
// TODO: may not be able to even extend `Event` here; could produce "incorrect"
// core attributes
class CredentialStoreEvent /*extends Event*/ {
constructor({
credentialHandler,
credentialRequestOrigin,
credential,
hintKey
}) {
//super('credentialstore');
this.type = 'credentialstore';
this._credentialHandler = credentialHandler;
this.credentialRequestOrigin = credentialRequestOrigin;
this.credential = credential;
this.hintKey = hintKey;
}
async openWindow(url) {
// TODO: disallow more than one call
// TODO: ensure `url` is to the same origin
await this._credentialHandler.show();
const appWindow = new web_request_rpc__WEBPACK_IMPORTED_MODULE_0__["WebAppWindow"](url);
appWindow.ready();
appWindow.show();
// TODO: note that `appWindow.handle` is not a ServiceWorker
// `WindowClient` polyfill... could be confusing here, should we
// implement one to wrap it? -- there is, for example, a
// `navigate` call on `WindowClient` that enforces same origin, would
// need to attempt to add or approximate that
return appWindow.handle;
}
respondWith(handlerResponse) {
// TODO: throw exception if `_promise` is already set
// TODO: validate handlerResponse
this._promise = handlerResponse;
}
}
/***/ }),
/***/ "./CredentialsContainer.js":
/*!*********************************!*\
!*** ./CredentialsContainer.js ***!
\*********************************/
/*! exports provided: CredentialsContainer */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialsContainer", function() { return CredentialsContainer; });
/* harmony import */ var _WebCredential_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./WebCredential.js */ "./WebCredential.js");
/*!
* Wrapper for native CredentialsContainer that uses remote Credential Mediator
* for WebCredential-related operations.
*
* Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved.
*/
/* global navigator, DOMException */
// RPC timeouts, 0 = indefinite
const CREDENTIAL_GET_TIMEOUT = 0;
const CREDENTIAL_STORE_TIMEOUT = 0;
class CredentialsContainer {
constructor(injector) {
this._nativeCredentialsContainer = {
get: navigator.credentials && navigator.credentials.get &&
navigator.credentials.get.bind(navigator.credentials),
store: navigator.credentials && navigator.credentials.store &&
navigator.credentials.store.bind(navigator.credentials),
};
this._init = (async () => {
this._remote = (await injector).get('credentialsContainer', {
functions: [
{name: 'get', options: {timeout: CREDENTIAL_GET_TIMEOUT}},
{name: 'store', options: {timeout: CREDENTIAL_STORE_TIMEOUT}}
]
});
})();
}
async get(/*CredentialRequestOptions*/ options = {}) {
if(options.web) {
await this._init;
const credential = await this._remote.get(options);
if(!credential) {
// no credential selected
return null;
}
// TODO: validate credential
return new _WebCredential_js__WEBPACK_IMPORTED_MODULE_0__["WebCredential"](credential.dataType, credential.data);
}
if(this._nativeCredentialsContainer.get) {
return this._nativeCredentialsContainer.get(options);
}
throw new DOMException('Not implemented.', 'NotSupportedError');
}
async store(credential) {
if(credential instanceof _WebCredential_js__WEBPACK_IMPORTED_MODULE_0__["WebCredential"]) {
await this._init;
const result = await this._remote.store(credential);
if(!result) {
// nothing stored
return null;
}
// TODO: validate result
return new _WebCredential_js__WEBPACK_IMPORTED_MODULE_0__["WebCredential"](result.dataType, result.data);
}
if(this._nativeCredentialsContainer.store) {
return this._nativeCredentialsContainer.store(credential);
}
throw new DOMException('Not implemented.', 'NotSupportedError');
}
}
/***/ }),
/***/ "./PermissionManager.js":
/*!******************************!*\
!*** ./PermissionManager.js ***!
\******************************/
/*! exports provided: PermissionManager */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PermissionManager", function() { return PermissionManager; });
/*!
* Provides an API for working with permissions.
*
* Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved.
*/
// RPC timeouts, 0 = indefinite
const PERMISSION_REQUEST_TIMEOUT = 0;
class PermissionManager {
constructor(injector) {
this._init = (async () => {
this._remote = (await injector).get('permissionManager', {
functions: [
'query',
{name: 'request', options: {timeout: PERMISSION_REQUEST_TIMEOUT}},
'revoke']
});
})();
}
async query(permissionDesc) {
await this._init;
return await this._remote.query(permissionDesc);
}
async request(permissionDesc) {
await this._init;
return await this._remote.request(permissionDesc);
}
async revoke(permissionDesc) {
await this._init;
return await this._remote.revoke(permissionDesc);
}
}
/***/ }),
/***/ "./WebCredential.js":
/*!**************************!*\
!*** ./WebCredential.js ***!
\**************************/
/*! exports provided: WebCredential */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WebCredential", function() { return WebCredential; });
/*!
* A WebCredential is a Credential that can be retrieved from or stored by a
* "credential handler" that runs in a third party Web application.
*
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
class WebCredential {
constructor(dataType, data) {
if(typeof dataType !== 'string') {
throw new TypeError('"dataType" must be a string.');
}
this.type = 'web';
this.dataType = dataType;
this.data = data;
}
}
/***/ }),
/***/ "./index.js":
/*!******************!*\
!*** ./index.js ***!
\******************/
/*! exports provided: loadOnce, load */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "loadOnce", function() { return loadOnce; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "load", function() { return load; });
/* harmony import */ var web_request_rpc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! web-request-rpc */ "./node_modules/web-request-rpc/index.js");
/* harmony import */ var _CredentialHandler_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./CredentialHandler.js */ "./CredentialHandler.js");
/* harmony import */ var _CredentialHandlers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./CredentialHandlers.js */ "./CredentialHandlers.js");
/* harmony import */ var _CredentialManager_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./CredentialManager.js */ "./CredentialManager.js");
/* harmony import */ var _CredentialsContainer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./CredentialsContainer.js */ "./CredentialsContainer.js");
/* harmony import */ var _PermissionManager_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./PermissionManager.js */ "./PermissionManager.js");
/* harmony import */ var _WebCredential_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./WebCredential.js */ "./WebCredential.js");
/*!
* Credential Handler API Polyfill.
*
* Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved.
*/
/* global navigator, window */
const DEFAULT_MEDIATOR = 'https://authn.io/mediator' + '?origin=' +
encodeURIComponent(window.location.origin);
let loaded;
async function loadOnce(mediatorUrl = DEFAULT_MEDIATOR) {
if(loaded) {
return loaded;
}
loaded = true;
return load(mediatorUrl);
}
async function load(mediatorUrl = DEFAULT_MEDIATOR) {
const appContext = new web_request_rpc__WEBPACK_IMPORTED_MODULE_0__["WebAppContext"]();
const injector = appContext.createWindow(mediatorUrl, {
className: 'credential-mediator',
// 30 second timeout for loading the mediator
timeout: 30000
});
// ensure backdrop is transparent by default
const style = document.createElement('style');
style.appendChild(document.createTextNode(
`dialog.web-app-window.credential-mediator > .web-app-window-backdrop {
background-color: rgba(0, 0, 0, 0.25);
}`));
document.body.appendChild(style);
const polyfill = {};
// TODO: only expose certain APIs when appropriate
polyfill.permissions = new _PermissionManager_js__WEBPACK_IMPORTED_MODULE_5__["PermissionManager"](injector);
polyfill.CredentialHandlers = new _CredentialHandlers_js__WEBPACK_IMPORTED_MODULE_2__["CredentialHandlers"](injector);
polyfill.CredentialHandler = _CredentialHandler_js__WEBPACK_IMPORTED_MODULE_1__["CredentialHandler"];
polyfill.CredentialManager = _CredentialManager_js__WEBPACK_IMPORTED_MODULE_3__["CredentialManager"];
polyfill.credentials = new _CredentialsContainer_js__WEBPACK_IMPORTED_MODULE_4__["CredentialsContainer"](injector);
polyfill.WebCredential = _WebCredential_js__WEBPACK_IMPORTED_MODULE_6__["WebCredential"];
// expose polyfill
navigator.credentialsPolyfill = polyfill;
// polyfill
if('credentials' in navigator) {
navigator.credentials.get = polyfill.credentials.get.bind(
polyfill.credentials);
navigator.credentials.store = polyfill.credentials.store.bind(
polyfill.credentials);
} else {
navigator.credentials = polyfill.credentials;
}
window.CredentialManager = _CredentialManager_js__WEBPACK_IMPORTED_MODULE_3__["CredentialManager"];
window.WebCredential = _WebCredential_js__WEBPACK_IMPORTED_MODULE_6__["WebCredential"];
return polyfill;
}
/***/ }),
/***/ "./node_modules/web-request-rpc/Client.js":
/*!************************************************!*\
!*** ./node_modules/web-request-rpc/Client.js ***!
\************************************************/
/*! exports provided: Client */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Client", function() { return Client; });
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils.js */ "./node_modules/web-request-rpc/utils.js");
/*!
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
// 30 second default timeout
const RPC_CLIENT_CALL_TIMEOUT = 30000;
class Client {
constructor() {
this.origin = null;
this._handle = null;
this._listener = null;
// all pending requests
this._pending = new Map();
}
/**
* Connects to a Web Request RPC server.
*
* The Promise will resolve to an RPC injector that can be used to get or
* define APIs to enable communication with the server.
*
* @param origin the origin to send messages to.
* @param options the options to use:
* [handle] a handle to the window (or a Promise that resolves to
* a handle) to send messages to
* (defaults to `window.parent || window.opener`).
*
* @return a Promise that resolves to an RPC injector once connected.
*/
async connect(origin, options) {
if(this._listener) {
throw new Error('Already connected.');
}
options = options || {};
// TODO: validate `origin` and `options.handle`
const self = this;
self.origin = _utils_js__WEBPACK_IMPORTED_MODULE_0__["parseUrl"](origin).origin;
self._handle = options.handle || window.parent || window.opener;
const pending = self._pending;
self._listener = _utils_js__WEBPACK_IMPORTED_MODULE_0__["createMessageListener"]({
origin: self.origin,
handle: self._handle,
expectRequest: false,
listener: message => {
// ignore messages that have no matching, pending request
if(!pending.has(message.id)) {
return;
}
// resolve or reject Promise associated with message
const {resolve, reject, cancelTimeout} = pending.get(message.id);
cancelTimeout();
if('result' in message) {
return resolve(message.result);
}
reject(_utils_js__WEBPACK_IMPORTED_MODULE_0__["deserializeError"](message.error));
}
});
window.addEventListener('message', self._listener);
return new Injector(self);
}
/**
* Performs a RPC by sending a message to the Web Request RPC server and
* awaiting a response.
*
* @param qualifiedMethodName the fully-qualified name of the method to call.
* @param parameters the parameters for the method.
* @param options the options to use:
* [timeout] a timeout, in milliseconds, for awaiting a response;
* a non-positive timeout (<= 0) will cause an indefinite wait.
*
* @return a Promise that resolves to the result (or error) of the call.
*/
async send(qualifiedMethodName, parameters, {
timeout = RPC_CLIENT_CALL_TIMEOUT
}) {
if(!this._listener) {
throw new Error('RPC client not connected.');
}
const self = this;
const message = {
jsonrpc: '2.0',
id: _utils_js__WEBPACK_IMPORTED_MODULE_0__["uuidv4"](),
method: qualifiedMethodName,
params: parameters
};
// HACK: we can't just `Promise.resolve(handle)` because Chrome has
// a bug that throws an exception if the handle is cross domain
if(_utils_js__WEBPACK_IMPORTED_MODULE_0__["isHandlePromise"](self._handle)) {
const handle = await self._handle;
handle.postMessage(message, self.origin);
} else {
self._handle.postMessage(message, self.origin);
}
// return Promise that will resolve once a response message has been
// received or once a timeout occurs
return new Promise((resolve, reject) => {
const pending = self._pending;
let cancelTimeout;
if(timeout > 0) {
const timeoutId = setTimeout(() => {
pending.delete(message.id);
reject(new Error('RPC call timed out.'));
}, timeout);
cancelTimeout = () => {
pending.delete(message.id);
clearTimeout(timeoutId);
};
} else {
cancelTimeout = () => {
pending.delete(message.id);
};
}
pending.set(message.id, {resolve, reject, cancelTimeout});
});
}
/**
* Disconnects from the remote Web Request RPC server and closes down this
* client.
*/
close() {
if(this._listener) {
window.removeEventListener('message', this._listener);
this._handle = this.origin = this._listener = null;
// reject all pending calls
for(const value of this._pending.values()) {
value.reject(new Error('RPC client closed.'));
}
this._pending = new Map();
}
}
}
class Injector {
constructor(client) {
this.client = client;
this._apis = new Map();
}
/**
* Defines a named API that will use an RPC client to implement its
* functions. Each of these functions will be asynchronous and return a
* Promise with the result from the RPC server.
*
* This function will return an interface with functions defined according
* to those provided in the given `definition`. The `name` parameter can be
* used to obtain this cached interface via `.get(name)`.
*
* @param name the name of the API.
* @param definition the definition for the API, including:
* functions: an array of function names (as strings) or objects
* containing: {name: <functionName>, options: <rpcClientOptions>}.
*
* @return an interface with the functions provided via `definition` that
* will make RPC calls to an RPC server to provide their
* implementation.
*/
define(name, definition) {
if(!(name && typeof name === 'string')) {
throw new TypeError('`name` must be a non-empty string.');
}
// TODO: support Web IDL as a definition format?
if(!(definition && typeof definition === 'object' &&
Array.isArray(definition.functions))) {
throw new TypeError(
'`definition.function` must be an array of function names or ' +
'function definition objects to be defined.');
}
const self = this;
const api = {};
definition.functions.forEach(fn => {
if(typeof fn === 'string') {
fn = {name: fn, options: {}};
}
api[fn.name] = async function() {
return self.client.send(
name + '.' + fn.name, [...arguments], fn.options);
};
});
self._apis[name] = api;
return api;
}
/**
* Get a named API, defining it if necessary when a definition is provided.
*
* @param name the name of the API.
* @param [definition] the definition for the API; if the API is already
* defined, this definition is ignored.
*
* @return the interface.
*/
get(name, definition) {
const api = this._apis[name];
if(!api) {
if(definition) {
return this.define(name, definition);
}
throw new Error(`API "${name}" has not been defined.`);
}
return this._apis[name];
}
}
/***/ }),
/***/ "./node_modules/web-request-rpc/EventEmitter.js":
/*!******************************************************!*\
!*** ./node_modules/web-request-rpc/EventEmitter.js ***!
\******************************************************/
/*! exports provided: EventEmitter */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EventEmitter", function() { return EventEmitter; });
/*!
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
class EventEmitter {
constructor({deserialize = e => e, waitUntil = async () => {}} = {}) {
this._listeners = [];
this._deserialize = deserialize;
this._waitUntil = waitUntil;
}
async emit(event) {
event = this._deserialize(event);
(this._listeners[event.type] || []).forEach(l => l(event));
return this._waitUntil(event);
}
addEventListener(eventType, fn) {
if(!this._listeners[eventType]) {
this._listeners[eventType] = [fn];
} else {
this._listeners[eventType].push(fn);
}
}
removeEventListener(eventType, fn) {
const listeners = this._listeners[eventType];
if(!listeners) {
return;
}
const idx = listeners.indexOf(fn);
if(idx !== -1) {
listeners.splice(idx, 1);
}
}
}
/***/ }),
/***/ "./node_modules/web-request-rpc/Server.js":
/*!************************************************!*\
!*** ./node_modules/web-request-rpc/Server.js ***!
\************************************************/
/*! exports provided: Server */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Server", function() { return Server; });
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils.js */ "./node_modules/web-request-rpc/utils.js");
/*!
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
class Server {
constructor() {
this.origin = null;
this._handle = null;
this._apis = new Map();
}
/**
* Provides an implementation for a named API. All functions in the given
* API will be made callable via RPC clients connected to this server.
*
* @param name the name of the API.
* @param api the API to add.
*/
define(name, api) {
if(!(name && typeof name === 'string')) {
throw new TypeError('`name` must be a non-empty string.');
}
if(!(api && api !== 'object')) {
throw new TypeError('`api` must be an object.');
}
if(name in this._apis) {
throw new Error(`The "${name}" API is already defined.`);
}
this._apis[name] = api;
}
/**
* Listens for RPC messages from clients from a particular origin and
* window handle and uses them to execute API calls based on predefined
* APIs.
*
* If messages are not from the given origin or window handle, they are
* ignored. If the messages refer to named APIs that have not been defined
* then an error message is sent in response. These error messages can
* be suppressed by using the `ignoreUnknownApi` option.
*
* If a message refers to an unknown method on a known named API, then an
* error message is sent in response.
*
* @param origin the origin to listen for.
* @param options the options to use:
* [handle] a handle to the window (or a Promise that resolves to
* a handle) to listen for messages from
* (defaults to `window.parent || window.opener`).
* [ignoreUnknownApi] `true` to ignore unknown API messages.
*/
async listen(origin, options) {
if(this._listener) {
throw new Error('Already listening.');
}
options = options || {};
// TODO: validate `origin` and `options.handle`
const self = this;
self.origin = _utils_js__WEBPACK_IMPORTED_MODULE_0__["parseUrl"](origin).origin;
self._handle = options.handle || window.parent || window.opener;
const ignoreUnknownApi = (options.ignoreUnknownApi === 'true') || false;
self._listener = _utils_js__WEBPACK_IMPORTED_MODULE_0__["createMessageListener"]({
origin: self.origin,
handle: self._handle,
expectRequest: true,
listener: message => {
const {name, method} = _utils_js__WEBPACK_IMPORTED_MODULE_0__["destructureMethodName"](message.method);
const api = self._apis[name];
// do not allow calling "private" methods (starts with `_`)
if(method && method.startsWith('_')) {
return sendMethodNotFound(self._handle, self.origin, message);
}
// API not found but ignore flag is on
if(!api && ignoreUnknownApi) {
// API not registered, ignore the message rather than raise error
return;
}
// no ignore flag and unknown API or unknown specific method
if(!api || typeof api[method] !== 'function') {
return sendMethodNotFound(self._handle, self.origin, message);
}
// API and specific function found
const fn = api[method];
(async () => {
const response = {
jsonrpc: '2.0',
id: message.id
};
try {
response.result = await fn.apply(api, message.params);
} catch(e) {
response.error = _utils_js__WEBPACK_IMPORTED_MODULE_0__["serializeError"](e);
}
// if server did not `close` while we waited for a response
if(self._handle) {
// HACK: we can't just `Promise.resolve(handle)` because Chrome has
// a bug that throws an exception if the handle is cross domain
if(_utils_js__WEBPACK_IMPORTED_MODULE_0__["isHandlePromise"](self._handle)) {
self._handle.then(h => h.postMessage(response, self.origin));
} else {
self._handle.postMessage(response, self.origin);
}
}
})();
}
});
window.addEventListener('message', self._listener);
}
close() {
if(this._listener) {
window.removeEventListener('message', this._listener);
this._handle = this.origin = this._listener = null;
}
}
}
function sendMethodNotFound(handle, origin, message) {
const response = {
jsonrpc: '2.0',
id: message.id,
error: Object.assign({}, _utils_js__WEBPACK_IMPORTED_MODULE_0__["RPC_ERRORS"].MethodNotFound)
};
// HACK: we can't just `Promise.resolve(handle)` because Chrome has
// a bug that throws an exception if the handle is cross domain
if(_utils_js__WEBPACK_IMPORTED_MODULE_0__["isHandlePromise"](handle)) {
return handle.then(h => h.postMessage(response, origin));
} else {
return handle.postMessage(response, origin);
}
}
/***/ }),
/***/ "./node_modules/web-request-rpc/WebApp.js":
/*!************************************************!*\
!*** ./node_modules/web-request-rpc/WebApp.js ***!
\************************************************/
/*! exports provided: WebApp */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WebApp", function() { return WebApp; });
/* harmony import */ var _Client_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Client.js */ "./node_modules/web-request-rpc/Client.js");
/* harmony import */ var _Server_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Server.js */ "./node_modules/web-request-rpc/Server.js");
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils.js */ "./node_modules/web-request-rpc/utils.js");
/*!
* A WebApp is a remote application that runs in a WebAppContext.
*
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
class WebApp {
constructor(relyingOrigin) {
// this is the origin that created the WebAppContext to run it in
// TODO: better name? `contextOrigin`?
this.relyingOrigin = Object(_utils_js__WEBPACK_IMPORTED_MODULE_2__["parseUrl"])(relyingOrigin).origin;
this.client = null;
this.injector = null;
this.client = new _Client_js__WEBPACK_IMPORTED_MODULE_0__["Client"]();
this.server = new _Server_js__WEBPACK_IMPORTED_MODULE_1__["Server"]();
this._control = null;
this._connected = false;
}
/**
* Connects this WebApp to the relying origin that instantiated it. Once
* connected, the WebApp can start servicing calls from that origin.
*
* @return a Promise that resolves to an injector for creating custom client
* APIs once the connection is ready.
*/
async connect() {
this.injector = await this.client.connect(this.relyingOrigin);
this._connected = true;
this._control = this.injector.define('core.control', {
functions: ['ready', 'show', 'hide']
});
this.server.listen(this.relyingOrigin);
return this.injector;
}
/**
* Must be called after `connect` when this WebApp is ready to start
* receiving calls from the remote end.
*/
async ready() {
if(!this._connected) {
throw new Error('WebApp not connected. Did you call ".connect()"?');
}
await this._control.ready();
return this;
}
/**
* Closes this WebApp's connection to the relying origin.
*/
close() {
if(this._connected) {
this.server.close();
this.client.close();
this._connected = false;
}
}
/**
* Shows the UI for this WebApp on the relying origin.
*/
async show() {
if(!this._connected) {
throw new Error(
'Cannot "show" yet; not connected. Did you call ".connect()"?');
}
return this._control.show();
}
/**
* Hides the UI for this WebApp on the relying origin.
*/
async hide() {
if(!this._connected) {
throw new Error(
'Cannot "hide" yet; not connected. Did you call ".connect()?"');
}
return this._control.hide();
}
}
/***/ }),
/***/ "./node_modules/web-request-rpc/WebAppContext.js":
/*!*******************************************************!*\
!*** ./node_modules/web-request-rpc/WebAppContext.js ***!
\*******************************************************/
/*! exports provided: WebAppContext */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WebAppContext", function() { return WebAppContext; });
/* harmony import */ var _Client_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Client.js */ "./node_modules/web-request-rpc/Client.js");
/* harmony import */ var _Server_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Server.js */ "./node_modules/web-request-rpc/Server.js");
/* harmony import */ var _WebAppWindow_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./WebAppWindow.js */ "./node_modules/web-request-rpc/WebAppWindow.js");
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils.js */ "./node_modules/web-request-rpc/utils.js");
/*!
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
// 10 seconds
const WEB_APP_CONTEXT_LOAD_TIMEOUT = 10000;
class WebAppContext {
constructor() {
this