@notabene/javascript-sdk 1.34.0 → 2.0.0-RC

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.
Files changed (44) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +500 -259
  3. package/dist/{es/index.js → js/notabene.js} +1 -1
  4. package/dist/notabene.cjs +1 -0
  5. package/dist/notabene.d.ts +1565 -0
  6. package/dist/notabene.js +343 -0
  7. package/dist/tsdoc-metadata.json +11 -0
  8. package/package.json +50 -64
  9. package/src/__tests__/notabene.test.ts +270 -0
  10. package/src/components/EmbeddedComponent.ts +237 -0
  11. package/src/components/__tests__/EmbeddedComponent.test.ts +530 -0
  12. package/src/ivms/types.ts +232 -152
  13. package/src/locales.ts +47 -0
  14. package/src/notabene.ts +292 -288
  15. package/src/types.ts +1068 -78
  16. package/src/utils/MessageEventManager.ts +115 -0
  17. package/src/utils/__tests__/MessageEventManager.test.ts +119 -0
  18. package/src/utils/__tests__/urls.test.ts +112 -0
  19. package/src/utils/arbitraries.ts +244 -0
  20. package/src/utils/caip.ts +16 -0
  21. package/src/utils/urls.ts +40 -0
  22. package/.editorconfig +0 -10
  23. package/.envrc.template +0 -1
  24. package/.eslintignore +0 -3
  25. package/.eslintrc.js +0 -13
  26. package/.gitlab-ci.yml +0 -74
  27. package/.husky/commit-msg +0 -4
  28. package/.husky/pre-commit +0 -4
  29. package/.husky/pre-push +0 -4
  30. package/.nvmrc +0 -1
  31. package/.prettierrc +0 -4
  32. package/.releaserc.json +0 -16
  33. package/.tool-versions +0 -3
  34. package/.vscode/extensions.json +0 -7
  35. package/.vscode/settings.json +0 -11
  36. package/CODEOWNERS +0 -1
  37. package/dist/lib/index.js +0 -376
  38. package/jest.config.js +0 -5
  39. package/public/index.html +0 -93
  40. package/src/lib/index.ts +0 -2
  41. package/src/module/index.ts +0 -10
  42. package/src/zoidComponentProps.ts +0 -81
  43. package/tsconfig.json +0 -18
  44. /package/{public/js/.gitkeep → src/arbitraries.ts} +0 -0
