@diegotsi/flint-core 1.9.1 → 1.10.1
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/{chunk-SO6WYKFF.js → chunk-FQ6TBPEG.js} +3 -1
- package/dist/chunk-FQ6TBPEG.js.map +1 -0
- package/dist/{datadog-FLEAFTUB.js → datadog-F3R66MH7.js} +2 -2
- package/dist/index.cjs +110 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js +110 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-SO6WYKFF.js.map +0 -1
- /package/dist/{datadog-FLEAFTUB.js.map → datadog-F3R66MH7.js.map} +0 -0
|
@@ -9,6 +9,8 @@ function createDatadogReplayProvider(site) {
|
|
|
9
9
|
return () => {
|
|
10
10
|
try {
|
|
11
11
|
const ddRum = window.DD_RUM;
|
|
12
|
+
const link = ddRum?.getSessionReplayLink?.();
|
|
13
|
+
if (link) return link;
|
|
12
14
|
const ctx = ddRum?.getInternalContext?.();
|
|
13
15
|
if (ctx?.session_id) {
|
|
14
16
|
const ts = Date.now();
|
|
@@ -39,4 +41,4 @@ export {
|
|
|
39
41
|
createDatadogReplayProvider,
|
|
40
42
|
trackDatadogBugReported
|
|
41
43
|
};
|
|
42
|
-
//# sourceMappingURL=chunk-
|
|
44
|
+
//# sourceMappingURL=chunk-FQ6TBPEG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/integrations/datadog.ts"],"sourcesContent":["/**\n * Datadog RUM integration — opt-in helper.\n *\n * Usage:\n * import { Flint } from \"@diegotsi/flint-core\";\n * import { createDatadogReplayProvider, DATADOG_BLOCKED_HOSTS } from \"@diegotsi/flint-core\";\n *\n * Flint.init({\n * projectKey: \"...\",\n * serverUrl: \"...\",\n * externalReplayProvider: createDatadogReplayProvider(\"app.datadoghq.com\"),\n * blockedHosts: DATADOG_BLOCKED_HOSTS,\n * });\n */\n\n/** Datadog intake hosts to exclude from network capture. */\nexport const DATADOG_BLOCKED_HOSTS = [\n \"browser-intake-datadoghq.com\",\n \"rum.browser-intake-datadoghq.com\",\n \"logs.browser-intake-datadoghq.com\",\n \"session-replay.browser-intake-datadoghq.com\",\n];\n\n/**\n * Creates an `externalReplayProvider` that returns a deep link to the current\n * Datadog RUM Session Replay.\n *\n * Prefers the SDK's own `getSessionReplayLink()` — Datadog builds it with the\n * current view + timestamp, so the player opens at the reported moment rather\n * than the session start. Falls back to a hand-built session URL (opens the\n * session, without a precise seek) only when that method isn't available.\n */\nexport function createDatadogReplayProvider(site: string): () => string | undefined {\n return () => {\n try {\n const ddRum = (window as unknown as Record<string, unknown>).DD_RUM as\n | {\n getSessionReplayLink?: () => string | undefined;\n getInternalContext?: () => { session_id?: string } | undefined;\n }\n | undefined;\n\n // Official link generator — lands on the current moment/view.\n const link = ddRum?.getSessionReplayLink?.();\n if (link) return link;\n\n // Fallback: build from the session id (opens the session, no exact seek).\n const ctx = ddRum?.getInternalContext?.();\n if (ctx?.session_id) {\n const ts = Date.now();\n const fromTs = ts - 30_000;\n const toTs = ts + 5_000;\n return `https://${site}/rum/replay/sessions/${ctx.session_id}?from_ts=${fromTs}&to_ts=${toTs}&tab=replay&live=false`;\n }\n } catch {\n // DD_RUM not available — silently skip\n }\n return undefined;\n };\n}\n\n/**\n * Emits a custom Datadog RUM action at the moment a bug is reported, so it\n * shows up as a clickable marker on the Session Replay timeline — making the\n * report moment easy to find in long sessions.\n *\n * No-op when Datadog RUM (`window.DD_RUM`) is not present on the page.\n */\nexport function trackDatadogBugReported(meta: {\n bugId: string;\n severity?: string;\n url?: string;\n title?: string;\n}): void {\n try {\n const ddRum = (window as unknown as Record<string, unknown>).DD_RUM as\n | { addAction?: (name: string, context?: Record<string, unknown>) => void }\n | undefined;\n ddRum?.addAction?.(\"flint.bug_reported\", {\n bug_id: meta.bugId,\n severity: meta.severity,\n url: meta.url,\n title: meta.title,\n });\n } catch {\n // DD_RUM not available — silently skip\n }\n}\n"],"mappings":";AAgBO,IAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAWO,SAAS,4BAA4B,MAAwC;AAClF,SAAO,MAAM;AACX,QAAI;AACF,YAAM,QAAS,OAA8C;AAQ7D,YAAM,OAAO,OAAO,uBAAuB;AAC3C,UAAI,KAAM,QAAO;AAGjB,YAAM,MAAM,OAAO,qBAAqB;AACxC,UAAI,KAAK,YAAY;AACnB,cAAM,KAAK,KAAK,IAAI;AACpB,cAAM,SAAS,KAAK;AACpB,cAAM,OAAO,KAAK;AAClB,eAAO,WAAW,IAAI,wBAAwB,IAAI,UAAU,YAAY,MAAM,UAAU,IAAI;AAAA,MAC9F;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACT;AACF;AASO,SAAS,wBAAwB,MAK/B;AACP,MAAI;AACF,UAAM,QAAS,OAA8C;AAG7D,WAAO,YAAY,sBAAsB;AAAA,MACvC,QAAQ,KAAK;AAAA,MACb,UAAU,KAAK;AAAA,MACf,KAAK,KAAK;AAAA,MACV,OAAO,KAAK;AAAA,IACd,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;","names":[]}
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
DATADOG_BLOCKED_HOSTS,
|
|
3
3
|
createDatadogReplayProvider,
|
|
4
4
|
trackDatadogBugReported
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-FQ6TBPEG.js";
|
|
6
6
|
export {
|
|
7
7
|
DATADOG_BLOCKED_HOSTS,
|
|
8
8
|
createDatadogReplayProvider,
|
|
9
9
|
trackDatadogBugReported
|
|
10
10
|
};
|
|
11
|
-
//# sourceMappingURL=datadog-
|
|
11
|
+
//# sourceMappingURL=datadog-F3R66MH7.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,8 @@ function createDatadogReplayProvider(site) {
|
|
|
31
31
|
return () => {
|
|
32
32
|
try {
|
|
33
33
|
const ddRum = window.DD_RUM;
|
|
34
|
+
const link = ddRum?.getSessionReplayLink?.();
|
|
35
|
+
if (link) return link;
|
|
34
36
|
const ctx = ddRum?.getInternalContext?.();
|
|
35
37
|
if (ctx?.session_id) {
|
|
36
38
|
const ts = Date.now();
|
|
@@ -989,7 +991,8 @@ function captureHeaders(raw, max) {
|
|
|
989
991
|
count++;
|
|
990
992
|
}
|
|
991
993
|
} else {
|
|
992
|
-
const
|
|
994
|
+
const headersLike = raw;
|
|
995
|
+
const entries = typeof headersLike.entries === "function" && typeof headersLike.forEach === "function" ? headersLike.entries() : Array.isArray(raw) ? raw : Object.entries(raw);
|
|
993
996
|
for (const [k, v] of entries) {
|
|
994
997
|
total++;
|
|
995
998
|
if (count >= max) continue;
|
|
@@ -1007,7 +1010,7 @@ function captureHeaders(raw, max) {
|
|
|
1007
1010
|
function extractContentType(headers) {
|
|
1008
1011
|
if (!headers) return void 0;
|
|
1009
1012
|
try {
|
|
1010
|
-
if (typeof
|
|
1013
|
+
if (typeof headers.get === "function") return headers.get("content-type") ?? void 0;
|
|
1011
1014
|
if (Array.isArray(headers)) {
|
|
1012
1015
|
const found = headers.find(([k]) => k.toLowerCase() === "content-type");
|
|
1013
1016
|
return found?.[1];
|
|
@@ -1020,17 +1023,22 @@ function extractContentType(headers) {
|
|
|
1020
1023
|
}
|
|
1021
1024
|
return void 0;
|
|
1022
1025
|
}
|
|
1023
|
-
function createNetworkCollector(extraBlockedHosts = []) {
|
|
1026
|
+
function createNetworkCollector(extraBlockedHosts = [], options) {
|
|
1027
|
+
const maxEntries = options?.maxEntries ?? MAX_ENTRIES3;
|
|
1028
|
+
const maxResponseBody = options?.maxResponseBody ?? MAX_RESPONSE_BODY;
|
|
1024
1029
|
const entries = [];
|
|
1025
1030
|
const blocked = /* @__PURE__ */ new Set([...DEFAULT_BLOCKED_HOSTS, ...extraBlockedHosts]);
|
|
1026
1031
|
let origFetch = null;
|
|
1027
1032
|
let origXHROpen = null;
|
|
1028
1033
|
let origXHRSend = null;
|
|
1029
1034
|
let origXHRSetHeader = null;
|
|
1035
|
+
let perfObserver = null;
|
|
1030
1036
|
let active = false;
|
|
1037
|
+
const seenUrls = /* @__PURE__ */ new Set();
|
|
1031
1038
|
function push(entry) {
|
|
1039
|
+
seenUrls.add(entry.fullUrl ?? entry.url);
|
|
1032
1040
|
entries.push(entry);
|
|
1033
|
-
if (entries.length >
|
|
1041
|
+
if (entries.length > maxEntries) entries.shift();
|
|
1034
1042
|
}
|
|
1035
1043
|
return {
|
|
1036
1044
|
start() {
|
|
@@ -1038,19 +1046,50 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1038
1046
|
active = true;
|
|
1039
1047
|
origFetch = window.fetch;
|
|
1040
1048
|
window.fetch = async (input, init2) => {
|
|
1041
|
-
const
|
|
1042
|
-
const
|
|
1049
|
+
const reqObj = typeof Request !== "undefined" && input instanceof Request ? input : null;
|
|
1050
|
+
const method = (init2?.method ?? reqObj?.method ?? "GET").toUpperCase();
|
|
1051
|
+
const url = reqObj ? reqObj.url : typeof input === "string" ? input : input.href;
|
|
1043
1052
|
const startTime = Date.now();
|
|
1044
|
-
const
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
|
|
1053
|
+
const reqHeaders = captureHeaders(init2?.headers ?? reqObj?.headers, MAX_HEADERS);
|
|
1054
|
+
const reqContentType = extractContentType(init2?.headers ?? reqObj?.headers);
|
|
1055
|
+
let reqBody = safeBodyPreview(init2?.body, MAX_REQUEST_BODY);
|
|
1056
|
+
if (reqBody === void 0 && reqObj && !shouldIgnore(url, blocked)) {
|
|
1057
|
+
try {
|
|
1058
|
+
const t = await reqObj.clone().text();
|
|
1059
|
+
if (t) reqBody = t.length > MAX_REQUEST_BODY ? t.slice(0, MAX_REQUEST_BODY) + "\u2026" : t;
|
|
1060
|
+
} catch {
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
let res;
|
|
1064
|
+
try {
|
|
1065
|
+
res = await origFetch.call(window, input, init2);
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
if (!shouldIgnore(url, blocked)) {
|
|
1068
|
+
push({
|
|
1069
|
+
method,
|
|
1070
|
+
url: truncateUrl(url),
|
|
1071
|
+
fullUrl: captureFullUrl(url),
|
|
1072
|
+
status: 0,
|
|
1073
|
+
duration: Date.now() - startTime,
|
|
1074
|
+
timestamp: startTime,
|
|
1075
|
+
source: "fetch",
|
|
1076
|
+
failed: true,
|
|
1077
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
1078
|
+
requestHeaders: reqHeaders,
|
|
1079
|
+
requestBody: reqBody,
|
|
1080
|
+
requestContentType: reqContentType
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
throw err;
|
|
1084
|
+
}
|
|
1048
1085
|
if (!shouldIgnore(url, blocked)) {
|
|
1049
1086
|
const entry = {
|
|
1050
1087
|
method,
|
|
1051
1088
|
url: truncateUrl(url),
|
|
1052
1089
|
fullUrl: captureFullUrl(url),
|
|
1053
1090
|
status: res.status,
|
|
1091
|
+
statusText: res.statusText || void 0,
|
|
1092
|
+
source: "fetch",
|
|
1054
1093
|
duration: Date.now() - startTime,
|
|
1055
1094
|
timestamp: startTime,
|
|
1056
1095
|
requestHeaders: reqHeaders,
|
|
@@ -1063,7 +1102,7 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1063
1102
|
try {
|
|
1064
1103
|
const clone = res.clone();
|
|
1065
1104
|
clone.text().then((text) => {
|
|
1066
|
-
entry.responseBody = text.length >
|
|
1105
|
+
entry.responseBody = text.length > maxResponseBody ? text.slice(0, maxResponseBody) + "\u2026" : text;
|
|
1067
1106
|
if (!entry.responseSize && text.length) entry.responseSize = text.length;
|
|
1068
1107
|
}).catch(() => {
|
|
1069
1108
|
});
|
|
@@ -1100,13 +1139,16 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1100
1139
|
XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
|
|
1101
1140
|
const startTime = Date.now();
|
|
1102
1141
|
const urlStr = typeof url === "string" ? url : url.href;
|
|
1142
|
+
let captured = false;
|
|
1103
1143
|
this.addEventListener("load", () => {
|
|
1144
|
+
if (captured) return;
|
|
1145
|
+
captured = true;
|
|
1104
1146
|
if (!shouldIgnore(urlStr, blocked)) {
|
|
1105
1147
|
const meta = xhrMeta.get(this);
|
|
1106
1148
|
let resBody;
|
|
1107
1149
|
try {
|
|
1108
1150
|
const text = this.responseText;
|
|
1109
|
-
resBody = text && text.length >
|
|
1151
|
+
resBody = text && text.length > maxResponseBody ? text.slice(0, maxResponseBody) + "\u2026" : text || void 0;
|
|
1110
1152
|
} catch {
|
|
1111
1153
|
}
|
|
1112
1154
|
push({
|
|
@@ -1114,6 +1156,8 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1114
1156
|
url: truncateUrl(urlStr),
|
|
1115
1157
|
fullUrl: captureFullUrl(urlStr),
|
|
1116
1158
|
status: this.status,
|
|
1159
|
+
statusText: this.statusText || void 0,
|
|
1160
|
+
source: "xhr",
|
|
1117
1161
|
duration: Date.now() - startTime,
|
|
1118
1162
|
timestamp: startTime,
|
|
1119
1163
|
requestHeaders: meta?.reqHeaders && Object.keys(meta.reqHeaders).length > 0 ? meta.reqHeaders : void 0,
|
|
@@ -1126,8 +1170,58 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1126
1170
|
});
|
|
1127
1171
|
}
|
|
1128
1172
|
});
|
|
1173
|
+
const pushFailure = (errorMessage) => {
|
|
1174
|
+
if (captured) return;
|
|
1175
|
+
captured = true;
|
|
1176
|
+
if (shouldIgnore(urlStr, blocked)) return;
|
|
1177
|
+
const meta = xhrMeta.get(this);
|
|
1178
|
+
push({
|
|
1179
|
+
method: method.toUpperCase(),
|
|
1180
|
+
url: truncateUrl(urlStr),
|
|
1181
|
+
fullUrl: captureFullUrl(urlStr),
|
|
1182
|
+
status: 0,
|
|
1183
|
+
source: "xhr",
|
|
1184
|
+
failed: true,
|
|
1185
|
+
errorMessage,
|
|
1186
|
+
duration: Date.now() - startTime,
|
|
1187
|
+
timestamp: startTime,
|
|
1188
|
+
requestHeaders: meta?.reqHeaders && Object.keys(meta.reqHeaders).length > 0 ? meta.reqHeaders : void 0,
|
|
1189
|
+
requestBody: meta?.reqBody,
|
|
1190
|
+
requestContentType: meta?.reqContentType
|
|
1191
|
+
});
|
|
1192
|
+
};
|
|
1193
|
+
this.addEventListener("error", () => pushFailure("Network error"));
|
|
1194
|
+
this.addEventListener("timeout", () => pushFailure("Timeout"));
|
|
1195
|
+
this.addEventListener("abort", () => pushFailure("Aborted"));
|
|
1129
1196
|
return origXHROpen.apply(this, [method, url, async ?? true, username, password]);
|
|
1130
1197
|
};
|
|
1198
|
+
if (typeof PerformanceObserver !== "undefined") {
|
|
1199
|
+
try {
|
|
1200
|
+
perfObserver = new PerformanceObserver((list) => {
|
|
1201
|
+
for (const entry of list.getEntries()) {
|
|
1202
|
+
const res = entry;
|
|
1203
|
+
if (res.initiatorType !== "fetch" && res.initiatorType !== "xmlhttprequest") continue;
|
|
1204
|
+
const url = res.name;
|
|
1205
|
+
if (shouldIgnore(url, blocked)) continue;
|
|
1206
|
+
if (seenUrls.has(captureFullUrl(url)) || seenUrls.has(truncateUrl(url))) continue;
|
|
1207
|
+
push({
|
|
1208
|
+
// Resource timing does not expose the HTTP method; default to GET.
|
|
1209
|
+
method: "GET",
|
|
1210
|
+
url: truncateUrl(url),
|
|
1211
|
+
fullUrl: captureFullUrl(url),
|
|
1212
|
+
status: res.responseStatus ?? 0,
|
|
1213
|
+
source: "perf",
|
|
1214
|
+
duration: Math.round(res.duration),
|
|
1215
|
+
timestamp: Math.round(performance.timeOrigin + res.startTime),
|
|
1216
|
+
responseSize: res.transferSize || res.encodedBodySize || void 0
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
perfObserver.observe({ type: "resource", buffered: true });
|
|
1221
|
+
} catch {
|
|
1222
|
+
perfObserver = null;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1131
1225
|
},
|
|
1132
1226
|
stop() {
|
|
1133
1227
|
if (!active) return;
|
|
@@ -1136,6 +1230,10 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1136
1230
|
if (origXHROpen) XMLHttpRequest.prototype.open = origXHROpen;
|
|
1137
1231
|
if (origXHRSend) XMLHttpRequest.prototype.send = origXHRSend;
|
|
1138
1232
|
if (origXHRSetHeader) XMLHttpRequest.prototype.setRequestHeader = origXHRSetHeader;
|
|
1233
|
+
if (perfObserver) {
|
|
1234
|
+
perfObserver.disconnect();
|
|
1235
|
+
perfObserver = null;
|
|
1236
|
+
}
|
|
1139
1237
|
},
|
|
1140
1238
|
getEntries() {
|
|
1141
1239
|
return [...entries];
|