@easyflow/javascript-sdk 2.1.7

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 (93) hide show
  1. package/.babelrc +5 -0
  2. package/.github/workflows/deploy-sdk-cf.yml +49 -0
  3. package/.github/workflows/release-sdk-cdn.yml +144 -0
  4. package/.github/workflows/release-sdk.yml +112 -0
  5. package/.prettierrc +6 -0
  6. package/CDN-DEPLOYMENT.md +175 -0
  7. package/DEMO.md +258 -0
  8. package/DEPLOYMENT.md +224 -0
  9. package/INTEGRATION-GUIDE.md +521 -0
  10. package/README.md +1013 -0
  11. package/coverage/base.css +224 -0
  12. package/coverage/block-navigation.js +87 -0
  13. package/coverage/easyflow-javascript-sdk/index.html +116 -0
  14. package/coverage/easyflow-javascript-sdk/libs/constants.mjs.html +268 -0
  15. package/coverage/easyflow-javascript-sdk/libs/errors.mjs.html +271 -0
  16. package/coverage/easyflow-javascript-sdk/libs/exception-handler.mjs.html +148 -0
  17. package/coverage/easyflow-javascript-sdk/libs/fingerprint.mjs.html +895 -0
  18. package/coverage/easyflow-javascript-sdk/libs/http.mjs.html +502 -0
  19. package/coverage/easyflow-javascript-sdk/libs/index.html +266 -0
  20. package/coverage/easyflow-javascript-sdk/libs/logger.mjs.html +568 -0
  21. package/coverage/easyflow-javascript-sdk/libs/sanitizer.mjs.html +1099 -0
  22. package/coverage/easyflow-javascript-sdk/libs/security.mjs.html +733 -0
  23. package/coverage/easyflow-javascript-sdk/libs/types.mjs.html +508 -0
  24. package/coverage/easyflow-javascript-sdk/libs/utils.mjs.html +379 -0
  25. package/coverage/easyflow-javascript-sdk/libs/validator.mjs.html +2623 -0
  26. package/coverage/easyflow-javascript-sdk/sdk.mjs.html +2434 -0
  27. package/coverage/favicon.png +0 -0
  28. package/coverage/index.html +131 -0
  29. package/coverage/lcov-report/base.css +224 -0
  30. package/coverage/lcov-report/block-navigation.js +87 -0
  31. package/coverage/lcov-report/easyflow-javascript-sdk/index.html +116 -0
  32. package/coverage/lcov-report/easyflow-javascript-sdk/libs/constants.mjs.html +268 -0
  33. package/coverage/lcov-report/easyflow-javascript-sdk/libs/errors.mjs.html +271 -0
  34. package/coverage/lcov-report/easyflow-javascript-sdk/libs/exception-handler.mjs.html +148 -0
  35. package/coverage/lcov-report/easyflow-javascript-sdk/libs/fingerprint.mjs.html +895 -0
  36. package/coverage/lcov-report/easyflow-javascript-sdk/libs/http.mjs.html +502 -0
  37. package/coverage/lcov-report/easyflow-javascript-sdk/libs/index.html +266 -0
  38. package/coverage/lcov-report/easyflow-javascript-sdk/libs/logger.mjs.html +568 -0
  39. package/coverage/lcov-report/easyflow-javascript-sdk/libs/sanitizer.mjs.html +1099 -0
  40. package/coverage/lcov-report/easyflow-javascript-sdk/libs/security.mjs.html +733 -0
  41. package/coverage/lcov-report/easyflow-javascript-sdk/libs/types.mjs.html +508 -0
  42. package/coverage/lcov-report/easyflow-javascript-sdk/libs/utils.mjs.html +379 -0
  43. package/coverage/lcov-report/easyflow-javascript-sdk/libs/validator.mjs.html +2623 -0
  44. package/coverage/lcov-report/easyflow-javascript-sdk/sdk.mjs.html +2434 -0
  45. package/coverage/lcov-report/favicon.png +0 -0
  46. package/coverage/lcov-report/index.html +131 -0
  47. package/coverage/lcov-report/prettify.css +1 -0
  48. package/coverage/lcov-report/prettify.js +2 -0
  49. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  50. package/coverage/lcov-report/sorter.js +196 -0
  51. package/coverage/lcov.info +1429 -0
  52. package/coverage/prettify.css +1 -0
  53. package/coverage/prettify.js +2 -0
  54. package/coverage/sort-arrow-sprite.png +0 -0
  55. package/coverage/sorter.js +196 -0
  56. package/dist/435.easyflow-sdk.min.js +1 -0
  57. package/dist/easyflow-sdk.min.js +1 -0
  58. package/dist/easyflow-sdk.min.js.LICENSE.txt +1 -0
  59. package/dist/index.html +756 -0
  60. package/docs/index.html +775 -0
  61. package/examples/lovable-integration.html +410 -0
  62. package/index.html +981 -0
  63. package/jest.config.js +37 -0
  64. package/jsdoc.json +42 -0
  65. package/libs/auto-integration.mjs +333 -0
  66. package/libs/constants.mjs +61 -0
  67. package/libs/constants.spec.js +198 -0
  68. package/libs/errors.mjs +62 -0
  69. package/libs/errors.spec.js +178 -0
  70. package/libs/exception-handler.mjs +21 -0
  71. package/libs/exception-handler.spec.js +237 -0
  72. package/libs/fingerprint.mjs +270 -0
  73. package/libs/http.mjs +163 -0
  74. package/libs/http.spec.js +427 -0
  75. package/libs/integration-wrapper.mjs +285 -0
  76. package/libs/logger.mjs +161 -0
  77. package/libs/logger.spec.js +389 -0
  78. package/libs/sanitizer.mjs +340 -0
  79. package/libs/sanitizer.spec.js +583 -0
  80. package/libs/security.mjs +217 -0
  81. package/libs/types.mjs +141 -0
  82. package/libs/utils.mjs +368 -0
  83. package/libs/utils.spec.js +231 -0
  84. package/libs/validator.mjs +952 -0
  85. package/libs/validator.spec.js +615 -0
  86. package/mocks/offer.mock.js +77 -0
  87. package/package.json +72 -0
  88. package/scripts/publish-npm.sh +82 -0
  89. package/sdk.mjs +945 -0
  90. package/sdk.spec.js +796 -0
  91. package/test-setup.cjs +211 -0
  92. package/test.html +154 -0
  93. package/webpack.config.cjs +41 -0
