@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.mjs
CHANGED
|
@@ -185,6 +185,62 @@ var parseQueryParams = (url) => {
|
|
|
185
185
|
return null;
|
|
186
186
|
}
|
|
187
187
|
};
|
|
188
|
+
var ALWAYS_LOG_ENDPOINTS = [
|
|
189
|
+
{ method: "POST", pattern: "/mytrip/seat/payment" },
|
|
190
|
+
{ method: "POST", pattern: "/seat/remove/{bookingCode}" },
|
|
191
|
+
{ method: "POST", pattern: "/seat/map/store/{bookingCode}" },
|
|
192
|
+
{ method: "GET", pattern: "/seat/summary/{bookingCode}" },
|
|
193
|
+
{ method: "POST", pattern: "/mytrip/baggage/save/{bookingCode}" },
|
|
194
|
+
{ method: "POST", pattern: "/mytrip/baggage-wtp/save/{bookingCode}" },
|
|
195
|
+
{ method: "GET", pattern: "/mytrip/baggage/list/{trx}" },
|
|
196
|
+
{ method: "GET", pattern: "/mytrip/baggage/list/{trx}" },
|
|
197
|
+
{ method: "POST", pattern: "/reschedule/list" },
|
|
198
|
+
{ method: "POST", pattern: "/reschedule/confirmation/{bookingCode}" },
|
|
199
|
+
{ method: "POST", pattern: "/reschedule/cancel" },
|
|
200
|
+
{ method: "GET", pattern: "/reschedule/resend-otp/{bookingCode}" },
|
|
201
|
+
{ method: "GET", pattern: "/reschedule/generate-otp/{bookingCode}" },
|
|
202
|
+
{ method: "POST", pattern: "/refund/upload-file" },
|
|
203
|
+
{ method: "POST", pattern: "/refund/submit-voluntary" },
|
|
204
|
+
{ method: "POST", pattern: "/refund/reupload-file" },
|
|
205
|
+
{ method: "POST", pattern: "/refund/price-different/confirmation" },
|
|
206
|
+
{ method: "POST", pattern: "/refund/list" },
|
|
207
|
+
{ method: "POST", pattern: "/refund/list-chatbot" },
|
|
208
|
+
{ method: "POST", pattern: "/refund/detail" },
|
|
209
|
+
{ method: "POST", pattern: "/refund/confirmation" },
|
|
210
|
+
{ method: "POST", pattern: "/refund/calculate" },
|
|
211
|
+
{ method: "GET", pattern: "/refund/resend-otp/{bookingCode}" },
|
|
212
|
+
{ method: "GET", pattern: "/refund/generate-otp/{bookingCode}" },
|
|
213
|
+
{ method: "GET", pattern: "/refund/checkEligibility/{orderId}" },
|
|
214
|
+
{ method: "GET", pattern: "/refund/bank-list" },
|
|
215
|
+
{ method: "POST", pattern: "/dapi/booking/insurance/remove" },
|
|
216
|
+
{ method: "POST", pattern: "/dapi/booking/insurance/acceptance" },
|
|
217
|
+
{ method: "POST", pattern: "/addon/service-insurance/{bookingCode}" },
|
|
218
|
+
{ method: "POST", pattern: "/specialRequest/gps/products/{bookingCode}" },
|
|
219
|
+
{ method: "POST", pattern: "/ancill/specialRequest/gps/products/{bookingCode}" },
|
|
220
|
+
{ method: "POST", pattern: "/ancill/specialRequest/payment/v1/ancillary_gps/pay" },
|
|
221
|
+
{ method: "POST", pattern: "/ancill/specialRequest/ancillary/save_gps/{bookingCode}" },
|
|
222
|
+
{ method: "POST", pattern: "/specialRequest/deprecated/service/{bookingCode}" },
|
|
223
|
+
{ method: "POST", pattern: "/ancill/specialRequest/payment/v1/ancillary_lounge/pay" },
|
|
224
|
+
{ method: "POST", pattern: "/ancill/specialRequest/ancillary/save_lounge/{bookingCode}" }
|
|
225
|
+
];
|
|
226
|
+
var ALWAYS_LOG_MATCHERS = ALWAYS_LOG_ENDPOINTS.map((entry) => ({
|
|
227
|
+
method: entry.method.toUpperCase(),
|
|
228
|
+
regex: new RegExp(
|
|
229
|
+
"^" + entry.pattern.split("/").map(
|
|
230
|
+
(seg) => seg.startsWith("{") && seg.endsWith("}") ? "[^/]+" : seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
231
|
+
).join("/") + "/?$"
|
|
232
|
+
)
|
|
233
|
+
}));
|
|
234
|
+
function shouldAlwaysLogEndpoint(url, method) {
|
|
235
|
+
let pathname;
|
|
236
|
+
try {
|
|
237
|
+
pathname = new URL(url, window.location.origin).pathname;
|
|
238
|
+
} catch {
|
|
239
|
+
pathname = url;
|
|
240
|
+
}
|
|
241
|
+
const upperMethod = method.toUpperCase();
|
|
242
|
+
return ALWAYS_LOG_MATCHERS.some((m) => m.method === upperMethod && m.regex.test(pathname));
|
|
243
|
+
}
|
|
188
244
|
var Logger = class {
|
|
189
245
|
constructor() {
|
|
190
246
|
this.config = null;
|
|
@@ -373,6 +429,7 @@ var Logger = class {
|
|
|
373
429
|
const key = this.buildErrorKey("onerror", finalMessage);
|
|
374
430
|
if (this.loggedErrorKeys.has(key)) return false;
|
|
375
431
|
this.loggedErrorKeys.add(key);
|
|
432
|
+
this.addBreadcrumb("error", finalMessage, { source: trimmedSource, lineno, colno });
|
|
376
433
|
void this.error({
|
|
377
434
|
fnName: "window.onerror",
|
|
378
435
|
messageError: new Error(finalMessage),
|
|
@@ -386,6 +443,7 @@ var Logger = class {
|
|
|
386
443
|
const key = this.buildErrorKey("unhandledrejection", msg);
|
|
387
444
|
if (this.loggedErrorKeys.has(key)) return;
|
|
388
445
|
this.loggedErrorKeys.add(key);
|
|
446
|
+
this.addBreadcrumb("error", msg);
|
|
389
447
|
void this.error({
|
|
390
448
|
fnName: "window.unhandledrejection",
|
|
391
449
|
messageError: new Error(msg)
|
|
@@ -400,6 +458,7 @@ var Logger = class {
|
|
|
400
458
|
const key = this.buildErrorKey("console.error", trimmedMsg);
|
|
401
459
|
if (this.loggedErrorKeys.has(key)) return;
|
|
402
460
|
this.loggedErrorKeys.add(key);
|
|
461
|
+
this.addBreadcrumb("error", trimmedMsg);
|
|
403
462
|
const originalError = args.find((a) => a instanceof Error);
|
|
404
463
|
const errorToSend = originalError ?? new Error(trimmedMsg);
|
|
405
464
|
void this.error({
|
|
@@ -506,7 +565,8 @@ var Logger = class {
|
|
|
506
565
|
status: response.status,
|
|
507
566
|
duration: `${duration}ms`
|
|
508
567
|
});
|
|
509
|
-
|
|
568
|
+
const isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
569
|
+
if (!isLocalhost && response.status >= 400) {
|
|
510
570
|
const dedupKey = this.buildErrorKey("interceptFetch", `${method} ${url} ${response.status}`);
|
|
511
571
|
if (!this.loggedErrorKeys.has(dedupKey)) {
|
|
512
572
|
this.loggedErrorKeys.add(dedupKey);
|
|
@@ -518,8 +578,8 @@ var Logger = class {
|
|
|
518
578
|
} catch {
|
|
519
579
|
responseBody = "[unable to read response body]";
|
|
520
580
|
}
|
|
521
|
-
const
|
|
522
|
-
if (!
|
|
581
|
+
const isLocalhost2 = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
582
|
+
if (!isLocalhost2) {
|
|
523
583
|
this.error({
|
|
524
584
|
fnName: "interceptFetch",
|
|
525
585
|
messageError: new Error(`HTTP ${response.status} ${response.statusText} \u2014 ${url}`),
|
|
@@ -536,6 +596,32 @@ var Logger = class {
|
|
|
536
596
|
});
|
|
537
597
|
}
|
|
538
598
|
}
|
|
599
|
+
} else if (!isLocalhost && response.status < 400 && shouldAlwaysLogEndpoint(url, method)) {
|
|
600
|
+
const dedupKey = this.buildErrorKey("interceptFetch", `${method} ${url} ${response.status}`);
|
|
601
|
+
if (!this.loggedErrorKeys.has(dedupKey)) {
|
|
602
|
+
this.loggedErrorKeys.add(dedupKey);
|
|
603
|
+
let responseBody = null;
|
|
604
|
+
try {
|
|
605
|
+
const cloned = response.clone();
|
|
606
|
+
const contentType = cloned.headers.get("content-type") ?? "";
|
|
607
|
+
responseBody = contentType.includes("application/json") ? await cloned.json() : await cloned.text();
|
|
608
|
+
} catch {
|
|
609
|
+
responseBody = "[unable to read response body]";
|
|
610
|
+
}
|
|
611
|
+
this.info({
|
|
612
|
+
fnName: "interceptFetch",
|
|
613
|
+
endpointName: url,
|
|
614
|
+
payload: {
|
|
615
|
+
method,
|
|
616
|
+
status: response.status,
|
|
617
|
+
statusText: response.statusText,
|
|
618
|
+
duration: `${duration}ms`,
|
|
619
|
+
query: parseQueryParams(url),
|
|
620
|
+
request: parseRequestBody(init?.body),
|
|
621
|
+
response: responseBody
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
}
|
|
539
625
|
}
|
|
540
626
|
return response;
|
|
541
627
|
} catch (networkError) {
|
|
@@ -605,7 +691,8 @@ var Logger = class {
|
|
|
605
691
|
status,
|
|
606
692
|
duration: `${duration}ms`
|
|
607
693
|
});
|
|
608
|
-
|
|
694
|
+
const isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
695
|
+
if (!isLocalhost && status >= 400) {
|
|
609
696
|
const dedupKey = self.buildErrorKey("interceptXHR", `${method} ${url} ${status}`);
|
|
610
697
|
if (self.loggedErrorKeys.has(dedupKey)) return;
|
|
611
698
|
self.loggedErrorKeys.add(dedupKey);
|
|
@@ -614,8 +701,8 @@ var Logger = class {
|
|
|
614
701
|
responseBody = JSON.parse(this.responseText);
|
|
615
702
|
} catch {
|
|
616
703
|
}
|
|
617
|
-
const
|
|
618
|
-
if (!
|
|
704
|
+
const isLocalhost2 = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
705
|
+
if (!isLocalhost2) {
|
|
619
706
|
void self.error({
|
|
620
707
|
fnName: "interceptXHR",
|
|
621
708
|
messageError: new Error(`HTTP ${status} ${this.statusText} \u2014 ${url}`),
|
|
@@ -631,6 +718,28 @@ var Logger = class {
|
|
|
631
718
|
}
|
|
632
719
|
});
|
|
633
720
|
}
|
|
721
|
+
} else if (!isLocalhost && status < 400 && shouldAlwaysLogEndpoint(url, method)) {
|
|
722
|
+
const dedupKey = self.buildErrorKey("interceptXHR", `${method} ${url} ${status}`);
|
|
723
|
+
if (self.loggedErrorKeys.has(dedupKey)) return;
|
|
724
|
+
self.loggedErrorKeys.add(dedupKey);
|
|
725
|
+
let responseBody = this.responseText;
|
|
726
|
+
try {
|
|
727
|
+
responseBody = JSON.parse(this.responseText);
|
|
728
|
+
} catch {
|
|
729
|
+
}
|
|
730
|
+
void self.info({
|
|
731
|
+
fnName: "interceptXHR",
|
|
732
|
+
endpointName: url,
|
|
733
|
+
payload: {
|
|
734
|
+
method,
|
|
735
|
+
status,
|
|
736
|
+
statusText: this.statusText,
|
|
737
|
+
duration: `${duration}ms`,
|
|
738
|
+
query: parseQueryParams(url),
|
|
739
|
+
request: requestBody,
|
|
740
|
+
response: responseBody
|
|
741
|
+
}
|
|
742
|
+
});
|
|
634
743
|
}
|
|
635
744
|
});
|
|
636
745
|
this.addEventListener("error", () => {
|
|
@@ -679,6 +788,13 @@ var Logger = class {
|
|
|
679
788
|
});
|
|
680
789
|
const disabledAtClick = this.isElementDisabled(target);
|
|
681
790
|
const invalidFieldAtClick = this.hasInvalidFieldNearby(target);
|
|
791
|
+
const ariaDisabledAtClick = target.getAttribute("aria-disabled") === "true";
|
|
792
|
+
const pointerEventsBlockedAtClick = window.getComputedStyle(target).pointerEvents === "none";
|
|
793
|
+
const overlayInterceptedAtClick = (() => {
|
|
794
|
+
const elAtPoint = document.elementFromPoint(event.clientX, event.clientY);
|
|
795
|
+
if (!elAtPoint || elAtPoint === target || target.contains(elAtPoint)) return null;
|
|
796
|
+
return elAtPoint;
|
|
797
|
+
})();
|
|
682
798
|
const clickTime = Date.now();
|
|
683
799
|
const domActivityAtClick = this.lastDomActivity;
|
|
684
800
|
const DEAD_CLICK_DELAY_MS = 4e3;
|
|
@@ -747,7 +863,25 @@ var Logger = class {
|
|
|
747
863
|
const recentFetch = this.breadcrumbs.filter(
|
|
748
864
|
(b) => b.type === "fetch" && clickTime - new Date(b.timestamp).getTime() <= RAGE_CLICK_WINDOW_MS
|
|
749
865
|
).slice(-1)[0];
|
|
750
|
-
const
|
|
866
|
+
const recentError = this.breadcrumbs.filter(
|
|
867
|
+
(b) => b.type === "error" && clickTime - new Date(b.timestamp).getTime() <= RAGE_CLICK_WINDOW_MS
|
|
868
|
+
).slice(-1)[0];
|
|
869
|
+
const possibleReason = disabledAtClick ? { type: "disabled", detail: "Button dalam kondisi disabled saat diklik" } : ariaDisabledAtClick ? {
|
|
870
|
+
type: "aria_disabled",
|
|
871
|
+
detail: 'Element memiliki aria-disabled="true" (disabled secara visual/aksesibilitas, bukan native disabled attribute)'
|
|
872
|
+
} : pointerEventsBlockedAtClick ? {
|
|
873
|
+
type: "pointer_events_blocked",
|
|
874
|
+
detail: "Element memiliki CSS pointer-events: none saat diklik (tidak bisa menerima event klik sama sekali)"
|
|
875
|
+
} : overlayInterceptedAtClick ? {
|
|
876
|
+
type: "overlay_intercepted",
|
|
877
|
+
detail: `Element lain (${getElementSelector(overlayInterceptedAtClick)}) menutupi target saat klik terjadi, klik tidak sampai ke target aslinya`
|
|
878
|
+
} : recentFetch ? { type: "api_error", detail: recentFetch.message } : invalidFieldAtClick ? {
|
|
879
|
+
type: "validation_error",
|
|
880
|
+
detail: "Field invalid terdeteksi di form terdekat"
|
|
881
|
+
} : recentError ? { type: "js_error", detail: recentError.message } : this.recentDomChanges.length ? { type: "dom_feedback", detail: this.recentDomChanges.slice(-1)[0] } : {
|
|
882
|
+
type: "no_feedback",
|
|
883
|
+
detail: "Tidak ada feedback UI terdeteksi setelah klik"
|
|
884
|
+
};
|
|
751
885
|
const formattedLog = formatClickLog({
|
|
752
886
|
fnName: "interceptClick.rageClick",
|
|
753
887
|
tagName,
|
|
@@ -874,6 +1008,7 @@ var Logger = class {
|
|
|
874
1008
|
y: window.scrollY,
|
|
875
1009
|
width: window.innerWidth,
|
|
876
1010
|
height: window.innerHeight,
|
|
1011
|
+
imageTimeout: 3e3,
|
|
877
1012
|
onclone: (clonedDoc) => {
|
|
878
1013
|
if (!canApplyHighlight) return;
|
|
879
1014
|
const clonedTarget = clonedDoc.querySelector(`[data-ga-highlight="${HIGHLIGHT_MARKER}"]`);
|