onelaraveljs 1.0.0
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/README.md +87 -0
- package/docs/integration_analysis.md +116 -0
- package/docs/onejs_analysis.md +108 -0
- package/docs/optimization_implementation_group2.md +458 -0
- package/docs/optimization_plan.md +130 -0
- package/index.js +16 -0
- package/package.json +13 -0
- package/src/app.js +61 -0
- package/src/core/API.js +72 -0
- package/src/core/ChildrenRegistry.js +410 -0
- package/src/core/DOMBatcher.js +207 -0
- package/src/core/ErrorBoundary.js +226 -0
- package/src/core/EventDelegator.js +416 -0
- package/src/core/Helper.js +817 -0
- package/src/core/LoopContext.js +97 -0
- package/src/core/OneDOM.js +246 -0
- package/src/core/OneMarkup.js +444 -0
- package/src/core/Router.js +996 -0
- package/src/core/SEOConfig.js +321 -0
- package/src/core/SectionEngine.js +75 -0
- package/src/core/TemplateEngine.js +83 -0
- package/src/core/View.js +273 -0
- package/src/core/ViewConfig.js +229 -0
- package/src/core/ViewController.js +1410 -0
- package/src/core/ViewControllerOptimized.js +164 -0
- package/src/core/ViewIdentifier.js +361 -0
- package/src/core/ViewLoader.js +272 -0
- package/src/core/ViewManager.js +1962 -0
- package/src/core/ViewState.js +761 -0
- package/src/core/ViewSystem.js +301 -0
- package/src/core/ViewTemplate.js +4 -0
- package/src/core/helpers/BindingHelper.js +239 -0
- package/src/core/helpers/ConfigHelper.js +37 -0
- package/src/core/helpers/EventHelper.js +172 -0
- package/src/core/helpers/LifecycleHelper.js +17 -0
- package/src/core/helpers/ReactiveHelper.js +169 -0
- package/src/core/helpers/RenderHelper.js +15 -0
- package/src/core/helpers/ResourceHelper.js +89 -0
- package/src/core/helpers/TemplateHelper.js +11 -0
- package/src/core/managers/BindingManager.js +671 -0
- package/src/core/managers/ConfigurationManager.js +136 -0
- package/src/core/managers/EventManager.js +309 -0
- package/src/core/managers/LifecycleManager.js +356 -0
- package/src/core/managers/ReactiveManager.js +334 -0
- package/src/core/managers/RenderEngine.js +292 -0
- package/src/core/managers/ResourceManager.js +441 -0
- package/src/core/managers/ViewHierarchyManager.js +258 -0
- package/src/core/managers/ViewTemplateManager.js +127 -0
- package/src/core/reactive/ReactiveComponent.js +592 -0
- package/src/core/services/EventService.js +418 -0
- package/src/core/services/HttpService.js +106 -0
- package/src/core/services/LoggerService.js +57 -0
- package/src/core/services/StateService.js +512 -0
- package/src/core/services/StorageService.js +856 -0
- package/src/core/services/StoreService.js +258 -0
- package/src/core/services/TemplateDetectorService.js +361 -0
- package/src/core/services/Test.js +18 -0
- package/src/helpers/devWarnings.js +205 -0
- package/src/helpers/performance.js +226 -0
- package/src/helpers/utils.js +287 -0
- package/src/init.js +343 -0
- package/src/plugins/auto-plugin.js +34 -0
- package/src/services/Test.js +18 -0
- package/src/types/index.js +193 -0
- package/src/utils/date-helper.js +51 -0
- package/src/utils/helpers.js +39 -0
- package/src/utils/validation.js +32 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
export class EventService {
|
|
2
|
+
static instances = new Map();
|
|
3
|
+
static getInstance(name = null) {
|
|
4
|
+
if (!name || name === '' || ame === undefined) {
|
|
5
|
+
name = 'default';
|
|
6
|
+
}
|
|
7
|
+
if (!EventService.instances.has(name)) {
|
|
8
|
+
EventService.instances.set(name, new EventService());
|
|
9
|
+
}
|
|
10
|
+
return EventService.instances.get(name);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static removeInstance(name) {
|
|
14
|
+
if (EventService.instances.has(name)) {
|
|
15
|
+
EventService.instances.delete(name);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static instance(name = null) {
|
|
20
|
+
return EventService.getInstance(name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static clearInstances() {
|
|
24
|
+
EventService.instances.clear();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
/**
|
|
29
|
+
* @type {Map<string, Array<{callback: Function, once: boolean}>>}
|
|
30
|
+
* @private
|
|
31
|
+
*/
|
|
32
|
+
this.listeners = new Map();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Multi-event listeners: Khi subscribe nhiều events với 1 callback
|
|
36
|
+
* @type {Array<{events: Set<string>, callback: Function, once: boolean, called: boolean}>}
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
this.multiEventListeners = [];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Track xem có pending flush không (để batch calls)
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
this.hasPendingFlush = false;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Set callbacks đã được gọi trong batch hiện tại
|
|
49
|
+
* @private
|
|
50
|
+
*/
|
|
51
|
+
this.calledInBatch = new Set();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Đăng ký lắng nghe một event
|
|
56
|
+
*
|
|
57
|
+
* @param {string|array|object} eventName - Tên event hoặc array các tên event hoặc object {event: callback}
|
|
58
|
+
* @param {function} callback - Hàm callback khi event được trigger
|
|
59
|
+
* @param {boolean} once - Chỉ chạy 1 lần rồi tự động unsubscribe
|
|
60
|
+
* @return {function} Hàm hủy đăng ký
|
|
61
|
+
*/
|
|
62
|
+
on(eventName, callback, once = false) {
|
|
63
|
+
// Hỗ trợ object syntax: on({event1: fn1, event2: fn2})
|
|
64
|
+
if(typeof eventName === 'object' && !Array.isArray(eventName)){
|
|
65
|
+
const unsubscribers = [];
|
|
66
|
+
for(const [event, fn] of Object.entries(eventName)){
|
|
67
|
+
if(typeof fn === 'function'){
|
|
68
|
+
unsubscribers.push(this.on(event, fn, callback === true));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Return combined unsubscribe function
|
|
72
|
+
return () => {
|
|
73
|
+
unsubscribers.forEach(unsub => unsub());
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Hỗ trợ string với spaces: "event1 event2 event3"
|
|
78
|
+
if(typeof eventName === 'string' && eventName.includes(' ')){
|
|
79
|
+
eventName = eventName.split(/\s+/).filter(e => e);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Hỗ trợ subscribe nhiều events cùng lúc với 1 callback
|
|
83
|
+
// Callback chỉ được gọi 1 lần trong cùng batch dù nhiều events được emit
|
|
84
|
+
if(Array.isArray(eventName)){
|
|
85
|
+
const events = new Set(eventName.filter(e => typeof e === 'string'));
|
|
86
|
+
if(events.size === 0){
|
|
87
|
+
return () => {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const listener = { events, callback, once, called: false };
|
|
91
|
+
this.multiEventListeners.push(listener);
|
|
92
|
+
|
|
93
|
+
// Return unsubscribe function
|
|
94
|
+
return () => {
|
|
95
|
+
const index = this.multiEventListeners.indexOf(listener);
|
|
96
|
+
if(index !== -1){
|
|
97
|
+
this.multiEventListeners.splice(index, 1);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if(typeof eventName !== 'string' || typeof callback !== 'function'){
|
|
103
|
+
return () => {};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if(!this.listeners.has(eventName)){
|
|
107
|
+
this.listeners.set(eventName, []);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const listener = { callback, once };
|
|
111
|
+
this.listeners.get(eventName).push(listener);
|
|
112
|
+
|
|
113
|
+
// Return unsubscribe function
|
|
114
|
+
return () => this.off(eventName, callback);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Đăng ký lắng nghe event chỉ chạy 1 lần
|
|
119
|
+
*
|
|
120
|
+
* @param {string|array} eventName - Tên event hoặc array các tên event
|
|
121
|
+
* @param {function} callback - Hàm callback
|
|
122
|
+
* @return {function} Hàm hủy đăng ký
|
|
123
|
+
*/
|
|
124
|
+
once(eventName, callback) {
|
|
125
|
+
return this.on(eventName, callback, true);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Hủy đăng ký lắng nghe event
|
|
130
|
+
*
|
|
131
|
+
* @param {string|array|object} eventName - Tên event hoặc array các tên event hoặc "event1 event2..." hoặc object {event: callback}
|
|
132
|
+
* @param {function|null} callback - Callback cụ thể (null = xóa tất cả)
|
|
133
|
+
*/
|
|
134
|
+
off(eventName, callback = null) {
|
|
135
|
+
// Hỗ trợ object syntax: off({event1: fn1, event2: fn2})
|
|
136
|
+
if(typeof eventName === 'object' && !Array.isArray(eventName)){
|
|
137
|
+
for(const [event, fn] of Object.entries(eventName)){
|
|
138
|
+
this.off(event, fn);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Hỗ trợ string với spaces: "event1 event2 event3"
|
|
144
|
+
if(typeof eventName === 'string' && eventName.includes(' ')){
|
|
145
|
+
eventName = eventName.split(/\s+/).filter(e => e);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Hỗ trợ unsubscribe nhiều events
|
|
149
|
+
if(Array.isArray(eventName)){
|
|
150
|
+
const eventSet = new Set(eventName);
|
|
151
|
+
|
|
152
|
+
const areSetsEqual = (set1, set2) => {
|
|
153
|
+
if(set1.size !== set2.size) return false;
|
|
154
|
+
for(const e of set1){
|
|
155
|
+
if(!set2.has(e)) return false;
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if(!callback){
|
|
161
|
+
// Xóa TẤT CẢ multi-event listeners có cùng set events
|
|
162
|
+
for(let i = this.multiEventListeners.length - 1; i >= 0; i--){
|
|
163
|
+
if(areSetsEqual(this.multiEventListeners[i].events, eventSet)){
|
|
164
|
+
this.multiEventListeners.splice(i, 1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Xóa tất cả single-event listeners của các events này
|
|
169
|
+
eventName.forEach(name => {
|
|
170
|
+
if(this.listeners.has(name)){
|
|
171
|
+
this.listeners.delete(name);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Xóa listener cụ thể từ multi-event listeners
|
|
178
|
+
const index = this.multiEventListeners.findIndex(listener => {
|
|
179
|
+
return listener.callback === callback && areSetsEqual(listener.events, eventSet);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if(index !== -1){
|
|
183
|
+
this.multiEventListeners.splice(index, 1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Xóa callback từ tất cả single-event listeners của các events này
|
|
187
|
+
eventName.forEach(name => {
|
|
188
|
+
if(this.listeners.has(name)){
|
|
189
|
+
const listeners = this.listeners.get(name);
|
|
190
|
+
const idx = listeners.findIndex(listener => listener.callback === callback);
|
|
191
|
+
|
|
192
|
+
if(idx !== -1){
|
|
193
|
+
listeners.splice(idx, 1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if(listeners.length === 0){
|
|
197
|
+
this.listeners.delete(name);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if(typeof eventName !== 'string'){
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if(!this.listeners.has(eventName)){
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Xóa tất cả listeners của event
|
|
213
|
+
if(!callback){
|
|
214
|
+
this.listeners.delete(eventName);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Xóa listener cụ thể
|
|
219
|
+
const listeners = this.listeners.get(eventName);
|
|
220
|
+
const index = listeners.findIndex(listener => listener.callback === callback);
|
|
221
|
+
|
|
222
|
+
if(index !== -1){
|
|
223
|
+
listeners.splice(index, 1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Cleanup nếu không còn listener nào
|
|
227
|
+
if(listeners.length === 0){
|
|
228
|
+
this.listeners.delete(eventName);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Trigger một event
|
|
234
|
+
*
|
|
235
|
+
* @param {string} eventName - Tên event
|
|
236
|
+
* @param {...any} args - Arguments truyền cho callback
|
|
237
|
+
*/
|
|
238
|
+
emit(eventName, ...args) {
|
|
239
|
+
if(typeof eventName !== 'string'){
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Schedule flush nếu chưa có
|
|
244
|
+
if(!this.hasPendingFlush){
|
|
245
|
+
this.hasPendingFlush = true;
|
|
246
|
+
Promise.resolve().then(() => {
|
|
247
|
+
this.flushBatch();
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Single-event listeners
|
|
252
|
+
if(this.listeners.has(eventName)){
|
|
253
|
+
const listeners = this.listeners.get(eventName);
|
|
254
|
+
const toRemove = [];
|
|
255
|
+
|
|
256
|
+
listeners.forEach((listener, index) => {
|
|
257
|
+
// Batch deduplication: Chỉ gọi callback 1 lần trong batch
|
|
258
|
+
if(!this.calledInBatch.has(listener.callback)){
|
|
259
|
+
this.calledInBatch.add(listener.callback);
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
listener.callback(...args);
|
|
263
|
+
} catch(error) {
|
|
264
|
+
console.error(`Error in event listener for "${eventName}":`, error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if(listener.once){
|
|
269
|
+
toRemove.push(index);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Remove once listeners
|
|
274
|
+
for(let i = toRemove.length - 1; i >= 0; i--){
|
|
275
|
+
listeners.splice(toRemove[i], 1);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if(listeners.length === 0){
|
|
279
|
+
this.listeners.delete(eventName);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Multi-event listeners
|
|
284
|
+
this.multiEventListeners.forEach(listener => {
|
|
285
|
+
if(!listener.called && listener.events.has(eventName)){
|
|
286
|
+
listener.called = true;
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
listener.callback(...args);
|
|
290
|
+
} catch(error) {
|
|
291
|
+
console.error(`Error in multi-event listener for "${eventName}":`, error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Flush batch và reset flags
|
|
299
|
+
* @private
|
|
300
|
+
*/
|
|
301
|
+
flushBatch() {
|
|
302
|
+
// Reset multi-event listeners called flag
|
|
303
|
+
this.multiEventListeners.forEach(listener => {
|
|
304
|
+
listener.called = false;
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Remove once multi-event listeners
|
|
308
|
+
for(let i = this.multiEventListeners.length - 1; i >= 0; i--){
|
|
309
|
+
if(this.multiEventListeners[i].once && this.multiEventListeners[i].called){
|
|
310
|
+
this.multiEventListeners.splice(i, 1);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Clear batch tracking
|
|
315
|
+
this.calledInBatch.clear();
|
|
316
|
+
this.hasPendingFlush = false;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Trigger event bất đồng bộ (async)
|
|
321
|
+
*
|
|
322
|
+
* @param {string} eventName - Tên event
|
|
323
|
+
* @param {...any} args - Arguments truyền cho callback
|
|
324
|
+
* @return {Promise<void>}
|
|
325
|
+
*/
|
|
326
|
+
async emitAsync(eventName, ...args) {
|
|
327
|
+
if(typeof eventName !== 'string'){
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if(!this.listeners.has(eventName)){
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const listeners = this.listeners.get(eventName);
|
|
336
|
+
const toRemove = [];
|
|
337
|
+
|
|
338
|
+
// Gọi tất cả listeners và chờ nếu là async
|
|
339
|
+
for(let i = 0; i < listeners.length; i++){
|
|
340
|
+
const listener = listeners[i];
|
|
341
|
+
try {
|
|
342
|
+
await listener.callback(...args);
|
|
343
|
+
} catch(error) {
|
|
344
|
+
console.error(`Error in async event listener for "${eventName}":`, error);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if(listener.once){
|
|
348
|
+
toRemove.push(i);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Remove once listeners
|
|
353
|
+
for(let i = toRemove.length - 1; i >= 0; i--){
|
|
354
|
+
listeners.splice(toRemove[i], 1);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if(listeners.length === 0){
|
|
358
|
+
this.listeners.delete(eventName);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Kiểm tra có listener nào cho event không
|
|
364
|
+
*
|
|
365
|
+
* @param {string} eventName - Tên event
|
|
366
|
+
* @return {boolean}
|
|
367
|
+
*/
|
|
368
|
+
hasListeners(eventName) {
|
|
369
|
+
return this.listeners.has(eventName) && this.listeners.get(eventName).length > 0;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Lấy số lượng listeners của event
|
|
374
|
+
*
|
|
375
|
+
* @param {string} eventName - Tên event
|
|
376
|
+
* @return {number}
|
|
377
|
+
*/
|
|
378
|
+
listenerCount(eventName) {
|
|
379
|
+
if(!this.listeners.has(eventName)){
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
382
|
+
return this.listeners.get(eventName).length;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Lấy danh sách tất cả event names
|
|
387
|
+
*
|
|
388
|
+
* @return {Array<string>}
|
|
389
|
+
*/
|
|
390
|
+
eventNames() {
|
|
391
|
+
return Array.from(this.listeners.keys());
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Xóa tất cả listeners của tất cả events
|
|
396
|
+
*/
|
|
397
|
+
clear() {
|
|
398
|
+
this.listeners.clear();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
createManager(packetName) {
|
|
402
|
+
return EventService.getInstance(packetName);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
addEventListener(eventName, callback) {
|
|
406
|
+
return this.on(eventName, callback);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
removeEventListener(eventName, callback = null) {
|
|
410
|
+
this.off(eventName, callback);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
dispatchEvent(eventName, ...args) {
|
|
414
|
+
this.emit(eventName, ...args);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export default EventService.getInstance();
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Service Module
|
|
3
|
+
* ES6 Module cho Blade Compiler
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HttpService {
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
this.baseUrl = '';
|
|
11
|
+
this.timeout = 10000;
|
|
12
|
+
this.defaultHeaders = {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setBaseUrl(url) {
|
|
16
|
+
this.baseUrl = url;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setTimeout(timeout) {
|
|
20
|
+
this.timeout = timeout;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setDefaultHeaders(headers) {
|
|
24
|
+
this.defaultHeaders = { ...this.defaultHeaders, ...headers };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setHeader(name, value) {
|
|
28
|
+
this.defaultHeaders[name] = value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async request(method, url, data = null, options = {}) {
|
|
32
|
+
let fullUrl = url.startsWith('http') ? url : this.baseUrl + url;
|
|
33
|
+
|
|
34
|
+
const config = {
|
|
35
|
+
method: method.toUpperCase(),
|
|
36
|
+
headers: {
|
|
37
|
+
...this.defaultHeaders,
|
|
38
|
+
...options.headers
|
|
39
|
+
},
|
|
40
|
+
...options
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (data && (method.toUpperCase() === 'POST' || method.toUpperCase() === 'PUT' || method.toUpperCase() === 'PATCH')) {
|
|
44
|
+
if (config.headers['Content-Type'] === 'application/json') {
|
|
45
|
+
config.body = JSON.stringify(data);
|
|
46
|
+
} else {
|
|
47
|
+
config.body = data;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (data && method.toUpperCase() === 'GET') {
|
|
51
|
+
const urlObj = new URL(fullUrl, window.location.origin);
|
|
52
|
+
Object.keys(data).forEach(key => urlObj.searchParams.append(key, data[key]));
|
|
53
|
+
fullUrl = urlObj.toString();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
59
|
+
|
|
60
|
+
const response = await fetch(fullUrl, {
|
|
61
|
+
...config,
|
|
62
|
+
signal: controller.signal
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
clearTimeout(timeoutId);
|
|
66
|
+
|
|
67
|
+
const responseData = await response.json().catch(() => ({}));
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
status: response.ok,
|
|
71
|
+
statusCode: response.status,
|
|
72
|
+
data: responseData,
|
|
73
|
+
headers: response.headers
|
|
74
|
+
};
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error.name === 'AbortError') {
|
|
77
|
+
throw new Error('Request timeout');
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async get(url, params, options = {}) {
|
|
84
|
+
return this.request('GET', url, params, options);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async post(url, data = null, options = {}) {
|
|
88
|
+
return this.request('POST', url, data, options);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async put(url, data = null, options = {}) {
|
|
92
|
+
return this.request('PUT', url, data, options);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async patch(url, data = null, options = {}) {
|
|
96
|
+
return this.request('PATCH', url, data, options);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async delete(url, options = {}) {
|
|
100
|
+
return this.request('DELETE', url, null, options);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
// Export cho ES6 modules
|
|
106
|
+
export { HttpService };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export class LoggerService {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
this.config = {
|
|
4
|
+
level: 'info',
|
|
5
|
+
enabled: false,
|
|
6
|
+
console: true,
|
|
7
|
+
remote: false
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
if (config) {
|
|
11
|
+
this.setConfig(config);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
this.logs = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
setConfig(config) {
|
|
18
|
+
this.config = { ...this.config, ...config };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getConfig() {
|
|
22
|
+
return this.config;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
consoleLog(method, ...args) {
|
|
26
|
+
this.logs.push(...args)
|
|
27
|
+
if (this.config.console && this.config.enabled) {
|
|
28
|
+
return console[method].apply(console, args);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
log(...args) {
|
|
33
|
+
return this.consoleLog('log', ...args);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
warn(...args) {
|
|
37
|
+
return this.consoleLog('warn', ...args);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
error(...args) {
|
|
41
|
+
return this.consoleLog('error', ...args);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
info(...args) {
|
|
45
|
+
return this.consoleLog('info', ...args);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
debug(...args) {
|
|
49
|
+
return this.consoleLog('debug', ...args);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
export const logger = new LoggerService({level: 'info', enabled: true, console: true, remote: false});
|
|
56
|
+
export default logger;
|
|
57
|
+
|