@gr4vy/secure-fields 0.8.0 → 1.1.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,55 @@
1
+ # v1.1.0 (Fri Jan 20 2023)
2
+
3
+ #### 🐛 Bug Fix
4
+
5
+ - build(deps-dev): Bump typedoc from 0.23.21 to 0.23.24 [#154](https://github.com/gr4vy/secure-fields/pull/154) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@luca-gr4vy](https://github.com/luca-gr4vy))
6
+ - build(deps): bump css-loader from 6.7.1 to 6.7.2 [#119](https://github.com/gr4vy/secure-fields/pull/119) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@brunodesde1987](https://github.com/brunodesde1987))
7
+ - build(deps-dev): bump typedoc from 0.23.18 to 0.23.19 [#103](https://github.com/gr4vy/secure-fields/pull/103) ([@dependabot[bot]](https://github.com/dependabot[bot]))
8
+ - build(deps-dev): bump typedoc from 0.23.16 to 0.23.18 [#99](https://github.com/gr4vy/secure-fields/pull/99) ([@dependabot[bot]](https://github.com/dependabot[bot]))
9
+
10
+ #### 🏠 Internal
11
+
12
+ - chore: absolute import [#145](https://github.com/gr4vy/secure-fields/pull/145) ([@theturboboy](https://github.com/theturboboy))
13
+ - build(deps-dev): bump typedoc from 0.23.19 to 0.23.21 [#118](https://github.com/gr4vy/secure-fields/pull/118) ([@dependabot[bot]](https://github.com/dependabot[bot]))
14
+
15
+ #### Authors: 4
16
+
17
+ - [@dependabot[bot]](https://github.com/dependabot[bot])
18
+ - Andrei Haidukevich ([@theturboboy](https://github.com/theturboboy))
19
+ - Bruno Carvalho ([@brunodesde1987](https://github.com/brunodesde1987))
20
+ - Luca Allievi ([@luca-gr4vy](https://github.com/luca-gr4vy))
21
+
22
+ ---
23
+
24
+ # v1.0.0 (Tue Oct 18 2022)
25
+
26
+ #### 🚀 Enhancement
27
+
28
+ - build(deps-dev): bump @gr4vy/node from 0.27.0 to 0.31.0 [#70](https://github.com/gr4vy/secure-fields/pull/70) ([@dependabot[bot]](https://github.com/dependabot[bot]))
29
+
30
+ #### 🐛 Bug Fix
31
+
32
+ - task: playwright tests [#73](https://github.com/gr4vy/secure-fields/pull/73) ([@luca-gr4vy](https://github.com/luca-gr4vy))
33
+ - chore: update deps [#80](https://github.com/gr4vy/secure-fields/pull/80) ([@douglaseggleton](https://github.com/douglaseggleton) [@luca-gr4vy](https://github.com/luca-gr4vy))
34
+
35
+ #### 🏠 Internal
36
+
37
+ - fix: jsdom local testing issue [#92](https://github.com/gr4vy/secure-fields/pull/92) ([@luca-gr4vy](https://github.com/luca-gr4vy))
38
+ - feat: playwright test [#54](https://github.com/gr4vy/secure-fields/pull/54) ([@luca-gr4vy](https://github.com/luca-gr4vy))
39
+
40
+ #### 📝 Documentation
41
+
42
+ - Update README.md [#58](https://github.com/gr4vy/secure-fields/pull/58) ([@cbetta](https://github.com/cbetta))
43
+
44
+ #### Authors: 4
45
+
46
+ - [@dependabot[bot]](https://github.com/dependabot[bot])
47
+ - Cristiano Betta ([@cbetta](https://github.com/cbetta))
48
+ - Douglas Eggleton ([@douglaseggleton](https://github.com/douglaseggleton))
49
+ - Luca Allievi ([@luca-gr4vy](https://github.com/luca-gr4vy))
50
+
51
+ ---
52
+
1
53
  # v0.8.0 (Thu Oct 06 2022)
2
54
 
3
55
  #### 🚀 Enhancement
package/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  ![NPM Version](https://img.shields.io/npm/v/@gr4vy/secure-fields?color=green)
4
4
  [![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/gr4vy/secure-fields/blob/main/LICENSE)
5
5
 
6
- Load Secure Fields in your Node app.
6
+ Add Secure Fields to your Node app.
7
7
 
8
- Use [`@gr4vy/secure-fields-react`](../secure-fields-react) in a React application or [`@gr4vy/secure-fields-cdn`](../secure-fields-cdn) in a regular web application.
8
+ Use [`@gr4vy/secure-fields-react`](../secure-fields-react) in a React application or [`@gr4vy/secure-fields-cdn`](../secure-fields-cdn) in non-Node web application.
9
9
 
10
10
  ## Usage
11
11
 
@@ -18,115 +18,129 @@ npm install @gr4vy/secure-fields --save-prod
18
18
 
19
19
  ## Get started
20
20
 
21
- To use Secure Fields, you first need a form with placeholder elements that will be replaced by secure fields (`iframe`s), each with a specific `id` attribute.
21
+ To use Secure Fields, you need some placeholder elements to attach the secure fields on to. Each of these elements will be replaced by a secure field. These elements can be `<input>` but do not need to be.
22
22
 
23
23
  ```html
24
- <form id="cc-form">
25
- <label for="cc-holder-name">Name</label>
26
- <input
27
- id="cc-holder-name"
28
- class="form-control"
29
- placeholder="Name on the card"
30
- />
31
- <label for="cc-number">Card Number</label>
32
- <input id="cc-number" class="form-control" />
33
- <label for="cc-security-code">Security Code</label>
34
- <input id="cc-security-code" class="form-control" />
35
- <label for="cc-expiry-date">Expiry date</label>
36
- <input id="cc-expiry-date" class="form-control" />
37
- <button id="pay-button">Pay now</button>
38
- </form>
24
+ <label for="cc-number">Card Number</label>
25
+ <input id="cc-number" />
26
+
27
+ <label for="cc-cvv">Security code</label>
28
+ <input id="cc-cvv" />
29
+
30
+ <label for="cc-expiry">Expiry date</label>
31
+ <input id="cc-expiry" />
32
+
33
+ <button id="cc-button">Submit</button>
39
34
  ```
40
35
 
41
- Generate a client token which is going to be used to make an authenticated call (you can use any of our [SDKs](https://gr4vy.com/docs/web-checkout/sdks)). Then, get a session id by making a request to the [Checkout Sessions API](https://gr4vy.com/docs/reference#tag/Checkout-Sessions), passing an `Authorization` header with the generated bearer token.
36
+ To use Secure Fields, call the `new SecureFields()` method with a configuration object that includes the `environment` (`sandbox` or `production`), your `gr4vyId` and a `sessionId` obtained from calling the [Checkout Sessions API](https://gr4vy.com/docs/reference#tag/Checkout-Sessions).
42
37
 
43
- Import the library and call the class constructor to create an instance. Pass the session id returned by the Checkout Sessions API, an environment and your gr4vyId.
38
+ Then, add the fields and event listeners needed to handle the form submission.
44
39
 
45
40
  ```js
46
- const { SecureFields } = require(`@gr4vy/secure-fields`)
41
+ const { SecureFields } = require('@gr4vy/secure-fields')
42
+
43
+ // Alternatively
44
+ // import { SecureFields } from '@gr4vy/secure-fields'
47
45
 
48
46
  const secureFields = new SecureFields({
47
+ gr4vyId: '[GR4VY_ID]',
49
48
  environment: 'sandbox',
50
- gr4vyId: '[GR4VY_ID]'
51
- sessionId: '[SESSION_ID]'
49
+ sessionId: '[SESSION_ID]',
52
50
  })
53
51
 
54
- > **Note**: Replace `[GR4VY_ID]` with your Gr4vy ID and `[SESSION_ID]` with the session id.
55
- ```
56
-
57
- Call methods to add / update fields, set flags, and attach event listeners. Call `submit` to capture the data and send it to Gr4vy, passing an optional callback to handle other UI interactions.
58
-
59
- ```js
60
- secureFields.setDebug(true)
61
-
62
52
  secureFields.addFont('Lato')
63
53
 
64
54
  const styles = {
65
55
  color: 'black',
66
56
  fontSize: '18px',
57
+ fontFamily: 'Lato',
67
58
  ':focus': {
68
59
  color: 'blue',
69
60
  },
70
61
  }
71
62
 
72
- const cardNumberField = secureFields.addCardNumberField('#cc-number', {
73
- styles,
74
- })
75
- const expiryDateField = secureFields.addExpiryDateField('#cc-number', {
76
- styles,
77
- })
78
- const securityCodeField = secureFields.addSecurityCodeField('#cc-number', {
79
- styles,
80
- })
63
+ const number = secureFields.addCardNumberField('#cc-number', { styles })
64
+ const expiry = secureFields.addExpiryDateField('#cc-expiry', { styles })
65
+ const cvv = secureFields.addSecurityCodeField('#cc-cvv', { styles })
81
66
 
82
- secureFields.addEventListener(SecureFields.Events.CARD_VAULT_SUCCESS, () => {
83
- console.log('Card has been tokenized successfully!')
84
- // create transaction using the Checkout Sessions API...
85
- })
67
+ secureFields.addEventListener(SecureFields.Events.CARD_VAULT_SUCCESS, () =>
68
+ console.log('Card tokenized successfully')
69
+ )
86
70
 
87
- secureFields.addEventListener(SecureFields.Events.CARD_VAULT_FAILURE, () => {
88
- console.log('Failed to tokenize card data')
89
- })
71
+ secureFields.addEventListener(SecureFields.Events.CARD_VAULT_FAILURE, () =>
72
+ console.log('Card tokenization failed')
73
+ )
90
74
 
91
- cardNumberField.addEventListener('input', (data) => {
75
+ number.addEventListener('input', (data) => {
92
76
  console.warn('Input changed', data) // data.schema, data.codeLabel...
93
77
  })
94
78
 
95
- const form = document.getElementById('cc-form')
96
- form.addEventListener('submit', (e) => {
97
- e.preventDefault()
79
+ const button = document.querySelector('#cc-button')
80
+ button.addEventListener('click', () => {
98
81
  secureFields.submit()
99
82
  })
100
83
  ```
101
84
 
102
- This will initialize Secure Fields and inject corresponding iframe elements into your form. On form submit, it will capture and store the payment method data securely via Gr4vy, so you can then create a transaction via the Checkout Sessions API.
85
+ > **Note**: Replace `[GR4VY_ID]` with your Gr4vy ID and `[SESSION_ID]` with a valid checkout session id.
86
+
87
+ This will initialize Secure Fields and replace each of the inputs with a corresponding secure element. On submit, it will store the card data securely with Gr4vy, after which you can create a transaction or vault the card for later use.
103
88
 
104
- ### Styles
89
+ For more information please
90
+ refer to the [Secure Fields get started guide](https://gr4vy.com/docs/web-secure-fields).
91
+
92
+ ## Styles
93
+
94
+ The outer styling of every Secure Field is completely in your control by applying your own CSS to the following class names.
95
+
96
+ ```css
97
+ /* Applied to each field */
98
+ .secure-fields__input {
99
+ }
100
+ /* Applied to the card number field */
101
+ .secure-fields__input--number {
102
+ }
103
+ /* Applied to the expiry date field */
104
+ .secure-fields__input--expiry-date {
105
+ }
106
+ /* Applied to the security code field */
107
+ .secure-fields__input--security-code {
108
+ }
109
+ /* Applied to a focused field */
110
+ .secure-fields__input[data-secure-fields-focused] {
111
+ }
112
+ /* Applied to an invalid field */
113
+ .secure-fields__input[data-secure-fields-invalid] {
114
+ }
115
+ ```
105
116
 
106
- The outer styling (any containers around fields, etc) is completely in the your control. SecureFields will also generate classes along with a set of data attributes and attach them to the outer iframe containers, so you can target them with your own CSS. Finally, an object of CSS rules can be passed to the `addCardNumberField`, `addExpiryDateField` and `addSecurityCodeField` methods options to style elements inside the iframe(s).
117
+ To set the CSS of the content of a field an object of CSS rules can be passed to the `addCardNumberField`, `addExpiryDateField` and `addSecurityCodeField` methods.
107
118
 
108
119
  ```js
109
120
  const styles = {
110
- // Default styles (any of the supported CSS property)
111
- color: ...,
112
- // Rules applied when the field has its value autofilled by a browser / extension
121
+ // Default styles applied to field
122
+ color: "#66666",
123
+ fontSize: "1.3em",
124
+ // ...etc
125
+
126
+ // Applied when field has a value autofilled by browser/extension
113
127
  ':autofill': { ... },
114
- // Rules applied when the field is hovered
128
+ // Applied when field is hovered
115
129
  ':hover': { ... },
116
- // Rules applied when the field is focused
130
+ // Applied when field is focused
117
131
  ':focus': { ... },
118
- // Rules applied when the field is disabled
132
+ // Applied when field is disabled
119
133
  ':disabled': { ... },
120
- // Rules applied when the field is valid
134
+ // Applied when field is valid
121
135
  ':valid': { ... },
122
- // Rules applied when the field is invalid
136
+ // Applied when field is invalid
123
137
  ':invalid': { ... },
124
- // Rules applied to the field placeholder
138
+ // Applied to field placeholder
125
139
  '::placeholder': { ... },
126
140
  }
127
141
  ```
128
142
 
129
- We only allow the following CSS properties:
143
+ Not all CSS properties can be set. The currently supported list is as follows.
130
144
 
131
145
  ```
132
146
  backgroundColor
@@ -163,40 +177,23 @@ MozOsxFontSmoothing
163
177
  WebkitFontSmoothing
164
178
  ```
165
179
 
166
- Here are the selectors you can use to style the outer frame containers:
180
+ ## Events
167
181
 
168
- ```css
169
- /* Applied to each field */
170
- .secure-fields__input {
171
- }
172
- /* Applied to the card number field */
173
- .secure-fields__input--number {
174
- }
175
- /* Applied to the expiry date field */
176
- .secure-fields__input--expiry-date {
177
- }
178
- /* Applied to the security code field */
179
- .secure-fields__input--security-code {
180
- }
181
- /* Applied to a focused field */
182
- .secure-fields__input[data-secure-fields-focused] {
183
- }
184
- /* Applied to an invalid field */
185
- .secure-fields__input[data-secure-fields-invalid] {
186
- }
187
- ```
182
+ ### Global events
183
+
184
+ The following events can be listened to by attaching an event handler to the `SecureFields` instance using the `addEventListener` method.
188
185
 
189
- ### Events
186
+ | Name | Description | Example |
187
+ | -------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
188
+ | `CARD_VAULT_SUCCESS` | Triggered when the card is successfully vaulted. | `secureFields.addEventListener(SecureFields.Events.CARD_VAULT_SUCCESS, () => { console.log('Card has been tokenized successfully!') })` |
189
+ | `CARD_VAULT_FAILURE` | Triggered when the card vaulting fails. | `secureFields.addEventListener(SecureFields.Events.CARD_VAULT_FAILURE, () => { console.log('Couldn\'t tokenize the card') })` |
190
+ | `READY` | Triggered when Secure Fields is loaded and ready to be used. | `secureFields.addEventListener(SecureFields.READY, () => { console.log('Secure fields loaded') })` |
190
191
 
191
- You can call the `addEventListener` on a Secure Fields instance or on fields, passing one of the supported events and a callback. Here are the events you can listen to on an instance:
192
+ ### Field events
192
193
 
193
- | Name | Description | Example |
194
- | -------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
195
- | `CARD_VAULT_SUCCESS` | Triggered when the card is successfully vaulted. | `secureFields.addEventListener(SecureFields.Events.CARD_VAULT_SUCCESS, () => { console.log('Card has been tokenized successfully!') })` |
196
- | `CARD_VAULT_FAILURE` | Triggered when the card vaulting fails. | `secureFields.addEventListener(SecureFields.Events.CARD_VAULT_FAILURE, () => { console.log('Couldn\'t tokenize the card') })` |
197
- | `READY` | Triggered when the SecureFields is loaded and ready to be used. | `secureFields.addEventListener(SecureFields.READY, () => { console.log('Secure fields loaded') })` |
194
+ The following events can be listened to by attaching an event handler to a field (returned by the `addCardNumberField`, `addExpiryDateField` and `addSecurityCodeField` methods) using the `addEventListener` method.
198
195
 
199
- For actual fields (returned by the `addCardNumberField`, `addExpiryDateField` and `addSecurityCodeField` methods), you can subscribe to default events and have access to more useful data (bin, validation status, etc). For example, the input event on a card number field might include `{ schema: 'american-express', codeLabel: 'CID', valid: true, ... }`. You can expect to be able to listen to the following events:
196
+ Some of these provide additional useful data like the card BIN, validation status, and scheme. For example, the input event on a card number field might include `{ schema: 'visa', codeLabel: 'CVV', valid: true, ... }`.
200
197
 
201
198
  | Name | Description | Example |
202
199
  | ------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
package/lib/index.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import { Events } from './constants';
2
2
  import { SecureInput } from './input';
3
3
  import type { CombinedEvents, Config, Field } from './types';
4
+ declare global {
5
+ interface Window {
6
+ SECURE_FIELDS_FRAME_URL?: string;
7
+ }
8
+ }
4
9
  declare class SecureFields {
5
10
  config: Config;
6
11
  controller: HTMLIFrameElement;
package/lib/index.js CHANGED
@@ -1 +1 @@
1
- !function(t,e){if("object"==typeof exports&&"object"==typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var r=e();for(var n in r)("object"==typeof exports?exports:t)[n]=r[n]}}(this,(()=>(()=>{"use strict";var t={d:(e,r)=>{for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{SecureFields:()=>U,SecureInput:()=>j});var r,n,o="@gr4vy-secure-fields-debug";!function(t){t.CARD_VAULT_SUCCESS="card-vault-success",t.CARD_VAULT_FAILURE="card-vault-failure",t.READY="ready"}(r||(r={})),function(t){t.FOCUSED="data-secure-fields-focused",t.INVALID="data-secure-fields-invalid"}(n||(n={}));var i="secure-fields";function a(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r<e;r++)n[r]=t[r];return n}function c(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i=[],a=!0,c=!1;try{for(r=r.call(t);!(a=(n=r.next()).done)&&(i.push(n.value),!e||i.length!==e);a=!0);}catch(t){c=!0,o=t}finally{try{a||null==r.return||r.return()}finally{if(c)throw o}}return i}}(t,e)||function(t,e){if(t){if("string"==typeof t)return a(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(r):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?a(t,e):void 0}}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}var s=new(function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.subscribers=[]}var e=t.prototype;return e.subscribe=function(t,e){this.subscribers.push([t,e])},e.unsubscribe=function(t,e){this.subscribers=this.subscribers.filter((function(r){var n=c(r,2),o=n[0],i=n[1];return o!==t||i.toString()!==e.toString()}))},e.publish=function(t,e){this.subscribers.forEach((function(r){var n=c(r,2),o=n[0],i=n[1];return setTimeout((function(){return o===t?i(e):null}),0)}))},t}()),u=function(t){return"[object Object]"===Object.prototype.toString.call(t)};function l(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}var f={debug:!1,level:"log"},p=function(t,e,r){var n=function(t){for(var e=1;e<arguments.length;e++){var r=null!=arguments[e]?arguments[e]:{},n=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(n=n.concat(Object.getOwnPropertySymbols(r).filter((function(t){return Object.getOwnPropertyDescriptor(r,t).enumerable})))),n.forEach((function(e){l(t,e,r[e])}))}return t}({},f,r),i=n.debug,a=n.level;(i||"true"===localStorage.getItem(o))&&console[a]("Gr4vy - Secure Fields - ".concat(t),e||{})},d=function(t){return t!=t.toLowerCase()&&(t=t.replace(/[A-Z]/g,(function(t){return"-"+t.toLowerCase()}))),t};function y(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r<e;r++)n[r]=t[r];return n}function b(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i=[],a=!0,c=!1;try{for(r=r.call(t);!(a=(n=r.next()).done)&&(i.push(n.value),!e||i.length!==e);a=!0);}catch(t){c=!0,o=t}finally{try{a||null==r.return||r.return()}finally{if(c)throw o}}return i}}(t,e)||m(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function h(t){return function(t){if(Array.isArray(t))return y(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||m(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function m(t,e){if(t){if("string"==typeof t)return y(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(r):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?y(t,e):void 0}}var v=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return Object.entries(t).reduce((function(n,o){var i,a=b(o,2),c=a[0],s=a[1];return u(s)?(i=n).push.apply(i,h(e(t[c],"".concat((r+c).match(/[a-zA-Z0-9]+/g).join("-"),"-")))):(c=d(c).replace(/^-/,""),n.push(["--".concat(r).concat(c),s])),n}),[])};return e(t)};function g(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}function O(t){for(var e=1;e<arguments.length;e++){var r=null!=arguments[e]?arguments[e]:{},n=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(n=n.concat(Object.getOwnPropertySymbols(r).filter((function(t){return Object.getOwnPropertyDescriptor(r,t).enumerable})))),n.forEach((function(e){g(t,e,r[e])}))}return t}function w(t,e){return e=null!=e?e:{},Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(e)):function(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);r.push.apply(r,n)}return r}(Object(e)).forEach((function(r){Object.defineProperty(t,r,Object.getOwnPropertyDescriptor(e,r))})),t}var j=function(){function t(e){var r=this;!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t);var n=e.frameUrl,o=e.parentOrigin,i=e.font,a=e.options.type,c=function(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r,n,o={},i=Object.keys(t);for(n=0;n<i.length;n++)r=i[n],e.indexOf(r)>=0||(o[r]=t[r]);return o}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n<i.length;n++)r=i[n],e.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(e.options,["type"]);this.frameUrl=n,this.parentOrigin=o,this.type=a,this.options=c;var s=v(c.styles);this.frame=document.createElement("iframe"),this.frame.id=a,this.frame.src="".concat(n,"/input.html?parentOrigin=").concat(o,"&type=").concat(this.type),this.frame.style.display="block",this.frame.style.height="100%",this.frame.style.border="none",this.frame.style.width="100%",i&&(this.frame.src+="&font=".concat(i)),this.frame.onload=function(){r._postMessage({type:"update",data:w(O({},r.options),{styles:s})}),p("Added field",r.options)}}var e=t.prototype;return e._postMessage=function(t){this.frame.contentWindow.postMessage(O({channel:i},t),this.frameUrl)},e.update=function(t){this.options=O({},this.options,t);var e=v(O({},this.options.styles,t.styles)),r=w(O({},this.options,t),{styles:e});this._postMessage({type:"update",data:w(O({},r),{styles:e})}),p("Updated field",this.options)},e.setPlaceholder=function(t){this.update({placeholder:t})},e.setStyles=function(t){this.update({styles:t})},t}();function S(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}function A(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}function E(t){for(var e=1;e<arguments.length;e++){var r=null!=arguments[e]?arguments[e]:{},n=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(n=n.concat(Object.getOwnPropertySymbols(r).filter((function(t){return Object.getOwnPropertyDescriptor(r,t).enumerable})))),n.forEach((function(e){A(t,e,r[e])}))}return t}function P(t,e){return e=null!=e?e:{},Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(e)):function(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);r.push.apply(r,n)}return r}(Object(e)).forEach((function(r){Object.defineProperty(t,r,Object.getOwnPropertyDescriptor(e,r))})),t}var U=function(){function t(e){var n=this;!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.config=E({environment:"production"},e);var o="sandbox"===this.config.environment?"sandbox.":"";this.frameUrl="https://secure-fields.".concat(o).concat(this.config.gr4vyId,".gr4vy.app"),this.parentOrigin=window.location.origin,this.controller=document.createElement("iframe"),this.controller.src="".concat(this.frameUrl,"/controller.html?parentOrigin=").concat(this.parentOrigin,"&sessionId=").concat(this.config.sessionId,"&gr4vyId=").concat(this.config.gr4vyId,"&environment=").concat(this.config.environment),this.controller.style.position="absolute",this.controller.style.left="-9999999px",document.body.appendChild(this.controller),window.onload=function(){s.publish(r.READY,E({version:t.version},n.config))},p("Initialized",P(E({},this.config),{frameUrl:this.frameUrl,parentOrigin:this.parentOrigin}))}var e,a,c=t.prototype;return c._addField=function(t,e){var r=this;if(!(t="string"==typeof t?document.querySelector(t):t))return e;var o=document.createElement("div");return o.classList.add("secure-fields__input","secure-fields__input--".concat(d(e.type))),o.appendChild(e.frame),t.parentNode.replaceChild(o,t),window.addEventListener("message",(function(t){var i;if(t.origin===r.frameUrl&&(null===(i=t.data.data)||void 0===i?void 0:i.id)===e.type)switch(t.data.type){case"blur":s.publish("".concat(e.type,":blur"),t.data.data),o.removeAttribute(n.FOCUSED),p("Field blurred",t.data.data);break;case"focus":s.publish("".concat(e.type,":focus"),t.data.data),o.setAttribute(n.FOCUSED,""),p("Field focused",t.data.data);break;case"input":s.publish("".concat(e.type,":input"),t.data.data),t.data.data.valid?o.removeAttribute(n.INVALID):o.setAttribute(n.INVALID,""),p("Field input changed",t.data.data)}})),e.addEventListener=this.addEventListener,e.removeEventListener=this.removeEventListener,e},c.addCardNumberField=function(t,e){return this.cardNumber||(this.cardNumber=new j({frameUrl:this.frameUrl,parentOrigin:this.parentOrigin,font:this.font,options:P(E({label:"Card number"},e),{type:"number"})})),this._addField(t,this.cardNumber)},c.addSecurityCodeField=function(t,e){return this.securityCode||(this.securityCode=new j({frameUrl:this.frameUrl,parentOrigin:this.parentOrigin,font:this.font,options:P(E({label:"Security code"},e),{type:"securityCode"})})),this._addField(t,this.securityCode)},c.addExpiryDateField=function(t,e){return this.expiryDate||(this.expiryDate=new j({frameUrl:this.frameUrl,parentOrigin:this.parentOrigin,font:this.font,options:P(E({label:"Expiry date"},e),{type:"expiryDate"})})),this._addField(t,this.expiryDate)},c.addEventListener=function(t,e){var r=this.type,n="".concat(this.constructor===j?"".concat(r,":"):"").concat(t);s.subscribe(n,e)},c.removeEventListener=function(t,e){var r=this.type,n="".concat(this.constructor===j?"".concat(r,":"):"").concat(t);s.unsubscribe(n,e)},c.submit=function(){var t=this,e=function(n){if(n.origin===t.frameUrl&&n.data.channel===i){switch(n.data.type){case"success":s.publish(r.CARD_VAULT_SUCCESS),p("Payment method tokenized successfully");break;case"error":s.publish(r.CARD_VAULT_FAILURE),p("Failed to update checkout session")}window.removeEventListener("message",e)}};window.addEventListener("message",e),this.controller.contentWindow.postMessage({type:"submit",channel:i},this.frameUrl)},c.setDebug=function(t){localStorage.setItem(o,String(t))},c.addFont=function(t){this.font=t.replace(/\s/g,"+")},e=t,a=[{key:"Events",get:function(){return r}},{key:"version",get:function(){}}],null&&S(e.prototype,null),a&&S(e,a),t}();return e})()));
1
+ !function(t,e){if("object"==typeof exports&&"object"==typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var r=e();for(var n in r)("object"==typeof exports?exports:t)[n]=r[n]}}(this,(()=>(()=>{"use strict";var t={d:(e,r)=>{for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{SecureFields:()=>U,SecureInput:()=>j});var r,n,o="@gr4vy-secure-fields-debug";!function(t){t.CARD_VAULT_SUCCESS="card-vault-success",t.CARD_VAULT_FAILURE="card-vault-failure",t.READY="ready"}(r||(r={})),function(t){t.FOCUSED="data-secure-fields-focused",t.INVALID="data-secure-fields-invalid"}(n||(n={}));var i="secure-fields";function a(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r<e;r++)n[r]=t[r];return n}function c(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i=[],a=!0,c=!1;try{for(r=r.call(t);!(a=(n=r.next()).done)&&(i.push(n.value),!e||i.length!==e);a=!0);}catch(t){c=!0,o=t}finally{try{a||null==r.return||r.return()}finally{if(c)throw o}}return i}}(t,e)||function(t,e){if(t){if("string"==typeof t)return a(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(r):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?a(t,e):void 0}}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}var s=new(function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.subscribers=[]}var e=t.prototype;return e.subscribe=function(t,e){this.subscribers.push([t,e])},e.unsubscribe=function(t,e){this.subscribers=this.subscribers.filter((function(r){var n=c(r,2),o=n[0],i=n[1];return o!==t||i.toString()!==e.toString()}))},e.publish=function(t,e){this.subscribers.forEach((function(r){var n=c(r,2),o=n[0],i=n[1];return setTimeout((function(){return o===t?i(e):null}),0)}))},t}()),u=function(t){return"[object Object]"===Object.prototype.toString.call(t)};function l(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}var f={debug:!1,level:"log"},d=function(t,e,r){var n=function(t){for(var e=1;e<arguments.length;e++){var r=null!=arguments[e]?arguments[e]:{},n=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(n=n.concat(Object.getOwnPropertySymbols(r).filter((function(t){return Object.getOwnPropertyDescriptor(r,t).enumerable})))),n.forEach((function(e){l(t,e,r[e])}))}return t}({},f,r),i=n.debug,a=n.level;(i||"true"===localStorage.getItem(o))&&console[a]("Gr4vy - Secure Fields - ".concat(t),e||{})},p=function(t){return t!=t.toLowerCase()&&(t=t.replace(/[A-Z]/g,(function(t){return"-"+t.toLowerCase()}))),t};function y(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r<e;r++)n[r]=t[r];return n}function b(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i=[],a=!0,c=!1;try{for(r=r.call(t);!(a=(n=r.next()).done)&&(i.push(n.value),!e||i.length!==e);a=!0);}catch(t){c=!0,o=t}finally{try{a||null==r.return||r.return()}finally{if(c)throw o}}return i}}(t,e)||m(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function h(t){return function(t){if(Array.isArray(t))return y(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||m(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function m(t,e){if(t){if("string"==typeof t)return y(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(r):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?y(t,e):void 0}}var v=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return Object.entries(t).reduce((function(n,o){var i,a=b(o,2),c=a[0],s=a[1];return u(s)?(i=n).push.apply(i,h(e(t[c],"".concat((r+c).match(/[a-zA-Z0-9]+/g).join("-"),"-")))):(c=p(c).replace(/^-/,""),n.push(["--".concat(r).concat(c),s])),n}),[])};return e(t)};function g(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}function O(t){for(var e=1;e<arguments.length;e++){var r=null!=arguments[e]?arguments[e]:{},n=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(n=n.concat(Object.getOwnPropertySymbols(r).filter((function(t){return Object.getOwnPropertyDescriptor(r,t).enumerable})))),n.forEach((function(e){g(t,e,r[e])}))}return t}function w(t,e){return e=null!=e?e:{},Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(e)):function(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);r.push.apply(r,n)}return r}(Object(e)).forEach((function(r){Object.defineProperty(t,r,Object.getOwnPropertyDescriptor(e,r))})),t}var j=function(){function t(e){var r=this;!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t);var n=e.frameUrl,o=e.parentOrigin,i=e.font,a=e.options.type,c=function(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r,n,o={},i=Object.keys(t);for(n=0;n<i.length;n++)r=i[n],e.indexOf(r)>=0||(o[r]=t[r]);return o}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n<i.length;n++)r=i[n],e.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(e.options,["type"]);this.frameUrl=n,this.parentOrigin=o,this.type=a,this.options=c;var s=v(c.styles);this.frame=document.createElement("iframe"),this.frame.id=a,this.frame.src="".concat(n,"/input.html?parentOrigin=").concat(o,"&type=").concat(this.type),this.frame.style.display="block",this.frame.style.height="100%",this.frame.style.border="none",this.frame.style.width="100%",i&&(this.frame.src+="&font=".concat(i)),this.frame.onload=function(){r._postMessage({type:"update",data:w(O({},r.options),{styles:s})}),d("Added field",r.options)}}var e=t.prototype;return e._postMessage=function(t){this.frame.contentWindow.postMessage(O({channel:i},t),this.frameUrl)},e.update=function(t){this.options=O({},this.options,t);var e=v(O({},this.options.styles,t.styles)),r=w(O({},this.options,t),{styles:e});this._postMessage({type:"update",data:w(O({},r),{styles:e})}),d("Updated field",this.options)},e.setPlaceholder=function(t){this.update({placeholder:t})},e.setStyles=function(t){this.update({styles:t})},t}();function S(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}function E(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}function A(t){for(var e=1;e<arguments.length;e++){var r=null!=arguments[e]?arguments[e]:{},n=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(n=n.concat(Object.getOwnPropertySymbols(r).filter((function(t){return Object.getOwnPropertyDescriptor(r,t).enumerable})))),n.forEach((function(e){E(t,e,r[e])}))}return t}function P(t,e){return e=null!=e?e:{},Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(e)):function(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);r.push.apply(r,n)}return r}(Object(e)).forEach((function(r){Object.defineProperty(t,r,Object.getOwnPropertyDescriptor(e,r))})),t}var U=function(){function t(e){var n=this;!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.config=A({environment:"production"},e);var o="sandbox"===this.config.environment?"sandbox.":"";this.frameUrl=window.SECURE_FIELDS_FRAME_URL||"https://secure-fields.".concat(o).concat(this.config.gr4vyId,".gr4vy.app"),this.parentOrigin=window.location.origin,this.controller=document.createElement("iframe"),this.controller.id="controller",this.controller.src="".concat(this.frameUrl,"/controller.html?parentOrigin=").concat(this.parentOrigin,"&sessionId=").concat(this.config.sessionId,"&gr4vyId=").concat(this.config.gr4vyId,"&environment=").concat(this.config.environment),this.controller.style.position="absolute",this.controller.style.left="-9999999px",document.body.appendChild(this.controller),window.onload=function(){s.publish(r.READY,A({version:t.version},n.config))},d("Initialized",P(A({},this.config),{frameUrl:this.frameUrl,parentOrigin:this.parentOrigin}))}var e,a,c=t.prototype;return c._addField=function(t,e){var r=this;if(!(t="string"==typeof t?document.querySelector(t):t))return e;var o=document.createElement("div");return o.classList.add("secure-fields__input","secure-fields__input--".concat(p(e.type))),o.appendChild(e.frame),t.parentNode.replaceChild(o,t),window.addEventListener("message",(function(t){var i;if(t.origin===r.frameUrl&&(null===(i=t.data.data)||void 0===i?void 0:i.id)===e.type)switch(t.data.type){case"blur":s.publish("".concat(e.type,":blur"),t.data.data),o.removeAttribute(n.FOCUSED),d("Field blurred",t.data.data);break;case"focus":s.publish("".concat(e.type,":focus"),t.data.data),o.setAttribute(n.FOCUSED,""),d("Field focused",t.data.data);break;case"input":s.publish("".concat(e.type,":input"),t.data.data),t.data.data.valid?o.removeAttribute(n.INVALID):o.setAttribute(n.INVALID,""),d("Field input changed",t.data.data)}})),e.addEventListener=this.addEventListener,e.removeEventListener=this.removeEventListener,e},c.addCardNumberField=function(t,e){return this.cardNumber||(this.cardNumber=new j({frameUrl:this.frameUrl,parentOrigin:this.parentOrigin,font:this.font,options:P(A({label:"Card number"},e),{type:"number"})})),this._addField(t,this.cardNumber)},c.addSecurityCodeField=function(t,e){return this.securityCode||(this.securityCode=new j({frameUrl:this.frameUrl,parentOrigin:this.parentOrigin,font:this.font,options:P(A({label:"Security code"},e),{type:"securityCode"})})),this._addField(t,this.securityCode)},c.addExpiryDateField=function(t,e){return this.expiryDate||(this.expiryDate=new j({frameUrl:this.frameUrl,parentOrigin:this.parentOrigin,font:this.font,options:P(A({label:"Expiry date"},e),{type:"expiryDate"})})),this._addField(t,this.expiryDate)},c.addEventListener=function(t,e){var r=this.type,n="".concat(this.constructor===j?"".concat(r,":"):"").concat(t);s.subscribe(n,e)},c.removeEventListener=function(t,e){var r=this.type,n="".concat(this.constructor===j?"".concat(r,":"):"").concat(t);s.unsubscribe(n,e)},c.submit=function(){var t=this,e=function(n){if(n.origin===t.frameUrl&&n.data.channel===i){switch(n.data.type){case"success":s.publish(r.CARD_VAULT_SUCCESS),d("Payment method tokenized successfully");break;case"error":s.publish(r.CARD_VAULT_FAILURE),d("Failed to update checkout session")}window.removeEventListener("message",e)}};window.addEventListener("message",e),this.controller.contentWindow.postMessage({type:"submit",channel:i},this.frameUrl)},c.setDebug=function(t){localStorage.setItem(o,String(t))},c.addFont=function(t){this.font=t.replace(/\s/g,"+")},e=t,a=[{key:"Events",get:function(){return r}},{key:"version",get:function(){}}],null&&S(e.prototype,null),a&&S(e,a),t}();return e})()));
package/lib/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type * as CSS from 'csstype';
2
1
  import { Events } from './constants';
2
+ import type * as CSS from 'csstype';
3
3
  export declare type Config = {
4
4
  environment?: 'sandbox' | 'production';
5
5
  gr4vyId: string;
@@ -1,2 +1,2 @@
1
- import type { Styles, StylesTuple } from '../types';
1
+ import type { Styles, StylesTuple } from "../types";
2
2
  export declare const stylesTransformer: (styles?: Styles) => StylesTuple;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gr4vy/secure-fields",
3
- "version": "0.8.0",
3
+ "version": "1.1.0",
4
4
  "description": "Gr4vy-hosted secure fields offering advanced theming, PCI compliance, event handling, and more.",
5
5
  "main": "lib/index",
6
6
  "types": "lib/index",
@@ -29,7 +29,8 @@
29
29
  }
30
30
  },
31
31
  "scripts": {
32
- "build": "tsc && webpack --config webpack.prod.js",
32
+ "prepare": "ts-patch install -s",
33
+ "build": "tsc -p tsconfig.prod.json && webpack --config webpack.prod.js",
33
34
  "clean": "rm -rf lib *.tgz",
34
35
  "dev": "TOKEN=$(yarn --silent token) webpack serve --config webpack.dev.js",
35
36
  "docs": "typedoc --plugin typedoc-plugin-missing-exports",
@@ -40,16 +41,17 @@
40
41
  "token": "node generate-token"
41
42
  },
42
43
  "devDependencies": {
43
- "@gr4vy/node": "^0.27.0",
44
- "css-loader": "^6.7.1",
45
- "csstype": "^3.1.0",
46
- "dotenv": "^16.0.1",
44
+ "css-loader": "^6.7.2",
45
+ "csstype": "^3.1.1",
46
+ "dotenv": "^16.0.3",
47
47
  "style-loader": "^3.3.1",
48
- "typedoc": "^0.23.5",
49
- "typedoc-plugin-missing-exports": "^0.23.0"
48
+ "ts-patch": "^2.1.0",
49
+ "typedoc": "^0.23.24",
50
+ "typedoc-plugin-missing-exports": "^0.23.0",
51
+ "typescript-transform-paths": "^3.4.4"
50
52
  },
51
53
  "publishConfig": {
52
54
  "access": "public"
53
55
  },
54
- "gitHead": "b5f4f0de1387377b37761fddc0ab7d4ee82a8de6"
56
+ "gitHead": "2bddb6226846b2fced4419ff156e99d4235dd9e3"
55
57
  }