npm package: @paypal/web-component-utilities
Description: ## 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.
Latest version: 1.0.14

# @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.

``` javascript
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:
``` html
<paypal-email prefilled='joe@paypal.com'></paypal-email>
```
Output:
``` html
<input type='text' value='joe@paypal.com' />
```

### 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:
``` javascript
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:
``` javascript
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:
``` html
<paypal-email prefilled='joe@paypal.com'></paypal-email>
```

Script:
``` javascript
document.querySelector('paypal-email').getAttribute('prefilled'); // joe@paypal.com
```
or
``` javascript
document.querySelector('paypal-email').prefilled; // joe@paypal.com
```
> **Note**: Can only use the direct method above after DOM has loaded.

Preact:
The `getState` method will retrieve state from `sessionStorage` if enabled.

``` javascript
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:
``` html
<paypal-email prefilled='joe@paypal.com'></paypal-email>
```

Script:
``` javascript
document.querySelector('paypal-email').setAttribute('prefilled') = 'jane@paypal.com';
```
or
``` javascript
document.querySelector('paypal-email').prefilled = 'jane@paypal.com';
```
> **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.

``` javascript
import { setState } from '@paypal/web-component-utilities';
...
setState('prefilled', 'jane@paypal.com');
```

### 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. 

``` javascript
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.

``` javascript
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.

``` javascript
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
```javascript
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
```javascript
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.

```javascript
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:
```javascript
getLogger().info('button_click', {
  sessionID: 'abc123'
});
```

If you've initialized a custom event, it will be available to use.

```javascript
getLogger().track('button_click', {
  sessionID: 'abc123'
});
```