From 61eff4c36db054a131e04885aceb4d732bbd7708 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Wed, 4 Jan 2023 11:05:35 +0100 Subject: [PATCH] :recycle: (js) Implement Payment input in bot v2 Closes #193 --- packages/js/package.json | 1 - .../payment/components/StripePaymentForm.tsx | 109 ++++++------ pnpm-lock.yaml | 167 +----------------- 3 files changed, 53 insertions(+), 224 deletions(-) diff --git a/packages/js/package.json b/packages/js/package.json index dc64e3b2a0..944afa925e 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -13,7 +13,6 @@ }, "license": "MIT", "dependencies": { - "@power-elements/stripe-elements": "3.3.0", "@stripe/stripe-js": "1.46.0", "models": "workspace:*", "phone": "3.1.32", diff --git a/packages/js/src/features/blocks/inputs/payment/components/StripePaymentForm.tsx b/packages/js/src/features/blocks/inputs/payment/components/StripePaymentForm.tsx index 9620105f8a..f8b0b83e20 100644 --- a/packages/js/src/features/blocks/inputs/payment/components/StripePaymentForm.tsx +++ b/packages/js/src/features/blocks/inputs/payment/components/StripePaymentForm.tsx @@ -1,18 +1,9 @@ import { SendButton } from '@/components/SendButton' -import { createEffect, createSignal, Show } from 'solid-js' -import { Stripe, StripeElements } from '@stripe/stripe-js' +import { createSignal, onMount, Show } from 'solid-js' +import { loadStripe } from '@stripe/stripe-js/pure' +import type { Stripe, StripeElements } from '@stripe/stripe-js' import { BotContext } from '@/types' import { PaymentInputOptions, RuntimeOptions } from 'models' -import '@power-elements/stripe-elements' -declare module 'solid-js' { - namespace JSX { - interface IntrinsicElements { - 'stripe-payment-request': unknown - } - } -} - -// TODO: Implement support for payment input. (WIP) type Props = { context: BotContext @@ -20,38 +11,37 @@ type Props = { onSuccess: () => void } -let stripe: Stripe | undefined -let elements: StripeElements | undefined -let ignoreFirstPaymentIntentCall = true +const slotName = 'stripe-payment-form' + +let paymentElementSlot: HTMLSlotElement +let stripe: Stripe | null = null +let elements: StripeElements | null = null export const StripePaymentForm = (props: Props) => { const [message, setMessage] = createSignal() + const [isMounted, setIsMounted] = createSignal(false) const [isLoading, setIsLoading] = createSignal(false) - createEffect(() => { + onMount(async () => { + initShadowMountPoint(paymentElementSlot) + stripe = await loadStripe(props.options.publicKey) if (!stripe) return - - if (ignoreFirstPaymentIntentCall) - return (ignoreFirstPaymentIntentCall = false) - - stripe - .retrievePaymentIntent(props.options.paymentIntentSecret) - .then(({ paymentIntent }) => { - switch (paymentIntent?.status) { - case 'succeeded': - setMessage('Payment succeeded!') - break - case 'processing': - setMessage('Your payment is processing.') - break - case 'requires_payment_method': - setMessage('Your payment was not successful, please try again.') - break - default: - setMessage('Something went wrong.') - break - } - }) + elements = stripe.elements({ + appearance: { + theme: 'stripe', + variables: { + colorPrimary: getComputedStyle(paymentElementSlot).getPropertyValue( + '--typebot-button-bg-color' + ), + }, + }, + clientSecret: props.options.paymentIntentSecret, + }) + const paymentElement = elements.create('payment', { + layout: 'tabs', + }) + paymentElement.mount('#payment-element') + setTimeout(() => setIsMounted(true), 1000) }) const handleSubmit = async (event: Event & { submitter: HTMLElement }) => { @@ -89,31 +79,34 @@ export const StripePaymentForm = (props: Props) => { onSubmit={handleSubmit} class="flex flex-col rounded-lg p-4 typebot-input w-full items-center" > - {/* */} - - {props.options.labels.button} {props.options.amountLabel} - + + + + {props.options.labels.button} {props.options.amountLabel} + + -
+
{message()}
) } + +const initShadowMountPoint = (element: HTMLElement) => { + const rootNode = element.getRootNode() as ShadowRoot + const host = rootNode.host + const slotPlaceholder = document.createElement('div') + slotPlaceholder.style.width = '100%' + slotPlaceholder.slot = slotName + host.appendChild(slotPlaceholder) + const paymentElementContainer = document.createElement('div') + paymentElementContainer.id = 'payment-element' + slotPlaceholder.appendChild(paymentElementContainer) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d46e845af1..162d2e2767 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -571,7 +571,6 @@ importers: packages/js: specifiers: - '@power-elements/stripe-elements': 3.3.0 '@rollup/plugin-babel': 6.0.3 '@rollup/plugin-node-resolve': 15.0.1 '@rollup/plugin-replace': 5.0.2 @@ -603,7 +602,6 @@ importers: vite: 4.0.3 vite-plugin-solid: 2.5.0 dependencies: - '@power-elements/stripe-elements': 3.3.0 '@stripe/stripe-js': 1.46.0 models: link:../models phone: 3.1.32 @@ -4958,14 +4956,6 @@ packages: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 - /@lavadrop/camel-case/0.4.0: - resolution: {integrity: sha512-1h8sU1aQ0Ms1aAenfRxC45AXAvayqtxyTOt/QNB2dpLQnyaTkT3ejJFNdoucBREmCZRZSZtCFU08Wq3QoMyJaQ==} - dev: false - - /@lavadrop/kebab-case/3.0.0: - resolution: {integrity: sha512-16m/BEvXl+e37jHUuIkeynwBbxNlsGvnl0CKwPD37A4V7XhPmOyld0XnY302TEx8+N4D/DonjScrXF91D5JzQw==} - dev: false - /@leichtgewicht/ip-codec/2.0.4: resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==} dev: false @@ -5015,10 +5005,6 @@ packages: '@lezer/common': 1.0.2 dev: false - /@lit/reactive-element/1.5.0: - resolution: {integrity: sha512-fQh9FDK0LPTwDk+0HhSZEtb8K0LTN1wXerwpGrWA+a8tWulYRDLI4vQDWp4GOIsewn0572KYV/oZ3+492D7osA==} - dev: false - /@mdx-js/mdx/1.6.22: resolution: {integrity: sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==} dependencies: @@ -5267,10 +5253,6 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.14.0 - /@pacote/memoize/1.1.8: - resolution: {integrity: sha512-LeMfu2X9A3PyUTya+UZp9BaLMht+K8hmCyLLPhZc+2hh1F+udHUymJUsbXFucdzN7ZrVrvr0mna4p/umYuEUuQ==} - dev: false - /@panva/hkdf/1.0.2: resolution: {integrity: sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==} dev: false @@ -5303,19 +5285,6 @@ packages: resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} dev: false - /@power-elements/stripe-elements/3.3.0: - resolution: {integrity: sha512-O5BUVOzo9BUykQptBnLKAR59cAFViJU4PpyAddQamPRyK3nreNzGi1b4zIlzsbbuArrn/u2KNPcFZAkYqX9N2A==} - requiresBuild: true - dependencies: - '@lavadrop/camel-case': 0.4.0 - '@lavadrop/kebab-case': 3.0.0 - '@pacote/memoize': 1.1.8 - '@stripe/stripe-js': 1.46.0 - lit: 2.5.0 - patch-package: 6.5.0 - tslib: 2.4.1 - dev: false - /@prettier/plugin-php/0.19.2_prettier@2.8.1: resolution: {integrity: sha512-j4Io//Js9ajKP7vgjKpS0rS8B/HKIJxutUshlbCpXvsP8Cz28MYp2VFJytpS9zyLEfsMPXordNWjkBVvHRL7ow==} peerDependencies: @@ -6516,10 +6485,6 @@ packages: resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==} dev: true - /@types/trusted-types/2.0.2: - resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==} - dev: false - /@types/unist/2.0.6: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: false @@ -7208,10 +7173,6 @@ packages: /@xtuc/long/4.2.2: resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - /@yarnpkg/lockfile/1.1.0: - resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} - dev: false - /@zag-js/element-size/0.3.0: resolution: {integrity: sha512-5/hEI+0c6ZNCx6KHlOS5/WeHsd6+I7gk7Y/b/zATp4Rp3tHirs/tu1frq+iy5BmfaG9hbQtfHfUJTjOcI5jnoQ==} dev: false @@ -8963,17 +8924,6 @@ packages: - encoding dev: false - /cross-spawn/6.0.5: - resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} - engines: {node: '>=4.8'} - dependencies: - nice-try: 1.0.5 - path-key: 2.0.1 - semver: 5.7.1 - shebang-command: 1.2.0 - which: 1.3.1 - dev: false - /cross-spawn/7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -10805,12 +10755,6 @@ packages: locate-path: 6.0.0 path-exists: 4.0.0 - /find-yarn-workspace-root/2.0.0: - resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==} - dependencies: - micromatch: 4.0.5 - dev: false - /flat-cache/3.0.4: resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -10960,15 +10904,6 @@ packages: universalify: 2.0.0 dev: false - /fs-extra/7.0.1: - resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} - engines: {node: '>=6 <7 || >=8'} - dependencies: - graceful-fs: 4.2.10 - jsonfile: 4.0.0 - universalify: 0.1.2 - dev: false - /fs-extra/9.1.0: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} @@ -13185,12 +13120,6 @@ packages: engines: {node: '>=6'} hasBin: true - /jsonfile/4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - optionalDependencies: - graceful-fs: 4.2.10 - dev: false - /jsonfile/6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: @@ -13276,12 +13205,6 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - /klaw-sync/6.0.0: - resolution: {integrity: sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==} - dependencies: - graceful-fs: 4.2.10 - dev: false - /kleur/3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -13357,27 +13280,6 @@ packages: engines: {node: '>=4'} dev: false - /lit-element/3.2.2: - resolution: {integrity: sha512-6ZgxBR9KNroqKb6+htkyBwD90XGRiqKDHVrW/Eh0EZ+l+iC+u+v+w3/BA5NGi4nizAVHGYvQBHUDuSmLjPp7NQ==} - dependencies: - '@lit/reactive-element': 1.5.0 - lit-html: 2.5.0 - dev: false - - /lit-html/2.5.0: - resolution: {integrity: sha512-bLHosg1XL3JRUcKdSVI0sLCs0y1wWrj2sqqAN3cZ7bDDPNgmDHH29RV48x6Wz3ZmkxIupaE+z7uXSZ/pXWAO1g==} - dependencies: - '@types/trusted-types': 2.0.2 - dev: false - - /lit/2.5.0: - resolution: {integrity: sha512-DtnUP6vR3l4Q8nRPPNBD+UxbAhwJPeky+OVbi3pdgMqm0g57xFSl1Sj64D1rIB+nVNdiVVg8YxB0hqKjvdadZA==} - dependencies: - '@lit/reactive-element': 1.5.0 - lit-element: 3.2.2 - lit-html: 2.5.0 - dev: false - /load-tsconfig/0.2.3: resolution: {integrity: sha512-iyT2MXws+dc2Wi6o3grCFtGXpeMvHmJqS27sMPGtV2eUu4PeFnG+33I8BlFK1t1NWMjOpcx9bridn5yxLDX2gQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -14422,10 +14324,6 @@ packages: next: 13.1.1_7xlrwlvvs7cv2obrs6a5y6oxxq dev: false - /nice-try/1.0.5: - resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - dev: false - /no-case/2.3.2: resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} dependencies: @@ -14702,14 +14600,6 @@ packages: dependencies: mimic-fn: 2.1.0 - /open/7.4.2: - resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} - engines: {node: '>=8'} - dependencies: - is-docker: 2.2.1 - is-wsl: 2.2.0 - dev: false - /open/8.4.0: resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} engines: {node: '>=12'} @@ -14778,6 +14668,7 @@ packages: /os-tmpdir/1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + dev: true /p-cancelable/1.1.0: resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} @@ -14970,27 +14861,6 @@ packages: tslib: 2.4.1 dev: false - /patch-package/6.5.0: - resolution: {integrity: sha512-tC3EqJmo74yKqfsMzELaFwxOAu6FH6t+FzFOsnWAuARm7/n2xB5AOeOueE221eM9gtMuIKMKpF9tBy/X2mNP0Q==} - engines: {node: '>=10', npm: '>5'} - hasBin: true - dependencies: - '@yarnpkg/lockfile': 1.1.0 - chalk: 4.1.2 - cross-spawn: 6.0.5 - find-yarn-workspace-root: 2.0.0 - fs-extra: 7.0.1 - is-ci: 2.0.0 - klaw-sync: 6.0.0 - minimist: 1.2.7 - open: 7.4.2 - rimraf: 2.7.1 - semver: 5.7.1 - slash: 2.0.0 - tmp: 0.0.33 - yaml: 1.10.2 - dev: false - /path-browserify/1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} dev: false @@ -15012,11 +14882,6 @@ packages: resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} dev: false - /path-key/2.0.1: - resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} - engines: {node: '>=4'} - dev: false - /path-key/3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -16789,13 +16654,6 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - /rimraf/2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: false - /rimraf/3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true @@ -17242,24 +17100,12 @@ packages: /shallowequal/1.1.0: resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} - /shebang-command/1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - dependencies: - shebang-regex: 1.0.0 - dev: false - /shebang-command/2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - /shebang-regex/1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - dev: false - /shebang-regex/3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -17322,11 +17168,6 @@ packages: sax: 1.2.4 dev: false - /slash/2.0.0: - resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} - engines: {node: '>=6'} - dev: false - /slash/3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -18129,6 +17970,7 @@ packages: engines: {node: '>=0.6.0'} dependencies: os-tmpdir: 1.0.2 + dev: true /tmpl/1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -18642,11 +18484,6 @@ packages: unist-util-visit-parents: 3.1.1 dev: false - /universalify/0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - dev: false - /universalify/0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'}