hellosign-embedded
Advanced tools
Comparing version 2.0.0-0 to 2.0.0-1
@@ -21,3 +21,4 @@ const globals = require('./globals'); | ||
coverageDirectory: '.coverage', | ||
setupFiles: ['./setup.js'], | ||
globals, | ||
}; |
{ | ||
"name": "hellosign-embedded", | ||
"version": "2.0.0-0", | ||
"version": "2.0.0-1", | ||
"description": "Embed HelloSign signature requests and templates from within your web application.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
import Emitter from 'tiny-emitter'; | ||
import { safeHtml } from 'common-tags'; | ||
@@ -7,2 +6,3 @@ import debug from './debug'; | ||
import settings from './settings'; | ||
import template from './template'; | ||
@@ -39,2 +39,10 @@ class HelloSign extends Emitter { | ||
/** | ||
* The base config object which "open" will extend. | ||
* | ||
* @type {?Object} | ||
* @private | ||
*/ | ||
_baseConfig = null; | ||
/** | ||
* A reference to the base HelloSign Embedded container | ||
@@ -46,3 +54,3 @@ * element. | ||
*/ | ||
_baseEl; | ||
_baseEl = null; | ||
@@ -55,11 +63,11 @@ /** | ||
*/ | ||
_closeBtnEl; | ||
_closeBtnEl = null; | ||
/** | ||
* The base config object which "open" will extend. | ||
* The HelloSign Embedded document fragment. | ||
* | ||
* @type {?Object} | ||
* @type {?DocumentFragment} | ||
* @private | ||
*/ | ||
_config; | ||
_fragment = null; | ||
@@ -72,3 +80,3 @@ /** | ||
*/ | ||
_iFrameURL; | ||
_iFrameURL = null; | ||
@@ -81,3 +89,3 @@ /** | ||
*/ | ||
_iFrameEl; | ||
_iFrameEl = null; | ||
@@ -90,3 +98,3 @@ /** | ||
*/ | ||
_initTimeout; | ||
_initTimeout = null; | ||
@@ -99,3 +107,3 @@ /** | ||
*/ | ||
_isOpen; | ||
_isOpen = false; | ||
@@ -106,3 +114,3 @@ /** | ||
*/ | ||
_onCloseButtonClick = this._onCloseButtonClick.bind(this); | ||
_onCloseBtnClick = this._onCloseBtnClick.bind(this); | ||
@@ -133,3 +141,3 @@ /** | ||
if (obj && typeof obj === 'object') { | ||
this._config = { ...obj }; | ||
this._baseConfig = { ...obj }; | ||
} else { | ||
@@ -386,5 +394,6 @@ throw new TypeError('Configuration must be an object'); | ||
* @param {Object} cfg | ||
* @returns {string} | ||
* @private | ||
*/ | ||
_setFrameURL(url, cfg) { | ||
_getFrameURL(url, cfg) { | ||
const frameURL = new URL(url); | ||
@@ -395,37 +404,52 @@ const frameParams = this._getFrameParams(frameURL, cfg); | ||
this._iFrameURL = frameURL; | ||
return frameURL; | ||
} | ||
/** | ||
* | ||
* | ||
* @private | ||
*/ | ||
_renderFragment() { | ||
const fragment = document.createRange().createContextualFragment(template); | ||
// Obtain element references. | ||
this._baseEl = fragment.querySelector(`.${settings.classNames.BASE}`); | ||
this._iFrameEl = fragment.querySelector(`.${settings.classNames.IFRAME}`); | ||
this._closeBtnEl = fragment.querySelector(`.${settings.classNames.MODAL_CLOSE_BTN}`); | ||
// Update iFrame URL. | ||
this._iFrameEl.setAttribute('src', this._iFrameURL.href); | ||
// Register event listeners. | ||
this._closeBtnEl.addEventListener('click', this._onCloseBtnClick); | ||
return fragment; | ||
} | ||
/** | ||
* Renders HelloSign Embedded into the DOM. | ||
* | ||
* @param {HTMLElement} container | ||
* @param {Object} cfg | ||
* @private | ||
*/ | ||
_renderMarkup(container, cfg) { | ||
const { classNames, iframe } = settings; | ||
_attachFragment(cfg) { | ||
const fragment = this._renderFragment(); | ||
if (cfg.container) { | ||
container.insertAdjacentHTML('beforeend', safeHtml` | ||
<div class="${classNames.BASE}"> | ||
<iframe class="${classNames.IFRAME}" name="${iframe.NAME}" src="${this._iFrameURL.href}" /> | ||
</div> | ||
`); | ||
if (cfg.allowCancel) { | ||
this._baseEl.classList.toggle(settings.classNames.BASE_NO_CANCEL, false); | ||
} else { | ||
container.insertAdjacentHTML('beforeend', safeHtml` | ||
<div class="${classNames.BASE} ${classNames.IN_MODAL}"> | ||
<div class="${classNames.MODAL_SCREEN}"></div> | ||
<div class="${classNames.MODAL_CONTENT}"> | ||
<iframe class="${classNames.IFRAME}" name="${iframe.NAME}" src="${this._iFrameURL.href}" /> | ||
</div> | ||
</div> | ||
`); | ||
this._baseEl.classList.toggle(settings.classNames.BASE_NO_CANCEL, true); | ||
} | ||
this._baseEl = document.getElementsByClassName(classNames.BASE).item(0); | ||
this._iFrameEl = document.getElementsByClassName(classNames.IFRAME).item(0); | ||
if (cfg.container) { | ||
this._baseEl.classList.toggle(settings.classNames.BASE_IN_CONTAINER, true); | ||
this._baseEl.classList.toggle(settings.classNames.BASE_IN_MODAL, false); | ||
if (!cfg.container && cfg.allowCancel) { | ||
this._renderCloseButton(); | ||
cfg.container.appendChild(fragment); | ||
} else { | ||
this._baseEl.classList.toggle(settings.classNames.BASE_IN_CONTAINER, false); | ||
this._baseEl.classList.toggle(settings.classNames.BASE_IN_MODAL, true); | ||
document.body.appendChild(fragment); | ||
} | ||
@@ -435,27 +459,26 @@ } | ||
/** | ||
* Renders the modal close button. | ||
* Removes the HelloSign Embedded markup from the DOM. | ||
* | ||
* | ||
* @private | ||
*/ | ||
_renderCloseButton() { | ||
const { classNames } = settings; | ||
this._baseEl.insertAdjacentHTML('beforeend', safeHtml` | ||
<button class="${classNames.MODAL_CLOSE_BTN}" type="button" title="Close" disabled></div> | ||
`); | ||
this._closeBtnEl = this._baseEl.getElementsByClassName(classNames.MODAL_CLOSE_BTN).item(0); | ||
this._closeBtnEl.addEventListener('click', this._onCloseButtonClick); | ||
_detachFragment() { | ||
this._baseEl.parentElement.removeChild(this._baseEl); | ||
} | ||
/** | ||
* @typedef {Object} HelloSignMessage | ||
* @property {string} type | ||
* @property {Object} [payload] | ||
*/ | ||
/** | ||
* Posts a cross-origin window message to the HelloSign | ||
* Embedded iFrame content window. | ||
* | ||
* @param {Object} data | ||
* @param {string} data.type | ||
* @param {HelloSignMessage} msg | ||
* @private | ||
*/ | ||
_sendMessage(data) { | ||
debug.info('posting message', data); | ||
_sendMessage(msg) { | ||
debug.info('posting message', msg); | ||
@@ -465,3 +488,3 @@ const targetOrigin = this._iFrameURL.href; | ||
targetWindow.postMessage(data, targetOrigin); | ||
targetWindow.postMessage(msg, targetOrigin); | ||
} | ||
@@ -495,18 +518,2 @@ | ||
/** | ||
* Removes the HelloSign Embedded markup from the DOM. | ||
* | ||
* | ||
* @private | ||
*/ | ||
_clearMarkup() { | ||
this._baseEl.parentElement.removeChild(this._baseEl); | ||
this._baseEl = null; | ||
if (this._closeBtnEl) { | ||
this._closeBtnEl.addEventListener('click', this._onCloseButtonClick); | ||
this._closeBtnEl = null; | ||
} | ||
} | ||
/** | ||
* @event HelloSign#error | ||
@@ -545,6 +552,2 @@ * @type {Object} | ||
if (this._closeBtnEl) { | ||
this._closeBtnEl.removeAttribute('disabled'); | ||
} | ||
this.emit(settings.events.INITIALIZE, payload); | ||
@@ -670,3 +673,3 @@ } | ||
*/ | ||
_onCloseButtonClick(evt) { | ||
_onCloseBtnClick(evt) { | ||
evt.preventDefault(); | ||
@@ -707,47 +710,61 @@ | ||
_onMessage({ data, origin }) { | ||
debug.info('received message', data, origin); | ||
if (/^https:\/\/app\.((dev|qa|staging)-)?hellosign\.com$/.test(origin)) { | ||
debug.info('last message was sent from a known origin'); | ||
if (typeof data === 'object') { | ||
const { type, payload } = data; | ||
this._delegateMessage(data); | ||
} | ||
} else { | ||
debug.warn('last message was sent from an unknown origin'); | ||
} | ||
} | ||
debug.info('received message', data); | ||
switch (type) { | ||
case settings.messages.APP_ERROR: { | ||
this._appDidError(payload); | ||
break; | ||
} | ||
case settings.messages.APP_INITIALIZE: { | ||
this._appDidInitialize(payload); | ||
break; | ||
} | ||
case settings.messages.USER_CLOSE_REQUEST: { | ||
this._userDidCloseRequest(payload); | ||
break; | ||
} | ||
case settings.messages.USER_CREATE_TEMPLATE: { | ||
this._userDidCreateTemplate(payload); | ||
break; | ||
} | ||
case settings.messages.USER_DECLINE_REQUEST: { | ||
this._userDidDeclineRequest(payload); | ||
break; | ||
} | ||
case settings.messages.USER_REASSIGN_REQUEST: { | ||
this._userDidReassignRequest(payload); | ||
break; | ||
} | ||
case settings.messages.USER_SEND_REQUEST: { | ||
this._userDidSendRequest(payload); | ||
break; | ||
} | ||
case settings.messages.USER_SIGN_REQUEST: { | ||
this._userDidSignRequest(payload); | ||
break; | ||
} | ||
default: { | ||
// Unhandled message. | ||
debug.warn('unhandled cross-origin window message'); | ||
} | ||
} | ||
/** | ||
* Called when a message is received by the window. | ||
* Validates the message origin and delegates to the | ||
* appropriate method based on the message type. | ||
* | ||
* @param {HelloSignMessage} msg | ||
* @private | ||
*/ | ||
_delegateMessage({ type, payload }) { | ||
switch (type) { | ||
case settings.messages.APP_ERROR: { | ||
this._appDidError(payload); | ||
break; | ||
} | ||
case settings.messages.APP_INITIALIZE: { | ||
this._appDidInitialize(payload); | ||
break; | ||
} | ||
case settings.messages.USER_CLOSE_REQUEST: { | ||
this._userDidCloseRequest(payload); | ||
break; | ||
} | ||
case settings.messages.USER_CREATE_TEMPLATE: { | ||
this._userDidCreateTemplate(payload); | ||
break; | ||
} | ||
case settings.messages.USER_DECLINE_REQUEST: { | ||
this._userDidDeclineRequest(payload); | ||
break; | ||
} | ||
case settings.messages.USER_REASSIGN_REQUEST: { | ||
this._userDidReassignRequest(payload); | ||
break; | ||
} | ||
case settings.messages.USER_SEND_REQUEST: { | ||
this._userDidSendRequest(payload); | ||
break; | ||
} | ||
case settings.messages.USER_SIGN_REQUEST: { | ||
this._userDidSignRequest(payload); | ||
break; | ||
} | ||
default: { | ||
// Unhandled message. | ||
debug.warn('unhandled cross-origin window message'); | ||
} | ||
} | ||
@@ -788,2 +805,9 @@ } | ||
const cfg = { | ||
...defaults, | ||
...this._baseConfig, | ||
...opts, | ||
}; | ||
// Close if embedded is already open. | ||
if (this._isOpen) { | ||
@@ -793,8 +817,5 @@ this.close(); | ||
const cfg = { ...defaults, ...this._config, ...opts }; | ||
const container = cfg.container || document.body; | ||
this._iFrameURL = this._getFrameURL(url, cfg); | ||
this._setFrameURL(url, cfg); | ||
this._renderMarkup(container, cfg); | ||
this._attachFragment(cfg); | ||
this._isOpen = true; | ||
@@ -827,14 +848,13 @@ | ||
this._iFrameEl = false; | ||
this._detachFragment(); | ||
this._clearInitTimeout(); | ||
this._closeBtnEl.removeEventListener('click', this._onCloseBtnClick); | ||
this._closeBtnEl = null; | ||
this._baseEl = null; | ||
this._iFrameEl = null; | ||
this._iFrameURL = null; | ||
this._isOpen = false; | ||
this._clearMarkup(); | ||
this._clearInitTimeout(); | ||
if (this._closeBtnEl) { | ||
this._closeBtnEl.removeEventListener('click', this._onCloseButtonClick); | ||
this._closeBtnEl = null; | ||
} | ||
window.removeEventListener('message', this._onMessage); | ||
@@ -841,0 +861,0 @@ } |
@@ -21,7 +21,55 @@ import HelloSign from './embedded'; | ||
describe('methods', () => { | ||
describe('accessors', () => { | ||
describe('element()', () => { | ||
test('returns the base element', () => { | ||
const client = new HelloSign({ clientId: mockClientId }); | ||
client.open(mockSignURL); | ||
expect(client.element).toBeInstanceOf(HTMLElement); | ||
client.close(); | ||
}); | ||
}); | ||
describe('isOpen()', () => { | ||
test('returns the open state', () => { | ||
const client = new HelloSign({ clientId: mockClientId }); | ||
expect(client.isOpen).toEqual(false); | ||
client.open(mockSignURL); | ||
expect(client.isOpen).toEqual(true); | ||
client.close(); | ||
expect(client.isOpen).toEqual(false); | ||
}); | ||
}); | ||
}); | ||
describe.skip('methods', () => { | ||
describe('open()', () => { | ||
test('emits the "close" event', (done) => { | ||
test('closes the old window if embedded is already open', (done) => { | ||
const client = new HelloSign({ | ||
clientId: mockClientId, | ||
}); | ||
const fn = jest.fn(() => { | ||
expect(fn).toBeCalledTimes(1); | ||
done(); | ||
}); | ||
client.once(HelloSign.events.CLOSE, fn); | ||
client.open(mockSignURL); | ||
client.open(mockSignURL); | ||
}); | ||
test('emits the "open" event', (done) => { | ||
const client = new HelloSign(); | ||
@@ -409,2 +457,4 @@ | ||
client.once(HelloSign.events.CLOSE, fn); | ||
client.once(HelloSign.events.OPEN, () => { | ||
@@ -414,4 +464,2 @@ client.close(); | ||
client.once(HelloSign.events.CLOSE, fn); | ||
client.open(mockSignURL, { | ||
@@ -421,4 +469,18 @@ clientId: mockClientId, | ||
}); | ||
test('does not attempt to close if not open', (done) => { | ||
const client = new HelloSign(); | ||
const fn = jest.fn(() => {}); | ||
client.once(HelloSign.events.CLOSE, fn); | ||
client.close(); | ||
setTimeout(() => { | ||
expect(fn).toBeCalledTimes(0); | ||
done(); | ||
}, 1000); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -9,7 +9,10 @@ /** | ||
BASE: 'x-hellosign-embedded', | ||
BASE_IN_CONTAINER: 'x-hellosign-embedded--in-container', | ||
BASE_IN_MODAL: 'x-hellosign-embedded--in-modal', | ||
BASE_NO_CANCEL: 'x-hellosign-embedded--no-cancel', | ||
IFRAME: 'x-hellosign-embedded__iframe', | ||
IN_MODAL: 'x-hellosign-embedded--in-modal', | ||
MODAL_CLOSE: 'x-hellosign-embedded__modal-close', | ||
MODAL_CLOSE_BTN: 'x-hellosign-embedded__modal-close-button', | ||
MODAL_CONTENT: 'x-hellosign-embedded__modal-content', | ||
MODAL_SCREEN: 'x-hellosign-embedded__modal-screen', | ||
MODAL_CONTENT: 'x-hellosign-embedded__modal-content', | ||
MODAL_CLOSE_BTN: 'x-hellosign-embedded__modal-close-button', | ||
}; | ||
@@ -16,0 +19,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
48473
19
1360
0