@@ -0,0 +1,427 @@
1
+ import { jest } from '@jest/globals'
2
+
3
+ // Mock dependencies ESM-safe
4
+ await jest.unstable_mockModule('./utils.mjs', () => ({
5
+ buildApiUrl: jest.fn(),
6
+ getBrowserFingerprint: jest.fn(() => 'mock-fingerprint-123'),
7
+ }))
8
+
9
+ await jest.unstable_mockModule('./exception-handler.mjs', () => ({
10
+ throwsError: jest.fn((error) => {
11
+ throw error
12
+ }),
13
+ }))
14
+
15
+ await jest.unstable_mockModule('./security.mjs', () => ({
16
+ SecureFetch: {
17
+ request: jest.fn(),
18
+ },
19
+ }))
20
+
21
+ await jest.unstable_mockModule('./errors.mjs', () => ({
22
+ NetworkError: jest.fn().mockImplementation((message) => ({
23
+ name: 'NetworkError',
24
+ message,
25
+ code: 'NETWORK_ERROR',
26
+ })),
27
+ SecurityError: jest.fn().mockImplementation((message) => ({
28
+ name: 'SecurityError',
29
+ message,
30
+ code: 'SECURITY_VIOLATION',
31
+ })),
32
+ ValidationError: jest.fn().mockImplementation((message) => ({
33
+ name: 'ValidationError',
34
+ message,
35
+ code: 'VALIDATION_ERROR',
36
+ })),
37
+ }))
38
+
39
+ await jest.unstable_mockModule('./constants.mjs', () => ({
40
+ DEFAULT_CONFIG: {
41
+ baseUrl: 'https://api.example.com',
42
+ },
43
+ HTTP_REQUEST_METHODS: {
44
+ POST: 'POST',
45
+ },
46
+ TARGETS: {
47
+ CHARGE: 'charge',
48
+ PLACE_ORDER: 'place-order',
49
+ ENCRYPT: 'encrypt',
50
+ GET_OFFER: 'get-offer',
51
+ GET_ORDER: 'get-order',
52
+ },
53
+ }))
54
+
55
+ const { callSecureApi } = await import('./http.mjs')
56
+ const { buildApiUrl } = await import('./utils.mjs')
57
+ const { SecureFetch } = await import('./security.mjs')
58
+ const { throwsError } = await import('./exception-handler.mjs')
59
+
60
+ describe('HTTP Module', () => {
61
+ let mockBuildApiUrl
62
+ let mockSecureFetchRequest
63
+ let mockThrowsError
64
+
65
+ beforeEach(() => {
66
+ jest.clearAllMocks()
67
+ mockBuildApiUrl = buildApiUrl
68
+ mockSecureFetchRequest = SecureFetch.request
69
+ mockThrowsError = throwsError
70
+ })
71
+
72
+ describe('callSecureApi', () => {
73
+ test('should make GET request for get-offer target', async () => {
74
+ const mockResponse = { data: { id: 'offer-123' } }
75
+ mockSecureFetchRequest.mockResolvedValue({
76
+ json: jest.fn().mockResolvedValue(mockResponse),
77
+ })
78
+ mockBuildApiUrl.mockReturnValue(
79
+ 'https://api.example.com/api/proxy?target=get-offer&offerId=123'
80
+ )
81
+
82
+ const result = await callSecureApi('get-offer', { offerId: '123' })
83
+
84
+ expect(result).toEqual(mockResponse)
85
+ expect(mockBuildApiUrl).toHaveBeenCalledWith(
86
+ 'https://api.example.com',
87
+ 'get-offer',
88
+ { offerId: '123' }
89
+ )
90
+ expect(mockSecureFetchRequest).toHaveBeenCalledWith(
91
+ 'https://api.example.com/api/proxy?target=get-offer&offerId=123',
92
+ {
93
+ method: 'POST',
94
+ headers: { 'x-fingerprint-id': 'mock-fingerprint-123' },
95
+ }
96
+ )
97
+ })
98
+
99
+ test('should make GET request for get-order target', async () => {
100
+ const mockResponse = { data: { id: 'order-123' } }
101
+ mockSecureFetchRequest.mockResolvedValue({
102
+ json: jest.fn().mockResolvedValue(mockResponse),
103
+ })
104
+ mockBuildApiUrl.mockReturnValue(
105
+ 'https://api.example.com/api/proxy?target=get-order&orderId=123'
106
+ )
107
+
108
+ const result = await callSecureApi('get-order', { orderId: '123' })
109
+
110
+ expect(result).toEqual(mockResponse)
111
+ expect(mockBuildApiUrl).toHaveBeenCalledWith(
112
+ 'https://api.example.com',
113
+ 'get-order',
114
+ { orderId: '123' }
115
+ )
116
+ })
117
+
118
+ test('should make POST request for other targets', async () => {
119
+ const mockResponse = { data: { orderId: 'order-123' } }
120
+ mockSecureFetchRequest.mockResolvedValue({
121
+ json: jest.fn().mockResolvedValue(mockResponse),
122
+ })
123
+ mockBuildApiUrl.mockReturnValue(
124
+ 'https://api.example.com/api/proxy?target=charge'
125
+ )
126
+
127
+ const payload = { businessId: '123', amount: 1000 }
128
+ const result = await callSecureApi('charge', payload, {
129
+ 'Content-Type': 'application/json',
130
+ })
131
+
132
+ expect(result).toEqual(mockResponse)
133
+ expect(mockBuildApiUrl).toHaveBeenCalledWith(
134
+ 'https://api.example.com',
135
+ 'charge'
136
+ )
137
+ expect(mockSecureFetchRequest).toHaveBeenCalledWith(
138
+ 'https://api.example.com/api/proxy?target=charge',
139
+ {
140
+ method: 'POST',
141
+ headers: {
142
+ 'Content-Type': 'application/json',
143
+ 'x-fingerprint-id': 'mock-fingerprint-123',
144
+ },
145
+ body: JSON.stringify(payload),
146
+ }
147
+ )
148
+ })
149
+
150
+ test('should handle API error responses', async () => {
151
+ mockSecureFetchRequest.mockResolvedValue({
152
+ json: jest.fn().mockResolvedValue({ error: 'API Error' }),
153
+ })
154
+ mockBuildApiUrl.mockReturnValue(
155
+ 'https://api.example.com/api/proxy?target=charge'
156
+ )
157
+ await expect(callSecureApi('charge', {})).resolves.toEqual({
158
+ error: 'API Error',
159
+ })
160
+ })
161
+
162
+ test('should handle network errors', async () => {
163
+ mockSecureFetchRequest.mockResolvedValue({
164
+ json: jest.fn().mockResolvedValue({
165
+ error: 'Network error: Network timeout',
166
+ }),
167
+ })
168
+ mockBuildApiUrl.mockReturnValue(
169
+ 'https://api.example.com/api/proxy?target=charge'
170
+ )
171
+ await expect(callSecureApi('charge', {})).resolves.toEqual({
172
+ error: 'Network error: Network timeout',
173
+ })
174
+ })
175
+
176
+ test('should handle known SDK errors', async () => {
177
+ mockSecureFetchRequest.mockResolvedValue({
178
+ json: jest
179
+ .fn()
180
+ .mockResolvedValue({ error: 'Security violation' }),
181
+ })
182
+ mockBuildApiUrl.mockReturnValue(
183
+ 'https://api.example.com/api/proxy?target=charge'
184
+ )
185
+ await expect(callSecureApi('charge', {})).resolves.toEqual({
186
+ error: 'Security violation',
187
+ })
188
+ })
189
+
190
+ test('should handle validation errors', async () => {
191
+ mockSecureFetchRequest.mockResolvedValue({
192
+ json: jest.fn().mockResolvedValue({ error: 'Invalid input' }),
193
+ })
194
+ mockBuildApiUrl.mockReturnValue(
195
+ 'https://api.example.com/api/proxy?target=charge'
196
+ )
197
+ await expect(callSecureApi('charge', {})).resolves.toEqual({
198
+ error: 'Invalid input',
199
+ })
200
+ })
201
+
202
+ test('should handle network errors from SecureFetch', async () => {
203
+ mockSecureFetchRequest.mockResolvedValue({
204
+ json: jest.fn().mockResolvedValue({
205
+ error: 'HTTP 500: Internal Server Error',
206
+ }),
207
+ })
208
+ mockBuildApiUrl.mockReturnValue(
209
+ 'https://api.example.com/api/proxy?target=charge'
210
+ )
211
+ await expect(callSecureApi('charge', {})).resolves.toEqual({
212
+ error: 'HTTP 500: Internal Server Error',
213
+ })
214
+ })
215
+
216
+ test('should pass headers to SecureFetch', async () => {
217
+ const mockResponse = { data: { orderId: 'order-123' } }
218
+ mockSecureFetchRequest.mockResolvedValue({
219
+ json: jest.fn().mockResolvedValue(mockResponse),
220
+ })
221
+ mockBuildApiUrl.mockReturnValue(
222
+ 'https://api.example.com/api/proxy?target=charge'
223
+ )
224
+
225
+ const customHeaders = { 'Content-Type': 'application/json' }
226
+ const result = await callSecureApi(
227
+ 'charge',
228
+ { amount: 1000 },
229
+ customHeaders
230
+ )
231
+
232
+ expect(result).toEqual(mockResponse)
233
+ expect(mockSecureFetchRequest).toHaveBeenCalledWith(
234
+ 'https://api.example.com/api/proxy?target=charge',
235
+ {
236
+ method: 'POST',
237
+ headers: {
238
+ ...customHeaders,
239
+ 'x-fingerprint-id': 'mock-fingerprint-123',
240
+ },
241
+ body: JSON.stringify({ amount: 1000 }),
242
+ }
243
+ )
244
+ })
245
+
246
+ test('should handle empty headers', async () => {
247
+ const mockResponse = { data: { success: true } }
248
+ mockSecureFetchRequest.mockResolvedValue({
249
+ json: jest.fn().mockResolvedValue(mockResponse),
250
+ })
251
+ mockBuildApiUrl.mockReturnValue(
252
+ 'https://api.example.com/api/proxy?target=charge'
253
+ )
254
+
255
+ await callSecureApi('charge', { amount: 1000 }, {})
256
+
257
+ expect(mockSecureFetchRequest).toHaveBeenCalledWith(
258
+ 'https://api.example.com/api/proxy?target=charge',
259
+ {
260
+ method: 'POST',
261
+ headers: { 'x-fingerprint-id': 'mock-fingerprint-123' },
262
+ body: JSON.stringify({ amount: 1000 }),
263
+ }
264
+ )
265
+ })
266
+
267
+ test('should handle undefined headers', async () => {
268
+ const mockResponse = { data: { success: true } }
269
+ mockSecureFetchRequest.mockResolvedValue({
270
+ json: jest.fn().mockResolvedValue(mockResponse),
271
+ })
272
+ mockBuildApiUrl.mockReturnValue(
273
+ 'https://api.example.com/api/proxy?target=charge'
274
+ )
275
+
276
+ await callSecureApi('charge', { amount: 1000 })
277
+
278
+ expect(mockSecureFetchRequest).toHaveBeenCalledWith(
279
+ 'https://api.example.com/api/proxy?target=charge',
280
+ {
281
+ method: 'POST',
282
+ headers: { 'x-fingerprint-id': 'mock-fingerprint-123' },
283
+ body: JSON.stringify({ amount: 1000 }),
284
+ }
285
+ )
286
+ })
287
+
288
+ test('should handle complex payload objects', async () => {
289
+ const mockResponse = { data: { orderId: 'order-123' } }
290
+ mockSecureFetchRequest.mockResolvedValue({
291
+ json: jest.fn().mockResolvedValue(mockResponse),
292
+ })
293
+ mockBuildApiUrl.mockReturnValue(
294
+ 'https://api.example.com/api/proxy?target=place-order'
295
+ )
296
+
297
+ const complexPayload = {
298
+ businessId: 'business-123',
299
+ payments: [
300
+ {
301
+ method: 'credit-card',
302
+ creditCard: {
303
+ cardNumber: '4111111111111111',
304
+ holderName: 'John Doe',
305
+ month: '12',
306
+ year: '2025',
307
+ cvv: '123',
308
+ },
309
+ numberInstallments: 1,
310
+ },
311
+ ],
312
+ buyer: {
313
+ name: 'John Doe',
314
+ email: 'john@example.com',
315
+ },
316
+ }
317
+
318
+ await callSecureApi('place-order', complexPayload)
319
+
320
+ expect(mockSecureFetchRequest).toHaveBeenCalledWith(
321
+ 'https://api.example.com/api/proxy?target=place-order',
322
+ {
323
+ method: 'POST',
324
+ headers: { 'x-fingerprint-id': 'mock-fingerprint-123' },
325
+ body: JSON.stringify(complexPayload),
326
+ }
327
+ )
328
+ })
329
+
330
+ test('should handle empty payload for GET requests', async () => {
331
+ const mockResponse = { data: { id: 'offer-123' } }
332
+ mockSecureFetchRequest.mockResolvedValue({
333
+ json: jest.fn().mockResolvedValue(mockResponse),
334
+ })
335
+ mockBuildApiUrl.mockReturnValue(
336
+ 'https://api.example.com/api/proxy?target=get-offer'
337
+ )
338
+
339
+ await callSecureApi('get-offer', {})
340
+
341
+ expect(mockSecureFetchRequest).toHaveBeenCalledWith(
342
+ 'https://api.example.com/api/proxy?target=get-offer',
343
+ {
344
+ method: 'POST',
345
+ headers: { 'x-fingerprint-id': 'mock-fingerprint-123' },
346
+ }
347
+ )
348
+ })
349
+
350
+ test('should generate fingerprint when not provided in headers', async () => {
351
+ const mockResponse = { data: { success: true } }
352
+ mockSecureFetchRequest.mockResolvedValue({
353
+ json: jest.fn().mockResolvedValue(mockResponse),
354
+ })
355
+ mockBuildApiUrl.mockReturnValue(
356
+ 'https://api.example.com/api/proxy?target=charge'
357
+ )
358
+
359
+ // Call without fingerprint header
360
+ await callSecureApi('charge', { amount: 1000 })
361
+
362
+ expect(mockSecureFetchRequest).toHaveBeenCalledWith(
363
+ 'https://api.example.com/api/proxy?target=charge',
364
+ {
365
+ method: 'POST',
366
+ headers: { 'x-fingerprint-id': 'mock-fingerprint-123' },
367
+ body: JSON.stringify({ amount: 1000 }),
368
+ }
369
+ )
370
+ })
371
+
372
+ test('should preserve custom fingerprint when provided', async () => {
373
+ const mockResponse = { data: { success: true } }
374
+ mockSecureFetchRequest.mockResolvedValue({
375
+ json: jest.fn().mockResolvedValue(mockResponse),
376
+ })
377
+ mockBuildApiUrl.mockReturnValue(
378
+ 'https://api.example.com/api/proxy?target=charge'
379
+ )
380
+
381
+ // Call with custom fingerprint header
382
+ const customHeaders = {
383
+ 'x-fingerprint-id': 'custom-fingerprint-456',
384
+ }
385
+ await callSecureApi('charge', { amount: 1000 }, customHeaders)
386
+
387
+ expect(mockSecureFetchRequest).toHaveBeenCalledWith(
388
+ 'https://api.example.com/api/proxy?target=charge',
389
+ {
390
+ method: 'POST',
391
+ headers: { 'x-fingerprint-id': 'custom-fingerprint-456' },
392
+ body: JSON.stringify({ amount: 1000 }),
393
+ }
394
+ )
395
+ })
396
+
397
+ test('should handle fingerprint generation failure gracefully', async () => {
398
+ // Mock fingerprint generation to fail
399
+ const mockUtils = await import('./utils.mjs')
400
+ jest.spyOn(mockUtils, 'getBrowserFingerprint').mockImplementation(
401
+ () => {
402
+ throw new Error('Fingerprint generation failed')
403
+ }
404
+ )
405
+
406
+ const mockResponse = { data: { success: true } }
407
+ mockSecureFetchRequest.mockResolvedValue({
408
+ json: jest.fn().mockResolvedValue(mockResponse),
409
+ })
410
+ mockBuildApiUrl.mockReturnValue(
411
+ 'https://api.example.com/api/proxy?target=charge'
412
+ )
413
+
414
+ // Should still work without fingerprint
415
+ await callSecureApi('charge', { amount: 1000 })
416
+
417
+ expect(mockSecureFetchRequest).toHaveBeenCalledWith(
418
+ 'https://api.example.com/api/proxy?target=charge',
419
+ {
420
+ method: 'POST',
421
+ headers: {},
422
+ body: JSON.stringify({ amount: 1000 }),
423
+ }
424
+ )
425
+ })
426
+ })
427
+ })