@notabene/javascript-sdk 1.34.0 → 2.0.0-next.3
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/.gitlab-ci.yml +12 -1
- package/.husky/commit-msg +0 -0
- package/.husky/pre-commit +0 -0
- package/.husky/pre-push +1 -1
- package/.vscode/extensions.json +1 -5
- package/.vscode/settings.json +4 -4
- package/.yarn/releases/yarn-berry.cjs +925 -0
- package/.yarnrc.yml +3 -0
- package/README.md +126 -69
- package/api-extractor.json +434 -0
- package/dist/notabene.cjs +1 -0
- package/dist/notabene.d.ts +678 -0
- package/dist/notabene.js +187 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/eslint.config.js +24 -0
- package/etc/javascript-sdk.api.md +363 -0
- package/index.html +135 -0
- package/package.json +29 -54
- package/src/__tests__/notabene.test.ts +131 -0
- package/src/arbitraries.ts +13 -0
- package/src/components/EmbeddedComponent.ts +110 -0
- package/src/components/__tests__/EmbeddedComponent.test.ts +160 -0
- package/src/ivms/types.ts +2 -0
- package/src/notabene.ts +174 -290
- package/src/types.ts +380 -49
- package/src/utils/MessageEventManager.ts +55 -0
- package/src/utils/__tests__/MessageEventManager.test.ts +95 -0
- package/src/utils/arbitraries.ts +21 -0
- package/src/utils/caip.ts +16 -0
- package/src/utils/urls.ts +15 -0
- package/temp/javascript-sdk.api.json +3509 -0
- package/temp/javascript-sdk.api.md +363 -0
- package/ts-out/src/__tests__/notabene.test.d.ts +1 -0
- package/ts-out/src/__tests__/notabene.test.js +97 -0
- package/ts-out/src/arbitraries.d.ts +4 -0
- package/ts-out/src/arbitraries.js +8 -0
- package/ts-out/src/components/EmbeddedComponent.d.ts +25 -0
- package/ts-out/src/components/EmbeddedComponent.js +87 -0
- package/ts-out/src/components/__tests__/EmbeddedComponent.test.d.ts +1 -0
- package/ts-out/src/components/__tests__/EmbeddedComponent.test.js +132 -0
- package/ts-out/src/ivms/types.d.ts +252 -0
- package/ts-out/src/ivms/types.js +1 -0
- package/ts-out/src/notabene.d.ts +35 -0
- package/ts-out/src/notabene.js +60 -0
- package/ts-out/src/types.d.ts +357 -0
- package/ts-out/src/types.js +85 -0
- package/ts-out/src/utils/MessageEventManager.d.ts +12 -0
- package/ts-out/src/utils/MessageEventManager.js +45 -0
- package/ts-out/src/utils/__tests__/MessageEventManager.test.d.ts +1 -0
- package/ts-out/src/utils/__tests__/MessageEventManager.test.js +73 -0
- package/ts-out/src/utils/arbitraries.d.ts +6 -0
- package/ts-out/src/utils/arbitraries.js +7 -0
- package/ts-out/src/utils/caip.d.ts +13 -0
- package/ts-out/src/utils/caip.js +15 -0
- package/ts-out/src/utils/urls.d.ts +1 -0
- package/ts-out/src/utils/urls.js +14 -0
- package/ts-out/tsconfig.tsbuildinfo +1 -0
- package/tsconfig.json +15 -11
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.js +13 -0
- package/.envrc.template +0 -1
- package/.eslintignore +0 -3
- package/.eslintrc.js +0 -13
- package/.nvmrc +0 -1
- package/.tool-versions +0 -3
- package/dist/es/index.js +0 -1
- package/dist/lib/index.js +0 -376
- package/jest.config.js +0 -5
- package/public/index.html +0 -93
- package/public/js/.gitkeep +0 -0
- package/src/lib/index.ts +0 -2
- package/src/module/index.ts +0 -10
- package/src/zoidComponentProps.ts +0 -81
package/index.html
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
|
+
<title>Notabene Widget Example</title>
|
|
9
|
+
<script type="module">
|
|
10
|
+
import Notabene from '/src/notabene.ts';
|
|
11
|
+
const nb = new Notabene({
|
|
12
|
+
nodeUrl: 'https://api-qa.eu.notabene.dev',
|
|
13
|
+
// uxUrl: 'https://connect.notabene.dev',
|
|
14
|
+
uxUrl: 'http://localhost:8000',
|
|
15
|
+
theme: {
|
|
16
|
+
// primaryColor: '#fff',
|
|
17
|
+
// primaryFontColor: '#fff',
|
|
18
|
+
// secondaryFontColor: '#fff',
|
|
19
|
+
// backgroundColor: 'black',
|
|
20
|
+
// mode: 'dark',
|
|
21
|
+
},
|
|
22
|
+
// transactionTypeAllowed: 'VASP_2_VASP_ONLY',
|
|
23
|
+
// nonCustodialDeclarationType: 'DECLARATION',
|
|
24
|
+
// optInFeatures: ['REUSE_ADDRESS_OWNERSHIP_PROOF'],
|
|
25
|
+
|
|
26
|
+
// @deprecated
|
|
27
|
+
// beneficiaryDetails: ['beneficiaryNationalIdentification'],
|
|
28
|
+
|
|
29
|
+
// fieldsProps: {
|
|
30
|
+
// nationalIdentification: {
|
|
31
|
+
// forceDisplay: true,
|
|
32
|
+
// },
|
|
33
|
+
// dateAndPlaceOfBirth: {
|
|
34
|
+
// forceDisplay: true,
|
|
35
|
+
// },
|
|
36
|
+
// geographicAddress: {
|
|
37
|
+
// forceDisplay: true,
|
|
38
|
+
// optional: true
|
|
39
|
+
// },
|
|
40
|
+
// counterparty: {
|
|
41
|
+
// optional: true
|
|
42
|
+
// }
|
|
43
|
+
// }
|
|
44
|
+
// fallbacks: [{flow: "WALLET_NOT_SUPPORTED", action: "REJECT"}]
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const component = nb.createWithdrawalAssist({
|
|
48
|
+
asset: 'ETH',
|
|
49
|
+
customer: {
|
|
50
|
+
name: 'John Doe',
|
|
51
|
+
type: 'natural',
|
|
52
|
+
},
|
|
53
|
+
destination: '0x8e73ea19a72e89f20339f5a9e4cda8e4b230455c',
|
|
54
|
+
amountDecimal: 40.1,
|
|
55
|
+
});
|
|
56
|
+
component.mount('#withdrawal-assist');
|
|
57
|
+
component.on('error', console.error);
|
|
58
|
+
component.on('complete', (result) => {
|
|
59
|
+
console.log('result', result);
|
|
60
|
+
document.getElementById('result').innerText = JSON.stringify(
|
|
61
|
+
result,
|
|
62
|
+
null,
|
|
63
|
+
2,
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const fullscreen = nb.createWithdrawalAssist(
|
|
68
|
+
{
|
|
69
|
+
asset: 'BTC',
|
|
70
|
+
destination: '0x8e73ea19a72e89f20339f5a9e4cda8e4b230455c',
|
|
71
|
+
amountDecimal: 40.1,
|
|
72
|
+
},
|
|
73
|
+
{},
|
|
74
|
+
{
|
|
75
|
+
redirectUri: document.location.href,
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
document
|
|
79
|
+
.getElementById('open-fullscreen')
|
|
80
|
+
.addEventListener('click', () => {
|
|
81
|
+
document.location.hash = undefined;
|
|
82
|
+
fullscreen.open();
|
|
83
|
+
});
|
|
84
|
+
if (document.location.hash) {
|
|
85
|
+
const response = nb.decodeFragmentToObject(document.location.hash);
|
|
86
|
+
console.log(response);
|
|
87
|
+
document.getElementById('result').innerText = JSON.stringify(
|
|
88
|
+
JSON.parse(response['message']),
|
|
89
|
+
null,
|
|
90
|
+
2,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
<link
|
|
95
|
+
rel="stylesheet"
|
|
96
|
+
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
<style>
|
|
100
|
+
body {
|
|
101
|
+
margin: 0;
|
|
102
|
+
padding: 1em;
|
|
103
|
+
max-width: 800px;
|
|
104
|
+
}
|
|
105
|
+
#container {
|
|
106
|
+
max-width: 100vw;
|
|
107
|
+
}
|
|
108
|
+
pre {
|
|
109
|
+
border: 1px solid #ccc;
|
|
110
|
+
padding: 1em;
|
|
111
|
+
font-family: monospace;
|
|
112
|
+
}
|
|
113
|
+
</style>
|
|
114
|
+
</head>
|
|
115
|
+
|
|
116
|
+
<body>
|
|
117
|
+
<h1>Notabene Javascript SDK</h1>
|
|
118
|
+
<section>
|
|
119
|
+
<h2>Withdrawal Assist</h2>
|
|
120
|
+
<section>
|
|
121
|
+
<h3>Embedded</h3>
|
|
122
|
+
<div id="withdrawal-assist"></div>
|
|
123
|
+
</section>
|
|
124
|
+
<section>
|
|
125
|
+
<h3>Full Screen</h3>
|
|
126
|
+
<button id="open-fullscreen">Open fullscreen</button>
|
|
127
|
+
</section>
|
|
128
|
+
<section>
|
|
129
|
+
<h3>result</h3>
|
|
130
|
+
<pre id="result"></pre>
|
|
131
|
+
</section>
|
|
132
|
+
</section>
|
|
133
|
+
</body>
|
|
134
|
+
|
|
135
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notabene/javascript-sdk",
|
|
3
|
+
"type": "module",
|
|
3
4
|
"description": "JavaScript SDK for Notabene",
|
|
4
5
|
"homepage": "https://gitlab.com/notabene/open-source/javascript-sdk",
|
|
5
6
|
"repository": {
|
|
@@ -8,74 +9,52 @@
|
|
|
8
9
|
},
|
|
9
10
|
"author": "Notabene <developers@notabene.id>",
|
|
10
11
|
"license": "MIT",
|
|
11
|
-
"version": "
|
|
12
|
-
"
|
|
13
|
-
"
|
|
12
|
+
"version": "2.0.0-next.3",
|
|
13
|
+
"source": "src/notabene.ts",
|
|
14
|
+
"main": "dist/notabene.js",
|
|
15
|
+
"types": "dist/notabene.d.ts",
|
|
14
16
|
"browserslist": "> 0.5%, last 2 versions, not dead",
|
|
15
|
-
"targets": {
|
|
16
|
-
"es": {
|
|
17
|
-
"source": "src/module/index.ts",
|
|
18
|
-
"isLibrary": false,
|
|
19
|
-
"context": "browser",
|
|
20
|
-
"outputFormat": "esmodule"
|
|
21
|
-
},
|
|
22
|
-
"lib": {
|
|
23
|
-
"source": "src/lib/index.ts",
|
|
24
|
-
"isLibrary": true,
|
|
25
|
-
"context": "node",
|
|
26
|
-
"outputFormat": "commonjs"
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
"publishConfig": {
|
|
30
|
-
"access": "public"
|
|
31
|
-
},
|
|
32
|
-
"alias": {
|
|
33
|
-
"process": false,
|
|
34
|
-
"buffer": false
|
|
35
|
-
},
|
|
36
17
|
"scripts": {
|
|
37
18
|
"prepare": "husky install",
|
|
19
|
+
"dev": "vite",
|
|
20
|
+
"preview": "vite preview",
|
|
38
21
|
"watch": "parcel watch",
|
|
39
|
-
"dev": "sirv --dev --port 3002 public",
|
|
40
22
|
"build:dev": "yarn build && yarn dev",
|
|
41
|
-
"build": "
|
|
42
|
-
"build
|
|
43
|
-
"build:compile": "parcel build --no-source-maps && cp dist/es/index.js public/js/notabene.js",
|
|
23
|
+
"build:clean": "rimraf dist/ ts-out",
|
|
24
|
+
"build": "vite build && tsc && api-extractor run",
|
|
44
25
|
"lint": "eslint .",
|
|
45
|
-
"test": "
|
|
46
|
-
"release": "semantic-release"
|
|
26
|
+
"test": "vitest",
|
|
27
|
+
"release": "semantic-release",
|
|
28
|
+
"publish": "yarn build && yarn semantic-release && npm publish --tag next"
|
|
47
29
|
},
|
|
48
30
|
"devDependencies": {
|
|
49
31
|
"@commitlint/cli": "^16.2.1",
|
|
50
32
|
"@commitlint/config-conventional": "^16.2.1",
|
|
51
|
-
"@
|
|
52
|
-
"@
|
|
33
|
+
"@eslint/js": "^9.7.0",
|
|
34
|
+
"@fast-check/vitest": "^0.1.2",
|
|
35
|
+
"@microsoft/api-extractor": "^7.47.7",
|
|
53
36
|
"@semantic-release/git": "^10.0.1",
|
|
54
37
|
"@semantic-release/gitlab": "^9.3.0",
|
|
55
38
|
"@semantic-release/npm": "^9.0.1",
|
|
56
|
-
"@
|
|
57
|
-
"@typescript-eslint/
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"eslint": "^
|
|
61
|
-
"eslint-config-prettier": "^8.4.0",
|
|
62
|
-
"eslint-config-standard": "^16.0.3",
|
|
39
|
+
"@typescript-eslint/eslint-plugin": "^8.2.0",
|
|
40
|
+
"@typescript-eslint/parser": "^8.2.0",
|
|
41
|
+
"eslint": "^9.9.0",
|
|
42
|
+
"eslint-config-prettier": "^9.1.0",
|
|
43
|
+
"eslint-config-standard": "^17.1.0",
|
|
63
44
|
"eslint-plugin-import": "^2.25.4",
|
|
64
45
|
"eslint-plugin-node": "^11.1.0",
|
|
65
|
-
"eslint-plugin-prettier": "^
|
|
66
|
-
"
|
|
46
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
47
|
+
"fast-check": "^3.20.0",
|
|
48
|
+
"globals": "^15.8.0",
|
|
67
49
|
"husky": "^7.0.4",
|
|
68
|
-
"jest": "^28.1.0",
|
|
69
50
|
"lint-staged": "^12.3.4",
|
|
70
51
|
"organize-imports-cli": "^0.9.0",
|
|
71
|
-
"
|
|
72
|
-
"prettier": "^2.6.2",
|
|
73
|
-
"process": "^0.11.10",
|
|
74
|
-
"rimraf": "^3.0.2",
|
|
52
|
+
"prettier": "^3.3.3",
|
|
75
53
|
"semantic-release": "^19.0.2",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
54
|
+
"typescript": "^5.5.4",
|
|
55
|
+
"typescript-eslint": "^8.2.0",
|
|
56
|
+
"vite": "^5.4.2",
|
|
57
|
+
"vitest": "^2.0.5"
|
|
79
58
|
},
|
|
80
59
|
"lint-staged": {
|
|
81
60
|
"*.ts": [
|
|
@@ -87,9 +66,5 @@
|
|
|
87
66
|
"extends": [
|
|
88
67
|
"@commitlint/config-conventional"
|
|
89
68
|
]
|
|
90
|
-
},
|
|
91
|
-
"dependencies": {
|
|
92
|
-
"jsx-pragmatic": "^2.0.22",
|
|
93
|
-
"zoid": "^9.0.86"
|
|
94
69
|
}
|
|
95
|
-
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { fc, test } from '@fast-check/vitest';
|
|
2
|
+
import { describe } from 'vitest';
|
|
3
|
+
import { abitraryCallbackOptions, arbitraryComponent } from '../arbitraries';
|
|
4
|
+
import Notabene from '../notabene';
|
|
5
|
+
|
|
6
|
+
describe('Notabene', () => {
|
|
7
|
+
describe('defaults', () => {
|
|
8
|
+
test.prop([
|
|
9
|
+
fc.string({ minLength: 3 }),
|
|
10
|
+
fc.webUrl(),
|
|
11
|
+
arbitraryComponent(),
|
|
12
|
+
fc.object(),
|
|
13
|
+
])(
|
|
14
|
+
'Should set the correct values',
|
|
15
|
+
(authToken, nodeUrl, component, value) => {
|
|
16
|
+
const notabene = new Notabene({
|
|
17
|
+
nodeUrl,
|
|
18
|
+
authToken,
|
|
19
|
+
});
|
|
20
|
+
const url = new URL(notabene.componentUrl(component, value));
|
|
21
|
+
return (
|
|
22
|
+
url.hash ===
|
|
23
|
+
`#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}` &&
|
|
24
|
+
url.searchParams.get('nodeUrl') === nodeUrl &&
|
|
25
|
+
url.pathname === `/${component}`
|
|
26
|
+
);
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('minimal', () => {
|
|
32
|
+
test.prop([arbitraryComponent(), fc.object()])(
|
|
33
|
+
'Should set the correct values',
|
|
34
|
+
(component, value) => {
|
|
35
|
+
const notabene = new Notabene({});
|
|
36
|
+
const url = new URL(notabene.componentUrl(component, value));
|
|
37
|
+
return (
|
|
38
|
+
url.hash === `#value=${encodeURIComponent(JSON.stringify(value))}` &&
|
|
39
|
+
url.hostname === 'connect.notabene.id' &&
|
|
40
|
+
url.pathname === `/${component}`
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('with uxUrl', () => {
|
|
47
|
+
test.prop([
|
|
48
|
+
fc.string({ minLength: 3 }),
|
|
49
|
+
fc.webUrl(),
|
|
50
|
+
arbitraryComponent(),
|
|
51
|
+
fc.webAuthority(),
|
|
52
|
+
fc.object(),
|
|
53
|
+
])(
|
|
54
|
+
'Should set the correct values',
|
|
55
|
+
(authToken, nodeUrl, component, uxAuthority, value) => {
|
|
56
|
+
const notabene = new Notabene({
|
|
57
|
+
nodeUrl,
|
|
58
|
+
authToken,
|
|
59
|
+
uxUrl: `https://${uxAuthority}`,
|
|
60
|
+
});
|
|
61
|
+
const url = new URL(notabene.componentUrl(component, value));
|
|
62
|
+
const [domain, port] = uxAuthority.split(':');
|
|
63
|
+
return (
|
|
64
|
+
url.hostname === domain &&
|
|
65
|
+
url.port === (port || '') &&
|
|
66
|
+
url.hash ===
|
|
67
|
+
`#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}` &&
|
|
68
|
+
url.searchParams.get('nodeUrl') === nodeUrl &&
|
|
69
|
+
url.pathname === `/${component}`
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('with value and configuration', () => {
|
|
76
|
+
test.prop([
|
|
77
|
+
fc.string({ minLength: 3 }),
|
|
78
|
+
fc.webUrl(),
|
|
79
|
+
arbitraryComponent(),
|
|
80
|
+
fc.object(),
|
|
81
|
+
fc.object(),
|
|
82
|
+
])(
|
|
83
|
+
'Should set the correct values',
|
|
84
|
+
(authToken, nodeUrl, component, value, configuration) => {
|
|
85
|
+
const notabene = new Notabene({
|
|
86
|
+
nodeUrl,
|
|
87
|
+
authToken,
|
|
88
|
+
});
|
|
89
|
+
const url = new URL(
|
|
90
|
+
notabene.componentUrl(component, value, configuration),
|
|
91
|
+
);
|
|
92
|
+
return (
|
|
93
|
+
url.hash ===
|
|
94
|
+
`#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}&configuration=${encodeURIComponent(JSON.stringify(configuration))}` &&
|
|
95
|
+
url.searchParams.get('nodeUrl') === nodeUrl &&
|
|
96
|
+
url.pathname === `/${component}`
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('with value and configuration', () => {
|
|
103
|
+
test.prop([
|
|
104
|
+
fc.string({ minLength: 3 }),
|
|
105
|
+
fc.webUrl(),
|
|
106
|
+
arbitraryComponent(),
|
|
107
|
+
fc.object(),
|
|
108
|
+
fc.object(),
|
|
109
|
+
abitraryCallbackOptions(),
|
|
110
|
+
])(
|
|
111
|
+
'Should set the correct values',
|
|
112
|
+
(authToken, nodeUrl, component, value, configuration, callbacks) => {
|
|
113
|
+
const notabene = new Notabene({
|
|
114
|
+
nodeUrl,
|
|
115
|
+
authToken,
|
|
116
|
+
});
|
|
117
|
+
const url = new URL(
|
|
118
|
+
notabene.componentUrl(component, value, configuration, callbacks),
|
|
119
|
+
);
|
|
120
|
+
return (
|
|
121
|
+
url.hash ===
|
|
122
|
+
`#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}&configuration=${encodeURIComponent(JSON.stringify(configuration))}` &&
|
|
123
|
+
url.searchParams.get('nodeUrl') === nodeUrl &&
|
|
124
|
+
url.pathname === `/${component}` &&
|
|
125
|
+
url.searchParams.get('callback_url') === callbacks.callback &&
|
|
126
|
+
url.searchParams.get('redirect_uri') === callbacks.redirectUri
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fc from 'fast-check';
|
|
2
|
+
import { CallbackOptions } from './types';
|
|
3
|
+
|
|
4
|
+
const COMPONENT_MATCHER = /^[a-z0-9-_]+$/;
|
|
5
|
+
// Arbitraries
|
|
6
|
+
export const arbitraryComponent = (): fc.Arbitrary<string> =>
|
|
7
|
+
fc.stringMatching(COMPONENT_MATCHER);
|
|
8
|
+
|
|
9
|
+
export const abitraryCallbackOptions = (): fc.Arbitrary<CallbackOptions> =>
|
|
10
|
+
fc.record({
|
|
11
|
+
callback: fc.webUrl(),
|
|
12
|
+
redirectUri: fc.webUrl(),
|
|
13
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CMType,
|
|
3
|
+
HMType,
|
|
4
|
+
HostMessage,
|
|
5
|
+
Transaction,
|
|
6
|
+
TransactionResponse,
|
|
7
|
+
ValidationError,
|
|
8
|
+
} from '../types';
|
|
9
|
+
import {
|
|
10
|
+
MessageCallback,
|
|
11
|
+
MessageEventManager,
|
|
12
|
+
} from '../utils/MessageEventManager';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* An embedded Notabene component
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export default class EmbeddedComponent {
|
|
19
|
+
private _url: string;
|
|
20
|
+
private _value: Partial<Transaction>;
|
|
21
|
+
private _errors: ValidationError[] = [];
|
|
22
|
+
|
|
23
|
+
private iframe?: HTMLIFrameElement;
|
|
24
|
+
private eventManager: MessageEventManager;
|
|
25
|
+
private ready = false;
|
|
26
|
+
|
|
27
|
+
constructor(url: string, value: Partial<Transaction>) {
|
|
28
|
+
this._url = url;
|
|
29
|
+
this._value = value;
|
|
30
|
+
this.eventManager = new MessageEventManager();
|
|
31
|
+
this.on(CMType.INVALID, (message) => {
|
|
32
|
+
if (message.type === CMType.INVALID) {
|
|
33
|
+
this._errors = message.errors;
|
|
34
|
+
this._value = message.value as Transaction;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
this.on(CMType.RESIZE, (message) => {
|
|
38
|
+
if (message.type === CMType.RESIZE) {
|
|
39
|
+
if (this.iframe) this.iframe.style.height = `${message.height}px`;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public get url(): string {
|
|
45
|
+
return this._url;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public get value(): Partial<Transaction> {
|
|
49
|
+
return this._value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public get errors(): ValidationError[] {
|
|
53
|
+
return this._errors;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public open() {
|
|
57
|
+
document.location.href = this.url;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
mount(parentId: string) {
|
|
61
|
+
this.iframe = document.createElement('iframe');
|
|
62
|
+
this.iframe.src = this.url;
|
|
63
|
+
this.iframe.style.width = '100%';
|
|
64
|
+
this.iframe.style.height = '100px';
|
|
65
|
+
this.iframe.style.border = 'none';
|
|
66
|
+
this.iframe.style.overflow = 'hidden';
|
|
67
|
+
|
|
68
|
+
window.addEventListener('message', (event) => {
|
|
69
|
+
if (event.source !== this.iframe?.contentWindow) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log('received message from iframe', event.data);
|
|
73
|
+
this.eventManager?.setPort(event.ports[0]);
|
|
74
|
+
this.ready = true;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const parent = document.querySelector(parentId);
|
|
78
|
+
if (!parent) throw new Error(`parentID ${parentId} not found`);
|
|
79
|
+
parent.appendChild(this.iframe);
|
|
80
|
+
this.iframe?.contentWindow?.focus();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
send(message: HostMessage): void {
|
|
84
|
+
this.eventManager.send(message);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
on(messageType: string, callback: MessageCallback): void {
|
|
88
|
+
this.eventManager.on(messageType, callback);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
off(messageType: string, callback: MessageCallback): void {
|
|
92
|
+
this.eventManager.off(messageType, callback);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
update(value: Partial<Transaction>) {
|
|
96
|
+
this._value = value;
|
|
97
|
+
this.send({ type: HMType.UPDATE, value });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
completion(): Promise<TransactionResponse> {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
this.on(CMType.COMPLETE, (message) => {
|
|
103
|
+
resolve((message as any).response);
|
|
104
|
+
});
|
|
105
|
+
this.on('error', (message) => {
|
|
106
|
+
reject(new Error((message as any).message));
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { CMType, HMType, HostMessage, Transaction } from '../../types';
|
|
3
|
+
import EmbeddedComponent from '../EmbeddedComponent';
|
|
4
|
+
|
|
5
|
+
const url = 'https://example.com';
|
|
6
|
+
const parentId = 'parent-container';
|
|
7
|
+
const value: Partial<Transaction> = { amountDecimal: 100 };
|
|
8
|
+
|
|
9
|
+
describe('EmbeddedComponent', () => {
|
|
10
|
+
describe('creation', () => {
|
|
11
|
+
let component: EmbeddedComponent;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
component = new EmbeddedComponent(url, { amountDecimal: 100 });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('sets value', () => {
|
|
17
|
+
expect(component.value).toStrictEqual(value);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('sets url', () => {
|
|
21
|
+
expect(component.url).toBe(url);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('iframe', () => {
|
|
26
|
+
let document: Document;
|
|
27
|
+
let parentElement: HTMLElement;
|
|
28
|
+
let iframe: HTMLIFrameElement;
|
|
29
|
+
let window: Window;
|
|
30
|
+
let iframeWindow: Window;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
parentElement = {
|
|
34
|
+
appendChild: vi.fn(),
|
|
35
|
+
} as unknown as HTMLElement;
|
|
36
|
+
|
|
37
|
+
iframe = {
|
|
38
|
+
style: {},
|
|
39
|
+
src: '',
|
|
40
|
+
contentWindow: iframeWindow,
|
|
41
|
+
setAttribute: vi.fn(),
|
|
42
|
+
addEventListener: vi.fn(),
|
|
43
|
+
} as unknown as HTMLIFrameElement;
|
|
44
|
+
|
|
45
|
+
document = {
|
|
46
|
+
createElement: vi.fn().mockReturnValue(iframe),
|
|
47
|
+
querySelector: vi.fn().mockReturnValue(parentElement),
|
|
48
|
+
} as unknown as Document;
|
|
49
|
+
|
|
50
|
+
window = {
|
|
51
|
+
addEventListener: vi.fn(),
|
|
52
|
+
} as unknown as Window;
|
|
53
|
+
|
|
54
|
+
vi.stubGlobal('document', document);
|
|
55
|
+
vi.stubGlobal('window', window);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
vi.unstubAllGlobals();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('mount', () => {
|
|
63
|
+
const component = new EmbeddedComponent(url, value);
|
|
64
|
+
component.mount(parentId);
|
|
65
|
+
expect(iframe?.src).toBe(url);
|
|
66
|
+
expect(parentElement.appendChild).toHaveBeenCalledWith(iframe);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('on', () => {
|
|
70
|
+
const component = new EmbeddedComponent(url, value);
|
|
71
|
+
const messageType = 'customEvent';
|
|
72
|
+
const callback = vi.fn();
|
|
73
|
+
component.on(messageType, callback);
|
|
74
|
+
component.mount(parentId);
|
|
75
|
+
|
|
76
|
+
// Simulate receiving a message of the specified type
|
|
77
|
+
// and assert that the callback is called with the correct payload.
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('off', () => {
|
|
81
|
+
const component = new EmbeddedComponent(url, value);
|
|
82
|
+
const messageType = 'customEvent';
|
|
83
|
+
const callback = vi.fn();
|
|
84
|
+
component.on(messageType, callback);
|
|
85
|
+
|
|
86
|
+
// Simulate receiving a message of the specified type
|
|
87
|
+
// and assert that the callback is called with the correct payload.
|
|
88
|
+
|
|
89
|
+
component.off(messageType, callback);
|
|
90
|
+
|
|
91
|
+
// Simulate receiving another message of the specified type
|
|
92
|
+
// and assert that the callback is not called.
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('connected to iframe', () => {
|
|
96
|
+
let messageHandler: (event: MessageEvent) => void;
|
|
97
|
+
let port: MessagePort;
|
|
98
|
+
let handshake: MessageEvent;
|
|
99
|
+
let component: EmbeddedComponent;
|
|
100
|
+
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
port = {
|
|
103
|
+
postMessage: vi.fn(),
|
|
104
|
+
start: vi.fn(),
|
|
105
|
+
onmessage: null,
|
|
106
|
+
} as unknown as MessagePort;
|
|
107
|
+
handshake = {
|
|
108
|
+
source: iframe.contentWindow,
|
|
109
|
+
ports: [port],
|
|
110
|
+
} as unknown as MessageEvent;
|
|
111
|
+
window.addEventListener = vi
|
|
112
|
+
.fn()
|
|
113
|
+
.mockImplementation((event, handler) => {
|
|
114
|
+
messageHandler = handler;
|
|
115
|
+
});
|
|
116
|
+
component = new EmbeddedComponent(url, value);
|
|
117
|
+
component.mount(parentId);
|
|
118
|
+
|
|
119
|
+
expect(messageHandler).toBeDefined();
|
|
120
|
+
messageHandler(handshake);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('send', () => {
|
|
124
|
+
const message: HostMessage = {
|
|
125
|
+
type: HMType.UPDATE,
|
|
126
|
+
value: { amountDecimal: 100 },
|
|
127
|
+
};
|
|
128
|
+
component.send(message);
|
|
129
|
+
|
|
130
|
+
// Assert that the message is sent to the embedded component
|
|
131
|
+
// and handled correctly.
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('update', () => {
|
|
135
|
+
const newValue: Partial<Transaction> = { amountDecimal: 200 };
|
|
136
|
+
component.update(newValue);
|
|
137
|
+
|
|
138
|
+
expect(component.value).toBe(newValue);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// TODO not sure this actually does what I want it to do
|
|
142
|
+
test('completion', async () => {
|
|
143
|
+
const response = { valid: true, value: 'completed' };
|
|
144
|
+
expect(port.onmessage).toBeDefined();
|
|
145
|
+
|
|
146
|
+
const promise = component.completion();
|
|
147
|
+
// @ts-expect-error - we've already verified that port exists
|
|
148
|
+
port.onmessage({
|
|
149
|
+
data: { type: CMType.COMPLETE, response },
|
|
150
|
+
} as MessageEvent);
|
|
151
|
+
|
|
152
|
+
promise.then((result) => {
|
|
153
|
+
expect(result).toBe(response);
|
|
154
|
+
});
|
|
155
|
+
return promise;
|
|
156
|
+
// Assert that the completion promise resolves with the expected response.
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|