@ga-components/logger 2.0.25 → 2.0.27
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/dist/index.js +142 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +142 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -222,6 +222,62 @@ var parseQueryParams = (url) => {
|
|
|
222
222
|
return null;
|
|
223
223
|
}
|
|
224
224
|
};
|
|
225
|
+
var ALWAYS_LOG_ENDPOINTS = [
|
|
226
|
+
{ method: "POST", pattern: "/mytrip/seat/payment" },
|
|
227
|
+
{ method: "POST", pattern: "/seat/remove/{bookingCode}" },
|
|
228
|
+
{ method: "POST", pattern: "/seat/map/store/{bookingCode}" },
|
|
229
|
+
{ method: "GET", pattern: "/seat/summary/{bookingCode}" },
|
|
230
|
+
{ method: "POST", pattern: "/mytrip/baggage/save/{bookingCode}" },
|
|
231
|
+
{ method: "POST", pattern: "/mytrip/baggage-wtp/save/{bookingCode}" },
|
|
232
|
+
{ method: "GET", pattern: "/mytrip/baggage/list/{trx}" },
|
|
233
|
+
{ method: "GET", pattern: "/mytrip/baggage/list/{trx}" },
|
|
234
|
+
{ method: "POST", pattern: "/reschedule/list" },
|
|
235
|
+
{ method: "POST", pattern: "/reschedule/confirmation/{bookingCode}" },
|
|
236
|
+
{ method: "POST", pattern: "/reschedule/cancel" },
|
|
237
|
+
{ method: "GET", pattern: "/reschedule/resend-otp/{bookingCode}" },
|
|
238
|
+
{ method: "GET", pattern: "/reschedule/generate-otp/{bookingCode}" },
|
|
239
|
+
{ method: "POST", pattern: "/refund/upload-file" },
|
|
240
|
+
{ method: "POST", pattern: "/refund/submit-voluntary" },
|
|
241
|
+
{ method: "POST", pattern: "/refund/reupload-file" },
|
|
242
|
+
{ method: "POST", pattern: "/refund/price-different/confirmation" },
|
|
243
|
+
{ method: "POST", pattern: "/refund/list" },
|
|
244
|
+
{ method: "POST", pattern: "/refund/list-chatbot" },
|
|
245
|
+
{ method: "POST", pattern: "/refund/detail" },
|
|
246
|
+
{ method: "POST", pattern: "/refund/confirmation" },
|
|
247
|
+
{ method: "POST", pattern: "/refund/calculate" },
|
|
248
|
+
{ method: "GET", pattern: "/refund/resend-otp/{bookingCode}" },
|
|
249
|
+
{ method: "GET", pattern: "/refund/generate-otp/{bookingCode}" },
|
|
250
|
+
{ method: "GET", pattern: "/refund/checkEligibility/{orderId}" },
|
|
251
|
+
{ method: "GET", pattern: "/refund/bank-list" },
|
|
252
|
+
{ method: "POST", pattern: "/dapi/booking/insurance/remove" },
|
|
253
|
+
{ method: "POST", pattern: "/dapi/booking/insurance/acceptance" },
|
|
254
|
+
{ method: "POST", pattern: "/addon/service-insurance/{bookingCode}" },
|
|
255
|
+
{ method: "POST", pattern: "/specialRequest/gps/products/{bookingCode}" },
|
|
256
|
+
{ method: "POST", pattern: "/ancill/specialRequest/gps/products/{bookingCode}" },
|
|
257
|
+
{ method: "POST", pattern: "/ancill/specialRequest/payment/v1/ancillary_gps/pay" },
|
|
258
|
+
{ method: "POST", pattern: "/ancill/specialRequest/ancillary/save_gps/{bookingCode}" },
|
|
259
|
+
{ method: "POST", pattern: "/specialRequest/deprecated/service/{bookingCode}" },
|
|
260
|
+
{ method: "POST", pattern: "/ancill/specialRequest/payment/v1/ancillary_lounge/pay" },
|
|
261
|
+
{ method: "POST", pattern: "/ancill/specialRequest/ancillary/save_lounge/{bookingCode}" }
|
|
262
|
+
];
|
|
263
|
+
var ALWAYS_LOG_MATCHERS = ALWAYS_LOG_ENDPOINTS.map((entry) => ({
|
|
264
|
+
method: entry.method.toUpperCase(),
|
|
265
|
+
regex: new RegExp(
|
|
266
|
+
"^" + entry.pattern.split("/").map(
|
|
267
|
+
(seg) => seg.startsWith("{") && seg.endsWith("}") ? "[^/]+" : seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
268
|
+
).join("/") + "/?$"
|
|
269
|
+
)
|
|
270
|
+
}));
|
|
271
|
+
function shouldAlwaysLogEndpoint(url, method) {
|
|
272
|
+
let pathname;
|
|
273
|
+
try {
|
|
274
|
+
pathname = new URL(url, window.location.origin).pathname;
|
|
275
|
+
} catch {
|
|
276
|
+
pathname = url;
|
|
277
|
+
}
|
|
278
|
+
const upperMethod = method.toUpperCase();
|
|
279
|
+
return ALWAYS_LOG_MATCHERS.some((m) => m.method === upperMethod && m.regex.test(pathname));
|
|
280
|
+
}
|
|
225
281
|
var Logger = class {
|
|
226
282
|
constructor() {
|
|
227
283
|
this.config = null;
|
|
@@ -410,6 +466,7 @@ var Logger = class {
|
|
|
410
466
|
const key = this.buildErrorKey("onerror", finalMessage);
|
|
411
467
|
if (this.loggedErrorKeys.has(key)) return false;
|
|
412
468
|
this.loggedErrorKeys.add(key);
|
|
469
|
+
this.addBreadcrumb("error", finalMessage, { source: trimmedSource, lineno, colno });
|
|
413
470
|
void this.error({
|
|
414
471
|
fnName: "window.onerror",
|
|
415
472
|
messageError: new Error(finalMessage),
|
|
@@ -423,6 +480,7 @@ var Logger = class {
|
|
|
423
480
|
const key = this.buildErrorKey("unhandledrejection", msg);
|
|
424
481
|
if (this.loggedErrorKeys.has(key)) return;
|
|
425
482
|
this.loggedErrorKeys.add(key);
|
|
483
|
+
this.addBreadcrumb("error", msg);
|
|
426
484
|
void this.error({
|
|
427
485
|
fnName: "window.unhandledrejection",
|
|
428
486
|
messageError: new Error(msg)
|
|
@@ -437,6 +495,7 @@ var Logger = class {
|
|
|
437
495
|
const key = this.buildErrorKey("console.error", trimmedMsg);
|
|
438
496
|
if (this.loggedErrorKeys.has(key)) return;
|
|
439
497
|
this.loggedErrorKeys.add(key);
|
|
498
|
+
this.addBreadcrumb("error", trimmedMsg);
|
|
440
499
|
const originalError = args.find((a) => a instanceof Error);
|
|
441
500
|
const errorToSend = originalError ?? new Error(trimmedMsg);
|
|
442
501
|
void this.error({
|
|
@@ -543,7 +602,8 @@ var Logger = class {
|
|
|
543
602
|
status: response.status,
|
|
544
603
|
duration: `${duration}ms`
|
|
545
604
|
});
|
|
546
|
-
|
|
605
|
+
const isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
606
|
+
if (!isLocalhost && response.status >= 400) {
|
|
547
607
|
const dedupKey = this.buildErrorKey("interceptFetch", `${method} ${url} ${response.status}`);
|
|
548
608
|
if (!this.loggedErrorKeys.has(dedupKey)) {
|
|
549
609
|
this.loggedErrorKeys.add(dedupKey);
|
|
@@ -555,8 +615,8 @@ var Logger = class {
|
|
|
555
615
|
} catch {
|
|
556
616
|
responseBody = "[unable to read response body]";
|
|
557
617
|
}
|
|
558
|
-
const
|
|
559
|
-
if (!
|
|
618
|
+
const isLocalhost2 = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
619
|
+
if (!isLocalhost2) {
|
|
560
620
|
this.error({
|
|
561
621
|
fnName: "interceptFetch",
|
|
562
622
|
messageError: new Error(`HTTP ${response.status} ${response.statusText} \u2014 ${url}`),
|
|
@@ -573,6 +633,32 @@ var Logger = class {
|
|
|
573
633
|
});
|
|
574
634
|
}
|
|
575
635
|
}
|
|
636
|
+
} else if (!isLocalhost && response.status < 400 && shouldAlwaysLogEndpoint(url, method)) {
|
|
637
|
+
const dedupKey = this.buildErrorKey("interceptFetch", `${method} ${url} ${response.status}`);
|
|
638
|
+
if (!this.loggedErrorKeys.has(dedupKey)) {
|
|
639
|
+
this.loggedErrorKeys.add(dedupKey);
|
|
640
|
+
let responseBody = null;
|
|
641
|
+
try {
|
|
642
|
+
const cloned = response.clone();
|
|
643
|
+
const contentType = cloned.headers.get("content-type") ?? "";
|
|
644
|
+
responseBody = contentType.includes("application/json") ? await cloned.json() : await cloned.text();
|
|
645
|
+
} catch {
|
|
646
|
+
responseBody = "[unable to read response body]";
|
|
647
|
+
}
|
|
648
|
+
this.info({
|
|
649
|
+
fnName: "interceptFetch",
|
|
650
|
+
endpointName: url,
|
|
651
|
+
payload: {
|
|
652
|
+
method,
|
|
653
|
+
status: response.status,
|
|
654
|
+
statusText: response.statusText,
|
|
655
|
+
duration: `${duration}ms`,
|
|
656
|
+
query: parseQueryParams(url),
|
|
657
|
+
request: parseRequestBody(init?.body),
|
|
658
|
+
response: responseBody
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
576
662
|
}
|
|
577
663
|
return response;
|
|
578
664
|
} catch (networkError) {
|
|
@@ -642,7 +728,8 @@ var Logger = class {
|
|
|
642
728
|
status,
|
|
643
729
|
duration: `${duration}ms`
|
|
644
730
|
});
|
|
645
|
-
|
|
731
|
+
const isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
732
|
+
if (!isLocalhost && status >= 400) {
|
|
646
733
|
const dedupKey = self.buildErrorKey("interceptXHR", `${method} ${url} ${status}`);
|
|
647
734
|
if (self.loggedErrorKeys.has(dedupKey)) return;
|
|
648
735
|
self.loggedErrorKeys.add(dedupKey);
|
|
@@ -651,8 +738,8 @@ var Logger = class {
|
|
|
651
738
|
responseBody = JSON.parse(this.responseText);
|
|
652
739
|
} catch {
|
|
653
740
|
}
|
|
654
|
-
const
|
|
655
|
-
if (!
|
|
741
|
+
const isLocalhost2 = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
742
|
+
if (!isLocalhost2) {
|
|
656
743
|
void self.error({
|
|
657
744
|
fnName: "interceptXHR",
|
|
658
745
|
messageError: new Error(`HTTP ${status} ${this.statusText} \u2014 ${url}`),
|
|
@@ -668,6 +755,28 @@ var Logger = class {
|
|
|
668
755
|
}
|
|
669
756
|
});
|
|
670
757
|
}
|
|
758
|
+
} else if (!isLocalhost && status < 400 && shouldAlwaysLogEndpoint(url, method)) {
|
|
759
|
+
const dedupKey = self.buildErrorKey("interceptXHR", `${method} ${url} ${status}`);
|
|
760
|
+
if (self.loggedErrorKeys.has(dedupKey)) return;
|
|
761
|
+
self.loggedErrorKeys.add(dedupKey);
|
|
762
|
+
let responseBody = this.responseText;
|
|
763
|
+
try {
|
|
764
|
+
responseBody = JSON.parse(this.responseText);
|
|
765
|
+
} catch {
|
|
766
|
+
}
|
|
767
|
+
void self.info({
|
|
768
|
+
fnName: "interceptXHR",
|
|
769
|
+
endpointName: url,
|
|
770
|
+
payload: {
|
|
771
|
+
method,
|
|
772
|
+
status,
|
|
773
|
+
statusText: this.statusText,
|
|
774
|
+
duration: `${duration}ms`,
|
|
775
|
+
query: parseQueryParams(url),
|
|
776
|
+
request: requestBody,
|
|
777
|
+
response: responseBody
|
|
778
|
+
}
|
|
779
|
+
});
|
|
671
780
|
}
|
|
672
781
|
});
|
|
673
782
|
this.addEventListener("error", () => {
|
|
@@ -716,6 +825,13 @@ var Logger = class {
|
|
|
716
825
|
});
|
|
717
826
|
const disabledAtClick = this.isElementDisabled(target);
|
|
718
827
|
const invalidFieldAtClick = this.hasInvalidFieldNearby(target);
|
|
828
|
+
const ariaDisabledAtClick = target.getAttribute("aria-disabled") === "true";
|
|
829
|
+
const pointerEventsBlockedAtClick = window.getComputedStyle(target).pointerEvents === "none";
|
|
830
|
+
const overlayInterceptedAtClick = (() => {
|
|
831
|
+
const elAtPoint = document.elementFromPoint(event.clientX, event.clientY);
|
|
832
|
+
if (!elAtPoint || elAtPoint === target || target.contains(elAtPoint)) return null;
|
|
833
|
+
return elAtPoint;
|
|
834
|
+
})();
|
|
719
835
|
const clickTime = Date.now();
|
|
720
836
|
const domActivityAtClick = this.lastDomActivity;
|
|
721
837
|
const DEAD_CLICK_DELAY_MS = 4e3;
|
|
@@ -784,7 +900,25 @@ var Logger = class {
|
|
|
784
900
|
const recentFetch = this.breadcrumbs.filter(
|
|
785
901
|
(b) => b.type === "fetch" && clickTime - new Date(b.timestamp).getTime() <= RAGE_CLICK_WINDOW_MS
|
|
786
902
|
).slice(-1)[0];
|
|
787
|
-
const
|
|
903
|
+
const recentError = this.breadcrumbs.filter(
|
|
904
|
+
(b) => b.type === "error" && clickTime - new Date(b.timestamp).getTime() <= RAGE_CLICK_WINDOW_MS
|
|
905
|
+
).slice(-1)[0];
|
|
906
|
+
const possibleReason = disabledAtClick ? { type: "disabled", detail: "Button dalam kondisi disabled saat diklik" } : ariaDisabledAtClick ? {
|
|
907
|
+
type: "aria_disabled",
|
|
908
|
+
detail: 'Element memiliki aria-disabled="true" (disabled secara visual/aksesibilitas, bukan native disabled attribute)'
|
|
909
|
+
} : pointerEventsBlockedAtClick ? {
|
|
910
|
+
type: "pointer_events_blocked",
|
|
911
|
+
detail: "Element memiliki CSS pointer-events: none saat diklik (tidak bisa menerima event klik sama sekali)"
|
|
912
|
+
} : overlayInterceptedAtClick ? {
|
|
913
|
+
type: "overlay_intercepted",
|
|
914
|
+
detail: `Element lain (${getElementSelector(overlayInterceptedAtClick)}) menutupi target saat klik terjadi, klik tidak sampai ke target aslinya`
|
|
915
|
+
} : recentFetch ? { type: "api_error", detail: recentFetch.message } : invalidFieldAtClick ? {
|
|
916
|
+
type: "validation_error",
|
|
917
|
+
detail: "Field invalid terdeteksi di form terdekat"
|
|
918
|
+
} : recentError ? { type: "js_error", detail: recentError.message } : this.recentDomChanges.length ? { type: "dom_feedback", detail: this.recentDomChanges.slice(-1)[0] } : {
|
|
919
|
+
type: "no_feedback",
|
|
920
|
+
detail: "Tidak ada feedback UI terdeteksi setelah klik"
|
|
921
|
+
};
|
|
788
922
|
const formattedLog = formatClickLog({
|
|
789
923
|
fnName: "interceptClick.rageClick",
|
|
790
924
|
tagName,
|
|
@@ -911,6 +1045,7 @@ var Logger = class {
|
|
|
911
1045
|
y: window.scrollY,
|
|
912
1046
|
width: window.innerWidth,
|
|
913
1047
|
height: window.innerHeight,
|
|
1048
|
+
imageTimeout: 3e3,
|
|
914
1049
|
onclone: (clonedDoc) => {
|
|
915
1050
|
if (!canApplyHighlight) return;
|
|
916
1051
|
const clonedTarget = clonedDoc.querySelector(`[data-ga-highlight="${HIGHLIGHT_MARKER}"]`);
|