@payyo/ptp-js 0.2.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/README.doc.md +82 -0
- package/README.md +6 -0
- package/dist/form/v1.js +2 -0
- package/dist/form/v1.js.map +1 -0
- package/package.json +42 -0
- package/src/form/index.js +241 -0
- package/webpack.config.js +111 -0
package/README.doc.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# PTP Form/Field
|
|
2
|
+
|
|
3
|
+
This repository is part of the PTP project. The goal of this JS library to provide a secure way for non-PCI compliant
|
|
4
|
+
applications to collect CHD (card holder data), tokenize it and use the tokens to make a payment via regular Payyo API
|
|
5
|
+
|
|
6
|
+
This library consists of two parts:
|
|
7
|
+
|
|
8
|
+
1. Form - manages a form of secure fields
|
|
9
|
+
2. and Field - secure field itself
|
|
10
|
+
|
|
11
|
+
Each field which is may contain CHD is an HTML IFrame and JS code hosted on PCI-compliant environment. Field and Form
|
|
12
|
+
components are communicates with each other by message passing. The Field component avoids exposing CHD from IFrame to
|
|
13
|
+
the Form component which is supposed to be running on client (non-PCI -compliant environment).
|
|
14
|
+
|
|
15
|
+
## Development
|
|
16
|
+
|
|
17
|
+
### Requirements
|
|
18
|
+
|
|
19
|
+
Before you start please make sure that you have installed and configured the following tools and utils:
|
|
20
|
+
|
|
21
|
+
1. [Docker](https://docs.docker.com/engine/install/)
|
|
22
|
+
|
|
23
|
+
### Install project dependencies
|
|
24
|
+
|
|
25
|
+
Project requirements are managed by [Yarn](https://yarnpkg.com/) which is running inside Docker container.
|
|
26
|
+
You can install all the dependencies with the command:
|
|
27
|
+
|
|
28
|
+
```shell
|
|
29
|
+
make inastall
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
To run arbitrary Yarn command you can use Makefile target like this:
|
|
33
|
+
|
|
34
|
+
```shell
|
|
35
|
+
make yarn <command> [arguments]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Dev Server
|
|
39
|
+
|
|
40
|
+
Webpack dev server is used for development. To run it execute the following command
|
|
41
|
+
|
|
42
|
+
```shell
|
|
43
|
+
make serve
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The dev server serves the test page with example form. The page used PTP project as backend to tokenize the card holder
|
|
47
|
+
data (CHD). To have a fully functional application please make sure:
|
|
48
|
+
|
|
49
|
+
1. you need to have PTP project up and running locally
|
|
50
|
+
2. or adjust the project settings to use PTP SANDBOX/PROD environment as backend by adjusting `PTP_BASE_URL` variable in `package.json`
|
|
51
|
+
|
|
52
|
+
## CI/CD
|
|
53
|
+
|
|
54
|
+
CI server is Bitbucket Pipeline. See CI configuration in `bitbucket-pipelines.yml`.
|
|
55
|
+
|
|
56
|
+
## Deployment
|
|
57
|
+
|
|
58
|
+
### Field component
|
|
59
|
+
|
|
60
|
+
Each push to the repository produces a build which can be deployed to the specific environment via Bitbucket Pipeline.
|
|
61
|
+
Each Git tag triggers automated build and deployment DEV and PROD packages.
|
|
62
|
+
|
|
63
|
+
### Form component
|
|
64
|
+
|
|
65
|
+
Form component is distributed via [NPM](https://www.npmjs.com/) to let the library easily pulled and used by Payyo team
|
|
66
|
+
as well as other integrators.
|
|
67
|
+
|
|
68
|
+
## NPM package publishing
|
|
69
|
+
|
|
70
|
+
To make the library available to the public and used in other Payyo projects and other user you need to publish it to NPM.
|
|
71
|
+
We have the NPM account registered to `developers@payyo.ch` email and organization `payyo` defined there. To publish the
|
|
72
|
+
package to NPM you need to execute the following commands:
|
|
73
|
+
|
|
74
|
+
1.
|
|
75
|
+
2. npm publish --access public
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## TODO:
|
|
79
|
+
1. create appropriate account on NPM
|
|
80
|
+
2. save account credentials in LastPass
|
|
81
|
+
3. describe publishing process here
|
|
82
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# PTP Form
|
|
2
|
+
|
|
3
|
+
The goal of this JS library to provide a secure way for non-PCI compliant applications to collect CHD (card holder data),
|
|
4
|
+
tokenize it and use the tokens to make a payment via regular [Payyo](httsp://payyo.ch) API
|
|
5
|
+
|
|
6
|
+
This library manages a form fields which may contain CHD is an HTML IFrame and JS code hosted on PCI-compliant environment.
|
package/dist/form/v1.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(()=>{var e={415:e=>{e.exports=function(e){const t={},r={},n=[],o={},s=i();let a=null;function i(){return Math.random().toString(36).substr(2,9)}function l(){if(e.debug&&window.console){const e=Array.prototype.slice.call(arguments);e.unshift("[PTP FORM]"),console.log.apply(console,e)}}function d(t){const r=t.container,o=t.iFrameName,a={id:o,name:o,class:o,src:e.iframeUri+"?"+new URLSearchParams({form_id:s,field_name:t.fieldName,field_names:n,placeholder:t.placeholder,input_type:t.inputType,debug:e.debug?"1":"0"}).toString(),frameborder:"0",scrolling:"no",style:"width: 100%; height: 100%"},i=document.createElement("iframe");for(Object.keys(a).forEach((function(e){i.setAttribute(e,a[e])}));r.hasChildNodes();)r.removeChild(r.lastChild);r.appendChild(i),t.iframe=i[0]}function c(e,t={}){const n={params:t,type:e,formId:s,messageId:null};return new Promise((t=>{const s=e+":"+i();o[s]=t,n.messageId=s,l("send message",n),window.frames[r[a].iFrameName].postMessage(n,"*")}))}t.init=function(e){for(let t in e){if(!e.hasOwnProperty(t))return;let o=e[t],a=o.container;"string"==typeof a&&(a=document.getElementById(a)),r[t]={fieldName:t,iFrameName:"ptp-field-iframe-"+s+"-"+t,container:a,placeholder:o.placeholder||"",inputType:o.inputType||"text",isReady:!1},n.push(t)}a=n[0];for(let e=0;e<n.length;e++)d(r[n[e]])};const f={};function m(e){const n=e.data;if(s!==n.formId)return;l(`message received of type "${n.type}"`,n);const a={fieldReady:e=>{if(void 0===r[e.fieldName])return;r[e.fieldName].isReady=!0;Object.keys(r).map((e=>r[e].isReady)).reduce(((e,t)=>e&&t),!0)&&u("ready")},fieldChanged:function(e){u("change",e)}},i=n.messageId,d=o[i];if(void 0!==d)delete o[i],d.apply(t,[n.response]);else{const e=a[n.type];e&&e.apply(t,[n.response])}}function u(){const e=Array.prototype.slice.call(arguments),t=e.shift(),r=f[t];r?(l(`emit event "${t}"`,e),r.apply(null,e)):l(`no handler for event "${t}"`,e)}function p(){for(let[,e]of Object.entries(r)){const t=window.frames[e.iFrameName];t.postMessage({type:"checkFocus"},"*")}}return t.onChange=function(e){f.change=e},t.onReady=function(e){f.ready=e},t.validate=function(){return c("validateRequest")},t.submit=function(e){return c("submitRequest",{params:e||{},browserDetails:{browserUserAgent:navigator.userAgent,browserJavaEnabled:navigator.javaEnabled(),browserLanguage:navigator.language,browserColorDepth:screen.colorDepth,browserScreenHeight:screen.height,browserScreenWidth:screen.width,browserTZ:(new Date).getTimezoneOffset()}}).then((e=>e.success?Promise.resolve(e.result):Promise.reject({error:e.error,error_details:e.error_details})))},t.focus=function(e){const t=r[e]||null;t&&window.frames[t.iFrameName].focus()},t.setStyle=function(e,t,n){r[e]&&window.frames[r[e].iFrameName].postMessage({type:"setStyle",params:{selector:t,rules:n}},"*")},window.addEventListener("message",m,!1),t.destroy=function(){window.removeEventListener("message",m,!1),window.addEventListener("touchend",p,!0);for(let e in r){let t=document.getElementById(r[e].iFrameName);t&&t.parentNode.removeChild(t)}},t}}},t={};(function r(n){var o=t[n];if(void 0!==o)return o.exports;var s=t[n]={exports:{}};return e[n](s,s.exports,r),s.exports})(415)})();
|
|
2
|
+
//# sourceMappingURL=v1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v1.js","mappings":"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","sources":["webpack://@payyoag/ptp-js/./src/form/index.js","webpack://@payyoag/ptp-js/webpack/bootstrap","webpack://@payyoag/ptp-js/webpack/startup"],"sourcesContent":["module.exports = function (config) {\n const form = {},\n fields = {},\n fieldNames = [],\n callbacks = {},\n formId = generateRandomId();\n\n const EVENT_ON_CHANGE = 'change';\n const EVENT_ON_READY = 'ready';\n\n let mainField = null;\n\n function generateRandomId() {\n return Math.random().toString(36).substr(2, 9);\n }\n\n function log() {\n if (config.debug && window.console) {\n const args = Array.prototype.slice.call(arguments);\n args.unshift('[PTP FORM]');\n console.log.apply(console, args);\n }\n }\n\n form.init = function (fieldsMap) {\n for (let fieldName in fieldsMap) {\n if (!fieldsMap.hasOwnProperty(fieldName)) {\n return;\n }\n\n let field = fieldsMap[fieldName];\n let fieldContainer = field.container;\n\n if (typeof fieldContainer === 'string') {\n fieldContainer = document.getElementById(fieldContainer);\n }\n\n fields[fieldName] = {\n fieldName: fieldName,\n iFrameName: 'ptp-field-iframe-' + formId + '-' + fieldName,\n container: fieldContainer,\n placeholder: field.placeholder || '',\n inputType: field.inputType || 'text',\n isReady: false\n };\n\n fieldNames.push(fieldName);\n }\n\n mainField = fieldNames[0];\n\n for (let i = 0; i < fieldNames.length; i++) {\n createIframe(fields[fieldNames[i]]);\n }\n };\n\n function createIframe(field) {\n const placeholder = field.container,\n iFrameName = field.iFrameName,\n attributes = {\n id: iFrameName,\n name: iFrameName,\n class: iFrameName,\n src: config.iframeUri + '?' + (new URLSearchParams({\n form_id: formId,\n field_name: field.fieldName,\n field_names: fieldNames,\n placeholder: field.placeholder,\n input_type: field.inputType,\n debug: config.debug ? '1' : '0',\n })).toString(),\n frameborder: \"0\",\n scrolling: \"no\",\n style: 'width: 100%; height: 100%',\n },\n iframe = document.createElement('iframe');\n\n Object.keys(attributes).forEach(function (key) {\n iframe.setAttribute(key, attributes[key]);\n });\n\n while (placeholder.hasChildNodes()) {\n placeholder.removeChild(placeholder.lastChild);\n }\n placeholder.appendChild(iframe);\n\n field.iframe = iframe[0];\n }\n\n function postMessage(type, params = {}) {\n const message = {params: params, type: type, formId: formId, messageId: null};\n\n return new Promise((resolve) => {\n const messageId = type + \":\" + generateRandomId();\n callbacks[messageId] = resolve;\n message.messageId = messageId;\n\n log('send message', message);\n window.frames[fields[mainField].iFrameName].postMessage(message, '*');\n });\n }\n\n const handlers = {};\n form.onChange = function (handler) {\n handlers[EVENT_ON_CHANGE] = handler;\n }\n\n form.onReady = function (handler) {\n handlers[EVENT_ON_READY] = handler;\n }\n\n form.validate = function () {\n return postMessage('validateRequest');\n }\n\n form.submit = function (params) {\n return postMessage('submitRequest', {\n params: params || {},\n browserDetails: {\n browserUserAgent: navigator.userAgent,\n browserJavaEnabled: navigator.javaEnabled(),\n browserLanguage: navigator.language,\n browserColorDepth: screen.colorDepth,\n browserScreenHeight: screen.height,\n browserScreenWidth: screen.width,\n browserTZ: (new Date()).getTimezoneOffset()\n }\n }).then((res) => {\n if (res.success) {\n return Promise.resolve(res.result);\n }\n\n return Promise.reject({\n error: res.error,\n error_details: res.error_details,\n });\n });\n }\n\n form.focus = function (field) {\n const f = fields[field] || null;\n if (f) {\n window.frames[f.iFrameName].focus();\n }\n };\n\n form.setStyle = function (fieldName, selector, cssRules) {\n if (!fields[fieldName]) {\n return;\n }\n\n window.frames[fields[fieldName].iFrameName].postMessage({\n type: 'setStyle',\n params: {\n selector: selector,\n rules: cssRules,\n }\n }, '*');\n };\n\n function receiveMessage(message) {\n const event = message.data;\n if (formId !== event.formId) {\n return;\n }\n\n log(`message received of type \"${event.type}\"`, event);\n\n const handlers = {\n fieldReady: (r) => {\n if (fields[r.fieldName] === undefined) {\n return;\n }\n fields[r.fieldName].isReady = true;\n const ready = Object.keys(fields)\n .map((key) => fields[key].isReady)\n .reduce((a, b) => a && b, true);\n\n if (ready) {\n emitEvent('ready');\n }\n },\n fieldChanged: function (response) {\n emitEvent('change', response);\n },\n };\n\n const messageId = event.messageId,\n callback = callbacks[messageId];\n\n if (callback !== undefined) {\n delete callbacks[messageId];\n callback.apply(form, [event.response]);\n } else {\n const handler = handlers[event.type];\n if (handler) {\n handler.apply(form, [event.response]);\n }\n }\n }\n\n function emitEvent() {\n const args = Array.prototype.slice.call(arguments);\n const eventName = args.shift();\n const handler = handlers[eventName];\n if (handler) {\n log(`emit event \"${eventName}\"`, args);\n handler.apply(null, args);\n } else {\n log(`no handler for event \"${eventName}\"`, args);\n }\n }\n\n window.addEventListener('message', receiveMessage, false);\n\n function addOnTouchedEventListener() {\n window.addEventListener('touchend', checkFocus, true);\n }\n\n function checkFocus() {\n for (let [, field] of Object.entries(fields)) {\n const iframe = window.frames[field.iFrameName];\n if (typeof iframe) {\n iframe.postMessage({'type': 'checkFocus'}, '*');\n }\n }\n }\n\n form.destroy = function () {\n window.removeEventListener('message', receiveMessage, false);\n window.addEventListener('touchend', checkFocus, true);\n for (let fieldName in fields) {\n let iframe = document.getElementById(fields[fieldName].iFrameName);\n if (iframe) {\n iframe.parentNode.removeChild(iframe);\n }\n }\n }\n\n return form;\n};\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(415);\n"],"names":["module","exports","config","form","fields","fieldNames","callbacks","formId","generateRandomId","mainField","Math","random","toString","substr","log","debug","window","console","args","Array","prototype","slice","call","arguments","unshift","apply","createIframe","field","placeholder","container","iFrameName","attributes","id","name","class","src","iframeUri","URLSearchParams","form_id","field_name","fieldName","field_names","input_type","inputType","frameborder","scrolling","style","iframe","document","createElement","Object","keys","forEach","key","setAttribute","hasChildNodes","removeChild","lastChild","appendChild","postMessage","type","params","message","messageId","Promise","resolve","frames","init","fieldsMap","hasOwnProperty","fieldContainer","getElementById","isReady","push","i","length","handlers","receiveMessage","event","data","fieldReady","r","undefined","map","reduce","a","b","emitEvent","fieldChanged","response","callback","handler","eventName","shift","checkFocus","entries","onChange","onReady","validate","submit","browserDetails","browserUserAgent","navigator","userAgent","browserJavaEnabled","javaEnabled","browserLanguage","language","browserColorDepth","screen","colorDepth","browserScreenHeight","height","browserScreenWidth","width","browserTZ","Date","getTimezoneOffset","then","res","success","result","reject","error","error_details","focus","f","setStyle","selector","cssRules","rules","addEventListener","destroy","removeEventListener","parentNode","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__"],"sourceRoot":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@payyo/ptp-js",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "JS library for Payyo Tokenization service (PTP)",
|
|
5
|
+
"private": false,
|
|
6
|
+
"author": "developers@payyo.ch",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "webpack.config.js",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+ssh://git@bitbucket.org/payyoag/ptp-js.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://bitbucket.org/payyoag/ptp-js/issues"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://bitbucket.org/payyoag/ptp-js#readme",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@babel/core": "^7.16.0",
|
|
19
|
+
"babel-loader": "^8.2.3",
|
|
20
|
+
"cleave.js": "^1.6.0",
|
|
21
|
+
"html-minimizer-webpack-plugin": "^3.3.1",
|
|
22
|
+
"html-webpack-plugin": "^5.5.0",
|
|
23
|
+
"react-dev-utils": "^11.0.4",
|
|
24
|
+
"terser-webpack-plugin": "^5.2.5",
|
|
25
|
+
"webpack": "^5.63.0",
|
|
26
|
+
"webpack-bundle-analyzer": "^4.5.0",
|
|
27
|
+
"webpack-cli": "^4.9.1",
|
|
28
|
+
"webpack-dev-server": "^4.4.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"serve": "PTP_BASE_URL=https://ptp.dev.payyo.ch:4430 webpack-dev-server --mode development --static www --devtool source-map --output-pathinfo --env target=all",
|
|
32
|
+
"build": "webpack --mode production --env target=all",
|
|
33
|
+
"build-field": "webpack --mode production --env target=field"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"src/form",
|
|
37
|
+
"dist/form",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENCE",
|
|
40
|
+
"package.json"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
module.exports = function (config) {
|
|
2
|
+
const form = {},
|
|
3
|
+
fields = {},
|
|
4
|
+
fieldNames = [],
|
|
5
|
+
callbacks = {},
|
|
6
|
+
formId = generateRandomId();
|
|
7
|
+
|
|
8
|
+
const EVENT_ON_CHANGE = 'change';
|
|
9
|
+
const EVENT_ON_READY = 'ready';
|
|
10
|
+
|
|
11
|
+
let mainField = null;
|
|
12
|
+
|
|
13
|
+
function generateRandomId() {
|
|
14
|
+
return Math.random().toString(36).substr(2, 9);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function log() {
|
|
18
|
+
if (config.debug && window.console) {
|
|
19
|
+
const args = Array.prototype.slice.call(arguments);
|
|
20
|
+
args.unshift('[PTP FORM]');
|
|
21
|
+
console.log.apply(console, args);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
form.init = function (fieldsMap) {
|
|
26
|
+
for (let fieldName in fieldsMap) {
|
|
27
|
+
if (!fieldsMap.hasOwnProperty(fieldName)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let field = fieldsMap[fieldName];
|
|
32
|
+
let fieldContainer = field.container;
|
|
33
|
+
|
|
34
|
+
if (typeof fieldContainer === 'string') {
|
|
35
|
+
fieldContainer = document.getElementById(fieldContainer);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fields[fieldName] = {
|
|
39
|
+
fieldName: fieldName,
|
|
40
|
+
iFrameName: 'ptp-field-iframe-' + formId + '-' + fieldName,
|
|
41
|
+
container: fieldContainer,
|
|
42
|
+
placeholder: field.placeholder || '',
|
|
43
|
+
inputType: field.inputType || 'text',
|
|
44
|
+
isReady: false
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
fieldNames.push(fieldName);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
mainField = fieldNames[0];
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < fieldNames.length; i++) {
|
|
53
|
+
createIframe(fields[fieldNames[i]]);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function createIframe(field) {
|
|
58
|
+
const placeholder = field.container,
|
|
59
|
+
iFrameName = field.iFrameName,
|
|
60
|
+
attributes = {
|
|
61
|
+
id: iFrameName,
|
|
62
|
+
name: iFrameName,
|
|
63
|
+
class: iFrameName,
|
|
64
|
+
src: config.iframeUri + '?' + (new URLSearchParams({
|
|
65
|
+
form_id: formId,
|
|
66
|
+
field_name: field.fieldName,
|
|
67
|
+
field_names: fieldNames,
|
|
68
|
+
placeholder: field.placeholder,
|
|
69
|
+
input_type: field.inputType,
|
|
70
|
+
debug: config.debug ? '1' : '0',
|
|
71
|
+
})).toString(),
|
|
72
|
+
frameborder: "0",
|
|
73
|
+
scrolling: "no",
|
|
74
|
+
style: 'width: 100%; height: 100%',
|
|
75
|
+
},
|
|
76
|
+
iframe = document.createElement('iframe');
|
|
77
|
+
|
|
78
|
+
Object.keys(attributes).forEach(function (key) {
|
|
79
|
+
iframe.setAttribute(key, attributes[key]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
while (placeholder.hasChildNodes()) {
|
|
83
|
+
placeholder.removeChild(placeholder.lastChild);
|
|
84
|
+
}
|
|
85
|
+
placeholder.appendChild(iframe);
|
|
86
|
+
|
|
87
|
+
field.iframe = iframe[0];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function postMessage(type, params = {}) {
|
|
91
|
+
const message = {params: params, type: type, formId: formId, messageId: null};
|
|
92
|
+
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
const messageId = type + ":" + generateRandomId();
|
|
95
|
+
callbacks[messageId] = resolve;
|
|
96
|
+
message.messageId = messageId;
|
|
97
|
+
|
|
98
|
+
log('send message', message);
|
|
99
|
+
window.frames[fields[mainField].iFrameName].postMessage(message, '*');
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const handlers = {};
|
|
104
|
+
form.onChange = function (handler) {
|
|
105
|
+
handlers[EVENT_ON_CHANGE] = handler;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
form.onReady = function (handler) {
|
|
109
|
+
handlers[EVENT_ON_READY] = handler;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
form.validate = function () {
|
|
113
|
+
return postMessage('validateRequest');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
form.submit = function (params) {
|
|
117
|
+
return postMessage('submitRequest', {
|
|
118
|
+
params: params || {},
|
|
119
|
+
browserDetails: {
|
|
120
|
+
browserUserAgent: navigator.userAgent,
|
|
121
|
+
browserJavaEnabled: navigator.javaEnabled(),
|
|
122
|
+
browserLanguage: navigator.language,
|
|
123
|
+
browserColorDepth: screen.colorDepth,
|
|
124
|
+
browserScreenHeight: screen.height,
|
|
125
|
+
browserScreenWidth: screen.width,
|
|
126
|
+
browserTZ: (new Date()).getTimezoneOffset()
|
|
127
|
+
}
|
|
128
|
+
}).then((res) => {
|
|
129
|
+
if (res.success) {
|
|
130
|
+
return Promise.resolve(res.result);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return Promise.reject({
|
|
134
|
+
error: res.error,
|
|
135
|
+
error_details: res.error_details,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
form.focus = function (field) {
|
|
141
|
+
const f = fields[field] || null;
|
|
142
|
+
if (f) {
|
|
143
|
+
window.frames[f.iFrameName].focus();
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
form.setStyle = function (fieldName, selector, cssRules) {
|
|
148
|
+
if (!fields[fieldName]) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
window.frames[fields[fieldName].iFrameName].postMessage({
|
|
153
|
+
type: 'setStyle',
|
|
154
|
+
params: {
|
|
155
|
+
selector: selector,
|
|
156
|
+
rules: cssRules,
|
|
157
|
+
}
|
|
158
|
+
}, '*');
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
function receiveMessage(message) {
|
|
162
|
+
const event = message.data;
|
|
163
|
+
if (formId !== event.formId) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
log(`message received of type "${event.type}"`, event);
|
|
168
|
+
|
|
169
|
+
const handlers = {
|
|
170
|
+
fieldReady: (r) => {
|
|
171
|
+
if (fields[r.fieldName] === undefined) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
fields[r.fieldName].isReady = true;
|
|
175
|
+
const ready = Object.keys(fields)
|
|
176
|
+
.map((key) => fields[key].isReady)
|
|
177
|
+
.reduce((a, b) => a && b, true);
|
|
178
|
+
|
|
179
|
+
if (ready) {
|
|
180
|
+
emitEvent('ready');
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
fieldChanged: function (response) {
|
|
184
|
+
emitEvent('change', response);
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const messageId = event.messageId,
|
|
189
|
+
callback = callbacks[messageId];
|
|
190
|
+
|
|
191
|
+
if (callback !== undefined) {
|
|
192
|
+
delete callbacks[messageId];
|
|
193
|
+
callback.apply(form, [event.response]);
|
|
194
|
+
} else {
|
|
195
|
+
const handler = handlers[event.type];
|
|
196
|
+
if (handler) {
|
|
197
|
+
handler.apply(form, [event.response]);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function emitEvent() {
|
|
203
|
+
const args = Array.prototype.slice.call(arguments);
|
|
204
|
+
const eventName = args.shift();
|
|
205
|
+
const handler = handlers[eventName];
|
|
206
|
+
if (handler) {
|
|
207
|
+
log(`emit event "${eventName}"`, args);
|
|
208
|
+
handler.apply(null, args);
|
|
209
|
+
} else {
|
|
210
|
+
log(`no handler for event "${eventName}"`, args);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
window.addEventListener('message', receiveMessage, false);
|
|
215
|
+
|
|
216
|
+
function addOnTouchedEventListener() {
|
|
217
|
+
window.addEventListener('touchend', checkFocus, true);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function checkFocus() {
|
|
221
|
+
for (let [, field] of Object.entries(fields)) {
|
|
222
|
+
const iframe = window.frames[field.iFrameName];
|
|
223
|
+
if (typeof iframe) {
|
|
224
|
+
iframe.postMessage({'type': 'checkFocus'}, '*');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
form.destroy = function () {
|
|
230
|
+
window.removeEventListener('message', receiveMessage, false);
|
|
231
|
+
window.addEventListener('touchend', checkFocus, true);
|
|
232
|
+
for (let fieldName in fields) {
|
|
233
|
+
let iframe = document.getElementById(fields[fieldName].iFrameName);
|
|
234
|
+
if (iframe) {
|
|
235
|
+
iframe.parentNode.removeChild(iframe);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return form;
|
|
241
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const webpack = require('webpack');
|
|
3
|
+
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
|
4
|
+
const TerserPlugin = require('terser-webpack-plugin');
|
|
5
|
+
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
|
6
|
+
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
|
|
7
|
+
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
|
|
8
|
+
const HtmlWebPackPlugin = require("html-webpack-plugin");
|
|
9
|
+
|
|
10
|
+
module.exports = (env, argv) => {
|
|
11
|
+
const modules = [];
|
|
12
|
+
const distFolder = path.resolve('dist');
|
|
13
|
+
const isDevelopment = argv.mode === 'development';
|
|
14
|
+
|
|
15
|
+
const plugins = [
|
|
16
|
+
new webpack.DefinePlugin({
|
|
17
|
+
PTP_BASE_URL: JSON.stringify(process.env.PTP_BASE_URL || 'https://ptp.payyo.ch'),
|
|
18
|
+
})
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const optimization = {
|
|
22
|
+
minimize: true,
|
|
23
|
+
minimizer: [
|
|
24
|
+
new TerserPlugin({
|
|
25
|
+
terserOptions: {
|
|
26
|
+
ecma: 6,
|
|
27
|
+
format: {
|
|
28
|
+
comments: false,
|
|
29
|
+
},
|
|
30
|
+
compress: {
|
|
31
|
+
drop_debugger: !isDevelopment,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
extractComments: false,
|
|
35
|
+
}),
|
|
36
|
+
new HtmlMinimizerPlugin({
|
|
37
|
+
test: /\.html/i,
|
|
38
|
+
}),
|
|
39
|
+
]
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (env.WEBPACK_SERVE) {
|
|
43
|
+
plugins.push(
|
|
44
|
+
new BundleAnalyzerPlugin({
|
|
45
|
+
// analyzerMode: 'static',
|
|
46
|
+
analyzerHost: '0.0.0.0',
|
|
47
|
+
analyzerPort: '8888',
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// test module
|
|
52
|
+
modules.push({
|
|
53
|
+
entry: './www/index.js',
|
|
54
|
+
devtool: 'source-map',
|
|
55
|
+
output: {
|
|
56
|
+
publicPath: '/',
|
|
57
|
+
filename: 'index.js',
|
|
58
|
+
clean: true,
|
|
59
|
+
},
|
|
60
|
+
plugins: [
|
|
61
|
+
...plugins,
|
|
62
|
+
new HtmlWebPackPlugin({
|
|
63
|
+
template: path.resolve(__dirname, "www/index.html"),
|
|
64
|
+
filename: 'index.html',
|
|
65
|
+
}),
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// default target
|
|
71
|
+
modules.push({
|
|
72
|
+
entry: './src/form/index.js',
|
|
73
|
+
optimization: optimization,
|
|
74
|
+
devtool: 'source-map',
|
|
75
|
+
output: {
|
|
76
|
+
filename: 'v1.js',
|
|
77
|
+
path: `${distFolder}/form`,
|
|
78
|
+
publicPath: '/form',
|
|
79
|
+
clean: true,
|
|
80
|
+
},
|
|
81
|
+
plugins: plugins,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (env.target === 'all' || env.target === 'field') {
|
|
85
|
+
// Field module
|
|
86
|
+
modules.push({
|
|
87
|
+
context: path.resolve(__dirname, 'src'),
|
|
88
|
+
entry: './field/index.js',
|
|
89
|
+
optimization: optimization,
|
|
90
|
+
devtool: 'source-map',
|
|
91
|
+
output: {
|
|
92
|
+
filename: 'v1.js',
|
|
93
|
+
path: `${distFolder}/field`,
|
|
94
|
+
publicPath: '/',
|
|
95
|
+
clean: true
|
|
96
|
+
},
|
|
97
|
+
plugins: [
|
|
98
|
+
new HtmlWebpackPlugin({
|
|
99
|
+
inject: 'body',
|
|
100
|
+
scriptLoading: 'defer',
|
|
101
|
+
filename: 'v1.html',
|
|
102
|
+
template: path.resolve('src/field/field.html'),
|
|
103
|
+
}),
|
|
104
|
+
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/.*[.]js/]),
|
|
105
|
+
...plugins,
|
|
106
|
+
]
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return modules;
|
|
111
|
+
};
|