@paypal/checkout-components 5.0.217 → 5.0.220

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paypal/checkout-components",
3
- "version": "5.0.217",
3
+ "version": "5.0.220",
4
4
  "description": "PayPal Checkout components, for integrating checkout products.",
5
5
  "main": "index.js",
6
6
  "engines": {
@@ -86,17 +86,17 @@
86
86
  "serve": "^13.0.0"
87
87
  },
88
88
  "dependencies": {
89
- "@paypal/common-components": "^1.0.27",
90
- "@paypal/funding-components": "^1.0.27",
91
- "@paypal/sdk-client": "^4.0.166",
92
- "@paypal/sdk-constants": "^1.0.107",
93
- "@paypal/sdk-logos": "^1.0.45",
94
89
  "@krakenjs/belter": "^2.0.0",
95
90
  "@krakenjs/cross-domain-utils": "^3.0.0",
96
91
  "@krakenjs/jsx-pragmatic": "^3",
97
92
  "@krakenjs/post-robot": "^11.0.0",
98
93
  "@krakenjs/zalgo-promise": "^2.0.0",
99
- "@krakenjs/zoid": "^10.0.0"
94
+ "@krakenjs/zoid": "^10.0.0",
95
+ "@paypal/common-components": "^1.0.27",
96
+ "@paypal/funding-components": "^1.0.27",
97
+ "@paypal/sdk-client": "^4.0.166",
98
+ "@paypal/sdk-constants": "^1.0.119",
99
+ "@paypal/sdk-logos": "^1.0.45"
100
100
  },
