@paypal/web-component-utilities
v1.0.14
Published
## register Registers a custom web component from a preact component. This was borrowed from https://github.com/preactjs/preact-custom-element and altered to be more secure and fit our needs at PayPal.
Downloads
43
Maintainers
Keywords
Readme
@paypal/web-component-utilities
register
Registers a custom web component from a preact component. This was borrowed from https://github.com/preactjs/preact-custom-element and altered to be more secure and fit our needs at PayPal.
Usage
Import @paypal/web-component-utilities
and pass the preact component, tag name, list of attributes to observe and shadow DOM requirement. Only shadow DOM is supported with both open
and closed
modes.
import { register } from '@paypal/web-component-utilities';
const Email = ({ prefilled }) => {
return (
<>
<input type='text' value={ prefilled } />
</>
);
};
register(Email, 'paypal-email', [ 'prefilled' ], { shadow: { mode: 'closed' } });
Note: The tag name in HTML must contain a hyphen
Use the tag name in HTML. Attribute keys and values will be passed in as props:
<paypal-email prefilled='[email protected]'></paypal-email>
Output:
<input type='text' value='[email protected]' />
Prop Names and Automatic Prop Names
The Custom Elements V1 specification requires explicitly stating the names of any attributes you want to observe. From your Preact component perspective, props
could be an object with any keys at runtime, so it's not always clear which props should be accepted as attributes.
If you omit the third parameter to register()
, the list of attributes to observe can be specified using a static observedAttributes
property on your Component. This also works for the Custom Element's name, which can be specified using a tagName
static property:
import { register } from '@paypal/web-component-utilities';
class Email extends Component {
static tagName = 'paypal-email';
static observedAttributes = [ 'prefilled' ];
render({ prefilled }) {
return (
<>
<input type='text' value={ prefilled } />
</>
);
}
};
register(Email);
If no observedAttributes
are specified, they will be inferred from the keys of propTypes
if present on the Component:
import { register } from '@paypal/web-component-utilities';
const Email = (props) => {
return (
<>
<input type='text' value={ props.prefilled } />
</>
);
};
Email.propTypes = {
prefilled: PropTypes.string
};
register(Email, 'paypal-email');
State Management
Part of the register function is to keep the state between the preact and web component in sync. State flows from the web component to the preact component. Here are some ways to set and retrieve state from the web component.
Note: Attribute names should be hyphenated if camel-case in your preact component. When getting or setting the attribute, be sure to NOT use the camel-case representation.
Getting state
Web Component:
<paypal-email prefilled='[email protected]'></paypal-email>
Script:
document.querySelector('paypal-email').getAttribute('prefilled'); // [email protected]
or
document.querySelector('paypal-email').prefilled; // [email protected]
Note: Can only use the direct method above after DOM has loaded.
Preact:
The getState
method will retrieve state from sessionStorage
if enabled.
import { getState } from '@paypal/web-component-utilities';
...
const prefilled = getState('prefilled');
Setting state
When setting the state of the web component, the preact component UI will be updated if the attribute that was changed was registered.
Web Component:
<paypal-email prefilled='[email protected]'></paypal-email>
Script:
document.querySelector('paypal-email').setAttribute('prefilled') = '[email protected]';
or
document.querySelector('paypal-email').prefilled = '[email protected]';
Note: Can only use the direct method above after DOM has loaded.
Preact:
The setState
method will set state to sessionStorage
if enabled. Use this in your preact components when you need to persist state in case the page is refreshed. This method will also fire off and event that the property has changed. See Events section below on event nomenclature.
import { setState } from '@paypal/web-component-utilities';
...
setState('prefilled', '[email protected]');
Component communication
State passed to the Web Component
Component attribute -> Web Component
Setting a component attribute, sets its state for that attribute.
sessionStorage -> Web Component
sessionStorage
is used on attributes that my require persistence across pages or if current page is refreshed. The merchant is allowed to also persist whatever state they need to for component intitialization. A web component will give precedence to attributes passed to it over the value stored in sessionStorage
. This scenario can occur when a component's attribute is set from another component and not directly in source code. sessionStorage
acts as a store in case user refreshes the page or their components are across multiple pages. If sessionStorage
is disabled in the browser, then the user will experience the same kind of experience they do on other pages dependent on sessionStorage
on other sites.
State passed from Web Component
Events
Events are used to send information to the merchant when they may need to take action or they need data from us for other reasons.
const paypalEmailComponent = document.querySelector('paypal-email');
paypalEmailComponent.addEventListener('OnAuth', e => {
const { authToken } = e.detail || {};
});
Security
We have taken measures to secure the web component internals from being accessed using JavaScript and are checking for the override of attachShadow
to prevent someone from changing the mode to open
if the mode was set to closed
. Also, when sending events to the merchant through the web component, we are preventing the event from bubbling outside of its boundaries.
Caution should be given whenever we store state in sessionStorage
and make sure data stored is PCI compliant. Therefore, no inputted card information should be stored locally anywhere and only passed to graphql to acquire the required payment token for the merchant.
Components variable
All preact components can have access to all of the registered web components on the current page through the components
export. Every component that is registered on the page is held in this closure variable. This allows components on the same page to send events privately to one another if needed, or to get the current state of those web components.
The components object is structured with the tag name as the key, and the DOM Element instance as the value.
import { components } from '@paypal/web-component-utilities';
const Email = () => {
const shippingComponent = components['paypal-shipping'];
const paymentComponent = components['payapl-payment'];
const onLogin = (e) => {
const { authToken } = e.detail || {};
const onAuthEvent = new CustomEvent('onAuth', {
bubbles: false,
cancelable: true,
detail: {
authToken
}
});
shippingComponent.dispatchEvent(onAuthEvent);
paymentComponent.dispatchEvent(onAuthEvent);
};
};
Shadows variable
All preact components can have access to all of the registered web component shadow roots on the current page through the shadows
export. Every component that is registered on the page is held in this closure variable. This allows us access to the shadow root when needed, like for specifying the root container for style-components
.
The components object is structured with the tag name as the key, and the DOM Element instance as the value.
import { shadows } from '@paypal/web-component-utilities';
const cache = createCache({
key: 'pp-email',
container: shadows['paypal-email']
});
Logging
The logging library supports a basic interface for the web components to interact with so that you can adapt any logger you are using by passing the instance into the Logger constructor. It utilizes the adapter pattern to adapt the web component interface with your specific logger interface by delegating to the passed logger. Methods not supported by the web component logger interface can be added at initialization.
Usage
Initialization
Import your logger and the web component Logger
import { Logger as BeaverLogger } from '@krakenjs/beaver-logger';
import { Logger, getLogger } from '@paypal/web-component-utilities';
Initialize your logger and pass into the web component Logger
const beaverLogger = BeaverLogger({
url: '/api/log',
enableSendBeacon: true
});
const logger = Logger({
logger: beaverLogger
});
Initialize with custom event
By default the Logger supports the basic logging events: info, debug, warn, error. If your logger instance also supports these methods off of the instance, than no further configuration is necessary. If your logger supports other events, or your events have different names correlating to the above, then you can initialize the logger with those.
const logger = Logger({
logger: beaverLogger,
info: beaverLogger.information,
track: beaverLogger.track
});
Log Event
Use the getLogger helper to access singleton logger instance.
Use the Logger in your web component:
getLogger().info('button_click', {
sessionID: 'abc123'
});
If you've initialized a custom event, it will be available to use.
getLogger().track('button_click', {
sessionID: 'abc123'
});