@blocklet/payment-broker-client 1.20.17
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/examples/README.md +277 -0
- package/examples/browser/README.md +119 -0
- package/examples/browser/simple-example.js +223 -0
- package/examples/nodejs/README.md +180 -0
- package/examples/nodejs/deploy-example.mjs +200 -0
- package/examples/nodejs/package.json +30 -0
- package/lib/adapters/config/browser.d.ts +12 -0
- package/lib/adapters/config/browser.d.ts.map +1 -0
- package/lib/adapters/config/browser.js +48 -0
- package/lib/adapters/config/browser.js.map +1 -0
- package/lib/adapters/config/node.d.ts +13 -0
- package/lib/adapters/config/node.d.ts.map +1 -0
- package/lib/adapters/config/node.js +62 -0
- package/lib/adapters/config/node.js.map +1 -0
- package/lib/adapters/http/browser.d.ts +12 -0
- package/lib/adapters/http/browser.d.ts.map +1 -0
- package/lib/adapters/http/browser.js +50 -0
- package/lib/adapters/http/browser.js.map +1 -0
- package/lib/adapters/http/node.d.ts +12 -0
- package/lib/adapters/http/node.d.ts.map +1 -0
- package/lib/adapters/http/node.js +30 -0
- package/lib/adapters/http/node.js.map +1 -0
- package/lib/browser/adapters/config/browser.d.ts +12 -0
- package/lib/browser/adapters/config/browser.d.ts.map +1 -0
- package/lib/browser/adapters/config/node.d.ts +13 -0
- package/lib/browser/adapters/config/node.d.ts.map +1 -0
- package/lib/browser/adapters/http/browser.d.ts +12 -0
- package/lib/browser/adapters/http/browser.d.ts.map +1 -0
- package/lib/browser/adapters/http/node.d.ts +12 -0
- package/lib/browser/adapters/http/node.d.ts.map +1 -0
- package/lib/browser/browser/factory.d.ts +19 -0
- package/lib/browser/browser/factory.d.ts.map +1 -0
- package/lib/browser/browser/index.d.ts +9 -0
- package/lib/browser/browser/index.d.ts.map +1 -0
- package/lib/browser/core/client.d.ts +29 -0
- package/lib/browser/core/client.d.ts.map +1 -0
- package/lib/browser/core/deployment.d.ts +20 -0
- package/lib/browser/core/deployment.d.ts.map +1 -0
- package/lib/browser/core/errors.d.ts +15 -0
- package/lib/browser/core/errors.d.ts.map +1 -0
- package/lib/browser/core/index.d.ts +10 -0
- package/lib/browser/core/index.d.ts.map +1 -0
- package/lib/browser/core/interfaces.d.ts +24 -0
- package/lib/browser/core/interfaces.d.ts.map +1 -0
- package/lib/browser/core/logger.d.ts +8 -0
- package/lib/browser/core/logger.d.ts.map +1 -0
- package/lib/browser/core/polling.d.ts +23 -0
- package/lib/browser/core/polling.d.ts.map +1 -0
- package/lib/browser/core/session.d.ts +31 -0
- package/lib/browser/core/session.d.ts.map +1 -0
- package/lib/browser/core/types.d.ts +175 -0
- package/lib/browser/core/types.d.ts.map +1 -0
- package/lib/browser/core/utils.d.ts +27 -0
- package/lib/browser/core/utils.d.ts.map +1 -0
- package/lib/browser/factory.d.ts +19 -0
- package/lib/browser/factory.d.ts.map +1 -0
- package/lib/browser/factory.js +30 -0
- package/lib/browser/factory.js.map +1 -0
- package/lib/browser/index.d.ts +8 -0
- package/lib/browser/index.d.ts.map +1 -0
- package/lib/browser/index.js +973 -0
- package/lib/browser/index.js.map +1 -0
- package/lib/browser/node/factory.d.ts +9 -0
- package/lib/browser/node/factory.d.ts.map +1 -0
- package/lib/browser/node/index.d.ts +8 -0
- package/lib/browser/node/index.d.ts.map +1 -0
- package/lib/core/client.d.ts +29 -0
- package/lib/core/client.d.ts.map +1 -0
- package/lib/core/client.js +150 -0
- package/lib/core/client.js.map +1 -0
- package/lib/core/deployment.d.ts +20 -0
- package/lib/core/deployment.d.ts.map +1 -0
- package/lib/core/deployment.js +184 -0
- package/lib/core/deployment.js.map +1 -0
- package/lib/core/errors.d.ts +15 -0
- package/lib/core/errors.d.ts.map +1 -0
- package/lib/core/errors.js +21 -0
- package/lib/core/errors.js.map +1 -0
- package/lib/core/index.d.ts +10 -0
- package/lib/core/index.d.ts.map +1 -0
- package/lib/core/index.js +32 -0
- package/lib/core/index.js.map +1 -0
- package/lib/core/interfaces.d.ts +24 -0
- package/lib/core/interfaces.d.ts.map +1 -0
- package/lib/core/interfaces.js +4 -0
- package/lib/core/interfaces.js.map +1 -0
- package/lib/core/logger.d.ts +8 -0
- package/lib/core/logger.d.ts.map +1 -0
- package/lib/core/logger.js +39 -0
- package/lib/core/logger.js.map +1 -0
- package/lib/core/polling.d.ts +23 -0
- package/lib/core/polling.d.ts.map +1 -0
- package/lib/core/polling.js +97 -0
- package/lib/core/polling.js.map +1 -0
- package/lib/core/session.d.ts +31 -0
- package/lib/core/session.d.ts.map +1 -0
- package/lib/core/session.js +202 -0
- package/lib/core/session.js.map +1 -0
- package/lib/core/types.d.ts +175 -0
- package/lib/core/types.d.ts.map +1 -0
- package/lib/core/types.js +48 -0
- package/lib/core/types.js.map +1 -0
- package/lib/core/utils.d.ts +27 -0
- package/lib/core/utils.d.ts.map +1 -0
- package/lib/core/utils.js +181 -0
- package/lib/core/utils.js.map +1 -0
- package/lib/index.d.ts +484 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.esm.js +969 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/index.js +984 -0
- package/lib/index.js.map +1 -0
- package/lib/node/adapters/config/browser.d.ts +12 -0
- package/lib/node/adapters/config/browser.d.ts.map +1 -0
- package/lib/node/adapters/config/node.d.ts +13 -0
- package/lib/node/adapters/config/node.d.ts.map +1 -0
- package/lib/node/adapters/http/browser.d.ts +12 -0
- package/lib/node/adapters/http/browser.d.ts.map +1 -0
- package/lib/node/adapters/http/node.d.ts +12 -0
- package/lib/node/adapters/http/node.d.ts.map +1 -0
- package/lib/node/browser/factory.d.ts +19 -0
- package/lib/node/browser/factory.d.ts.map +1 -0
- package/lib/node/browser/index.d.ts +9 -0
- package/lib/node/browser/index.d.ts.map +1 -0
- package/lib/node/core/client.d.ts +29 -0
- package/lib/node/core/client.d.ts.map +1 -0
- package/lib/node/core/deployment.d.ts +20 -0
- package/lib/node/core/deployment.d.ts.map +1 -0
- package/lib/node/core/errors.d.ts +15 -0
- package/lib/node/core/errors.d.ts.map +1 -0
- package/lib/node/core/index.d.ts +10 -0
- package/lib/node/core/index.d.ts.map +1 -0
- package/lib/node/core/interfaces.d.ts +24 -0
- package/lib/node/core/interfaces.d.ts.map +1 -0
- package/lib/node/core/logger.d.ts +8 -0
- package/lib/node/core/logger.d.ts.map +1 -0
- package/lib/node/core/polling.d.ts +23 -0
- package/lib/node/core/polling.d.ts.map +1 -0
- package/lib/node/core/session.d.ts +31 -0
- package/lib/node/core/session.d.ts.map +1 -0
- package/lib/node/core/types.d.ts +175 -0
- package/lib/node/core/types.d.ts.map +1 -0
- package/lib/node/core/utils.d.ts +27 -0
- package/lib/node/core/utils.d.ts.map +1 -0
- package/lib/node/factory.d.ts +9 -0
- package/lib/node/factory.d.ts.map +1 -0
- package/lib/node/factory.js +34 -0
- package/lib/node/factory.js.map +1 -0
- package/lib/node/index.d.ts +8 -0
- package/lib/node/index.d.ts.map +1 -0
- package/lib/node/index.js +984 -0
- package/lib/node/index.js.map +1 -0
- package/lib/node/node/factory.d.ts +9 -0
- package/lib/node/node/factory.d.ts.map +1 -0
- package/lib/node/node/index.d.ts +8 -0
- package/lib/node/node/index.d.ts.map +1 -0
- package/package.json +98 -0
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
/* eslint-disable import/prefer-default-export */
|
|
2
|
+
// Deployment step constants
|
|
3
|
+
const STEPS = {
|
|
4
|
+
PAYMENT_PENDING: 'payment_pending',
|
|
5
|
+
PAYMENT_COMPLETED: 'payment_completed',
|
|
6
|
+
INSTALLATION_STARTING: 'installation_starting',
|
|
7
|
+
INSTALLATION_COMPLETED: 'installation_completed',
|
|
8
|
+
SERVICE_STARTING: 'service_starting',
|
|
9
|
+
SERVICE_READY: 'service_ready',
|
|
10
|
+
ACCESS_PREPARING: 'access_preparing',
|
|
11
|
+
ACCESS_READY: 'access_ready',
|
|
12
|
+
};
|
|
13
|
+
const DEFAULT_CONFIG = {
|
|
14
|
+
timeout: 300000, // 5 minutes
|
|
15
|
+
polling: {
|
|
16
|
+
interval: 3000, // 3 seconds
|
|
17
|
+
maxAttempts: 100,
|
|
18
|
+
backoffStrategy: 'linear',
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
const API_ENDPOINTS = {
|
|
22
|
+
createCheckout: '/api/checkout-sessions/start',
|
|
23
|
+
paymentPage: '/checkout/pay/{id}',
|
|
24
|
+
orderStatus: '/api/vendors/order/{id}/status',
|
|
25
|
+
orderDetail: '/api/vendors/order/{id}/detail',
|
|
26
|
+
vendors: '/api/sessions/{id}/vendors',
|
|
27
|
+
};
|
|
28
|
+
const DEPLOYMENT_STEPS = {
|
|
29
|
+
CREATING_SESSION: 'creating_session',
|
|
30
|
+
WAITING_PAYMENT: 'waiting_payment',
|
|
31
|
+
INSTALLING: 'installing',
|
|
32
|
+
STARTING_SERVICE: 'starting_service',
|
|
33
|
+
GETTING_URLS: 'getting_urls',
|
|
34
|
+
COMPLETED: 'completed',
|
|
35
|
+
};
|
|
36
|
+
const ERROR_CODES = {
|
|
37
|
+
NETWORK_ERROR: 'NETWORK_ERROR',
|
|
38
|
+
TIMEOUT_ERROR: 'TIMEOUT_ERROR',
|
|
39
|
+
PAYMENT_ERROR: 'PAYMENT_ERROR',
|
|
40
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
41
|
+
SESSION_ERROR: 'SESSION_ERROR',
|
|
42
|
+
DEPLOYMENT_ERROR: 'DEPLOYMENT_ERROR',
|
|
43
|
+
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/* eslint-disable import/prefer-default-export */
|
|
47
|
+
function createDeploymentError(message, code, options) {
|
|
48
|
+
const error = new Error(message);
|
|
49
|
+
error.code = code;
|
|
50
|
+
error.step = options?.step;
|
|
51
|
+
error.sessionId = options?.sessionId;
|
|
52
|
+
error.paymentUrl = options?.paymentUrl;
|
|
53
|
+
error.linkId = options?.linkId;
|
|
54
|
+
error.timestamp = new Date().toISOString();
|
|
55
|
+
error.details = options?.details;
|
|
56
|
+
error.recovery = options?.recovery;
|
|
57
|
+
return error;
|
|
58
|
+
}
|
|
59
|
+
function isDeploymentError(error) {
|
|
60
|
+
return error && typeof error === 'object' && 'code' in error;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* eslint-disable import/prefer-default-export */
|
|
64
|
+
const httpUtils = {
|
|
65
|
+
async request(url, options = {}) {
|
|
66
|
+
const response = await fetch(url, {
|
|
67
|
+
...options,
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
...options.headers,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw createDeploymentError(`HTTP ${response.status}: ${response.statusText}`, 'NETWORK_ERROR', {
|
|
75
|
+
details: { status: response.status, statusText: response.statusText },
|
|
76
|
+
recovery: {
|
|
77
|
+
canRetry: response.status >= 500, // Server errors can be retried
|
|
78
|
+
suggestions: response.status === 404
|
|
79
|
+
? ['Check if the URL is correct', 'Verify the service is running']
|
|
80
|
+
: ['Check your network connection', 'Try again later'],
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return response.json();
|
|
85
|
+
},
|
|
86
|
+
get(url, headers) {
|
|
87
|
+
return this.request(url, { method: 'GET', headers });
|
|
88
|
+
},
|
|
89
|
+
post(url, data, headers) {
|
|
90
|
+
return this.request(url, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
93
|
+
headers,
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
put(url, data, headers) {
|
|
97
|
+
return this.request(url, {
|
|
98
|
+
method: 'PUT',
|
|
99
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
100
|
+
headers,
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
delete(url, headers) {
|
|
104
|
+
return this.request(url, { method: 'DELETE', headers });
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
const urlUtils = {
|
|
108
|
+
join(...parts) {
|
|
109
|
+
return parts
|
|
110
|
+
.map((part, index) => {
|
|
111
|
+
if (index === 0) {
|
|
112
|
+
return part.replace(/\/+$/, '');
|
|
113
|
+
}
|
|
114
|
+
return part.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
115
|
+
})
|
|
116
|
+
.filter(Boolean)
|
|
117
|
+
.join('/');
|
|
118
|
+
},
|
|
119
|
+
addParams(url, params) {
|
|
120
|
+
const urlObj = new URL(url);
|
|
121
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
122
|
+
if (value !== undefined && value !== null) {
|
|
123
|
+
urlObj.searchParams.set(key, String(value));
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
return urlObj.toString();
|
|
127
|
+
},
|
|
128
|
+
replaceParams(url, params) {
|
|
129
|
+
let result = url;
|
|
130
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
131
|
+
result = result.replace(`:${key}`, value);
|
|
132
|
+
});
|
|
133
|
+
return result;
|
|
134
|
+
},
|
|
135
|
+
isValidUrl(url) {
|
|
136
|
+
try {
|
|
137
|
+
// eslint-disable-next-line no-new
|
|
138
|
+
new URL(url);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
const timeUtils = {
|
|
147
|
+
formatDuration(ms) {
|
|
148
|
+
const seconds = Math.floor(ms / 1000);
|
|
149
|
+
const minutes = Math.floor(seconds / 60);
|
|
150
|
+
const hours = Math.floor(minutes / 60);
|
|
151
|
+
if (hours > 0) {
|
|
152
|
+
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
|
|
153
|
+
}
|
|
154
|
+
if (minutes > 0) {
|
|
155
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
156
|
+
}
|
|
157
|
+
return `${seconds}s`;
|
|
158
|
+
},
|
|
159
|
+
sleep(ms) {
|
|
160
|
+
return new Promise((resolve) => {
|
|
161
|
+
setTimeout(resolve, ms);
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
timeout(promise, ms) {
|
|
165
|
+
return Promise.race([
|
|
166
|
+
promise,
|
|
167
|
+
new Promise((_, reject) => {
|
|
168
|
+
setTimeout(() => reject(createDeploymentError('Operation timed out', 'TIMEOUT_ERROR', {
|
|
169
|
+
recovery: {
|
|
170
|
+
canRetry: true,
|
|
171
|
+
suggestions: ['Try again with a longer timeout', 'Check your network connection'],
|
|
172
|
+
},
|
|
173
|
+
})), ms);
|
|
174
|
+
}),
|
|
175
|
+
]);
|
|
176
|
+
},
|
|
177
|
+
now() {
|
|
178
|
+
return new Date().toISOString();
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
const objectUtils = {
|
|
182
|
+
deepMerge(target, ...sources) {
|
|
183
|
+
if (!sources.length)
|
|
184
|
+
return target;
|
|
185
|
+
const source = sources.shift();
|
|
186
|
+
if (this.isObject(target) && this.isObject(source)) {
|
|
187
|
+
Object.keys(source).forEach((key) => {
|
|
188
|
+
if (this.isObject(source[key])) {
|
|
189
|
+
if (!target[key])
|
|
190
|
+
Object.assign(target, { [key]: {} });
|
|
191
|
+
this.deepMerge(target[key], source[key]);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
Object.assign(target, { [key]: source[key] });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return this.deepMerge(target, ...sources);
|
|
199
|
+
},
|
|
200
|
+
isObject(item) {
|
|
201
|
+
return item && typeof item === 'object' && !Array.isArray(item);
|
|
202
|
+
},
|
|
203
|
+
pick(obj, keys) {
|
|
204
|
+
const result = {};
|
|
205
|
+
keys.forEach((key) => {
|
|
206
|
+
if (key in obj) {
|
|
207
|
+
result[key] = obj[key];
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
return result;
|
|
211
|
+
},
|
|
212
|
+
omit(obj, keys) {
|
|
213
|
+
const result = { ...obj };
|
|
214
|
+
keys.forEach((key) => {
|
|
215
|
+
delete result[key];
|
|
216
|
+
});
|
|
217
|
+
return result;
|
|
218
|
+
},
|
|
219
|
+
deepClone(obj) {
|
|
220
|
+
if (obj === null || typeof obj !== 'object') {
|
|
221
|
+
return obj;
|
|
222
|
+
}
|
|
223
|
+
if (obj instanceof Date) {
|
|
224
|
+
return new Date(obj.getTime());
|
|
225
|
+
}
|
|
226
|
+
if (obj instanceof Array) {
|
|
227
|
+
return obj.map((item) => this.deepClone(item));
|
|
228
|
+
}
|
|
229
|
+
if (typeof obj === 'object') {
|
|
230
|
+
const cloned = {};
|
|
231
|
+
Object.keys(obj).forEach((key) => {
|
|
232
|
+
cloned[key] = this.deepClone(obj[key]);
|
|
233
|
+
});
|
|
234
|
+
return cloned;
|
|
235
|
+
}
|
|
236
|
+
return obj;
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/* eslint-disable import/prefer-default-export */
|
|
241
|
+
// Internal debug logger - only logs when BROKER_CLIENT_DEBUG=true
|
|
242
|
+
const isDebugEnabled = (() => {
|
|
243
|
+
try {
|
|
244
|
+
return typeof process !== 'undefined' && process.env?.BROKER_CLIENT_DEBUG === 'true';
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// In browser environments, check localStorage or global variables
|
|
248
|
+
return typeof window !== 'undefined' && window.BROKER_CLIENT_DEBUG === 'true';
|
|
249
|
+
}
|
|
250
|
+
})();
|
|
251
|
+
const logger = {
|
|
252
|
+
debug: (message, data) => {
|
|
253
|
+
if (isDebugEnabled) {
|
|
254
|
+
// eslint-disable-next-line no-console
|
|
255
|
+
console.log(`[BROKER-CLIENT-DEBUG] ${message}`, data ? JSON.stringify(data) : '');
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
info: (message, data) => {
|
|
259
|
+
if (isDebugEnabled) {
|
|
260
|
+
// eslint-disable-next-line no-console
|
|
261
|
+
console.log(`[BROKER-CLIENT-DEBUG] ${message}`, data ? JSON.stringify(data) : '');
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
warn: (message, data) => {
|
|
265
|
+
if (isDebugEnabled) {
|
|
266
|
+
console.warn(`[BROKER-CLIENT-DEBUG] ${message}`, data ? JSON.stringify(data) : '');
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
error: (message, data) => {
|
|
270
|
+
if (isDebugEnabled) {
|
|
271
|
+
console.error(`[BROKER-CLIENT-DEBUG] ${message}`, data ? JSON.stringify(data) : '');
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/* eslint-disable import/prefer-default-export */
|
|
277
|
+
class SessionManager {
|
|
278
|
+
constructor(config, deps) {
|
|
279
|
+
this.config = config;
|
|
280
|
+
this.deps = deps;
|
|
281
|
+
}
|
|
282
|
+
async getConfigInfo() {
|
|
283
|
+
if (this.cachedConfig) {
|
|
284
|
+
return this.cachedConfig;
|
|
285
|
+
}
|
|
286
|
+
const blockletInfo = await this.deps.configProvider.getBlockletInfo();
|
|
287
|
+
const paymentLinkKey = this.config.paymentLinkKey || 'PAYMENT_LINK_ID';
|
|
288
|
+
const linkId = blockletInfo[paymentLinkKey];
|
|
289
|
+
if (!linkId) {
|
|
290
|
+
throw createDeploymentError(`Payment link ID not found using key '${paymentLinkKey}' in blocklet configuration`, 'VALIDATION_ERROR', {
|
|
291
|
+
step: 'creating_session',
|
|
292
|
+
recovery: {
|
|
293
|
+
canRetry: false,
|
|
294
|
+
suggestions: [
|
|
295
|
+
`Make sure ${paymentLinkKey} is set in the environment`,
|
|
296
|
+
'Check if the Payment Kit is properly configured',
|
|
297
|
+
'Verify the paymentLinkKey configuration is correct',
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const paymentKitDid = 'z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk';
|
|
303
|
+
let mountPoint = '/';
|
|
304
|
+
if (blockletInfo.componentMountPoints && Array.isArray(blockletInfo.componentMountPoints)) {
|
|
305
|
+
const paymentKitComponent = blockletInfo.componentMountPoints.find((component) => component.did === paymentKitDid || component.appId === paymentKitDid);
|
|
306
|
+
if (paymentKitComponent?.mountPoint) {
|
|
307
|
+
mountPoint = paymentKitComponent.mountPoint;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
this.cachedConfig = { linkId, mountPoint, paymentKitDid };
|
|
311
|
+
logger.info('Configuration processed', this.cachedConfig);
|
|
312
|
+
return this.cachedConfig;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get linkId
|
|
316
|
+
*/
|
|
317
|
+
async getLinkId() {
|
|
318
|
+
const { linkId } = await this.getConfigInfo();
|
|
319
|
+
return linkId;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get mountPoint
|
|
323
|
+
*/
|
|
324
|
+
async getMountPoint() {
|
|
325
|
+
const { mountPoint } = await this.getConfigInfo();
|
|
326
|
+
return mountPoint;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get paymentKitDid
|
|
330
|
+
*/
|
|
331
|
+
async getPaymentKitDid() {
|
|
332
|
+
const { paymentKitDid } = await this.getConfigInfo();
|
|
333
|
+
return paymentKitDid;
|
|
334
|
+
}
|
|
335
|
+
async checkCacheCheckoutId(checkoutId) {
|
|
336
|
+
try {
|
|
337
|
+
if (!checkoutId) {
|
|
338
|
+
return '';
|
|
339
|
+
}
|
|
340
|
+
// API_ENDPOINTS already includes /api prefix
|
|
341
|
+
const orderStatusUrl = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderStatus.replace('{id}', checkoutId));
|
|
342
|
+
const response = await this.deps.httpClient.get(orderStatusUrl);
|
|
343
|
+
if (response.error) {
|
|
344
|
+
throw new Error(response.error);
|
|
345
|
+
}
|
|
346
|
+
// Check payment status
|
|
347
|
+
const isPaid = response.payment_status === 'paid';
|
|
348
|
+
return isPaid ? checkoutId : '';
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
return '';
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async createSession(options = {}) {
|
|
355
|
+
const { linkId, mountPoint, paymentKitDid } = await this.getConfigInfo();
|
|
356
|
+
logger.info('Creating payment session', { linkId, mountPoint, paymentKitDid });
|
|
357
|
+
try {
|
|
358
|
+
// Build URL - API_ENDPOINTS already includes /api prefix
|
|
359
|
+
const url = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.createCheckout, linkId);
|
|
360
|
+
const { needShortUrl = true, page_info: pageInfo } = options;
|
|
361
|
+
const payload = {
|
|
362
|
+
needShortUrl,
|
|
363
|
+
metadata: {
|
|
364
|
+
page_info: {
|
|
365
|
+
has_vendor: true,
|
|
366
|
+
...pageInfo,
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
const response = await timeUtils.timeout(this.deps.httpClient.post(url, payload), this.config.timeout || 30000);
|
|
371
|
+
const session = {
|
|
372
|
+
id: response.checkoutSession.id,
|
|
373
|
+
paymentUrl: response.paymentUrl,
|
|
374
|
+
status: 'pending',
|
|
375
|
+
linkId,
|
|
376
|
+
vendorDid: paymentKitDid,
|
|
377
|
+
createdAt: timeUtils.now(),
|
|
378
|
+
updatedAt: timeUtils.now(),
|
|
379
|
+
expiresAt: new Date(Date.now() + (this.config.timeout || 300000)).toISOString(),
|
|
380
|
+
currency: 'USD',
|
|
381
|
+
};
|
|
382
|
+
logger.info('Session created successfully', { sessionId: session.id });
|
|
383
|
+
return session;
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
logger.error('Failed to create session', { error: error.message, linkId });
|
|
387
|
+
throw createDeploymentError(`Failed to create session: ${error.message}`, 'SESSION_ERROR', {
|
|
388
|
+
step: 'creating_session',
|
|
389
|
+
linkId,
|
|
390
|
+
details: { originalError: error },
|
|
391
|
+
recovery: {
|
|
392
|
+
canRetry: true,
|
|
393
|
+
suggestions: ['Check your network connection', 'Verify the Payment Kit service is available'],
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
async getSessionStatus(sessionId) {
|
|
399
|
+
logger.debug('Getting session status', { sessionId });
|
|
400
|
+
try {
|
|
401
|
+
// API_ENDPOINTS already includes /api prefix
|
|
402
|
+
const url = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderStatus.replace('{id}', sessionId));
|
|
403
|
+
const response = await timeUtils.timeout(this.deps.httpClient.get(url), this.config.timeout || 30000);
|
|
404
|
+
logger.debug('Session status retrieved', { sessionId, status: response.payment_status });
|
|
405
|
+
return response;
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
logger.error('Failed to get session status', { error: error.message, sessionId });
|
|
409
|
+
throw createDeploymentError(`Failed to get session status: ${error.message}`, 'SESSION_ERROR', {
|
|
410
|
+
sessionId,
|
|
411
|
+
recovery: {
|
|
412
|
+
canRetry: true,
|
|
413
|
+
suggestions: ['Check your network connection', 'Verify the session ID is correct'],
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
waitForPayment(sessionId) {
|
|
419
|
+
logger.info('Waiting for payment completion', { sessionId });
|
|
420
|
+
return this.deps.pollingManager.poll({
|
|
421
|
+
checkCondition: async () => {
|
|
422
|
+
const status = await this.getSessionStatus(sessionId);
|
|
423
|
+
if (status.payment_status === 'paid') {
|
|
424
|
+
logger.info('Payment completed', { sessionId });
|
|
425
|
+
return status.vendors || true;
|
|
426
|
+
}
|
|
427
|
+
if (status.payment_status === 'expired' || status.payment_status === 'cancelled') {
|
|
428
|
+
throw createDeploymentError(`Payment ${status.payment_status}`, 'SESSION_ERROR', {
|
|
429
|
+
sessionId,
|
|
430
|
+
recovery: {
|
|
431
|
+
canRetry: status.payment_status === 'expired',
|
|
432
|
+
suggestions: status.payment_status === 'expired'
|
|
433
|
+
? ['Create a new payment session', 'Try the deployment again']
|
|
434
|
+
: ['Payment was cancelled', 'Start a new deployment if needed'],
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return false;
|
|
439
|
+
},
|
|
440
|
+
stepName: 'waiting for payment',
|
|
441
|
+
maxAttempts: this.config.polling?.maxAttempts || 100,
|
|
442
|
+
interval: this.config.polling?.interval || 3000,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
async getOrderDetails(sessionId) {
|
|
446
|
+
logger.debug('Getting order details', { sessionId });
|
|
447
|
+
try {
|
|
448
|
+
// API_ENDPOINTS already includes /api prefix
|
|
449
|
+
const url = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderDetail.replace('{id}', sessionId));
|
|
450
|
+
const response = await timeUtils.timeout(this.deps.httpClient.get(url), this.config.timeout || 30000);
|
|
451
|
+
logger.debug('Order details retrieved', { sessionId });
|
|
452
|
+
return response;
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
logger.error('Failed to get order details', { error: error.message, sessionId });
|
|
456
|
+
throw createDeploymentError(`Failed to get order details: ${error.message}`, 'SESSION_ERROR', {
|
|
457
|
+
sessionId,
|
|
458
|
+
recovery: {
|
|
459
|
+
canRetry: true,
|
|
460
|
+
suggestions: ['Check your network connection', 'Verify the session is still active'],
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/* eslint-disable import/prefer-default-export */
|
|
468
|
+
/**
|
|
469
|
+
* Unified DeploymentManager class
|
|
470
|
+
* Uses dependency injection to support different environment implementations
|
|
471
|
+
*/
|
|
472
|
+
class DeploymentManager {
|
|
473
|
+
constructor(config, deps) {
|
|
474
|
+
this.config = config;
|
|
475
|
+
this.deps = deps;
|
|
476
|
+
this.installationStarted = false;
|
|
477
|
+
this.serviceStartCallbackCalled = false;
|
|
478
|
+
this.currentDeployment = null;
|
|
479
|
+
}
|
|
480
|
+
async startDeployment(sessionId, options) {
|
|
481
|
+
this.currentDeployment = {
|
|
482
|
+
sessionId,
|
|
483
|
+
startTime: timeUtils.now(),
|
|
484
|
+
};
|
|
485
|
+
this.installationStarted = false;
|
|
486
|
+
this.serviceStartCallbackCalled = false;
|
|
487
|
+
logger.info('Starting deployment', { sessionId });
|
|
488
|
+
try {
|
|
489
|
+
await this.waitForPaymentCompletion(sessionId, options);
|
|
490
|
+
const vendors = await this.waitForInstallation(sessionId, options);
|
|
491
|
+
await this.waitForServiceStartup(vendors, options);
|
|
492
|
+
const result = await this.getFinalUrls(sessionId, vendors, options);
|
|
493
|
+
this.currentDeployment.result = result;
|
|
494
|
+
logger.info('Deployment completed successfully', { sessionId, duration: result.duration });
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
logger.error('Deployment failed', { sessionId, error: error.message });
|
|
499
|
+
this.currentDeployment.error = error;
|
|
500
|
+
options.onError?.(error, 'getting_urls'); // Call onError
|
|
501
|
+
throw error;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
async waitForPaymentCompletion(sessionId, options) {
|
|
505
|
+
logger.info('Waiting for payment to be marked as paid', { sessionId });
|
|
506
|
+
await this.deps.pollingManager.poll({
|
|
507
|
+
checkCondition: async () => {
|
|
508
|
+
// API_ENDPOINTS already includes /api prefix
|
|
509
|
+
const orderStatusUrl = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderStatus.replace('{id}', sessionId));
|
|
510
|
+
const response = await this.deps.httpClient.get(orderStatusUrl);
|
|
511
|
+
if (response.session_status === 'complete') {
|
|
512
|
+
const vendors = response.vendors || [];
|
|
513
|
+
await options.hooks?.[STEPS.PAYMENT_COMPLETED]?.({
|
|
514
|
+
sessionId,
|
|
515
|
+
vendors,
|
|
516
|
+
});
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
return false;
|
|
520
|
+
},
|
|
521
|
+
stepName: 'waiting_payment',
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
waitForInstallation(sessionId, options) {
|
|
525
|
+
logger.info('Waiting for installation to start', { sessionId });
|
|
526
|
+
return this.deps.pollingManager.poll({
|
|
527
|
+
checkCondition: async () => {
|
|
528
|
+
// API_ENDPOINTS already includes /api prefix
|
|
529
|
+
const url = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderStatus.replace('{id}', sessionId));
|
|
530
|
+
const response = await this.deps.httpClient.get(url);
|
|
531
|
+
const vendors = response.vendors || [];
|
|
532
|
+
if (vendors.length === 0) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
if (vendors.length > 0 && !this.installationStarted) {
|
|
536
|
+
this.installationStarted = true;
|
|
537
|
+
await options.hooks?.[STEPS.INSTALLATION_STARTING]?.({
|
|
538
|
+
vendors,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
// Check if all vendors meet conditions: progress >= 80 and appUrl exists
|
|
542
|
+
const isInstalled = vendors.every((vendor) => vendor.progress >= 80 && vendor.appUrl);
|
|
543
|
+
if (isInstalled) {
|
|
544
|
+
const avgProgress = vendors.reduce((sum, v) => sum + v.progress, 0) / vendors.length;
|
|
545
|
+
await options.hooks?.[STEPS.INSTALLATION_COMPLETED]?.({
|
|
546
|
+
vendors,
|
|
547
|
+
progress: Math.round(avgProgress),
|
|
548
|
+
});
|
|
549
|
+
return vendors;
|
|
550
|
+
}
|
|
551
|
+
return false;
|
|
552
|
+
},
|
|
553
|
+
stepName: 'installation_started',
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
async waitForServiceStartup(vendors, options) {
|
|
557
|
+
logger.info('Services starting', { vendorCount: vendors.length });
|
|
558
|
+
// Since we don't need to check service status, we can directly call the callbacks
|
|
559
|
+
await options.hooks?.[STEPS.SERVICE_STARTING]?.({
|
|
560
|
+
vendors,
|
|
561
|
+
});
|
|
562
|
+
// Wait a brief moment for services to initialize
|
|
563
|
+
await new Promise((resolve) => {
|
|
564
|
+
setTimeout(resolve, 2000);
|
|
565
|
+
});
|
|
566
|
+
logger.info('Services started successfully');
|
|
567
|
+
await options.hooks?.[STEPS.SERVICE_READY]?.({
|
|
568
|
+
vendors,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
async getFinalUrls(sessionId, runningVendors, options) {
|
|
572
|
+
try {
|
|
573
|
+
await options.hooks?.[STEPS.ACCESS_PREPARING]?.({
|
|
574
|
+
sessionId,
|
|
575
|
+
});
|
|
576
|
+
// API_ENDPOINTS already includes /api prefix
|
|
577
|
+
const orderDetailUrl = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderDetail.replace('{id}', sessionId));
|
|
578
|
+
const data = await this.deps.httpClient.get(orderDetailUrl);
|
|
579
|
+
if (data.vendors.length === 0) {
|
|
580
|
+
throw createDeploymentError('No vendors found in order details', 'DEPLOYMENT_ERROR', {
|
|
581
|
+
step: 'getting_urls',
|
|
582
|
+
sessionId,
|
|
583
|
+
recovery: {
|
|
584
|
+
canRetry: true,
|
|
585
|
+
suggestions: ['Wait a moment and try again', 'Check if the deployment completed successfully'],
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
// Wait 3 seconds like in the original code
|
|
590
|
+
await timeUtils.sleep(3000);
|
|
591
|
+
// Return the URLs from the API response, fallback to running vendor data
|
|
592
|
+
const apiVendor = data.vendors[0];
|
|
593
|
+
const primaryVendor = runningVendors[0];
|
|
594
|
+
const endTime = timeUtils.now();
|
|
595
|
+
const duration = this.currentDeployment.startTime
|
|
596
|
+
? new Date(endTime).getTime() - new Date(this.currentDeployment.startTime).getTime()
|
|
597
|
+
: 0;
|
|
598
|
+
const result = {
|
|
599
|
+
success: true,
|
|
600
|
+
sessionId,
|
|
601
|
+
appUrl: apiVendor?.appUrl || primaryVendor?.appUrl,
|
|
602
|
+
dashboardUrl: apiVendor?.dashboardUrl || primaryVendor?.dashboardUrl,
|
|
603
|
+
homeUrl: apiVendor?.homeUrl || primaryVendor?.homeUrl,
|
|
604
|
+
token: apiVendor?.token || primaryVendor?.token,
|
|
605
|
+
subscriptionUrl: data.subscriptionUrl,
|
|
606
|
+
vendors: data.vendors || runningVendors,
|
|
607
|
+
duration,
|
|
608
|
+
startTime: this.currentDeployment.startTime || timeUtils.now(),
|
|
609
|
+
endTime,
|
|
610
|
+
metadata: data.metadata,
|
|
611
|
+
data: data.data,
|
|
612
|
+
};
|
|
613
|
+
await options.hooks?.[STEPS.ACCESS_READY]?.({
|
|
614
|
+
sessionId,
|
|
615
|
+
appUrl: result.appUrl,
|
|
616
|
+
dashboardUrl: result.dashboardUrl,
|
|
617
|
+
homeUrl: result.homeUrl,
|
|
618
|
+
subscriptionUrl: result.subscriptionUrl,
|
|
619
|
+
});
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
logger.error('Failed to get final URLs', { sessionId, error: error.message });
|
|
624
|
+
// If getting details fails, use the appUrl of running vendor
|
|
625
|
+
return {
|
|
626
|
+
success: true,
|
|
627
|
+
sessionId,
|
|
628
|
+
appUrl: runningVendors[0]?.appUrl || null,
|
|
629
|
+
dashboardUrl: runningVendors[0]?.dashboardUrl || null,
|
|
630
|
+
homeUrl: runningVendors[0]?.homeUrl || null,
|
|
631
|
+
token: runningVendors[0]?.token || null,
|
|
632
|
+
duration: this.currentDeployment.startTime
|
|
633
|
+
? new Date().getTime() - new Date(this.currentDeployment.startTime).getTime()
|
|
634
|
+
: 0,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Unified BrokerClient class
|
|
642
|
+
* Uses dependency injection to support different environment implementations
|
|
643
|
+
*/
|
|
644
|
+
class BrokerClient {
|
|
645
|
+
constructor(config, dependencies) {
|
|
646
|
+
this.isDeploying = false;
|
|
647
|
+
this.currentDeployment = null;
|
|
648
|
+
this.validateConfig(config);
|
|
649
|
+
this.config = objectUtils.deepMerge({}, DEFAULT_CONFIG, config);
|
|
650
|
+
this.sessionManager = new SessionManager(this.config, dependencies);
|
|
651
|
+
this.deploymentManager = new DeploymentManager(this.config, dependencies);
|
|
652
|
+
}
|
|
653
|
+
async deploy(options) {
|
|
654
|
+
if (this.isDeploying) {
|
|
655
|
+
throw createDeploymentError('Deployment already in progress', 'VALIDATION_ERROR');
|
|
656
|
+
}
|
|
657
|
+
this.isDeploying = true;
|
|
658
|
+
this.currentDeployment = {
|
|
659
|
+
sessionId: '', // Will be set after session creation
|
|
660
|
+
startTime: timeUtils.now(),
|
|
661
|
+
};
|
|
662
|
+
try {
|
|
663
|
+
let sessionId;
|
|
664
|
+
let paymentUrl = options.cachedPaymentUrl;
|
|
665
|
+
// Check cached checkout session
|
|
666
|
+
if (options.cachedCheckoutId) {
|
|
667
|
+
logger.info('Checking cached checkout session', { cachedCheckoutId: options.cachedCheckoutId });
|
|
668
|
+
const cachedCheckoutId = await this.sessionManager.checkCacheCheckoutId(options.cachedCheckoutId);
|
|
669
|
+
if (cachedCheckoutId) {
|
|
670
|
+
sessionId = cachedCheckoutId;
|
|
671
|
+
logger.info('Using cached session', { sessionId });
|
|
672
|
+
// Build payment URL if not cached
|
|
673
|
+
if (!paymentUrl) {
|
|
674
|
+
const mountPoint = await this.sessionManager.getMountPoint();
|
|
675
|
+
const basePath = `${this.config.baseUrl}${mountPoint}`;
|
|
676
|
+
paymentUrl = `${basePath}/checkout/pay/${sessionId}`;
|
|
677
|
+
}
|
|
678
|
+
await options.hooks?.[STEPS.PAYMENT_PENDING]?.({
|
|
679
|
+
sessionId,
|
|
680
|
+
paymentUrl,
|
|
681
|
+
isResuming: true,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// Create new session if no valid cached session
|
|
686
|
+
if (!sessionId) {
|
|
687
|
+
logger.info('Creating new checkout session');
|
|
688
|
+
const linkId = await this.sessionManager.getLinkId();
|
|
689
|
+
const session = await this.sessionManager.createSession(options);
|
|
690
|
+
sessionId = session.id;
|
|
691
|
+
paymentUrl = session.paymentUrl;
|
|
692
|
+
await options.hooks?.[STEPS.PAYMENT_PENDING]?.({
|
|
693
|
+
sessionId: session.id,
|
|
694
|
+
paymentUrl: session.paymentUrl,
|
|
695
|
+
linkId,
|
|
696
|
+
isResuming: false,
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
this.currentDeployment.sessionId = sessionId;
|
|
700
|
+
const result = await this.deploymentManager.startDeployment(sessionId, options);
|
|
701
|
+
return result;
|
|
702
|
+
}
|
|
703
|
+
catch (error) {
|
|
704
|
+
this.isDeploying = false;
|
|
705
|
+
if (this.currentDeployment) {
|
|
706
|
+
if (isDeploymentError(error)) {
|
|
707
|
+
this.currentDeployment.error = error;
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
this.currentDeployment.error = createDeploymentError(error.message, 'UNKNOWN_ERROR', {
|
|
711
|
+
sessionId: this.currentDeployment.sessionId,
|
|
712
|
+
details: { originalError: error },
|
|
713
|
+
recovery: {
|
|
714
|
+
canRetry: true,
|
|
715
|
+
suggestions: ['Check your network connection', 'Verify the baseUrl is correct'],
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (options.onError) {
|
|
721
|
+
const deploymentError = isDeploymentError(error) ? error : this.currentDeployment?.error;
|
|
722
|
+
if (deploymentError) {
|
|
723
|
+
options.onError(deploymentError, deploymentError.step);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
throw error;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
createSession(options = {}) {
|
|
730
|
+
return this.sessionManager.createSession(options);
|
|
731
|
+
}
|
|
732
|
+
getSessionStatus(sessionId) {
|
|
733
|
+
return this.sessionManager.getSessionStatus(sessionId);
|
|
734
|
+
}
|
|
735
|
+
cancelDeployment() {
|
|
736
|
+
if (this.isDeploying) {
|
|
737
|
+
logger.info('Cancelling deployment');
|
|
738
|
+
this.isDeploying = false;
|
|
739
|
+
// Note: Actual cancellation would need to be implemented in deployment manager
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
getDeploymentStatus() {
|
|
743
|
+
const result = this.currentDeployment?.result;
|
|
744
|
+
const error = this.currentDeployment?.error;
|
|
745
|
+
const sessionId = this.currentDeployment?.sessionId;
|
|
746
|
+
const startTime = this.currentDeployment?.startTime;
|
|
747
|
+
return {
|
|
748
|
+
isDeploying: this.isDeploying,
|
|
749
|
+
result,
|
|
750
|
+
error,
|
|
751
|
+
sessionId,
|
|
752
|
+
duration: startTime ? new Date().getTime() - new Date(startTime).getTime() : 0,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
updateConfig(newConfig) {
|
|
756
|
+
this.config = objectUtils.deepMerge(this.config, newConfig);
|
|
757
|
+
// Note: In a real implementation, we might need to recreate the dependencies
|
|
758
|
+
// For now, we assume the dependencies remain the same
|
|
759
|
+
}
|
|
760
|
+
getConfig() {
|
|
761
|
+
return this.config;
|
|
762
|
+
}
|
|
763
|
+
validateConfig(config) {
|
|
764
|
+
if (!config.baseUrl) {
|
|
765
|
+
throw createDeploymentError('baseUrl is required in configuration', 'VALIDATION_ERROR');
|
|
766
|
+
}
|
|
767
|
+
try {
|
|
768
|
+
// eslint-disable-next-line no-new
|
|
769
|
+
new URL(config.baseUrl);
|
|
770
|
+
}
|
|
771
|
+
catch {
|
|
772
|
+
throw createDeploymentError('Invalid baseUrl format', 'VALIDATION_ERROR');
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/* eslint-disable import/prefer-default-export */
|
|
778
|
+
class PollingManager {
|
|
779
|
+
constructor(options = {}) {
|
|
780
|
+
this.baseInterval = 1000;
|
|
781
|
+
this.maxAttempts = 60;
|
|
782
|
+
this.backoffStrategy = 'linear';
|
|
783
|
+
this.baseInterval = options.baseInterval || 1000;
|
|
784
|
+
this.maxAttempts = options.maxAttempts || 60;
|
|
785
|
+
this.backoffStrategy = options.backoffStrategy || 'linear';
|
|
786
|
+
}
|
|
787
|
+
async poll(options) {
|
|
788
|
+
const { checkCondition, stepName = 'polling', interval = this.baseInterval, maxAttempts = this.maxAttempts, backoffStrategy = this.backoffStrategy, onProgress, } = options;
|
|
789
|
+
for (let attempts = 1; attempts <= maxAttempts; attempts++) {
|
|
790
|
+
try {
|
|
791
|
+
logger.debug(`${stepName} - Attempt ${attempts}/${maxAttempts}`);
|
|
792
|
+
// eslint-disable-next-line no-await-in-loop
|
|
793
|
+
const result = await checkCondition();
|
|
794
|
+
if (result !== null && result !== false && result !== undefined) {
|
|
795
|
+
logger.debug(`${stepName} - Success on attempt ${attempts}`);
|
|
796
|
+
return result;
|
|
797
|
+
}
|
|
798
|
+
onProgress?.(attempts, maxAttempts);
|
|
799
|
+
if (attempts < maxAttempts) {
|
|
800
|
+
const waitTime = this.calculateWaitTime(attempts, interval, backoffStrategy);
|
|
801
|
+
logger.debug(`${stepName} - Waiting ${waitTime}ms before next attempt`);
|
|
802
|
+
// eslint-disable-next-line no-await-in-loop
|
|
803
|
+
await timeUtils.sleep(waitTime);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
logger.error(`${stepName} - Error on attempt ${attempts}:`, error.message);
|
|
808
|
+
if (attempts === maxAttempts) {
|
|
809
|
+
throw createDeploymentError(`${stepName} failed after ${maxAttempts} attempts: ${error.message}`, 'TIMEOUT_ERROR');
|
|
810
|
+
}
|
|
811
|
+
const waitTime = this.calculateWaitTime(attempts, interval, backoffStrategy);
|
|
812
|
+
// eslint-disable-next-line no-await-in-loop
|
|
813
|
+
await timeUtils.sleep(waitTime);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
throw createDeploymentError(`${stepName} timed out after ${maxAttempts} attempts`, 'TIMEOUT_ERROR');
|
|
817
|
+
}
|
|
818
|
+
calculateWaitTime(attempt, baseInterval, strategy) {
|
|
819
|
+
switch (strategy) {
|
|
820
|
+
case 'linear':
|
|
821
|
+
return baseInterval;
|
|
822
|
+
case 'exponential':
|
|
823
|
+
return Math.min(baseInterval * 2 ** (attempt - 1), 30000);
|
|
824
|
+
default:
|
|
825
|
+
return baseInterval;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
pollWithTimeout(options, timeoutMs) {
|
|
829
|
+
return Promise.race([
|
|
830
|
+
this.poll(options),
|
|
831
|
+
new Promise((_, reject) => {
|
|
832
|
+
setTimeout(() => reject(createDeploymentError('Polling timed out', 'TIMEOUT_ERROR')), timeoutMs);
|
|
833
|
+
}),
|
|
834
|
+
]);
|
|
835
|
+
}
|
|
836
|
+
async pollBatch(items, checkCondition, options = {}) {
|
|
837
|
+
const completed = [];
|
|
838
|
+
const remaining = [...items];
|
|
839
|
+
while (remaining.length > 0) {
|
|
840
|
+
const batchPromises = remaining.map(async (item) => {
|
|
841
|
+
const isComplete = await checkCondition(item);
|
|
842
|
+
return { item, isComplete };
|
|
843
|
+
});
|
|
844
|
+
// eslint-disable-next-line no-await-in-loop
|
|
845
|
+
const results = await Promise.all(batchPromises);
|
|
846
|
+
results.forEach(({ item, isComplete }) => {
|
|
847
|
+
if (isComplete) {
|
|
848
|
+
completed.push(item);
|
|
849
|
+
const index = remaining.indexOf(item);
|
|
850
|
+
if (index > -1) {
|
|
851
|
+
remaining.splice(index, 1);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
if (remaining.length > 0) {
|
|
856
|
+
// eslint-disable-next-line no-await-in-loop
|
|
857
|
+
await timeUtils.sleep(options.interval || this.baseInterval);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return completed;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/* eslint-disable import/prefer-default-export */
|
|
865
|
+
/**
|
|
866
|
+
* Browser HTTP client implementation
|
|
867
|
+
* Uses the passed api object (typically an axios instance)
|
|
868
|
+
*/
|
|
869
|
+
class BrowserHttpClient {
|
|
870
|
+
constructor(api) {
|
|
871
|
+
if (api) {
|
|
872
|
+
this.api = api;
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
// Create simple fetch-compatible interface as fallback
|
|
876
|
+
this.api = {
|
|
877
|
+
get: async (url) => {
|
|
878
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
879
|
+
const response = await fetch(url, { method: 'GET', headers });
|
|
880
|
+
if (!response.ok) {
|
|
881
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
882
|
+
}
|
|
883
|
+
return { data: await response.json() };
|
|
884
|
+
},
|
|
885
|
+
post: async (url, data) => {
|
|
886
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
887
|
+
const response = await fetch(url, {
|
|
888
|
+
method: 'POST',
|
|
889
|
+
headers,
|
|
890
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
891
|
+
});
|
|
892
|
+
if (!response.ok) {
|
|
893
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
894
|
+
}
|
|
895
|
+
return { data: await response.json() };
|
|
896
|
+
},
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
async get(url) {
|
|
901
|
+
const response = await this.api.get(url);
|
|
902
|
+
return response.data;
|
|
903
|
+
}
|
|
904
|
+
async post(url, data) {
|
|
905
|
+
const response = await this.api.post(url, data);
|
|
906
|
+
return response.data;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/* eslint-disable import/prefer-default-export */
|
|
911
|
+
/**
|
|
912
|
+
* Browser configuration provider implementation
|
|
913
|
+
* Gets raw blocklet information from window.blocklet
|
|
914
|
+
*/
|
|
915
|
+
class BrowserConfigProvider {
|
|
916
|
+
constructor(config) {
|
|
917
|
+
this.config = config;
|
|
918
|
+
}
|
|
919
|
+
getBlockletInfo() {
|
|
920
|
+
return Promise.resolve().then(() => {
|
|
921
|
+
try {
|
|
922
|
+
// Get blocklet info from window.blocklet object
|
|
923
|
+
const { blocklet } = window;
|
|
924
|
+
if (!blocklet) {
|
|
925
|
+
throw new Error('window.blocklet is not available. Make sure you are running in a blocklet environment.');
|
|
926
|
+
}
|
|
927
|
+
logger.info('Blocklet info found from window.blocklet', { hasBlocklet: true });
|
|
928
|
+
return blocklet;
|
|
929
|
+
}
|
|
930
|
+
catch (error) {
|
|
931
|
+
logger.error('Failed to get blocklet info from window.blocklet', { error: error.message });
|
|
932
|
+
throw createDeploymentError(`Failed to get blocklet info from window.blocklet: ${error.message}. Please ensure you are running in a blocklet environment.`, 'VALIDATION_ERROR', {
|
|
933
|
+
step: 'creating_session',
|
|
934
|
+
details: { originalError: error },
|
|
935
|
+
recovery: {
|
|
936
|
+
canRetry: false,
|
|
937
|
+
suggestions: [
|
|
938
|
+
'Make sure you are running this in a blocklet environment',
|
|
939
|
+
'Check if window.blocklet is available',
|
|
940
|
+
'Verify the Payment Kit is properly configured',
|
|
941
|
+
],
|
|
942
|
+
},
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/* eslint-disable import/prefer-default-export */
|
|
950
|
+
/**
|
|
951
|
+
* Creates a Browser specific BrokerClient instance
|
|
952
|
+
* @param config - Browser specific configuration for the broker client
|
|
953
|
+
* @returns Configured BrokerClient instance with Browser dependencies
|
|
954
|
+
*/
|
|
955
|
+
function createBrowserBrokerClient(config) {
|
|
956
|
+
const fullConfig = { ...config };
|
|
957
|
+
// Create HTTP client with optional api instance
|
|
958
|
+
const httpClient = new BrowserHttpClient(config.api);
|
|
959
|
+
// Create dependencies for Browser environment
|
|
960
|
+
const dependencies = {
|
|
961
|
+
httpClient,
|
|
962
|
+
configProvider: new BrowserConfigProvider(fullConfig),
|
|
963
|
+
pollingManager: new PollingManager({
|
|
964
|
+
baseInterval: fullConfig.polling?.interval || 3000,
|
|
965
|
+
maxAttempts: fullConfig.polling?.maxAttempts || 100,
|
|
966
|
+
backoffStrategy: fullConfig.polling?.backoffStrategy || 'linear',
|
|
967
|
+
}),
|
|
968
|
+
};
|
|
969
|
+
return new BrokerClient(fullConfig, dependencies);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
export { API_ENDPOINTS, createBrowserBrokerClient as BrokerClient, DEFAULT_CONFIG, DEPLOYMENT_STEPS, ERROR_CODES, PollingManager, STEPS, createDeploymentError, httpUtils, isDeploymentError, logger, objectUtils, timeUtils, urlUtils };
|
|
973
|
+
//# sourceMappingURL=index.js.map
|