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