101
101
  "lint-staged": {
102
102
  "*.sh": "prettier --write"
@@ -122,15 +122,16 @@ export function getCardConfig() : FundingSourceConfig {
122
122
  },
123
123
 
124
124
  Label: ({ logo, locale, content, custom }) => {
125
- if (custom) {
125
+ if (custom && custom.label && custom.label.length) {
126
126
  const validLabels = {
127
127
  checkout: 'Checkout'
128
128
  };
129
129
 
130
130
  let label = validLabels.checkout;
131
131
 
132
- if (custom.label && typeof custom.label === 'string' && validLabels[custom.label.toLowerCase()]) {
133
- label = validLabels[custom.label.toLowerCase()];
132
+ const lowerCaseLabel = custom.label.toLowerCase();
133
+ if (validLabels[lowerCaseLabel]) {
134
+ label = validLabels[lowerCaseLabel];
134
135
  }
135
136
 
136
137
  return (
package/src/lib/index.js CHANGED
@@ -4,3 +4,4 @@ export * from './errors';
4
4
  export * from './isRTLLanguage';
5
5
  export * from './security';
6
6
  export * from './session';
7
+ export * from './perceived-latency-instrumentation';
@@ -0,0 +1,61 @@
1
+ /* @flow */
2
+
3
+ type LogLatencyInstrumentationPhaseParams = {|
4
+ buttonSessionID : string,
5
+ phase : string
6
+ |};
7
+
8
+ type InstrumentationPayload = {|
9
+ comp? : Object,
10
+ chunk? : Object,
11
+ query? : Object
12
+ |};
13
+
14
+ function getNavigationTimeOrigin() : number {
15
+ if (window.performance) {
16
+ const hrSyncPoint = performance.now();
17
+ const unixSyncPoint = new Date().getTime();
18
+ return window.performance.timeOrigin || window.performance.timing.navigationStart || (unixSyncPoint - hrSyncPoint);
19
+ } else {
20
+ throw new Error('window.performance not supported');
21
+ }
22
+ }
23
+
24
+ function getStartTimeFromMark({ buttonSessionID, phase } : LogLatencyInstrumentationPhaseParams) : number {
25
+ if (window.performance) {
26
+ return performance.getEntriesByName(`${ buttonSessionID }_${ phase }`).pop().startTime;
27
+ } else {
28
+ throw new Error('window.performance not supported');
29
+ }
30
+ }
31
+
32
+ /* To Track time spent in each phase(cdn download, chunks download, etc)
33
+ logLatencyInstrumentationPhase({
34
+ buttonID: buttonId,
35
+ phase: 'html_body'
36
+ })
37
+ logLatencyInstrumentationPhase({
38
+ buttonID: buttonId,
39
+ phase: 'html_body'
40
+ })
41
+ */
42
+ export const logLatencyInstrumentationPhase = ({ buttonSessionID, phase } : LogLatencyInstrumentationPhaseParams) => {
43
+ if (window.performance && window.performance.mark) {
44
+ window.performance.mark(`${ buttonSessionID }_${ phase }`);
45
+ }
46
+ };
47
+
48
+ export const prepareInstrumentationPayload = (buttonSessionID : string) : InstrumentationPayload => {
49
+ const timeOrigin = getNavigationTimeOrigin();
50
+ const renderStartTime = getStartTimeFromMark({ buttonSessionID, phase: 'buttons-first-render' });
51
+ const renderEndTime = getStartTimeFromMark({ buttonSessionID, phase: 'buttons-first-render-end' });
52
+ return {
53
+ comp: {
54
+ 'first-render': {
55
+ start: timeOrigin + renderStartTime,
56
+ tt: renderEndTime - renderStartTime
57
+ }
58
+ }
59
+ };
60
+ };
61
+
package/src/types.js CHANGED
@@ -54,10 +54,10 @@ export type Requires = {|
54
54
  |};
55
55
 
56
56
  export type CustomStyle = {|
57
- css : {|
57
+ css? : {|
58
58
  [string] : string
59
59
  |},
60
- label : string
60
+ label? : string
61
61
  |};
62
62
 
63
63
  export type LazyExport<T> = {|
@@ -46,7 +46,7 @@ export function Button({ fundingSource, style, multiple, locale, env, fundingEli
46
46
  userIDToken, personalization, onClick = noop, content, tagline, commit, experiment, instrument, experience } : IndividualButtonProps) : ElementNode {
47
47
 
48
48
  const { custom, layout, shape } = style;
49
- const inlineExperience = experience === EXPERIENCE.INLINE && custom;
49
+ const inlineExperience = experience === EXPERIENCE.INLINE && custom && custom.label;
50
50
  const fundingConfig = getFundingConfig()[fundingSource];
51
51
 
52
52
  if (!fundingConfig) {
@@ -130,7 +130,7 @@ export function Button({ fundingSource, style, multiple, locale, env, fundingEli
130
130
  personalization={ personalization }
131
131
  tagline={ tagline }
132
132
  content={ content }
133
- custom={ inlineExperience ? custom : null }
133
+ custom={ inlineExperience ? custom : undefined }
134
134
  experiment={ experiment }
135
135
  />
136
136
  );
@@ -102,7 +102,7 @@ export function Buttons(props : ButtonsProps) : ElementNode {
102
102
  nonce, components, onShippingChange, personalization, userIDToken, content, flow, experiment, applePaySupport,
103
103
  supportsPopups, supportedNativeBrowser, experience } = normalizeButtonProps(props);
104
104
  const { custom, layout, shape, tagline } = style;
105
- const inlineExperience = experience === EXPERIENCE.INLINE && custom;
105
+ const inlineExperience = experience === EXPERIENCE.INLINE && custom && custom.label;
106
106
 
107
107
  let fundingSources = determineEligibleFunding({ fundingSource, layout, remembered, platform, fundingEligibility, components, onShippingChange, flow, wallet, applePaySupport, supportsPopups, supportedNativeBrowser, experiment });
108
108
  const multiple = fundingSources.length > 1;
@@ -138,7 +138,7 @@ export type ButtonStyle = {|
138
138
  menuPlacement : $Values<typeof MENU_PLACEMENT>,
139
139
  period? : number,
140
140
  height? : number,
141
- custom? : CustomStyle
141
+ custom? : ?CustomStyle
142
142
  |};
143
143
 
144
144
  export type ButtonStyleInputs = {|
@@ -149,7 +149,7 @@ export type ButtonStyleInputs = {|
149
149
  layout? : $Values<typeof BUTTON_LAYOUT> | void,
150
150
  period? : number | void,
151
151
  height? : number | void,
152
- custom? : CustomStyle
152
+ custom? : ?CustomStyle
153
153
  |};
154
154
 
155
155
  type PersonalizationComponentProps = {|
@@ -454,6 +454,28 @@ export function normalizeButtonStyle(props : ?ButtonPropsInputs, style : ButtonS
454
454
  }
455
455
  }
456
456
 
457
+ if (custom) {
458
+ if (custom.label && typeof custom.label !== 'string') {
459
+ throw new Error(`style.custom.label is expected to be a String.`);
460
+ }
461
+
462
+ if (custom.css && typeof custom.css !== 'object') {
463
+ throw new Error(`style.custom.css is expected to be JSON.`);
464
+ }
465
+
466
+ if (custom.css && custom.label && custom.label.length === 0) {
467
+ throw new Error(`Expected style.custom.label to be used with style.custom.css`);
468
+ }
469
+
470
+ if (custom.label && custom.label.length > 0 && !custom.css) {
471
+ custom.css = {
472
+ 'background-color': 'black',
473
+ 'height': '48px',
474
+ 'margin-bottom': '15px'
475
+ }
476
+ }
477
+ }
478
+
457
479
  return { custom, label, layout, color, shape, tagline, height, period, menuPlacement };
458
480
  }
459
481
 
@@ -15,7 +15,7 @@ type StyleProps = {|
15
15
 
16
16
  export function Style({ style, nonce, fundingEligibility } : StyleProps) : ElementNode {
17
17
 
18
- const { custom, height } = style;
18
+ const { custom = { label: undefined, css: undefined }, height } = style;
19
19
  const css = componentStyle({ custom, height, fundingEligibility });
20
20
 
21
21
  return (
@@ -11,7 +11,7 @@ import { buttonResponsiveStyle } from './responsive';
11
11
  import { buttonColorStyle } from './color';
12
12
  import { customStyle } from './custom';
13
13
 
14
- export function componentStyle({ custom, height, fundingEligibility } : {| custom? : CustomStyle, height? : ?number, fundingEligibility : FundingEligibilityType |}) : string {
14
+ export function componentStyle({ custom, height, fundingEligibility } : {| custom? : ?CustomStyle, height? : ?number, fundingEligibility : FundingEligibilityType |}) : string {
15
15
  return `
16
16
  ${ pageStyle }
17
17
  ${ buttonStyle }
@@ -3,7 +3,7 @@
3
3
  import type { CustomStyle } from '../../../types';
4
4
  import { CLASS } from '../../../constants';
5
5
 
6
- export const customStyle = ({ custom } : {| custom? : CustomStyle |}) : string => {
6
+ export const customStyle = ({ custom } : {| custom? : ?CustomStyle |}) : string => {
7
7
  const { css } = custom || {};
8
8
 
9
9
  if (!css) {
@@ -8,12 +8,18 @@ import { getLogger, getLocale, getClientID, getEnv, getIntent, getCommit, getVau
8
8
  getUserIDToken, getClientMetadataID, getAmount, getEnableFunding, getStorageID, getUserExperienceFlow, getMerchantRequestedPopupsDisabled, getVersion } from '@paypal/sdk-client/src';
9
9
  import { rememberFunding, getRememberedFunding, getRefinedFundingEligibility } from '@paypal/funding-components/src';
10
10
  import { ZalgoPromise } from '@krakenjs/zalgo-promise/src';
11
- import { create, type ZoidComponent } from '@krakenjs/zoid/src';
11
+ import { create, EVENT, type ZoidComponent } from '@krakenjs/zoid/src';
12
12
  import { uniqueID, memoize, isApplePaySupported, supportsPopups as userAgentSupportsPopups, noop, isLocalStorageEnabled } from '@krakenjs/belter/src';
13
13
  import { FUNDING, FUNDING_BRAND_LABEL, QUERY_BOOL, ENV, FPTI_KEY } from '@paypal/sdk-constants/src';
14
14
  import { node, dom } from '@krakenjs/jsx-pragmatic/src';
15
15
 
16
- import { getSessionID, storageState, sessionState } from '../../lib';
16
+ import {
17
+ getSessionID,
18
+ storageState,
19
+ sessionState,
20
+ logLatencyInstrumentationPhase,
21
+ prepareInstrumentationPayload
22
+ } from '../../lib';
17
23
  import { normalizeButtonStyle, type ButtonProps } from '../../ui/buttons/props';
18
24
  import { isFundingEligible } from '../../funding';
19
25
  import { EXPERIENCE } from '../../constants';
@@ -44,7 +50,7 @@ export const getButtonsComponent : () => ButtonsComponent = memoize(() => {
44
50
 
45
51
  logger: getLogger(),
46
52
 
47
- prerenderTemplate: ({ state, props, doc }) => {
53
+ prerenderTemplate: ({ state, props, doc, event }) => {
48
54
  const { buttonSessionID } = props;
49
55
 
50
56
  if (!isLocalStorageEnabled()) {
@@ -56,6 +62,28 @@ export const getButtonsComponent : () => ButtonsComponent = memoize(() => {
56
62
  });
57
63
  }
58
64
 
65
+ event.on(EVENT.PRERENDERED, () => {
66
+ // CPL stands for Consumer Perceived Latency
67
+ logLatencyInstrumentationPhase({ buttonSessionID, phase: 'buttons-first-render-end' });
68
+ try {
69
+ const cplPhases = prepareInstrumentationPayload(buttonSessionID);
70
+ const cplLatencyMetrics = {
71
+ [FPTI_KEY.STATE]: 'CPL_LATENCY_METRICS',
72
+ [FPTI_KEY.TRANSITION]: 'process_client_metrics',
73
+ [FPTI_KEY.CONTEXT_ID]: buttonSessionID,
74
+ [FPTI_KEY.PAGE]: 'main:xo:paypal-components:smart-payment-buttons',
75
+ [FPTI_KEY.CPL_COMP_METRICS]: JSON.stringify(cplPhases?.comp || {})
76
+ };
77
+ getLogger().info('CPL_LATENCY_METRICS_FIRST_RENDER').track(cplLatencyMetrics);
78
+ } catch (err) {
79
+ getLogger().info('button_render_CPL_instrumentation_log_error').track({
80
+ err: err.message || 'CPL_LOG_PHASE_ERROR',
81
+ details: err.details,
82
+ stack: JSON.stringify(err.stack || err)
83
+ });
84
+ }
85
+ });
86
+
59
87
  return (
60
88
  <PrerenderedButtons
61
89
  nonce={ props.nonce }
@@ -275,6 +303,8 @@ export const getButtonsComponent : () => ButtonsComponent = memoize(() => {
275
303
  required: false,
276
304
  default: () => noop,
277
305
  decorate: ({ props, value = noop }) => {
306
+ logLatencyInstrumentationPhase({ buttonSessionID: props.buttonSessionID, phase: 'buttons-first-render' });
307
+
278
308
  return (...args) => {
279
309
  const { fundingSource } = props;
280
310
  const venmoExperiment = createVenmoExperiment();