@log-drop/web 1.0.0-alpha3 → 1.0.0-alpha4

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.cjs CHANGED
@@ -38,7 +38,7 @@ LogDropConstants.DEFAULT_BASE_URL = "https://server.logdrop.io";
38
38
  LogDropConstants.DEFAULT_WEB_SECRET_KEY = "xuM9w0BAQEFx";
39
39
  LogDropConstants.FILE_EXPORT_INTERVAL_MS = 3e5;
40
40
  LogDropConstants.DEVICE_INFO_INTERVAL_MS = 1e4;
41
- LogDropConstants.SDK_VERSION = true ? "1.0.0-alpha3" : "1.0.0";
41
+ LogDropConstants.SDK_VERSION = true ? "1.0.0-alpha4" : "1.0.0";
42
42
  LogDropConstants.STORAGE_CHECK_INTERVAL_MS = 12e4;
43
43
  LogDropConstants.MAX_LOCAL_STORAGE_SIZE = 5242880;
44
44
  LogDropConstants.DATABASE_MAX_STORAGE_LIMIT = 16e6;
@@ -540,6 +540,168 @@ var NetworkInstrumentation = class {
540
540
  }
541
541
  };
542
542
 
543
+ // src/instrumentation/httpCapturePolicy.js
544
+ var ALWAYS_MASKED_HEADERS = /* @__PURE__ */ new Set([
545
+ "authorization",
546
+ "proxy-authorization",
547
+ "x-auth-token",
548
+ "x-api-key",
549
+ "x-session-token",
550
+ "cookie",
551
+ "set-cookie"
552
+ ]);
553
+ var MASK = "***";
554
+ var MAX_PATTERN_LENGTH = 500;
555
+ var DANGEROUS_PATTERN = /(a\+)\+|\(\.\*\)\+|\(\.\+\)\+/;
556
+ function normalizeHost(value) {
557
+ const host = String(value ?? "").trim().replace(/\.+$/, "").toLowerCase();
558
+ if (!host || /[/:?#@]/.test(host) || host.length > 253) return null;
559
+ return host;
560
+ }
561
+ function normalizePath(value) {
562
+ const trimmed = String(value ?? "").trim();
563
+ if (!trimmed) return "/";
564
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
565
+ return withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
566
+ }
567
+ function domainMatches(host, ruleHost) {
568
+ return host === ruleHost || host.endsWith(`.${ruleHost}`);
569
+ }
570
+ function pathMatches(path, rulePath, matchType) {
571
+ if (matchType === "EXACT") return path === rulePath;
572
+ if (matchType === "PREFIX") return rulePath === "/" || path === rulePath || path.startsWith(`${rulePath}/`);
573
+ return false;
574
+ }
575
+ function parseUrl(url) {
576
+ try {
577
+ const parsed = new URL(String(url ?? ""));
578
+ const host = normalizeHost(parsed.hostname);
579
+ return host ? { host, path: normalizePath(parsed.pathname) } : null;
580
+ } catch {
581
+ return null;
582
+ }
583
+ }
584
+ function compileSensitivePatterns(filters) {
585
+ const patterns = [];
586
+ for (const filter of Array.isArray(filters) ? filters : []) {
587
+ const source = String(filter ?? "");
588
+ if (!source || source.length > MAX_PATTERN_LENGTH) continue;
589
+ const parts = source.match(/^\/(.+)\/([gimsuy]*)$/);
590
+ const body = parts ? parts[1] : source;
591
+ if (DANGEROUS_PATTERN.test(body)) continue;
592
+ let flags = parts ? parts[2] : "";
593
+ if (!flags.includes("g")) flags += "g";
594
+ if (!flags.includes("i")) flags += "i";
595
+ try {
596
+ patterns.push(new RegExp(body, flags));
597
+ } catch {
598
+ }
599
+ }
600
+ return patterns;
601
+ }
602
+ function hashString(text) {
603
+ let hash = 2166136261;
604
+ for (let i = 0; i < text.length; i++) {
605
+ hash ^= text.charCodeAt(i);
606
+ hash = Math.imul(hash, 16777619);
607
+ }
608
+ return (hash >>> 0).toString(16);
609
+ }
610
+ function buildHttpCapturePolicy(config) {
611
+ const policy = {
612
+ domainBlacklist: (config?.httpDomainBlackList ?? []).map(normalizeHost).filter(Boolean),
613
+ pathBlacklist: Array.isArray(config?.httpPathBlackList) ? config.httpPathBlackList : [],
614
+ captureConfig: config?.httpPayloadCaptureConfig ?? null,
615
+ sensitivePatterns: compileSensitivePatterns(config?.sensitiveInfoFilter)
616
+ };
617
+ policy.fingerprint = hashString(JSON.stringify([
618
+ policy.domainBlacklist,
619
+ policy.pathBlacklist,
620
+ policy.captureConfig,
621
+ policy.sensitivePatterns.map((pattern) => `${pattern.source}/${pattern.flags}`)
622
+ ]));
623
+ return policy;
624
+ }
625
+ function shouldIgnoreHttpUrl(url, policy) {
626
+ const target = parseUrl(url);
627
+ if (!target || !policy) return false;
628
+ if (policy.domainBlacklist.some((domain) => domainMatches(target.host, domain))) return true;
629
+ return policy.pathBlacklist.some((rule) => {
630
+ const ruleHost = normalizeHost(rule?.host);
631
+ if (!ruleHost || !domainMatches(target.host, ruleHost)) return false;
632
+ return pathMatches(target.path, normalizePath(rule.path), rule.matchType);
633
+ });
634
+ }
635
+ function resolveDisabledPayloadFields(url, captureConfig) {
636
+ const disabled = {
637
+ requestHeaders: captureConfig?.disableRequestHeaders === true,
638
+ requestBody: captureConfig?.disableRequestBody === true,
639
+ responseHeaders: captureConfig?.disableResponseHeaders === true,
640
+ responseBody: captureConfig?.disableResponseBody === true
641
+ };
642
+ const target = parseUrl(url);
643
+ if (!captureConfig || !target) return disabled;
644
+ for (const rule of captureConfig.rules ?? []) {
645
+ const ruleHost = normalizeHost(rule?.host);
646
+ if (!ruleHost || !domainMatches(target.host, ruleHost)) continue;
647
+ if (rule.path && String(rule.path).trim() && !pathMatches(target.path, normalizePath(rule.path), rule.matchType)) {
648
+ continue;
649
+ }
650
+ disabled.requestHeaders || (disabled.requestHeaders = rule.disableRequestHeaders === true);
651
+ disabled.requestBody || (disabled.requestBody = rule.disableRequestBody === true);
652
+ disabled.responseHeaders || (disabled.responseHeaders = rule.disableResponseHeaders === true);
653
+ disabled.responseBody || (disabled.responseBody = rule.disableResponseBody === true);
654
+ }
655
+ return disabled;
656
+ }
657
+ function maskSensitive(text, patterns) {
658
+ if (typeof text !== "string" || !patterns?.length) return text;
659
+ let output = text;
660
+ for (const regex of patterns) {
661
+ regex.lastIndex = 0;
662
+ output = output.replace(regex, (...args) => {
663
+ const groups = args.slice(1, -2).filter((group) => typeof group === "string" || group === void 0);
664
+ const present = groups.filter((group) => group !== void 0 && group !== "");
665
+ if (present.length === 3) return `${groups[0]}${MASK}${groups[2]}`;
666
+ if (present.length === 2) return `${groups[0]}${MASK}`;
667
+ return MASK;
668
+ });
669
+ }
670
+ return output;
671
+ }
672
+ function maskStructured(value, patterns) {
673
+ if (value == null || !patterns.length) return value;
674
+ if (typeof value === "string") return maskSensitive(value, patterns);
675
+ try {
676
+ return JSON.parse(maskSensitive(JSON.stringify(value), patterns));
677
+ } catch {
678
+ return value;
679
+ }
680
+ }
681
+ function maskCredentialHeaders(headers) {
682
+ if (!headers || typeof headers !== "object") return {};
683
+ const out = {};
684
+ for (const [key, value] of Object.entries(headers)) {
685
+ out[key] = ALWAYS_MASKED_HEADERS.has(String(key).toLowerCase()) ? MASK : value;
686
+ }
687
+ return out;
688
+ }
689
+ function applyHttpCapturePolicy(payload, policy) {
690
+ if (!payload) return null;
691
+ const effective = policy ?? buildHttpCapturePolicy(null);
692
+ if (shouldIgnoreHttpUrl(payload.url, effective)) return null;
693
+ const disabled = resolveDisabledPayloadFields(payload.url, effective.captureConfig);
694
+ const patterns = effective.sensitivePatterns;
695
+ return {
696
+ ...payload,
697
+ url: maskSensitive(String(payload.url ?? ""), patterns),
698
+ requestHeaders: disabled.requestHeaders ? {} : maskStructured(maskCredentialHeaders(payload.requestHeaders), patterns),
699
+ requestBody: disabled.requestBody ? null : maskStructured(payload.requestBody ?? null, patterns),
700
+ responseHeaders: disabled.responseHeaders ? {} : maskStructured(maskCredentialHeaders(payload.responseHeaders), patterns),
701
+ responseBody: disabled.responseBody ? null : maskStructured(payload.responseBody ?? null, patterns)
702
+ };
703
+ }
704
+
543
705
  // src/push/pushModule.js
544
706
  function hasWindow2() {
545
707
  return typeof window !== "undefined";
@@ -1725,6 +1887,105 @@ function decodeWebPushConfig(buf) {
1725
1887
  }
1726
1888
  return result;
1727
1889
  }
1890
+ var HTTP_PATH_MATCH_TYPES = ["EXACT", "PREFIX"];
1891
+ function decodeHttpPathMatchType(value) {
1892
+ return HTTP_PATH_MATCH_TYPES[value] ?? null;
1893
+ }
1894
+ function decodeHttpPathBlacklistRule(buf) {
1895
+ const r = new ProtoReader(buf);
1896
+ const result = { host: "", path: "", matchType: "EXACT" };
1897
+ while (r.hasMore()) {
1898
+ const tag = r.readTag();
1899
+ switch (tag.fieldNumber) {
1900
+ case 1:
1901
+ result.host = r.readString();
1902
+ break;
1903
+ case 2:
1904
+ result.path = r.readString();
1905
+ break;
1906
+ case 3:
1907
+ result.matchType = decodeHttpPathMatchType(r.readVarint());
1908
+ break;
1909
+ default:
1910
+ r.skip(tag.wireType);
1911
+ }
1912
+ }
1913
+ return result;
1914
+ }
1915
+ function decodeHttpPayloadCaptureRule(buf) {
1916
+ const r = new ProtoReader(buf);
1917
+ const result = {
1918
+ host: "",
1919
+ path: null,
1920
+ matchType: "EXACT",
1921
+ disableRequestHeaders: false,
1922
+ disableRequestBody: false,
1923
+ disableResponseHeaders: false,
1924
+ disableResponseBody: false
1925
+ };
1926
+ while (r.hasMore()) {
1927
+ const tag = r.readTag();
1928
+ switch (tag.fieldNumber) {
1929
+ case 1:
1930
+ result.host = r.readString();
1931
+ break;
1932
+ case 2:
1933
+ result.path = r.readString();
1934
+ break;
1935
+ case 3:
1936
+ result.matchType = decodeHttpPathMatchType(r.readVarint());
1937
+ break;
1938
+ case 4:
1939
+ result.disableRequestHeaders = r.readBool();
1940
+ break;
1941
+ case 5:
1942
+ result.disableRequestBody = r.readBool();
1943
+ break;
1944
+ case 6:
1945
+ result.disableResponseHeaders = r.readBool();
1946
+ break;
1947
+ case 7:
1948
+ result.disableResponseBody = r.readBool();
1949
+ break;
1950
+ default:
1951
+ r.skip(tag.wireType);
1952
+ }
1953
+ }
1954
+ return result;
1955
+ }
1956
+ function decodeHttpPayloadCaptureConfig(buf) {
1957
+ const r = new ProtoReader(buf);
1958
+ const result = {
1959
+ disableRequestHeaders: false,
1960
+ disableRequestBody: false,
1961
+ disableResponseHeaders: false,
1962
+ disableResponseBody: false,
1963
+ rules: []
1964
+ };
1965
+ while (r.hasMore()) {
1966
+ const tag = r.readTag();
1967
+ switch (tag.fieldNumber) {
1968
+ case 1:
1969
+ result.disableRequestHeaders = r.readBool();
1970
+ break;
1971
+ case 2:
1972
+ result.disableRequestBody = r.readBool();
1973
+ break;
1974
+ case 3:
1975
+ result.disableResponseHeaders = r.readBool();
1976
+ break;
1977
+ case 4:
1978
+ result.disableResponseBody = r.readBool();
1979
+ break;
1980
+ case 5:
1981
+ result.rules.push(decodeHttpPayloadCaptureRule(r.readBytes()));
1982
+ break;
1983
+ default:
1984
+ r.skip(tag.wireType);
1985
+ }
1986
+ }
1987
+ return result;
1988
+ }
1728
1989
  function decodeSdkRiskConfig(buf) {
1729
1990
  const r = new ProtoReader(buf);
1730
1991
  const result = {
@@ -1761,6 +2022,8 @@ function decodeConfigResponse(buf) {
1761
2022
  isCrashTrackingEnabled: false,
1762
2023
  isEventTrackingEnabled: false,
1763
2024
  httpDomainBlackList: [],
2025
+ httpPathBlackList: [],
2026
+ httpPayloadCaptureConfig: null,
1764
2027
  sensitiveInfoFilter: [],
1765
2028
  sslPinning: null,
1766
2029
  accessToken: "",
@@ -1821,6 +2084,12 @@ function decodeConfigResponse(buf) {
1821
2084
  case 15:
1822
2085
  result.isEventTrackingEnabled = r.readBool();
1823
2086
  break;
2087
+ case 16:
2088
+ result.httpPathBlackList.push(decodeHttpPathBlacklistRule(r.readBytes()));
2089
+ break;
2090
+ case 17:
2091
+ result.httpPayloadCaptureConfig = decodeHttpPayloadCaptureConfig(r.readBytes());
2092
+ break;
1824
2093
  case 18:
1825
2094
  result.webPushConfig = decodeWebPushConfig(r.readBytes());
1826
2095
  break;
@@ -3504,6 +3773,7 @@ var LogDropClient = class {
3504
3773
  };
3505
3774
  this._activeIpReputationAction = this._loadStoredIpReputationAction();
3506
3775
  this._ipReputationExpiryTimer = null;
3776
+ this._httpCapturePolicy = null;
3507
3777
  this._loadCachedServerConfig();
3508
3778
  this._user = this._loadPersistedExternalUser();
3509
3779
  this._errorHandlersInstalled = false;
@@ -4330,6 +4600,7 @@ var LogDropClient = class {
4330
4600
  if (typeof parsed.data.isSDKDisabled === "boolean") {
4331
4601
  this._serverConfig.isSDKDisabled = parsed.data.isSDKDisabled;
4332
4602
  }
4603
+ this._httpCapturePolicy = buildHttpCapturePolicy(parsed.data);
4333
4604
  }
4334
4605
  }
4335
4606
  } catch {
@@ -4351,6 +4622,9 @@ var LogDropClient = class {
4351
4622
  if (typeof data.isHttpLoggingEnabled === "boolean") {
4352
4623
  this._serverConfig.isHttpLoggingEnabled = data.isHttpLoggingEnabled;
4353
4624
  }
4625
+ if ("httpDomainBlackList" in data || "httpPayloadCaptureConfig" in data || "sensitiveInfoFilter" in data) {
4626
+ this._httpCapturePolicy = buildHttpCapturePolicy(data);
4627
+ }
4354
4628
  if (typeof data.isCrashTrackingEnabled === "boolean" && this._crashTrackingOverride === null) {
4355
4629
  this._serverConfig.isCrashTrackingEnabled = data.isCrashTrackingEnabled;
4356
4630
  }
@@ -4410,7 +4684,7 @@ var LogDropClient = class {
4410
4684
  onEvent: (eventName, payload) => {
4411
4685
  if (eventName !== "network_activity") return;
4412
4686
  if (!this._serverConfig.isHttpLoggingEnabled || !this._config?.network?.enabled) return;
4413
- this._enqueue(LogLevel.NETWORK_ACTIVITY, payload, new LogFlow("NetworkActivity", payload.uuid));
4687
+ this._recordNetworkActivity(payload);
4414
4688
  },
4415
4689
  getBaseHref,
4416
4690
  excludeUrls: this._config.network.excludeUrls,
@@ -4429,6 +4703,31 @@ var LogDropClient = class {
4429
4703
  });
4430
4704
  this._net.install();
4431
4705
  }
4706
+ _recordNetworkActivity(payload) {
4707
+ const recorded = applyHttpCapturePolicy(payload, this._httpCapturePolicy);
4708
+ if (!recorded) return;
4709
+ this._enqueue(LogLevel.NETWORK_ACTIVITY, recorded, new LogFlow("NetworkActivity", recorded.uuid));
4710
+ }
4711
+ // Logs captured before the init response, or under rules that have changed
4712
+ // since, are filtered again with the current policy before upload; logs
4713
+ // stored under the same rules are sent as they are. Returns null when the
4714
+ // log must be dropped.
4715
+ _applyHttpCapturePolicyToStoredLog(log) {
4716
+ const policy = this._httpCapturePolicy;
4717
+ if (!policy || log.level === LogLevel.DEVICEINFO) return log.message;
4718
+ if (log.httpPolicyFingerprint === policy.fingerprint) return log.message;
4719
+ if (log.level !== LogLevel.NETWORK_ACTIVITY) {
4720
+ return maskSensitive(log.message, policy.sensitivePatterns);
4721
+ }
4722
+ let parsed;
4723
+ try {
4724
+ parsed = JSON.parse(log.message);
4725
+ } catch {
4726
+ return maskSensitive(log.message, policy.sensitivePatterns);
4727
+ }
4728
+ const recorded = applyHttpCapturePolicy(parsed, policy);
4729
+ return recorded ? safeStringify(recorded) : null;
4730
+ }
4432
4731
  _uninstallNetworkInstrumentation() {
4433
4732
  if (this._net) {
4434
4733
  this._net.uninstall();
@@ -5044,6 +5343,13 @@ var LogDropClient = class {
5044
5343
  // IMPORTANT: if message object convert => JSON string
5045
5344
  message: typeof message === "string" ? message : safeStringify(message)
5046
5345
  };
5346
+ const policy = this._httpCapturePolicy;
5347
+ if (policy && level !== LogLevel.DEVICEINFO) {
5348
+ if (level !== LogLevel.NETWORK_ACTIVITY) {
5349
+ entry.message = maskSensitive(entry.message, policy.sensitivePatterns);
5350
+ }
5351
+ entry.httpPolicyFingerprint = policy.fingerprint;
5352
+ }
5047
5353
  if (tag) {
5048
5354
  entry.tag = tag;
5049
5355
  }
@@ -5129,6 +5435,7 @@ var LogDropClient = class {
5129
5435
  message: entry.message
5130
5436
  };
5131
5437
  if (entry.tag) logRecord.tag = entry.tag;
5438
+ if (entry.httpPolicyFingerprint) logRecord.httpPolicyFingerprint = entry.httpPolicyFingerprint;
5132
5439
  if (logFlow && logFlow instanceof LogFlow) {
5133
5440
  logRecord.flowId = logFlow.id;
5134
5441
  logRecord.flowName = logFlow.name;
@@ -5430,11 +5737,16 @@ var LogDropClient = class {
5430
5737
  for (const [sessionId, logs] of Object.entries(sessionGroups)) {
5431
5738
  const sessionLogs = [];
5432
5739
  for (const log of logs) {
5740
+ const message = this._applyHttpCapturePolicyToStoredLog(log);
5741
+ if (message === null) {
5742
+ chunk.droppedLogIds.push(log.id);
5743
+ continue;
5744
+ }
5433
5745
  const logEntry = {
5434
5746
  id: log.id,
5435
5747
  ts: log.ts,
5436
5748
  level: log.level,
5437
- message: log.message,
5749
+ message,
5438
5750
  flowId: log.flowId,
5439
5751
  flowName: log.flowName,
5440
5752
  flowAttributes: log.flowAttributes
@@ -5652,7 +5964,8 @@ var LogDropClient = class {
5652
5964
  if (!this._dbPromise) return false;
5653
5965
  this._fileFlushInProgress = true;
5654
5966
  try {
5655
- const allLogs = await this._readAllLogs();
5967
+ const storedLogs = await this._readAllLogs();
5968
+ const allLogs = this._httpCapturePolicy ? storedLogs : storedLogs.filter((log) => log.level !== LogLevel.NETWORK_ACTIVITY);
5656
5969
  if (this._isInternalLoggingEnabled()) {
5657
5970
  this._logInternal(`[LogDrop DEBUG] _flushLogs called. DB log count: ${allLogs.length}`);
5658
5971
  }