@@ -0,0 +1,270 @@
1
+ import { fc, test } from '@fast-check/vitest';
2
+ import { describe } from 'vitest';
3
+ import Notabene from '../notabene';
4
+ import {
5
+ abitraryCallbackOptions,
6
+ arbitraryComponent,
7
+ arbitraryConnectionOptions,
8
+ arbitraryConnectionRequest,
9
+ arbitraryDepositRequest,
10
+ arbitraryDepositRequestOptions,
11
+ arbitraryLocale,
12
+ arbitraryTransactionOptions,
13
+ arbitraryWithdrawal,
14
+ } from '../utils/arbitraries';
15
+
16
+ describe('Notabene', () => {
17
+ describe('defaults', () => {
18
+ test.prop([
19
+ fc.string({ minLength: 3 }),
20
+ fc.webUrl(),
21
+ arbitraryComponent(),
22
+ fc.object(),
23
+ ])(
24
+ 'Should set the correct values',
25
+ (authToken, nodeUrl, component, value) => {
26
+ const notabene = new Notabene({
27
+ nodeUrl,
28
+ authToken,
29
+ });
30
+ const url = new URL(notabene.componentUrl(component, value));
31
+ return (
32
+ url.hash ===
33
+ `#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}` &&
34
+ url.searchParams.get('nodeUrl') === nodeUrl &&
35
+ url.pathname === `/${component}`
36
+ );
37
+ },
38
+ );
39
+ });
40
+
41
+ describe('locale', () => {
42
+ test.prop([
43
+ fc.string({ minLength: 3 }),
44
+ fc.webUrl(),
45
+ arbitraryComponent(),
46
+ fc.object(),
47
+ arbitraryLocale(),
48
+ ])(
49
+ 'Should set the correct values',
50
+ (authToken, nodeUrl, component, value, locale) => {
51
+ const notabene = new Notabene({
52
+ nodeUrl,
53
+ authToken,
54
+ locale,
55
+ });
56
+ const url = new URL(notabene.componentUrl(component, value));
57
+ return (
58
+ url.hash ===
59
+ `#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}` &&
60
+ url.searchParams.get('nodeUrl') === nodeUrl &&
61
+ url.searchParams.get('locale') === locale &&
62
+ url.pathname === `/${component}`
63
+ );
64
+ },
65
+ );
66
+ });
67
+
68
+ describe('minimal', () => {
69
+ test.prop([arbitraryComponent(), fc.object()])(
70
+ 'Should set the correct values',
71
+ (component, value) => {
72
+ const notabene = new Notabene({});
73
+ const url = new URL(notabene.componentUrl(component, value));
74
+ return (
75
+ url.hash === `#value=${encodeURIComponent(JSON.stringify(value))}` &&
76
+ url.hostname === 'connect.notabene.id' &&
77
+ url.pathname === `/${component}`
78
+ );
79
+ },
80
+ );
81
+ });
82
+
83
+ describe('with uxUrl', () => {
84
+ test.prop([
85
+ fc.string({ minLength: 3 }),
86
+ fc.webUrl(),
87
+ arbitraryComponent(),
88
+ fc.webAuthority(),
89
+ fc.object(),
90
+ ])(
91
+ 'Should set the correct values',
92
+ (authToken, nodeUrl, component, uxAuthority, value) => {
93
+ const notabene = new Notabene({
94
+ nodeUrl,
95
+ authToken,
96
+ uxUrl: `https://${uxAuthority}`,
97
+ });
98
+ const url = new URL(notabene.componentUrl(component, value));
99
+ const [domain, port] = uxAuthority.split(':');
100
+ return (
101
+ url.hostname === domain &&
102
+ url.port === (port || '') &&
103
+ url.hash ===
104
+ `#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}` &&
105
+ url.searchParams.get('nodeUrl') === nodeUrl &&
106
+ url.pathname === `/${component}`
107
+ );
108
+ },
109
+ );
110
+ });
111
+
112
+ describe('with value and configuration', () => {
113
+ test.prop([
114
+ fc.string({ minLength: 3 }),
115
+ fc.webUrl(),
116
+ arbitraryComponent(),
117
+ fc.object(),
118
+ fc.object(),
119
+ ])(
120
+ 'Should set the correct values',
121
+ (authToken, nodeUrl, component, value, configuration) => {
122
+ const notabene = new Notabene({
123
+ nodeUrl,
124
+ authToken,
125
+ });
126
+ const url = new URL(
127
+ notabene.componentUrl(component, value, configuration),
128
+ );
129
+ return (
130
+ url.hash ===
131
+ `#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}&configuration=${encodeURIComponent(JSON.stringify(configuration))}` &&
132
+ url.searchParams.get('nodeUrl') === nodeUrl &&
133
+ url.pathname === `/${component}`
134
+ );
135
+ },
136
+ );
137
+ });
138
+
139
+ describe('with value and configuration', () => {
140
+ test.prop([
141
+ fc.string({ minLength: 3 }),
142
+ fc.webUrl(),
143
+ arbitraryComponent(),
144
+ fc.object(),
145
+ fc.object(),
146
+ abitraryCallbackOptions(),
147
+ ])(
148
+ 'Should set the correct values',
149
+ (authToken, nodeUrl, component, value, configuration, callbacks) => {
150
+ const notabene = new Notabene({
151
+ nodeUrl,
152
+ authToken,
153
+ });
154
+ const url = new URL(
155
+ notabene.componentUrl(component, value, configuration, callbacks),
156
+ );
157
+ return (
158
+ url.hash ===
159
+ `#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}&configuration=${encodeURIComponent(JSON.stringify(configuration))}` &&
160
+ url.searchParams.get('nodeUrl') === nodeUrl &&
161
+ url.pathname === `/${component}` &&
162
+ url.searchParams.get('callback_url') === callbacks.callback &&
163
+ url.searchParams.get('redirect_uri') === callbacks.redirectUri
164
+ );
165
+ },
166
+ );
167
+ });
168
+
169
+ describe('createWithdrawalAssist', () => {
170
+ test.prop([
171
+ fc.string({ minLength: 3 }), // authToken
172
+ fc.webUrl(), // nodeUrl
173
+ arbitraryWithdrawal(),
174
+ arbitraryTransactionOptions(), // options
175
+ abitraryCallbackOptions(), // callbacks
176
+ ])(
177
+ 'Should create withdrawal assist component with correct URL',
178
+ (authToken, nodeUrl, value, options, callbacks) => {
179
+ const notabene = new Notabene({
180
+ nodeUrl,
181
+ authToken,
182
+ });
183
+
184
+ const component = notabene.createWithdrawalAssist(
185
+ value,
186
+ options,
187
+ callbacks,
188
+ );
189
+ const url = new URL(component.url);
190
+
191
+ return (
192
+ url.pathname === '/withdrawal-assist' &&
193
+ url.hash ===
194
+ `#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}&configuration=${encodeURIComponent(JSON.stringify(options))}` &&
195
+ url.searchParams.get('nodeUrl') === nodeUrl &&
196
+ url.searchParams.get('callback_url') === callbacks.callback &&
197
+ url.searchParams.get('redirect_uri') === callbacks.redirectUri
198
+ );
199
+ },
200
+ );
201
+ });
202
+
203
+ describe('createDepositRequest', () => {
204
+ test.prop([
205
+ fc.string({ minLength: 3 }), // authToken
206
+ fc.webUrl(), // nodeUrl
207
+ arbitraryDepositRequest(),
208
+ arbitraryDepositRequestOptions(), // options
209
+ abitraryCallbackOptions(), // callbacks
210
+ ])(
211
+ 'Should create deposit assist component with correct URL',
212
+ (authToken, nodeUrl, value, options, callbacks) => {
213
+ const notabene = new Notabene({
214
+ nodeUrl,
215
+ authToken,
216
+ });
217
+
218
+ const component = notabene.createDepositRequest(
219
+ value,
220
+ options,
221
+ callbacks,
222
+ );
223
+ const url = new URL(component.url);
224
+
225
+ return (
226
+ url.pathname === '/deposit-request' &&
227
+ url.hash ===
228
+ `#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}&configuration=${encodeURIComponent(JSON.stringify(options))}` &&
229
+ url.searchParams.get('nodeUrl') === nodeUrl &&
230
+ url.searchParams.get('callback_url') === callbacks.callback &&
231
+ url.searchParams.get('redirect_uri') === callbacks.redirectUri
232
+ );
233
+ },
234
+ );
235
+ });
236
+
237
+ describe('createConnectWallet', () => {
238
+ test.prop([
239
+ fc.string({ minLength: 3 }), // authToken
240
+ fc.webUrl(), // nodeUrl
241
+ arbitraryConnectionRequest(),
242
+ arbitraryConnectionOptions(), // options
243
+ abitraryCallbackOptions(), // callbacks
244
+ ])(
245
+ 'Should create connect wallet component with correct URL',
246
+ (authToken, nodeUrl, value, options, callbacks) => {
247
+ const notabene = new Notabene({
248
+ nodeUrl,
249
+ authToken,
250
+ });
251
+
252
+ const component = notabene.createConnectWallet(
253
+ value,
254
+ options,
255
+ callbacks,
256
+ );
257
+ const url = new URL(component.url);
258
+
259
+ return (
260
+ url.pathname === '/connect' &&
261
+ url.hash ===
262
+ `#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}&configuration=${encodeURIComponent(JSON.stringify(options))}` &&
263
+ url.searchParams.get('nodeUrl') === nodeUrl &&
264
+ url.searchParams.get('callback_url') === callbacks.callback &&
265
+ url.searchParams.get('redirect_uri') === callbacks.redirectUri
266
+ );
267
+ },
268
+ );
269
+ });
270
+ });
@@ -0,0 +1,237 @@
1
+ import {
2
+ CMType,
3
+ HMType,
4
+ HostMessage,
5
+ TransactionResponse,
6
+ ValidationError,
7
+ } from '../types';
8
+ import {
9
+ MessageCallback,
10
+ MessageEventManager,
11
+ } from '../utils/MessageEventManager';
12
+
13
+ /**
14
+ * An embedded Notabene component
15
+ * @public
16
+ */
17
+
18
+ export default class EmbeddedComponent<V, O> {
19
+ private _url: string;
20
+ private _value: Partial<V>;
21
+ private _options?: O;
22
+ private _errors: ValidationError[] = [];
23
+
24
+ private iframe?: HTMLIFrameElement;
25
+ private eventManager: MessageEventManager<V, O>;
26
+ private modal?: HTMLDialogElement;
27
+
28
+ /**
29
+ * Creates an instance of EmbeddedComponent.
30
+ * @param url - The URL of the embedded component
31
+ * @param value - The initial transaction value
32
+ */
33
+
34
+ constructor(url: string, value: Partial<V>, options?: O) {
35
+ this._url = url;
36
+ this._value = value;
37
+ this._options = options;
38
+ this.eventManager = new MessageEventManager<V, O>();
39
+ this.on(CMType.INVALID, (message) => {
40
+ if (message.type === CMType.INVALID) {
41
+ this._errors = message.errors;
42
+ this._value = message.value as V;
43
+ }
44
+ });
45
+ this.on(CMType.RESIZE, (message) => {
46
+ if (message.type === CMType.RESIZE) {
47
+ if (this.iframe) this.iframe.style.height = `${message.height}px`;
48
+ }
49
+ });
50
+ }
51
+ /**
52
+ * Gets the URL of the embedded component
53
+ * @returns The URL of the embedded component
54
+ */
55
+
56
+ public get url(): string {
57
+ return this._url;
58
+ }
59
+ /**
60
+ * Gets the current transaction value
61
+ * @returns The current transaction value
62
+ */
63
+
64
+ public get value(): Partial<V> {
65
+ return this._value;
66
+ }
67
+
68
+ public get options(): O | undefined {
69
+ return this._options;
70
+ }
71
+
72
+ public get errors(): ValidationError[] {
73
+ return this._errors;
74
+ }
75
+
76
+ /**
77
+ * Opens the component URL in the current window
78
+ */
79
+
80
+ public open() {
81
+ document.location.href = this.url;
82
+ }
83
+
84
+ /**
85
+ * Mounts the component to a parent element
86
+ * @param parentId - The ID of the parent element
87
+ * @throws Will throw an error if the parent element is not found
88
+ */
89
+
90
+ mount(parentId: string) {
91
+ const parent = document.querySelector(parentId);
92
+ if (!parent) throw new Error(`parentID ${parentId} not found`);
93
+ this.embed(parent);
94
+ }
95
+
96
+ /**
97
+ * Embeds the component into a parent element
98
+ * @param parent - The parent element to embed the component into
99
+ */
100
+ embed(parent: Element, modal: boolean = false) {
101
+ // console.log(this.url);
102
+ this.removeEmbed();
103
+ this.iframe = document.createElement('iframe');
104
+ this.iframe.src = this.url + (!modal ? '&embedded=true' : '');
105
+ this.iframe.allow = 'web-share';
106
+ this.iframe.style.width = '100%';
107
+ this.iframe.style.height = '100px';
108
+ this.iframe.style.border = 'none';
109
+ this.iframe.style.overflow = 'hidden';
110
+ // this.iframe.setAttribute('allowtransparency', 'true');
111
+ // this.iframe.style.backgroundColor = 'transparent';
112
+ parent.appendChild(this.iframe);
113
+
114
+ window.addEventListener('message', (event: MessageEvent) => {
115
+ if (event.source !== this.iframe?.contentWindow) {
116
+ return;
117
+ }
118
+ // console.log('received message from iframe', event.data);
119
+ this.eventManager?.setPort(event.ports[0]);
120
+ });
121
+
122
+ this.iframe?.contentWindow?.focus();
123
+ }
124
+
125
+ removeEmbed() {
126
+ if (this.iframe) this.iframe.remove();
127
+ }
128
+
129
+ /**
130
+ * Sends a message to the embedded component
131
+ * @param message - The message to send
132
+ */
133
+ send(message: HostMessage<V, O>): void {
134
+ this.eventManager.send(message);
135
+ }
136
+
137
+ /**
138
+ * Adds an event listener for a specific message type
139
+ * @param messageType - The type of message to listen for
140
+ * @param callback - The callback function to execute when the message is received
141
+ */
142
+
143
+ on(messageType: string, callback: MessageCallback<V>): () => void {
144
+ return this.eventManager.on(messageType, callback);
145
+ }
146
+
147
+ /**
148
+ * Removes an event listener for a specific message type
149
+ * @param messageType - The type of message to stop listening for
150
+ * @param callback - The callback function to remove
151
+ */
152
+
153
+ off(messageType: string, callback: MessageCallback<V>): void {
154
+ this.eventManager.off(messageType, callback);
155
+ }
156
+
157
+ /**
158
+ * Updates the transaction value and sends an update message to the component
159
+ * @param value - The new transaction value
160
+ */
161
+ update(value: Partial<V>, options?: O) {
162
+ this._value = value;
163
+ if (options) this._options = options;
164
+ this.send({ type: HMType.UPDATE, value, options: this._options });
165
+ }
166
+
167
+ /**
168
+ * Waits for the component to complete and returns the transaction response
169
+ * @returns A promise that resolves with the transaction response
170
+ */
171
+
172
+ completion(): Promise<TransactionResponse<V>> {
173
+ return new Promise((resolve, reject) => {
174
+ let removeComplete: undefined | (() => void) = undefined;
175
+ let removeError: undefined | (() => void) = undefined;
176
+ function cleanup() {
177
+ if (removeComplete) removeComplete();
178
+ if (removeError) removeError();
179
+ }
180
+ removeComplete = this.on(CMType.COMPLETE, (message) => {
181
+ resolve((message as any).response);
182
+ cleanup();
183
+ });
184
+ removeError = this.on('error', (message) => {
185
+ reject(new Error((message as any).message));
186
+ cleanup();
187
+ });
188
+ });
189
+ }
190
+
191
+ /**
192
+ * Opens the component in a modal dialog
193
+ */
194
+
195
+ openModal(): Promise<TransactionResponse<V>> {
196
+ if (this.modal) {
197
+ this.closeModal();
198
+ }
199
+ this.modal = document.createElement('dialog');
200
+ this.modal.style.border = 'none';
201
+ this.modal.style.backgroundColor = 'white';
202
+ this.modal.style.maxWidth = '100vw';
203
+ this.modal.style.maxHeight = '100vh';
204
+ this.modal.style.width = '600px';
205
+ this.modal.style.height = '600px';
206
+
207
+ document.body.appendChild(this.modal);
208
+ this.embed(this.modal, true);
209
+ this.on(CMType.CANCEL, () => {
210
+ this.closeModal();
211
+ });
212
+
213
+ this.on(CMType.COMPLETE, () => {
214
+ this.closeModal();
215
+ });
216
+
217
+ this.modal.showModal();
218
+ this.modal.addEventListener('click', () => {
219
+ // console.log('click');
220
+ this.closeModal();
221
+ });
222
+ return this.completion();
223
+ }
224
+ /**
225
+ * Closes the modal dialog
226
+ *
227
+ */
228
+
229
+ closeModal(): void {
230
+ if (this.modal) {
231
+ // console.log('closeModal');
232
+ this.modal?.close();
233
+ this.modal.remove();
234
+ this.modal = undefined;
235
+ }
236
+ }
237
+ }