@amplitude/session-replay-browser 1.50.4 → 1.51.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 +15 -0
- package/lib/cjs/config/joined-config.d.ts.map +1 -1
- package/lib/cjs/config/joined-config.js +6 -25
- package/lib/cjs/config/joined-config.js.map +1 -1
- package/lib/cjs/config/types.d.ts +2 -25
- package/lib/cjs/config/types.d.ts.map +1 -1
- package/lib/cjs/config/types.js +0 -2
- package/lib/cjs/config/types.js.map +1 -1
- package/lib/cjs/helpers.d.ts +1 -16
- package/lib/cjs/helpers.d.ts.map +1 -1
- package/lib/cjs/helpers.js +8 -166
- package/lib/cjs/helpers.js.map +1 -1
- package/lib/cjs/session-replay.d.ts.map +1 -1
- package/lib/cjs/session-replay.js +6 -35
- package/lib/cjs/session-replay.js.map +1 -1
- package/lib/cjs/version.d.ts +1 -1
- package/lib/cjs/version.js +1 -1
- package/lib/cjs/version.js.map +1 -1
- package/lib/esm/config/joined-config.d.ts.map +1 -1
- package/lib/esm/config/joined-config.js +6 -25
- package/lib/esm/config/joined-config.js.map +1 -1
- package/lib/esm/config/types.d.ts +2 -25
- package/lib/esm/config/types.d.ts.map +1 -1
- package/lib/esm/config/types.js +1 -1
- package/lib/esm/config/types.js.map +1 -1
- package/lib/esm/helpers.d.ts +1 -16
- package/lib/esm/helpers.d.ts.map +1 -1
- package/lib/esm/helpers.js +7 -161
- package/lib/esm/helpers.js.map +1 -1
- package/lib/esm/session-replay.d.ts.map +1 -1
- package/lib/esm/session-replay.js +8 -37
- package/lib/esm/session-replay.js.map +1 -1
- package/lib/esm/version.d.ts +1 -1
- package/lib/esm/version.js +1 -1
- package/lib/esm/version.js.map +1 -1
- package/lib/scripts/index-min.js +1 -1
- package/lib/scripts/index-min.js.gz +0 -0
- package/lib/scripts/index-min.js.map +1 -1
- package/lib/scripts/session-replay-browser-min.js +1 -1
- package/lib/scripts/session-replay-browser-min.js.gz +0 -0
- package/lib/scripts/session-replay-browser-min.js.map +1 -1
- package/package.json +4 -3
- package/lib/cjs/utils/get-input-type.d.ts +0 -9
- package/lib/cjs/utils/get-input-type.d.ts.map +0 -1
- package/lib/cjs/utils/get-input-type.js +0 -25
- package/lib/cjs/utils/get-input-type.js.map +0 -1
- package/lib/esm/utils/get-input-type.d.ts +0 -9
- package/lib/esm/utils/get-input-type.d.ts.map +0 -1
- package/lib/esm/utils/get-input-type.js +0 -20
- package/lib/esm/utils/get-input-type.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { __assign, __awaiter, __generator, __read, __spreadArray, __values } from "tslib";
|
|
2
|
+
import { validatePrivacySelectors } from '@amplitude/session-replay-dom-privacy';
|
|
2
3
|
import { RemoteConfigClient } from '@amplitude/analytics-core';
|
|
3
4
|
import { getDebugConfig } from '../helpers';
|
|
4
5
|
import { SessionReplayLocalConfig } from './local-config';
|
|
@@ -13,32 +14,12 @@ import { SessionReplayLocalConfig } from './local-config';
|
|
|
13
14
|
// otherwise win the synchronous race in 'all' mode.
|
|
14
15
|
var REMOTE_CONFIG_TIMEOUT_MS = 1500;
|
|
15
16
|
export var removeInvalidSelectorsFromPrivacyConfig = function (privacyConfig, loggerProvider) {
|
|
16
|
-
// This allows us to not search the DOM.
|
|
17
17
|
var fragment = document.createDocumentFragment();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
selectors = selectors.filter(function (selector) {
|
|
24
|
-
try {
|
|
25
|
-
fragment.querySelector(selector);
|
|
26
|
-
}
|
|
27
|
-
catch (_a) {
|
|
28
|
-
loggerProvider.warn("[session-replay-browser] omitting selector \"".concat(selector, "\" because it is invalid"));
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
return true;
|
|
32
|
-
});
|
|
33
|
-
if (selectors.length === 0) {
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
return selectors;
|
|
37
|
-
};
|
|
38
|
-
privacyConfig.blockSelector = dropInvalidSelectors(privacyConfig.blockSelector);
|
|
39
|
-
privacyConfig.maskSelector = dropInvalidSelectors(privacyConfig.maskSelector);
|
|
40
|
-
privacyConfig.unmaskSelector = dropInvalidSelectors(privacyConfig.unmaskSelector);
|
|
41
|
-
return privacyConfig;
|
|
18
|
+
return validatePrivacySelectors(privacyConfig, function (selector) {
|
|
19
|
+
fragment.querySelector(selector);
|
|
20
|
+
}, function (selector) {
|
|
21
|
+
loggerProvider.warn("[session-replay-browser] omitting selector \"".concat(selector, "\" because it is invalid"));
|
|
22
|
+
});
|
|
42
23
|
};
|
|
43
24
|
var SessionReplayJoinedConfigGenerator = /** @class */ (function () {
|
|
44
25
|
function SessionReplayJoinedConfigGenerator(remoteConfigClient, localConfig) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"joined-config.js","sourceRoot":"","sources":["../../../src/config/joined-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { ILogger, IRemoteConfigClient, RemoteConfigClient, RemoteConfig, Source } from '@amplitude/analytics-core';\nimport { getDebugConfig } from '../helpers';\nimport { SessionReplayOptions } from '../typings/session-replay';\nimport { SessionReplayLocalConfig } from './local-config';\nimport {\n SessionReplayLocalConfig as ISessionReplayLocalConfig,\n PrivacyConfig,\n SessionReplayConfigs,\n SessionReplayJoinedConfig,\n SessionReplayRemoteConfig,\n} from './types';\n\n// Budget for waiting on the remote config response before falling back to the local cache.\n// The inner fetch in analytics-core uses a 1000ms per-attempt AbortController timeout with\n// up to 3 retries — but this outer timeout is the only thing the SR plugin waits on, so\n// only the first attempt can possibly complete in time (attempt 2 starts at ~1000-1333ms\n// after backoff jitter and runs to ~2000-2333ms, well past any reasonable outer budget).\n// 1500ms is set above the 1000ms inner abort to avoid a tie with the inner cutoff and\n// give the first attempt's resolution path room to finish; everything else falls through\n// to the cache fallback. See SR-4234: prefer remote over a stale cache that would\n// otherwise win the synchronous race in 'all' mode.\nconst REMOTE_CONFIG_TIMEOUT_MS = 1500;\n\nexport const removeInvalidSelectorsFromPrivacyConfig = (privacyConfig: PrivacyConfig, loggerProvider: ILogger) => {\n // This allows us to not search the DOM.\n const fragment = document.createDocumentFragment();\n\n const dropInvalidSelectors = (selectors: string[] | string = []): string[] | undefined => {\n if (typeof selectors === 'string') {\n selectors = [selectors];\n }\n selectors = selectors.filter((selector: string) => {\n try {\n fragment.querySelector(selector);\n } catch {\n loggerProvider.warn(`[session-replay-browser] omitting selector \"${selector}\" because it is invalid`);\n return false;\n }\n return true;\n });\n if (selectors.length === 0) {\n return undefined;\n }\n return selectors;\n };\n privacyConfig.blockSelector = dropInvalidSelectors(privacyConfig.blockSelector);\n privacyConfig.maskSelector = dropInvalidSelectors(privacyConfig.maskSelector);\n privacyConfig.unmaskSelector = dropInvalidSelectors(privacyConfig.unmaskSelector);\n return privacyConfig;\n};\nexport class SessionReplayJoinedConfigGenerator {\n private readonly localConfig: ISessionReplayLocalConfig;\n private readonly remoteConfigClient: IRemoteConfigClient;\n\n constructor(remoteConfigClient: IRemoteConfigClient, localConfig: ISessionReplayLocalConfig) {\n this.localConfig = localConfig;\n this.remoteConfigClient = remoteConfigClient;\n }\n\n async generateJoinedConfig(): Promise<SessionReplayConfigs> {\n const config: SessionReplayJoinedConfig = { ...this.localConfig };\n // Special case here as optOut is implemented via getter/setter\n config.optOut = this.localConfig.optOut;\n // We always want captureEnabled to be true, unless there's an override\n // in the remote config.\n config.captureEnabled = true;\n let sessionReplayRemoteConfig: SessionReplayRemoteConfig | undefined;\n\n try {\n // Subscribe with a timeout so the SDK prefers the remote response and only falls back\n // to cache after the budget elapses. 'all' mode would race a synchronous cache read\n // against the network and resolve on whichever fires first — cache always wins, so a\n // stale cache silently overrides the live config (SR-4234).\n await new Promise<void>((resolve, reject) => {\n this.remoteConfigClient.subscribe(\n 'configs.sessionReplay',\n { timeout: REMOTE_CONFIG_TIMEOUT_MS },\n (remoteConfig: RemoteConfig | null, source: Source) => {\n this.localConfig.loggerProvider.debug(\n `Session Replay remote configuration received from ${source}:`,\n JSON.stringify(remoteConfig, null, 2),\n );\n\n if (!remoteConfig) {\n reject(new Error('No remote config received'));\n return;\n }\n\n // remoteConfig is already filtered to 'configs.sessionReplay' namespace\n const namespaceConfig = remoteConfig as SessionReplayRemoteConfig;\n const samplingConfig = namespaceConfig.sr_sampling_config;\n const privacyConfig = namespaceConfig.sr_privacy_config;\n const targetingConfig = namespaceConfig.sr_targeting_config;\n\n const ugcFilterRules = config.interactionConfig?.ugcFilterRules;\n // This is intentionally forced to only be set through the remote config.\n config.interactionConfig = namespaceConfig.sr_interaction_config;\n if (config.interactionConfig && ugcFilterRules) {\n config.interactionConfig.ugcFilterRules = ugcFilterRules;\n }\n\n // This is intentionally forced to only be set through the remote config.\n config.loggingConfig = namespaceConfig.sr_logging_config;\n\n if (samplingConfig || privacyConfig || targetingConfig) {\n sessionReplayRemoteConfig = {};\n if (samplingConfig) {\n sessionReplayRemoteConfig.sr_sampling_config = samplingConfig;\n }\n if (privacyConfig) {\n sessionReplayRemoteConfig.sr_privacy_config = privacyConfig;\n }\n if (targetingConfig) {\n sessionReplayRemoteConfig.sr_targeting_config = targetingConfig;\n }\n }\n\n resolve();\n },\n );\n });\n } catch (error) {\n this.localConfig.loggerProvider.error('Failed to generate joined config: ', error);\n config.captureEnabled = false;\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: undefined,\n };\n }\n\n if (!sessionReplayRemoteConfig) {\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: sessionReplayRemoteConfig,\n };\n }\n\n const {\n sr_sampling_config: samplingConfig,\n sr_privacy_config: remotePrivacyConfig,\n sr_targeting_config: targetingConfig,\n } = sessionReplayRemoteConfig;\n if (samplingConfig && Object.keys(samplingConfig).length > 0) {\n if (Object.prototype.hasOwnProperty.call(samplingConfig, 'capture_enabled')) {\n config.captureEnabled = samplingConfig.capture_enabled;\n } else {\n config.captureEnabled = false;\n }\n\n if (Object.prototype.hasOwnProperty.call(samplingConfig, 'sample_rate')) {\n config.sampleRate = samplingConfig.sample_rate;\n }\n\n if (Object.prototype.hasOwnProperty.call(samplingConfig, 'min_session_duration_ms')) {\n config.minSessionDurationMs = this.sanitizeMinSessionDurationMs(samplingConfig.min_session_duration_ms);\n }\n } else {\n // If config API response was valid (ie 200), but no config returned, assume that\n // customer has not yet set up config, and use sample rate from SDK options,\n // allowing for immediate replay capture\n config.captureEnabled = true;\n this.localConfig.loggerProvider.debug(\n 'Remote config successfully fetched, but no values set for project, Session Replay capture enabled.',\n );\n }\n\n // Remote config join acts somewhat like a left join between the remote and the local\n // config. That is, remote config has precedence over local values as with sampling.\n // However, non conflicting values will be added to the lists.\n // Here's an example to illustrate:\n //\n // Remote config: {'.selector1': 'MASK', '.selector2': 'UNMASK'}\n // Local config: {'.selector1': 'UNMASK', '.selector3': 'MASK'}\n //\n // Resolved config: {'.selector1': 'MASK', '.selector2': 'UNMASK', '.selector3': 'MASK'}\n // config.privacyConfig = {\n // ...(config.privacyConfig ?? {}),\n // ...remotePrivacyConfig,\n // };\n\n if (remotePrivacyConfig) {\n const localPrivacyConfig: PrivacyConfig = config.privacyConfig ?? {};\n\n const joinedPrivacyConfig: Required<PrivacyConfig> & { blockSelector: string[] } = {\n defaultMaskLevel: remotePrivacyConfig.defaultMaskLevel ?? localPrivacyConfig.defaultMaskLevel ?? 'medium',\n blockSelector: [],\n maskSelector: [],\n unmaskSelector: [],\n maskAttributes: [\n ...new Set([...(localPrivacyConfig.maskAttributes ?? []), ...(remotePrivacyConfig.maskAttributes ?? [])]),\n ],\n urlMaskLevels: [...(remotePrivacyConfig.urlMaskLevels ?? []), ...(localPrivacyConfig.urlMaskLevels ?? [])],\n };\n\n const privacyConfigSelectorMap = (privacyConfig: PrivacyConfig): Record<string, 'mask' | 'unmask' | 'block'> => {\n const selectorMap: Record<string, 'mask' | 'unmask' | 'block'> = {};\n if (typeof privacyConfig.blockSelector === 'string') {\n privacyConfig.blockSelector = [privacyConfig.blockSelector];\n }\n\n for (const selector of privacyConfig.blockSelector ?? []) {\n selectorMap[selector] = 'block';\n }\n for (const selector of privacyConfig.maskSelector ?? []) {\n selectorMap[selector] = 'mask';\n }\n for (const selector of privacyConfig.unmaskSelector ?? []) {\n selectorMap[selector] = 'unmask';\n }\n return selectorMap;\n };\n\n const selectorMap: Record<string, 'mask' | 'unmask' | 'block'> = {\n ...privacyConfigSelectorMap(localPrivacyConfig),\n ...privacyConfigSelectorMap(remotePrivacyConfig),\n };\n\n for (const [selector, selectorType] of Object.entries(selectorMap)) {\n if (selectorType === 'mask') {\n joinedPrivacyConfig.maskSelector.push(selector);\n } else if (selectorType === 'block') {\n joinedPrivacyConfig.blockSelector.push(selector);\n } else if (selectorType === 'unmask') {\n joinedPrivacyConfig.unmaskSelector.push(selector);\n }\n }\n\n config.privacyConfig = removeInvalidSelectorsFromPrivacyConfig(\n joinedPrivacyConfig,\n this.localConfig.loggerProvider,\n );\n }\n\n if (targetingConfig && Object.keys(targetingConfig).length > 0) {\n config.targetingConfig = targetingConfig;\n }\n\n this.localConfig.loggerProvider.debug(\n JSON.stringify({ name: 'session replay joined config', config: getDebugConfig(config) }, null, 2),\n );\n\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: sessionReplayRemoteConfig,\n };\n }\n\n /**\n * Defensive bounds for the remote-supplied min_session_duration_ms. A misconfigured\n * value (e.g. 30_000_000) would silently suppress every replay until the config is\n * pushed again, so we clamp to a sane ceiling and warn on out-of-range inputs.\n * Returns undefined for clearly invalid values so the gate falls back to disabled\n * rather than carrying a NaN through downstream comparisons.\n */\n private sanitizeMinSessionDurationMs(raw: unknown): number | undefined {\n if (typeof raw !== 'number' || !Number.isFinite(raw)) {\n this.localConfig.loggerProvider.warn(\n `min_session_duration_ms remote value is not a finite number (got ${String(raw)}); ignoring.`,\n );\n return undefined;\n }\n if (raw < 0) {\n this.localConfig.loggerProvider.warn(`min_session_duration_ms remote value is negative (${raw}); ignoring.`);\n return undefined;\n }\n if (raw > MAX_MIN_SESSION_DURATION_MS) {\n this.localConfig.loggerProvider.warn(\n `min_session_duration_ms remote value ${raw} exceeds ${MAX_MIN_SESSION_DURATION_MS}ms ceiling; clamping.`,\n );\n return MAX_MIN_SESSION_DURATION_MS;\n }\n return raw;\n }\n}\n\n/**\n * Upper bound for the remote-configured replay min duration. 60 seconds is well above\n * any reasonable bounce threshold; values higher than this are almost certainly typos\n * (e.g. seconds confused for milliseconds, or an extra zero) and would otherwise\n * suppress every replay until the config is corrected.\n */\nexport const MAX_MIN_SESSION_DURATION_MS = 60_000;\n\nexport const createSessionReplayJoinedConfigGenerator = async (apiKey: string, options: SessionReplayOptions) => {\n const localConfig = new SessionReplayLocalConfig(apiKey, options);\n\n const remoteConfigClient = new RemoteConfigClient(\n apiKey,\n localConfig.loggerProvider,\n localConfig.serverZone,\n options.configServerUrl,\n // Custom transport for the config GET. SR's FetchConfigRequest is structurally compatible\n // with RemoteConfigFetchRequest, so the customer's handleFetchConfig drops straight in.\n options.handleFetchConfig,\n );\n\n return new SessionReplayJoinedConfigGenerator(remoteConfigClient, localConfig);\n};\n"]}
|
|
1
|
+
{"version":3,"file":"joined-config.js","sourceRoot":"","sources":["../../../src/config/joined-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { validatePrivacySelectors } from '@amplitude/session-replay-dom-privacy';\nimport { ILogger, IRemoteConfigClient, RemoteConfigClient, RemoteConfig, Source } from '@amplitude/analytics-core';\nimport { getDebugConfig } from '../helpers';\nimport { SessionReplayOptions } from '../typings/session-replay';\nimport { SessionReplayLocalConfig } from './local-config';\nimport {\n SessionReplayLocalConfig as ISessionReplayLocalConfig,\n PrivacyConfig,\n SessionReplayConfigs,\n SessionReplayJoinedConfig,\n SessionReplayRemoteConfig,\n} from './types';\n\n// Budget for waiting on the remote config response before falling back to the local cache.\n// The inner fetch in analytics-core uses a 1000ms per-attempt AbortController timeout with\n// up to 3 retries — but this outer timeout is the only thing the SR plugin waits on, so\n// only the first attempt can possibly complete in time (attempt 2 starts at ~1000-1333ms\n// after backoff jitter and runs to ~2000-2333ms, well past any reasonable outer budget).\n// 1500ms is set above the 1000ms inner abort to avoid a tie with the inner cutoff and\n// give the first attempt's resolution path room to finish; everything else falls through\n// to the cache fallback. See SR-4234: prefer remote over a stale cache that would\n// otherwise win the synchronous race in 'all' mode.\nconst REMOTE_CONFIG_TIMEOUT_MS = 1500;\n\nexport const removeInvalidSelectorsFromPrivacyConfig = (privacyConfig: PrivacyConfig, loggerProvider: ILogger) => {\n const fragment = document.createDocumentFragment();\n return validatePrivacySelectors(\n privacyConfig,\n (selector) => {\n fragment.querySelector(selector);\n },\n (selector) => {\n loggerProvider.warn(`[session-replay-browser] omitting selector \"${selector}\" because it is invalid`);\n },\n );\n};\nexport class SessionReplayJoinedConfigGenerator {\n private readonly localConfig: ISessionReplayLocalConfig;\n private readonly remoteConfigClient: IRemoteConfigClient;\n\n constructor(remoteConfigClient: IRemoteConfigClient, localConfig: ISessionReplayLocalConfig) {\n this.localConfig = localConfig;\n this.remoteConfigClient = remoteConfigClient;\n }\n\n async generateJoinedConfig(): Promise<SessionReplayConfigs> {\n const config: SessionReplayJoinedConfig = { ...this.localConfig };\n // Special case here as optOut is implemented via getter/setter\n config.optOut = this.localConfig.optOut;\n // We always want captureEnabled to be true, unless there's an override\n // in the remote config.\n config.captureEnabled = true;\n let sessionReplayRemoteConfig: SessionReplayRemoteConfig | undefined;\n\n try {\n // Subscribe with a timeout so the SDK prefers the remote response and only falls back\n // to cache after the budget elapses. 'all' mode would race a synchronous cache read\n // against the network and resolve on whichever fires first — cache always wins, so a\n // stale cache silently overrides the live config (SR-4234).\n await new Promise<void>((resolve, reject) => {\n this.remoteConfigClient.subscribe(\n 'configs.sessionReplay',\n { timeout: REMOTE_CONFIG_TIMEOUT_MS },\n (remoteConfig: RemoteConfig | null, source: Source) => {\n this.localConfig.loggerProvider.debug(\n `Session Replay remote configuration received from ${source}:`,\n JSON.stringify(remoteConfig, null, 2),\n );\n\n if (!remoteConfig) {\n reject(new Error('No remote config received'));\n return;\n }\n\n // remoteConfig is already filtered to 'configs.sessionReplay' namespace\n const namespaceConfig = remoteConfig as SessionReplayRemoteConfig;\n const samplingConfig = namespaceConfig.sr_sampling_config;\n const privacyConfig = namespaceConfig.sr_privacy_config;\n const targetingConfig = namespaceConfig.sr_targeting_config;\n\n const ugcFilterRules = config.interactionConfig?.ugcFilterRules;\n // This is intentionally forced to only be set through the remote config.\n config.interactionConfig = namespaceConfig.sr_interaction_config;\n if (config.interactionConfig && ugcFilterRules) {\n config.interactionConfig.ugcFilterRules = ugcFilterRules;\n }\n\n // This is intentionally forced to only be set through the remote config.\n config.loggingConfig = namespaceConfig.sr_logging_config;\n\n if (samplingConfig || privacyConfig || targetingConfig) {\n sessionReplayRemoteConfig = {};\n if (samplingConfig) {\n sessionReplayRemoteConfig.sr_sampling_config = samplingConfig;\n }\n if (privacyConfig) {\n sessionReplayRemoteConfig.sr_privacy_config = privacyConfig;\n }\n if (targetingConfig) {\n sessionReplayRemoteConfig.sr_targeting_config = targetingConfig;\n }\n }\n\n resolve();\n },\n );\n });\n } catch (error) {\n this.localConfig.loggerProvider.error('Failed to generate joined config: ', error);\n config.captureEnabled = false;\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: undefined,\n };\n }\n\n if (!sessionReplayRemoteConfig) {\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: sessionReplayRemoteConfig,\n };\n }\n\n const {\n sr_sampling_config: samplingConfig,\n sr_privacy_config: remotePrivacyConfig,\n sr_targeting_config: targetingConfig,\n } = sessionReplayRemoteConfig;\n if (samplingConfig && Object.keys(samplingConfig).length > 0) {\n if (Object.prototype.hasOwnProperty.call(samplingConfig, 'capture_enabled')) {\n config.captureEnabled = samplingConfig.capture_enabled;\n } else {\n config.captureEnabled = false;\n }\n\n if (Object.prototype.hasOwnProperty.call(samplingConfig, 'sample_rate')) {\n config.sampleRate = samplingConfig.sample_rate;\n }\n\n if (Object.prototype.hasOwnProperty.call(samplingConfig, 'min_session_duration_ms')) {\n config.minSessionDurationMs = this.sanitizeMinSessionDurationMs(samplingConfig.min_session_duration_ms);\n }\n } else {\n // If config API response was valid (ie 200), but no config returned, assume that\n // customer has not yet set up config, and use sample rate from SDK options,\n // allowing for immediate replay capture\n config.captureEnabled = true;\n this.localConfig.loggerProvider.debug(\n 'Remote config successfully fetched, but no values set for project, Session Replay capture enabled.',\n );\n }\n\n // Remote config join acts somewhat like a left join between the remote and the local\n // config. That is, remote config has precedence over local values as with sampling.\n // However, non conflicting values will be added to the lists.\n // Here's an example to illustrate:\n //\n // Remote config: {'.selector1': 'MASK', '.selector2': 'UNMASK'}\n // Local config: {'.selector1': 'UNMASK', '.selector3': 'MASK'}\n //\n // Resolved config: {'.selector1': 'MASK', '.selector2': 'UNMASK', '.selector3': 'MASK'}\n // config.privacyConfig = {\n // ...(config.privacyConfig ?? {}),\n // ...remotePrivacyConfig,\n // };\n\n if (remotePrivacyConfig) {\n const localPrivacyConfig: PrivacyConfig = config.privacyConfig ?? {};\n\n const joinedPrivacyConfig: Required<PrivacyConfig> & { blockSelector: string[] } = {\n defaultMaskLevel: remotePrivacyConfig.defaultMaskLevel ?? localPrivacyConfig.defaultMaskLevel ?? 'medium',\n blockSelector: [],\n maskSelector: [],\n unmaskSelector: [],\n maskAttributes: [\n ...new Set([...(localPrivacyConfig.maskAttributes ?? []), ...(remotePrivacyConfig.maskAttributes ?? [])]),\n ],\n urlMaskLevels: [...(remotePrivacyConfig.urlMaskLevels ?? []), ...(localPrivacyConfig.urlMaskLevels ?? [])],\n };\n\n const privacyConfigSelectorMap = (privacyConfig: PrivacyConfig): Record<string, 'mask' | 'unmask' | 'block'> => {\n const selectorMap: Record<string, 'mask' | 'unmask' | 'block'> = {};\n if (typeof privacyConfig.blockSelector === 'string') {\n privacyConfig.blockSelector = [privacyConfig.blockSelector];\n }\n\n for (const selector of privacyConfig.blockSelector ?? []) {\n selectorMap[selector] = 'block';\n }\n for (const selector of privacyConfig.maskSelector ?? []) {\n selectorMap[selector] = 'mask';\n }\n for (const selector of privacyConfig.unmaskSelector ?? []) {\n selectorMap[selector] = 'unmask';\n }\n return selectorMap;\n };\n\n const selectorMap: Record<string, 'mask' | 'unmask' | 'block'> = {\n ...privacyConfigSelectorMap(localPrivacyConfig),\n ...privacyConfigSelectorMap(remotePrivacyConfig),\n };\n\n for (const [selector, selectorType] of Object.entries(selectorMap)) {\n if (selectorType === 'mask') {\n joinedPrivacyConfig.maskSelector.push(selector);\n } else if (selectorType === 'block') {\n joinedPrivacyConfig.blockSelector.push(selector);\n } else if (selectorType === 'unmask') {\n joinedPrivacyConfig.unmaskSelector.push(selector);\n }\n }\n\n config.privacyConfig = removeInvalidSelectorsFromPrivacyConfig(\n joinedPrivacyConfig,\n this.localConfig.loggerProvider,\n );\n }\n\n if (targetingConfig && Object.keys(targetingConfig).length > 0) {\n config.targetingConfig = targetingConfig;\n }\n\n this.localConfig.loggerProvider.debug(\n JSON.stringify({ name: 'session replay joined config', config: getDebugConfig(config) }, null, 2),\n );\n\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: sessionReplayRemoteConfig,\n };\n }\n\n /**\n * Defensive bounds for the remote-supplied min_session_duration_ms. A misconfigured\n * value (e.g. 30_000_000) would silently suppress every replay until the config is\n * pushed again, so we clamp to a sane ceiling and warn on out-of-range inputs.\n * Returns undefined for clearly invalid values so the gate falls back to disabled\n * rather than carrying a NaN through downstream comparisons.\n */\n private sanitizeMinSessionDurationMs(raw: unknown): number | undefined {\n if (typeof raw !== 'number' || !Number.isFinite(raw)) {\n this.localConfig.loggerProvider.warn(\n `min_session_duration_ms remote value is not a finite number (got ${String(raw)}); ignoring.`,\n );\n return undefined;\n }\n if (raw < 0) {\n this.localConfig.loggerProvider.warn(`min_session_duration_ms remote value is negative (${raw}); ignoring.`);\n return undefined;\n }\n if (raw > MAX_MIN_SESSION_DURATION_MS) {\n this.localConfig.loggerProvider.warn(\n `min_session_duration_ms remote value ${raw} exceeds ${MAX_MIN_SESSION_DURATION_MS}ms ceiling; clamping.`,\n );\n return MAX_MIN_SESSION_DURATION_MS;\n }\n return raw;\n }\n}\n\n/**\n * Upper bound for the remote-configured replay min duration. 60 seconds is well above\n * any reasonable bounce threshold; values higher than this are almost certainly typos\n * (e.g. seconds confused for milliseconds, or an extra zero) and would otherwise\n * suppress every replay until the config is corrected.\n */\nexport const MAX_MIN_SESSION_DURATION_MS = 60_000;\n\nexport const createSessionReplayJoinedConfigGenerator = async (apiKey: string, options: SessionReplayOptions) => {\n const localConfig = new SessionReplayLocalConfig(apiKey, options);\n\n const remoteConfigClient = new RemoteConfigClient(\n apiKey,\n localConfig.loggerProvider,\n localConfig.serverZone,\n options.configServerUrl,\n // Custom transport for the config GET. SR's FetchConfigRequest is structurally compatible\n // with RemoteConfigFetchRequest, so the customer's handleFetchConfig drops straight in.\n options.handleFetchConfig,\n );\n\n return new SessionReplayJoinedConfigGenerator(remoteConfigClient, localConfig);\n};\n"]}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { PrivacyConfig } from '@amplitude/session-replay-dom-privacy';
|
|
2
|
+
export type { PrivacyConfig, MaskLevel } from '@amplitude/session-replay-dom-privacy';
|
|
1
3
|
import { IConfig, LogLevel, ILogger } from '@amplitude/analytics-core';
|
|
2
4
|
import { StoreType, ConsoleLogLevel } from '../typings/session-replay';
|
|
3
5
|
import { TargetingFlag } from '@amplitude/targeting';
|
|
@@ -43,31 +45,6 @@ export interface SessionReplayRemoteConfigAPIResponse {
|
|
|
43
45
|
sessionReplay: SessionReplayRemoteConfig;
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
|
-
export type MaskLevel = 'light' | 'medium' | 'conservative';
|
|
47
|
-
export declare const DEFAULT_MASK_LEVEL = "medium";
|
|
48
|
-
export type PrivacyConfig = {
|
|
49
|
-
blockSelector?: string | string[];
|
|
50
|
-
defaultMaskLevel?: MaskLevel;
|
|
51
|
-
maskSelector?: string[];
|
|
52
|
-
unmaskSelector?: string[];
|
|
53
|
-
maskAttributes?: string[];
|
|
54
|
-
/**
|
|
55
|
-
* Per-URL overrides for `defaultMaskLevel`. Each entry contains a glob pattern (`match`)
|
|
56
|
-
* and a `maskLevel` to apply when the current page URL matches that pattern.
|
|
57
|
-
* Rules are evaluated in order; the first match wins. Remote rules take precedence
|
|
58
|
-
* over local rules (remote entries are prepended before local entries).
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* urlMaskLevels: [
|
|
62
|
-
* { match: 'https://example.com/checkout/*', maskLevel: 'conservative' },
|
|
63
|
-
* { match: 'https://example.com/public/*', maskLevel: 'light' },
|
|
64
|
-
* ]
|
|
65
|
-
*/
|
|
66
|
-
urlMaskLevels?: Array<{
|
|
67
|
-
match: string;
|
|
68
|
-
maskLevel: MaskLevel;
|
|
69
|
-
}>;
|
|
70
|
-
};
|
|
71
48
|
/**
|
|
72
49
|
* UGC filter rule.
|
|
73
50
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf;;OAEG;IACH,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE;QACP,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,eAAe,EAAE,CAAC;KAC3B,CAAC;IACF,OAAO,CAAC,EAAE;QACR,OAAO,EAAE,OAAO,CAAC;QACjB,IAAI,CAAC,EAAE;YACL,OAAO,CAAC,EAAE,qBAAqB,CAAC;YAChC,QAAQ,CAAC,EAAE,qBAAqB,CAAC;YACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;SAC3B,CAAC;KACH,CAAC;CACH;AAED,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC;AAE5C,MAAM,MAAM,yBAAyB,GAAG;IACtC,kBAAkB,CAAC,EAAE,cAAc,CAAC;IACpC,iBAAiB,CAAC,EAAE,aAAa,CAAC;IAClC,qBAAqB,CAAC,EAAE,iBAAiB,CAAC;IAC1C,iBAAiB,CAAC,EAAE,aAAa,CAAC;IAClC,mBAAmB,CAAC,EAAE,eAAe,CAAC;CACvC,CAAC;AAEF,MAAM,WAAW,oCAAoC;IACnD,OAAO,EAAE;QACP,aAAa,EAAE,yBAAyB,CAAC;KAC1C,CAAC;CACH;AAED
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":"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"}
|
package/lib/esm/config/types.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {};
|
|
2
2
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":"AAyDA,MAAM,CAAC,IAAM,kBAAkB,GAAG,QAAQ,CAAC","sourcesContent":["import { IConfig, LogLevel, ILogger } from '@amplitude/analytics-core';\nimport { StoreType, ConsoleLogLevel } from '../typings/session-replay';\nimport { TargetingFlag } from '@amplitude/targeting';\nimport type { BodyCaptureRuleConfig } from '../network-body-capture';\n\nexport interface SamplingConfig {\n sample_rate: number;\n capture_enabled: boolean;\n min_session_duration_ms?: number;\n}\n\nexport interface InteractionConfig {\n trackEveryNms?: number;\n enabled: boolean; // defaults to false\n batch: boolean; // defaults to false\n /**\n * UGC filter rules.\n */\n ugcFilterRules?: UGCFilterRule[];\n}\n\nexport interface LoggingConfig {\n console: {\n enabled: boolean;\n levels: ConsoleLogLevel[];\n };\n network?: {\n enabled: boolean;\n body?: {\n request?: BodyCaptureRuleConfig;\n response?: BodyCaptureRuleConfig;\n maxBodySizeBytes?: number;\n };\n };\n}\n\nexport type TargetingConfig = TargetingFlag;\n\nexport type SessionReplayRemoteConfig = {\n sr_sampling_config?: SamplingConfig;\n sr_privacy_config?: PrivacyConfig;\n sr_interaction_config?: InteractionConfig;\n sr_logging_config?: LoggingConfig;\n sr_targeting_config?: TargetingConfig;\n};\n\nexport interface SessionReplayRemoteConfigAPIResponse {\n configs: {\n sessionReplay: SessionReplayRemoteConfig;\n };\n}\n\nexport type MaskLevel =\n | 'light' // only mask a subset of inputs that's deemed sensitive - password, credit card, telephone #, email. These are information we never want to capture.\n | 'medium' // mask all form fields (inputs); page text is captured as-is\n | 'conservative'; // mask all inputs and all texts\n\nexport const DEFAULT_MASK_LEVEL = 'medium';\n\n// err on the side of excluding more\nexport type PrivacyConfig = {\n blockSelector?: string | string[]; // exclude in the UI\n defaultMaskLevel?: MaskLevel;\n maskSelector?: string[];\n unmaskSelector?: string[];\n maskAttributes?: string[]; // HTML attribute names to mask (e.g. [\"placeholder\", \"aria-label\"])\n /**\n * Per-URL overrides for `defaultMaskLevel`. Each entry contains a glob pattern (`match`)\n * and a `maskLevel` to apply when the current page URL matches that pattern.\n * Rules are evaluated in order; the first match wins. Remote rules take precedence\n * over local rules (remote entries are prepended before local entries).\n *\n * @example\n * urlMaskLevels: [\n * { match: 'https://example.com/checkout/*', maskLevel: 'conservative' },\n * { match: 'https://example.com/public/*', maskLevel: 'light' },\n * ]\n */\n urlMaskLevels?: Array<{ match: string; maskLevel: MaskLevel }>;\n};\n\n/**\n * UGC filter rule.\n */\nexport type UGCFilterRule = {\n /**\n * The selector of the UGC element.\n */\n selector: string;\n /**\n * The replacement text for the UGC element.\n */\n replacement: string;\n};\n\n/**\n * The fully-formed request the SDK hands to a custom `handleSendEvents` transport when\n * uploading recorded replay events. The callback's only job is to execute this request and\n * return the resulting `Response`; the SDK owns batching, retry, serialization and error\n * handling around it.\n */\nexport interface SendEventsRequest {\n /** The resolved track URL (respects `trackServerUrl`) including query params. */\n url: string;\n method: 'POST';\n /**\n * SDK-supplied default headers. When the body was gzipped, this includes\n * `Content-Encoding: gzip` — drop it only if you also stop forwarding the gzipped body,\n * otherwise the payload will not decode server-side.\n */\n headers: Record<string, string>;\n /**\n * The serialized payload, ready to send. A gzipped `Uint8Array` when transport compression\n * is active, otherwise the raw JSON string. Forward it unchanged — do not re-serialize.\n *\n * Typed as `string | Uint8Array` (not the broader `BodyInit`) because the body must be\n * structured-cloneable to cross `postMessage` in web-worker mode; `Blob`/`FormData`/\n * `ReadableStream` are not, and the SDK never produces them here.\n */\n body: string | Uint8Array;\n /**\n * Whether the built-in path would set `keepalive` on this request. `true` for page-exit\n * sends (so the request survives unload) and for small in-session batches. Forward it to\n * your `fetch` call — i.e. `fetch(url, { method, headers, body, keepalive })` — so exit-time\n * batches are not dropped when the page is tearing down.\n */\n keepalive: boolean;\n}\n\n/**\n * The fully-formed request the SDK hands to a custom `handleFetchConfig` transport when\n * fetching remote configuration.\n */\nexport interface FetchConfigRequest {\n /** The resolved config URL (respects `configServerUrl`) including query params. */\n url: string;\n method: 'GET';\n /** SDK-supplied default headers. */\n headers: Record<string, string>;\n /** Abort signal the SDK uses to enforce the remote-config fetch timeout. Honor it in your fetch. */\n signal?: AbortSignal;\n}\n\n/**\n * Custom transport for replay event uploads. Invoked by the SDK in place of its internal\n * `fetch`. Must return a `Response` (or Response-like with `ok`, `status`, `text()`). Called\n * once per logical request attempt — retry is handled by the SDK around the callback, so do\n * not implement your own retry. Thrown errors / rejected promises are surfaced to the SDK's\n * existing retry logic.\n */\nexport type SessionReplaySendEventsHandler = (request: SendEventsRequest) => Promise<Response>;\n\n/**\n * Custom transport for remote-config fetches. Same contract as {@link SessionReplaySendEventsHandler}.\n */\nexport type SessionReplayFetchConfigHandler = (request: FetchConfigRequest) => Promise<Response>;\n\nexport interface CrossOriginIframesConfig {\n enabled: boolean;\n /**\n * When true (default), the parent SDK sends start/stop signals to child iframes via\n * postMessage, keeping their recording lifecycle in sync with the parent.\n *\n * **Privacy note:** The child page's rrweb instance performs its own DOM serialization,\n * so the parent's privacy config (mask levels, block selectors) does NOT automatically\n * apply inside the iframe. Privacy settings must be configured independently on the child page.\n *\n * **Third-party iframes:** Cannot capture iframes you don't control (e.g. Stripe, Google\n * Maps) — both parent and child pages must load the SDK with `crossOriginIframes.enabled: true`.\n *\n * Set to `false` to skip coordination and manage the child recording lifecycle yourself.\n * @defaultValue true\n */\n coordinateChildren?: boolean;\n}\n\nexport interface SessionReplayLocalConfig extends IConfig {\n apiKey: string;\n loggerProvider: ILogger;\n /**\n * LogLevel.None or LogLevel.Error or LogLevel.Warn or LogLevel.Verbose or LogLevel.Debug.\n * Sets the log level.\n *\n * @defaultValue LogLevel.Warn\n */\n logLevel: LogLevel;\n /**\n * The maximum number of retries allowed for sending replay events.\n * Once this limit is reached, failed events will no longer be sent.\n *\n * @defaultValue 2\n */\n flushMaxRetries: number;\n /**\n * Use this option to control how many sessions to select for replay collection.\n * The number should be a decimal between 0 and 1, for example 0.4, representing\n * the fraction of sessions to have randomly selected for replay collection.\n * Over a large number of sessions, 0.4 would select 40% of those sessions.\n * Sample rates as small as six decimal places (0.000001) are supported.\n *\n * @defaultValue 0\n */\n sampleRate: number;\n privacyConfig?: PrivacyConfig;\n /**\n * Adds additional debug event property to help debug instrumentation issues\n * (such as mismatching apps). Only recommended for debugging initial setup,\n * and not recommended for production.\n */\n debugMode?: boolean;\n /**\n * Specifies the endpoint URL to fetch remote configuration.\n * If provided, it overrides the default server zone configuration.\n */\n configServerUrl?: string;\n /**\n * Specifies the endpoint URL for sending session replay data.\n * If provided, it overrides the default server zone configuration.\n */\n trackServerUrl?: string;\n /**\n * If stylesheets are inlined, the contents of the stylesheet will be stored.\n * During replay, the stored stylesheet will be used instead of attempting to fetch it remotely.\n * This prevents replays from appearing broken due to missing stylesheets.\n * Note: Inlining stylesheets may not work in all cases.\n */\n shouldInlineStylesheet?: boolean;\n /**\n * When true, image sources are inlined as data URLs in the rrweb snapshot so replays do not\n * depend on fetching remote image assets at playback time. Increases snapshot payload size.\n * Passed through to rrweb's `inlineImages` record option; when unset, rrweb defaults to false.\n */\n inlineImages?: boolean;\n version?: SessionReplayVersion;\n /**\n * Performance configuration config. If enabled, we will defer compression\n * to be done during the browser's idle periods.\n */\n performanceConfig?: SessionReplayPerformanceConfig;\n /**\n * Specifies how replay events should be stored. `idb` uses IndexedDB to persist replay events\n * when all events cannot be sent during capture. `memory` stores replay events only in memory,\n * meaning events are lost when the page is closed. If IndexedDB is unavailable, the system falls back to `memory`.\n *\n * @defaultValue 'memory' — reflects the validated amp-on-amp perf config (SR-4646). `memory`\n * drops cross-navigation persistence (unsent events do not survive a full page reload), which\n * is the intended trade-off for lower IDB overhead.\n */\n storeType: StoreType;\n\n /**\n * If true, the SDK will compress replay events using a web worker.\n * This offloads compression to a separate thread, improving performance on the main thread.\n * Set to `false` to keep compression on the main thread.\n *\n * @defaultValue true — reflects the validated amp-on-amp perf config (SR-4646). Was `false`\n * prior to that change.\n */\n useWebWorker?: boolean;\n\n /**\n * Controls transport-layer gzip compression of session replay request bodies.\n * When true (default), the SDK gzip-compresses the JSON request body via the browser's\n * `CompressionStream` API and sets `Content-Encoding: gzip` on the POST. When false,\n * the SDK sends the raw JSON body with no `Content-Encoding` header.\n *\n * Disabling is intended as a debugging / safety opt-out (e.g. for diagnosing\n * server-side decompression issues); it increases egress bytes and is not\n * recommended for production.\n *\n * Note: This is independent of `useWebWorker` / `performanceConfig`, which control\n * per-event rrweb compression that runs before events are queued.\n *\n * @defaultValue true\n */\n enableTransportCompression?: boolean;\n\n /**\n * Milliseconds to wait for a send request before aborting it. `fetch()` has no native\n * timeout, so a request stuck \"pending\" would block the serial flush loop indefinitely;\n * the SDK aborts after this many ms and routes the abort as a retryable failure.\n *\n * Set to `0` (or a negative value) to disable the timeout entirely — note this\n * reintroduces the head-of-line-blocking risk a wedged request can cause, so it is\n * intended only as a diagnostic/experiment opt-out.\n *\n * Tuning this higher is useful when large, slow-but-succeeding uploads are being aborted\n * at the default and counted as failures (which also triggers a retry, inflating request\n * volume / throttle pressure).\n *\n * @defaultValue 10000\n */\n sendTimeoutMs?: number;\n\n userProperties?: { [key: string]: any };\n\n /**\n * If true, applies a background color to blocked elements in the replay.\n * This helps visualize which elements are blocked from being captured.\n */\n applyBackgroundColorToBlockedElements?: boolean;\n /**\n * Enables URL change polling as a fallback for SPA route tracking.\n * When enabled, the SDK will periodically check for URL changes every second\n * in addition to patching the History API. This is useful for edge cases where\n * route changes might bypass the standard History API methods.\n *\n * @defaultValue false\n */\n enableUrlChangePolling?: boolean;\n /**\n * Specifies the interval in milliseconds for URL change polling when enableUrlChangePolling is true.\n * The SDK will check for URL changes at this interval as a fallback for SPA route tracking.\n *\n * @defaultValue 1000\n */\n urlChangePollingInterval?: number;\n /**\n * Whether to capture document title in URL change events.\n * When disabled, the title field will be empty in URL change events.\n *\n * @defaultValue false\n */\n captureDocumentTitle?: boolean;\n interactionConfig?: InteractionConfig;\n /**\n * When true (default), the CSS rules of any `adoptedStyleSheets` on shadow roots and\n * the document are serialized **inline** within the full snapshot. This makes the snapshot\n * self-contained so that shadow DOM styles are replayed correctly even if subsequent\n * incremental `AdoptedStyleSheet` events are dropped in transit.\n *\n * Set to `false` to revert to the legacy behavior where adopted stylesheet rules are\n * emitted as separate incremental events (which may be lost if delivery is unreliable).\n * Only consider opting out if snapshot payload size is a critical concern.\n *\n * @defaultValue true\n */\n captureAdoptedStyleSheets?: boolean;\n /**\n * Enables recording of cross-origin iframes. Both the parent page and each child iframe\n * page must load the Amplitude Session Replay SDK with this option enabled.\n *\n * When enabled, rrweb uses `postMessage` to relay child DOM events to the parent, which\n * merges them into a single unified event stream.\n */\n crossOriginIframes?: CrossOriginIframesConfig;\n /** Interval in ms at which the SDK takes a full DOM snapshot. Disabled by default — periodic snapshots are expensive. Recommended value: 300000 (5 min). */\n fullSnapshotIntervalMs?: number;\n /**\n * Controls how often the SDK splits buffered rrweb events into a sequence and dispatches\n * the resulting batch to the server. The interval starts at `minIntervalMs` and grows by\n * `minIntervalMs` after each split, capped at `maxIntervalMs`. Lowering values increases\n * replay availability latency improvements at the cost of more requests; raising them\n * reduces request volume (and 200+`X-Session-Replay-Event-Skipped` throttling responses)\n * at the cost of slightly delayed replay availability.\n *\n * Defaults to `{ minIntervalMs: 1000, maxIntervalMs: 10_000 }`, reflecting the validated\n * amp-on-amp perf config (SR-4646); the `minIntervalMs` default was `500` prior to that\n * change. Tune up if the server is back-pressuring the SDK on session start.\n */\n flushIntervalConfig?: FlushIntervalConfig;\n /**\n * When true, every rrweb full snapshot is flushed to the server immediately so replays\n * become playable as early as possible. When false (default), full-snapshot sends are\n * deferred to the normal interval/size flush cadence instead. The snapshot is still\n * compressed and buffered immediately either way (ordering and page-exit beacon coverage\n * are preserved); only the eager network send is suppressed. The default-off behavior\n * reduces request volume for pages that produce many full snapshots (e.g. focus-driven or\n * `fullSnapshotIntervalMs` checkouts), especially when many SDK instances run on the same\n * page.\n *\n * @defaultValue false — reflects the validated amp-on-amp perf config (SR-4646). Was `true`\n * prior to that change.\n */\n eagerFullSnapshotSend?: boolean;\n /**\n * When true, the window `focus` listener forces a fresh rrweb full snapshot\n * (`takeFullSnapshot`) every time the page regains focus, so the replay reflects any DOM\n * changes that happened while the tab was backgrounded. When false (default), the on-focus\n * full snapshot is skipped entirely (recording simply continues from the existing stream).\n *\n * On pages with heavy focus churn (e.g. embedded iframes, inline editors that repeatedly\n * steal and return focus) this fires constantly, and when combined with\n * `eagerFullSnapshotSend` each focus produces an immediate network send — the primary\n * driver of focus-driven request storms. The default-off behavior removes the snapshot (and\n * therefore the send) at the cost of slightly staler post-focus frames.\n *\n * @defaultValue false — reflects the validated amp-on-amp perf config (SR-4646). Was `true`\n * prior to that change.\n */\n captureFullSnapshotOnFocus?: boolean;\n /**\n * Raw (uncompressed) UTF-8 byte cap for a single buffered events list before the store\n * splits it into its own request. Larger values produce fewer, larger requests (the primary\n * steady-state lever for request volume); smaller values split sooner. Payloads are gzipped\n * on the wire, so several hundred KB of replay JSON compresses to well under 100 KB.\n *\n * Advanced/debug knob — the default already balances request volume against the server's\n * decompressed-size split threshold. Clamped to a safe range; values outside it are clamped\n * and logged. Defaults to the SDK's internal `DEFAULT_MAX_PERSISTED_EVENTS_SIZE_BYTES`.\n *\n * @defaultValue 6000000 — reflects the validated amp-on-amp perf config (SR-4646). Was\n * `MAX_EVENT_LIST_SIZE` (2000000) prior to that change.\n */\n maxPersistedEventsSizeBytes?: number;\n /**\n * Raw (uncompressed) UTF-8 byte cap for a single rrweb event. Events larger than this are\n * dropped (with a warning) both at capture time and as a pre-send backstop, because the SR\n * ingest service rejects a single event above ~10 MB. Lower this to exercise drop behavior\n * for large full snapshots while debugging.\n *\n * Advanced/debug knob. Clamped to a safe range; values outside it are clamped and logged.\n * Defaults to the SDK's internal `MAX_SINGLE_EVENT_SIZE`.\n *\n * @defaultValue 9000000\n */\n maxSingleEventSizeBytes?: number;\n /**\n * Optional custom transport for replay event uploads. When provided, the SDK invokes this\n * callback in place of its internal `fetch` for every event-upload attempt, passing a\n * fully-formed {@link SendEventsRequest} (resolved URL, headers, serialized body). The SDK\n * still owns batching, retry, serialization and error handling. Use it to attach custom\n * auth (e.g. a JWT `Authorization` header) and route through an authenticated proxy.\n *\n * Works alongside `trackServerUrl`: the URL passed to the callback already respects it.\n */\n handleSendEvents?: SessionReplaySendEventsHandler;\n /**\n * Optional custom transport for the remote-config fetch. Same contract as `handleSendEvents`\n * but for the config GET. The URL passed to the callback already respects `configServerUrl`.\n */\n handleFetchConfig?: SessionReplayFetchConfigHandler;\n}\n\nexport interface FlushIntervalConfig {\n /**\n * Lower bound on the rrweb event-split interval in milliseconds. Also the increment\n * added to the interval after each split. Must be > 0; values are clamped to a 100ms floor.\n *\n * @defaultValue 1000 — reflects the validated amp-on-amp perf config (SR-4646). Was `500`\n * prior to that change.\n */\n minIntervalMs?: number;\n /**\n * Upper bound on the rrweb event-split interval in milliseconds. Must be >= `minIntervalMs`.\n *\n * @defaultValue 10000\n */\n maxIntervalMs?: number;\n}\n\nexport interface SessionReplayJoinedConfig extends SessionReplayLocalConfig {\n captureEnabled?: boolean;\n interactionConfig?: InteractionConfig;\n loggingConfig?: LoggingConfig;\n targetingConfig?: TargetingConfig;\n minSessionDurationMs?: number;\n}\n\nexport interface SessionReplayConfigs {\n localConfig: SessionReplayLocalConfig;\n joinedConfig: SessionReplayJoinedConfig;\n remoteConfig: SessionReplayRemoteConfig | undefined;\n}\nexport interface SessionReplayJoinedConfigGenerator {\n generateJoinedConfig: () => Promise<SessionReplayConfigs>;\n}\n\nexport interface SessionReplayMetadata {\n remoteConfig: SessionReplayRemoteConfig | undefined;\n localConfig: SessionReplayLocalConfig;\n joinedConfig: SessionReplayJoinedConfig;\n framework?: {\n name: string;\n version: string;\n };\n sessionId: string | number | undefined;\n hashValue?: number;\n sampleRate: number;\n replaySDKType: string | null;\n replaySDKVersion: string | undefined;\n standaloneSDKType: string;\n standaloneSDKVersion: string | undefined;\n}\n\nexport interface SessionReplayVersion {\n version: string;\n type: SessionReplayType;\n}\n\n/**\n * Configuration options for session replay performance.\n */\nexport interface SessionReplayPerformanceConfig {\n /**\n * If enabled, event compression will be deferred to occur during the browser's idle periods.\n */\n enabled: boolean;\n /**\n * Optional timeout in milliseconds for the `requestIdleCallback` API.\n * If specified, this value will be used to set a maximum time for the browser to wait\n * before executing the deferred compression task, even if the browser is not idle.\n */\n timeout?: number;\n /**\n * If enabled, consecutive mutation events will be merged into a single event before\n * compression, reducing stored event count without changing replay semantics.\n * Defaults to true, reflecting the validated amp-on-amp perf config (SR-4646); set to\n * false to disable merging.\n */\n mergeMutations?: boolean;\n /**\n * Performance configuration for interaction tracking (clicks, scrolls).\n */\n interaction?: InteractionPerformanceConfig;\n}\n\n/**\n * Performance configuration for interaction tracking, specifically for CSS selector generation.\n */\nexport interface InteractionPerformanceConfig {\n /**\n * Maximum time in milliseconds allowed for CSS selector generation.\n * If selector generation takes longer than this, it will throw a timeout error.\n * Default: undefined (no timeout limit)\n */\n timeoutMs?: number;\n /**\n * Maximum number of attempts to optimize/simplify the CSS selector path.\n * Higher values may produce shorter selectors but take longer to compute.\n * Default: 10000\n */\n maxNumberOfTries?: number;\n /**\n * Maximum number of CSS selector combinations to test for uniqueness.\n * If more combinations would be generated, falls back to a simpler strategy.\n * Default: 1000\n */\n threshold?: number;\n}\n\nexport type SessionReplayType = 'standalone' | 'plugin' | 'segment';\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { PrivacyConfig } from '@amplitude/session-replay-dom-privacy';\nexport type { PrivacyConfig, MaskLevel } from '@amplitude/session-replay-dom-privacy';\nimport { IConfig, LogLevel, ILogger } from '@amplitude/analytics-core';\nimport { StoreType, ConsoleLogLevel } from '../typings/session-replay';\nimport { TargetingFlag } from '@amplitude/targeting';\nimport type { BodyCaptureRuleConfig } from '../network-body-capture';\n\nexport interface SamplingConfig {\n sample_rate: number;\n capture_enabled: boolean;\n min_session_duration_ms?: number;\n}\n\nexport interface InteractionConfig {\n trackEveryNms?: number;\n enabled: boolean; // defaults to false\n batch: boolean; // defaults to false\n /**\n * UGC filter rules.\n */\n ugcFilterRules?: UGCFilterRule[];\n}\n\nexport interface LoggingConfig {\n console: {\n enabled: boolean;\n levels: ConsoleLogLevel[];\n };\n network?: {\n enabled: boolean;\n body?: {\n request?: BodyCaptureRuleConfig;\n response?: BodyCaptureRuleConfig;\n maxBodySizeBytes?: number;\n };\n };\n}\n\nexport type TargetingConfig = TargetingFlag;\n\nexport type SessionReplayRemoteConfig = {\n sr_sampling_config?: SamplingConfig;\n sr_privacy_config?: PrivacyConfig;\n sr_interaction_config?: InteractionConfig;\n sr_logging_config?: LoggingConfig;\n sr_targeting_config?: TargetingConfig;\n};\n\nexport interface SessionReplayRemoteConfigAPIResponse {\n configs: {\n sessionReplay: SessionReplayRemoteConfig;\n };\n}\n\n/**\n * UGC filter rule.\n */\nexport type UGCFilterRule = {\n /**\n * The selector of the UGC element.\n */\n selector: string;\n /**\n * The replacement text for the UGC element.\n */\n replacement: string;\n};\n\n/**\n * The fully-formed request the SDK hands to a custom `handleSendEvents` transport when\n * uploading recorded replay events. The callback's only job is to execute this request and\n * return the resulting `Response`; the SDK owns batching, retry, serialization and error\n * handling around it.\n */\nexport interface SendEventsRequest {\n /** The resolved track URL (respects `trackServerUrl`) including query params. */\n url: string;\n method: 'POST';\n /**\n * SDK-supplied default headers. When the body was gzipped, this includes\n * `Content-Encoding: gzip` — drop it only if you also stop forwarding the gzipped body,\n * otherwise the payload will not decode server-side.\n */\n headers: Record<string, string>;\n /**\n * The serialized payload, ready to send. A gzipped `Uint8Array` when transport compression\n * is active, otherwise the raw JSON string. Forward it unchanged — do not re-serialize.\n *\n * Typed as `string | Uint8Array` (not the broader `BodyInit`) because the body must be\n * structured-cloneable to cross `postMessage` in web-worker mode; `Blob`/`FormData`/\n * `ReadableStream` are not, and the SDK never produces them here.\n */\n body: string | Uint8Array;\n /**\n * Whether the built-in path would set `keepalive` on this request. `true` for page-exit\n * sends (so the request survives unload) and for small in-session batches. Forward it to\n * your `fetch` call — i.e. `fetch(url, { method, headers, body, keepalive })` — so exit-time\n * batches are not dropped when the page is tearing down.\n */\n keepalive: boolean;\n}\n\n/**\n * The fully-formed request the SDK hands to a custom `handleFetchConfig` transport when\n * fetching remote configuration.\n */\nexport interface FetchConfigRequest {\n /** The resolved config URL (respects `configServerUrl`) including query params. */\n url: string;\n method: 'GET';\n /** SDK-supplied default headers. */\n headers: Record<string, string>;\n /** Abort signal the SDK uses to enforce the remote-config fetch timeout. Honor it in your fetch. */\n signal?: AbortSignal;\n}\n\n/**\n * Custom transport for replay event uploads. Invoked by the SDK in place of its internal\n * `fetch`. Must return a `Response` (or Response-like with `ok`, `status`, `text()`). Called\n * once per logical request attempt — retry is handled by the SDK around the callback, so do\n * not implement your own retry. Thrown errors / rejected promises are surfaced to the SDK's\n * existing retry logic.\n */\nexport type SessionReplaySendEventsHandler = (request: SendEventsRequest) => Promise<Response>;\n\n/**\n * Custom transport for remote-config fetches. Same contract as {@link SessionReplaySendEventsHandler}.\n */\nexport type SessionReplayFetchConfigHandler = (request: FetchConfigRequest) => Promise<Response>;\n\nexport interface CrossOriginIframesConfig {\n enabled: boolean;\n /**\n * When true (default), the parent SDK sends start/stop signals to child iframes via\n * postMessage, keeping their recording lifecycle in sync with the parent.\n *\n * **Privacy note:** The child page's rrweb instance performs its own DOM serialization,\n * so the parent's privacy config (mask levels, block selectors) does NOT automatically\n * apply inside the iframe. Privacy settings must be configured independently on the child page.\n *\n * **Third-party iframes:** Cannot capture iframes you don't control (e.g. Stripe, Google\n * Maps) — both parent and child pages must load the SDK with `crossOriginIframes.enabled: true`.\n *\n * Set to `false` to skip coordination and manage the child recording lifecycle yourself.\n * @defaultValue true\n */\n coordinateChildren?: boolean;\n}\n\nexport interface SessionReplayLocalConfig extends IConfig {\n apiKey: string;\n loggerProvider: ILogger;\n /**\n * LogLevel.None or LogLevel.Error or LogLevel.Warn or LogLevel.Verbose or LogLevel.Debug.\n * Sets the log level.\n *\n * @defaultValue LogLevel.Warn\n */\n logLevel: LogLevel;\n /**\n * The maximum number of retries allowed for sending replay events.\n * Once this limit is reached, failed events will no longer be sent.\n *\n * @defaultValue 2\n */\n flushMaxRetries: number;\n /**\n * Use this option to control how many sessions to select for replay collection.\n * The number should be a decimal between 0 and 1, for example 0.4, representing\n * the fraction of sessions to have randomly selected for replay collection.\n * Over a large number of sessions, 0.4 would select 40% of those sessions.\n * Sample rates as small as six decimal places (0.000001) are supported.\n *\n * @defaultValue 0\n */\n sampleRate: number;\n privacyConfig?: PrivacyConfig;\n /**\n * Adds additional debug event property to help debug instrumentation issues\n * (such as mismatching apps). Only recommended for debugging initial setup,\n * and not recommended for production.\n */\n debugMode?: boolean;\n /**\n * Specifies the endpoint URL to fetch remote configuration.\n * If provided, it overrides the default server zone configuration.\n */\n configServerUrl?: string;\n /**\n * Specifies the endpoint URL for sending session replay data.\n * If provided, it overrides the default server zone configuration.\n */\n trackServerUrl?: string;\n /**\n * If stylesheets are inlined, the contents of the stylesheet will be stored.\n * During replay, the stored stylesheet will be used instead of attempting to fetch it remotely.\n * This prevents replays from appearing broken due to missing stylesheets.\n * Note: Inlining stylesheets may not work in all cases.\n */\n shouldInlineStylesheet?: boolean;\n /**\n * When true, image sources are inlined as data URLs in the rrweb snapshot so replays do not\n * depend on fetching remote image assets at playback time. Increases snapshot payload size.\n * Passed through to rrweb's `inlineImages` record option; when unset, rrweb defaults to false.\n */\n inlineImages?: boolean;\n version?: SessionReplayVersion;\n /**\n * Performance configuration config. If enabled, we will defer compression\n * to be done during the browser's idle periods.\n */\n performanceConfig?: SessionReplayPerformanceConfig;\n /**\n * Specifies how replay events should be stored. `idb` uses IndexedDB to persist replay events\n * when all events cannot be sent during capture. `memory` stores replay events only in memory,\n * meaning events are lost when the page is closed. If IndexedDB is unavailable, the system falls back to `memory`.\n *\n * @defaultValue 'memory' — reflects the validated amp-on-amp perf config (SR-4646). `memory`\n * drops cross-navigation persistence (unsent events do not survive a full page reload), which\n * is the intended trade-off for lower IDB overhead.\n */\n storeType: StoreType;\n\n /**\n * If true, the SDK will compress replay events using a web worker.\n * This offloads compression to a separate thread, improving performance on the main thread.\n * Set to `false` to keep compression on the main thread.\n *\n * @defaultValue true — reflects the validated amp-on-amp perf config (SR-4646). Was `false`\n * prior to that change.\n */\n useWebWorker?: boolean;\n\n /**\n * Controls transport-layer gzip compression of session replay request bodies.\n * When true (default), the SDK gzip-compresses the JSON request body via the browser's\n * `CompressionStream` API and sets `Content-Encoding: gzip` on the POST. When false,\n * the SDK sends the raw JSON body with no `Content-Encoding` header.\n *\n * Disabling is intended as a debugging / safety opt-out (e.g. for diagnosing\n * server-side decompression issues); it increases egress bytes and is not\n * recommended for production.\n *\n * Note: This is independent of `useWebWorker` / `performanceConfig`, which control\n * per-event rrweb compression that runs before events are queued.\n *\n * @defaultValue true\n */\n enableTransportCompression?: boolean;\n\n /**\n * Milliseconds to wait for a send request before aborting it. `fetch()` has no native\n * timeout, so a request stuck \"pending\" would block the serial flush loop indefinitely;\n * the SDK aborts after this many ms and routes the abort as a retryable failure.\n *\n * Set to `0` (or a negative value) to disable the timeout entirely — note this\n * reintroduces the head-of-line-blocking risk a wedged request can cause, so it is\n * intended only as a diagnostic/experiment opt-out.\n *\n * Tuning this higher is useful when large, slow-but-succeeding uploads are being aborted\n * at the default and counted as failures (which also triggers a retry, inflating request\n * volume / throttle pressure).\n *\n * @defaultValue 10000\n */\n sendTimeoutMs?: number;\n\n userProperties?: { [key: string]: any };\n\n /**\n * If true, applies a background color to blocked elements in the replay.\n * This helps visualize which elements are blocked from being captured.\n */\n applyBackgroundColorToBlockedElements?: boolean;\n /**\n * Enables URL change polling as a fallback for SPA route tracking.\n * When enabled, the SDK will periodically check for URL changes every second\n * in addition to patching the History API. This is useful for edge cases where\n * route changes might bypass the standard History API methods.\n *\n * @defaultValue false\n */\n enableUrlChangePolling?: boolean;\n /**\n * Specifies the interval in milliseconds for URL change polling when enableUrlChangePolling is true.\n * The SDK will check for URL changes at this interval as a fallback for SPA route tracking.\n *\n * @defaultValue 1000\n */\n urlChangePollingInterval?: number;\n /**\n * Whether to capture document title in URL change events.\n * When disabled, the title field will be empty in URL change events.\n *\n * @defaultValue false\n */\n captureDocumentTitle?: boolean;\n interactionConfig?: InteractionConfig;\n /**\n * When true (default), the CSS rules of any `adoptedStyleSheets` on shadow roots and\n * the document are serialized **inline** within the full snapshot. This makes the snapshot\n * self-contained so that shadow DOM styles are replayed correctly even if subsequent\n * incremental `AdoptedStyleSheet` events are dropped in transit.\n *\n * Set to `false` to revert to the legacy behavior where adopted stylesheet rules are\n * emitted as separate incremental events (which may be lost if delivery is unreliable).\n * Only consider opting out if snapshot payload size is a critical concern.\n *\n * @defaultValue true\n */\n captureAdoptedStyleSheets?: boolean;\n /**\n * Enables recording of cross-origin iframes. Both the parent page and each child iframe\n * page must load the Amplitude Session Replay SDK with this option enabled.\n *\n * When enabled, rrweb uses `postMessage` to relay child DOM events to the parent, which\n * merges them into a single unified event stream.\n */\n crossOriginIframes?: CrossOriginIframesConfig;\n /** Interval in ms at which the SDK takes a full DOM snapshot. Disabled by default — periodic snapshots are expensive. Recommended value: 300000 (5 min). */\n fullSnapshotIntervalMs?: number;\n /**\n * Controls how often the SDK splits buffered rrweb events into a sequence and dispatches\n * the resulting batch to the server. The interval starts at `minIntervalMs` and grows by\n * `minIntervalMs` after each split, capped at `maxIntervalMs`. Lowering values increases\n * replay availability latency improvements at the cost of more requests; raising them\n * reduces request volume (and 200+`X-Session-Replay-Event-Skipped` throttling responses)\n * at the cost of slightly delayed replay availability.\n *\n * Defaults to `{ minIntervalMs: 1000, maxIntervalMs: 10_000 }`, reflecting the validated\n * amp-on-amp perf config (SR-4646); the `minIntervalMs` default was `500` prior to that\n * change. Tune up if the server is back-pressuring the SDK on session start.\n */\n flushIntervalConfig?: FlushIntervalConfig;\n /**\n * When true, every rrweb full snapshot is flushed to the server immediately so replays\n * become playable as early as possible. When false (default), full-snapshot sends are\n * deferred to the normal interval/size flush cadence instead. The snapshot is still\n * compressed and buffered immediately either way (ordering and page-exit beacon coverage\n * are preserved); only the eager network send is suppressed. The default-off behavior\n * reduces request volume for pages that produce many full snapshots (e.g. focus-driven or\n * `fullSnapshotIntervalMs` checkouts), especially when many SDK instances run on the same\n * page.\n *\n * @defaultValue false — reflects the validated amp-on-amp perf config (SR-4646). Was `true`\n * prior to that change.\n */\n eagerFullSnapshotSend?: boolean;\n /**\n * When true, the window `focus` listener forces a fresh rrweb full snapshot\n * (`takeFullSnapshot`) every time the page regains focus, so the replay reflects any DOM\n * changes that happened while the tab was backgrounded. When false (default), the on-focus\n * full snapshot is skipped entirely (recording simply continues from the existing stream).\n *\n * On pages with heavy focus churn (e.g. embedded iframes, inline editors that repeatedly\n * steal and return focus) this fires constantly, and when combined with\n * `eagerFullSnapshotSend` each focus produces an immediate network send — the primary\n * driver of focus-driven request storms. The default-off behavior removes the snapshot (and\n * therefore the send) at the cost of slightly staler post-focus frames.\n *\n * @defaultValue false — reflects the validated amp-on-amp perf config (SR-4646). Was `true`\n * prior to that change.\n */\n captureFullSnapshotOnFocus?: boolean;\n /**\n * Raw (uncompressed) UTF-8 byte cap for a single buffered events list before the store\n * splits it into its own request. Larger values produce fewer, larger requests (the primary\n * steady-state lever for request volume); smaller values split sooner. Payloads are gzipped\n * on the wire, so several hundred KB of replay JSON compresses to well under 100 KB.\n *\n * Advanced/debug knob — the default already balances request volume against the server's\n * decompressed-size split threshold. Clamped to a safe range; values outside it are clamped\n * and logged. Defaults to the SDK's internal `DEFAULT_MAX_PERSISTED_EVENTS_SIZE_BYTES`.\n *\n * @defaultValue 6000000 — reflects the validated amp-on-amp perf config (SR-4646). Was\n * `MAX_EVENT_LIST_SIZE` (2000000) prior to that change.\n */\n maxPersistedEventsSizeBytes?: number;\n /**\n * Raw (uncompressed) UTF-8 byte cap for a single rrweb event. Events larger than this are\n * dropped (with a warning) both at capture time and as a pre-send backstop, because the SR\n * ingest service rejects a single event above ~10 MB. Lower this to exercise drop behavior\n * for large full snapshots while debugging.\n *\n * Advanced/debug knob. Clamped to a safe range; values outside it are clamped and logged.\n * Defaults to the SDK's internal `MAX_SINGLE_EVENT_SIZE`.\n *\n * @defaultValue 9000000\n */\n maxSingleEventSizeBytes?: number;\n /**\n * Optional custom transport for replay event uploads. When provided, the SDK invokes this\n * callback in place of its internal `fetch` for every event-upload attempt, passing a\n * fully-formed {@link SendEventsRequest} (resolved URL, headers, serialized body). The SDK\n * still owns batching, retry, serialization and error handling. Use it to attach custom\n * auth (e.g. a JWT `Authorization` header) and route through an authenticated proxy.\n *\n * Works alongside `trackServerUrl`: the URL passed to the callback already respects it.\n */\n handleSendEvents?: SessionReplaySendEventsHandler;\n /**\n * Optional custom transport for the remote-config fetch. Same contract as `handleSendEvents`\n * but for the config GET. The URL passed to the callback already respects `configServerUrl`.\n */\n handleFetchConfig?: SessionReplayFetchConfigHandler;\n}\n\nexport interface FlushIntervalConfig {\n /**\n * Lower bound on the rrweb event-split interval in milliseconds. Also the increment\n * added to the interval after each split. Must be > 0; values are clamped to a 100ms floor.\n *\n * @defaultValue 1000 — reflects the validated amp-on-amp perf config (SR-4646). Was `500`\n * prior to that change.\n */\n minIntervalMs?: number;\n /**\n * Upper bound on the rrweb event-split interval in milliseconds. Must be >= `minIntervalMs`.\n *\n * @defaultValue 10000\n */\n maxIntervalMs?: number;\n}\n\nexport interface SessionReplayJoinedConfig extends SessionReplayLocalConfig {\n captureEnabled?: boolean;\n interactionConfig?: InteractionConfig;\n loggingConfig?: LoggingConfig;\n targetingConfig?: TargetingConfig;\n minSessionDurationMs?: number;\n}\n\nexport interface SessionReplayConfigs {\n localConfig: SessionReplayLocalConfig;\n joinedConfig: SessionReplayJoinedConfig;\n remoteConfig: SessionReplayRemoteConfig | undefined;\n}\nexport interface SessionReplayJoinedConfigGenerator {\n generateJoinedConfig: () => Promise<SessionReplayConfigs>;\n}\n\nexport interface SessionReplayMetadata {\n remoteConfig: SessionReplayRemoteConfig | undefined;\n localConfig: SessionReplayLocalConfig;\n joinedConfig: SessionReplayJoinedConfig;\n framework?: {\n name: string;\n version: string;\n };\n sessionId: string | number | undefined;\n hashValue?: number;\n sampleRate: number;\n replaySDKType: string | null;\n replaySDKVersion: string | undefined;\n standaloneSDKType: string;\n standaloneSDKVersion: string | undefined;\n}\n\nexport interface SessionReplayVersion {\n version: string;\n type: SessionReplayType;\n}\n\n/**\n * Configuration options for session replay performance.\n */\nexport interface SessionReplayPerformanceConfig {\n /**\n * If enabled, event compression will be deferred to occur during the browser's idle periods.\n */\n enabled: boolean;\n /**\n * Optional timeout in milliseconds for the `requestIdleCallback` API.\n * If specified, this value will be used to set a maximum time for the browser to wait\n * before executing the deferred compression task, even if the browser is not idle.\n */\n timeout?: number;\n /**\n * If enabled, consecutive mutation events will be merged into a single event before\n * compression, reducing stored event count without changing replay semantics.\n * Defaults to true, reflecting the validated amp-on-amp perf config (SR-4646); set to\n * false to disable merging.\n */\n mergeMutations?: boolean;\n /**\n * Performance configuration for interaction tracking (clicks, scrolls).\n */\n interaction?: InteractionPerformanceConfig;\n}\n\n/**\n * Performance configuration for interaction tracking, specifically for CSS selector generation.\n */\nexport interface InteractionPerformanceConfig {\n /**\n * Maximum time in milliseconds allowed for CSS selector generation.\n * If selector generation takes longer than this, it will throw a timeout error.\n * Default: undefined (no timeout limit)\n */\n timeoutMs?: number;\n /**\n * Maximum number of attempts to optimize/simplify the CSS selector path.\n * Higher values may produce shorter selectors but take longer to compute.\n * Default: 10000\n */\n maxNumberOfTries?: number;\n /**\n * Maximum number of CSS selector combinations to test for uniqueness.\n * If more combinations would be generated, falls back to a simpler strategy.\n * Default: 1000\n */\n threshold?: number;\n}\n\nexport type SessionReplayType = 'standalone' | 'plugin' | 'segment';\n"]}
|
package/lib/esm/helpers.d.ts
CHANGED
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SessionReplayJoinedConfig, UGCFilterRule } from './config/types';
|
|
2
2
|
import { StorageData } from './typings/session-replay';
|
|
3
3
|
export { getServerUrl } from './utils/server-url';
|
|
4
|
-
/**
|
|
5
|
-
* Returns the effective mask level for a given URL by checking `urlMaskLevels`
|
|
6
|
-
* (first match wins) and falling back to `defaultMaskLevel`.
|
|
7
|
-
*/
|
|
8
|
-
export declare const getEffectiveMaskLevel: (url: string | undefined, config: PrivacyConfig) => MaskLevel;
|
|
9
|
-
/**
|
|
10
|
-
* Checks if the given element set to be masked by rrweb
|
|
11
|
-
*
|
|
12
|
-
* Priority is:
|
|
13
|
-
* 1. [In code] Element/class based masking/unmasking <> [Config based] Selector based masking/unmasking
|
|
14
|
-
* 2. Use app defaults
|
|
15
|
-
*/
|
|
16
|
-
export declare const isMasked: (elementType: 'input' | 'text', config: PrivacyConfig | undefined, element: HTMLElement | null, currentUrl?: string) => boolean;
|
|
17
|
-
export declare const maskFn: (elementType: 'text' | 'input', config?: PrivacyConfig, getCurrentUrl?: () => string) => (text: string, element: HTMLElement | null) => string;
|
|
18
|
-
export declare const maskAttributeFn: (config?: PrivacyConfig, getCurrentUrl?: () => string) => (key: string, value: string, element: HTMLElement) => string;
|
|
19
4
|
export declare const getCurrentUrl: () => string;
|
|
20
5
|
export declare const generateSessionReplayId: (sessionId: string | number, deviceId: string) => string;
|
|
21
6
|
export declare const validateUGCFilterRules: (ugcFilterRules: UGCFilterRule[]) => void;
|
package/lib/esm/helpers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE1E,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQlD,eAAO,MAAM,aAAa,cAGzB,CAAC;AAEF,eAAO,MAAM,uBAAuB,cAAe,MAAM,GAAG,MAAM,YAAY,MAAM,KAAG,MAEtF,CAAC;AASF,eAAO,MAAM,sBAAsB,mBAAoB,aAAa,EAAE,SAUrE,CAAC;AAEF,eAAO,MAAM,UAAU,YAAa,MAAM,kBAAkB,aAAa,EAAE,WAW1E,CAAC;AAEF,eAAO,MAAM,cAAc,QAAa,QAAQ,WAAW,CAa1D,CAAC;AAEF,eAAO,MAAM,cAAc,WAAY,yBAAyB,KAAG,yBAOlE,CAAC"}
|
package/lib/esm/helpers.js
CHANGED
|
@@ -1,122 +1,8 @@
|
|
|
1
1
|
import { __assign, __awaiter, __generator, __values } from "tslib";
|
|
2
2
|
import { getGlobalScope } from '@amplitude/analytics-core';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { getInputType } from './utils/get-input-type';
|
|
3
|
+
import { KB_SIZE } from './constants';
|
|
4
|
+
import { globToRegex } from '@amplitude/session-replay-dom-privacy';
|
|
6
5
|
export { getServerUrl } from './utils/server-url';
|
|
7
|
-
/**
|
|
8
|
-
* Light: Subset of inputs (sensitive types only — password, hidden, email, tel, cc-*)
|
|
9
|
-
* Medium: All inputs (form fields), text is NOT masked
|
|
10
|
-
* Conservative: All inputs and all texts
|
|
11
|
-
*/
|
|
12
|
-
var isMaskedForLevel = function (elementType, level, element) {
|
|
13
|
-
switch (level) {
|
|
14
|
-
case 'light': {
|
|
15
|
-
if (elementType !== 'input') {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
var inputType = element ? getInputType(element) : '';
|
|
19
|
-
/* istanbul ignore if */ // TODO(lew): For some reason it's impossible to test this.
|
|
20
|
-
if (!inputType) {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
if (['password', 'hidden', 'email', 'tel'].includes(inputType)) {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
if (element.autocomplete.startsWith('cc-')) {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
case 'medium':
|
|
32
|
-
return elementType === 'input';
|
|
33
|
-
case 'conservative':
|
|
34
|
-
return true;
|
|
35
|
-
default:
|
|
36
|
-
return isMaskedForLevel(elementType, DEFAULT_MASK_LEVEL, element);
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* Returns the effective mask level for a given URL by checking `urlMaskLevels`
|
|
41
|
-
* (first match wins) and falling back to `defaultMaskLevel`.
|
|
42
|
-
*/
|
|
43
|
-
export var getEffectiveMaskLevel = function (url, config) {
|
|
44
|
-
var e_1, _a;
|
|
45
|
-
var _b;
|
|
46
|
-
if (url && config.urlMaskLevels) {
|
|
47
|
-
try {
|
|
48
|
-
for (var _c = __values(config.urlMaskLevels), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
49
|
-
var rule = _d.value;
|
|
50
|
-
if (globToRegex(rule.match).test(url)) {
|
|
51
|
-
return rule.maskLevel;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
56
|
-
finally {
|
|
57
|
-
try {
|
|
58
|
-
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
59
|
-
}
|
|
60
|
-
finally { if (e_1) throw e_1.error; }
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return (_b = config.defaultMaskLevel) !== null && _b !== void 0 ? _b : DEFAULT_MASK_LEVEL;
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Checks if the given element set to be masked by rrweb
|
|
67
|
-
*
|
|
68
|
-
* Priority is:
|
|
69
|
-
* 1. [In code] Element/class based masking/unmasking <> [Config based] Selector based masking/unmasking
|
|
70
|
-
* 2. Use app defaults
|
|
71
|
-
*/
|
|
72
|
-
export var isMasked = function (elementType, config, element, currentUrl) {
|
|
73
|
-
var _a, _b;
|
|
74
|
-
if (config === void 0) { config = { defaultMaskLevel: DEFAULT_MASK_LEVEL }; }
|
|
75
|
-
if (element) {
|
|
76
|
-
// Element or parent is explicitly instrumented in code to mask
|
|
77
|
-
if (element.closest('.' + MASK_TEXT_CLASS)) {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
// Config has override for mask
|
|
81
|
-
var shouldMask = ((_a = config.maskSelector) !== null && _a !== void 0 ? _a : []).some(function (selector) { return element.closest(selector); });
|
|
82
|
-
if (shouldMask) {
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
// Code or config has override to unmask
|
|
86
|
-
if (element.closest('.' + UNMASK_TEXT_CLASS)) {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
// Here we are probably sent an element, but we want to match if they have a
|
|
90
|
-
// parent with an unmask selector.
|
|
91
|
-
var shouldUnmask = ((_b = config.unmaskSelector) !== null && _b !== void 0 ? _b : []).some(function (selector) { return element.closest(selector); });
|
|
92
|
-
if (shouldUnmask) {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return isMaskedForLevel(elementType, getEffectiveMaskLevel(currentUrl, config), element);
|
|
97
|
-
};
|
|
98
|
-
export var maskFn = function (elementType, config, getCurrentUrl) {
|
|
99
|
-
return function (text, element) {
|
|
100
|
-
return isMasked(elementType, config, element, getCurrentUrl === null || getCurrentUrl === void 0 ? void 0 : getCurrentUrl()) ? text.replace(/[^\s]/g, '*') : text;
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
export var maskAttributeFn = function (config, getCurrentUrl) {
|
|
104
|
-
return function (key, value, element) {
|
|
105
|
-
var _a;
|
|
106
|
-
// Never mask style — rrweb has a separate styleDiff path for attribute mutations
|
|
107
|
-
// that reads directly from the DOM, bypassing maskAttributeFn.
|
|
108
|
-
if (key === 'style')
|
|
109
|
-
return value;
|
|
110
|
-
// Short-circuit: only proceed if this attribute is in the allowlist.
|
|
111
|
-
if (!((_a = config === null || config === void 0 ? void 0 : config.maskAttributes) !== null && _a !== void 0 ? _a : []).includes(key))
|
|
112
|
-
return value;
|
|
113
|
-
// Use 'input' for form elements so that `medium` (which masks inputs but not text)
|
|
114
|
-
// still masks attributes on inputs/selects/textareas. For non-form elements, use
|
|
115
|
-
// 'text' so medium leaves them visible.
|
|
116
|
-
var elementType = ['INPUT', 'SELECT', 'TEXTAREA'].includes(element.tagName) ? 'input' : 'text';
|
|
117
|
-
return isMasked(elementType, config, element, getCurrentUrl === null || getCurrentUrl === void 0 ? void 0 : getCurrentUrl()) ? value.replace(/[^\s]/g, '*') : value;
|
|
118
|
-
};
|
|
119
|
-
};
|
|
120
6
|
export var getCurrentUrl = function () {
|
|
121
7
|
var globalScope = getGlobalScope();
|
|
122
8
|
return (globalScope === null || globalScope === void 0 ? void 0 : globalScope.location) ? globalScope.location.href : '';
|
|
@@ -132,46 +18,6 @@ var isValidGlobUrl = function (globUrl) {
|
|
|
132
18
|
return false;
|
|
133
19
|
return true;
|
|
134
20
|
};
|
|
135
|
-
var globRegexCache = new Map();
|
|
136
|
-
var globToRegex = function (glob) {
|
|
137
|
-
var cached = globRegexCache.get(glob);
|
|
138
|
-
if (cached)
|
|
139
|
-
return cached;
|
|
140
|
-
// Glob → regex conversion. Glob substitution must happen BEFORE regex escaping so that
|
|
141
|
-
// the escaper does not corrupt the glob characters (e.g. turning `/**` into `/\*\*`).
|
|
142
|
-
// Placeholder tokens use null bytes, which are illegal in HTTP URLs and therefore can
|
|
143
|
-
// never collide with real pattern content.
|
|
144
|
-
//
|
|
145
|
-
// Substitution order (most-specific first):
|
|
146
|
-
// trailing /** → (/.*)? — path with or without a trailing slash/subpath
|
|
147
|
-
// middle /**/ → /(.*\/)? — zero-or-more path segments (including zero)
|
|
148
|
-
// bare ** → .* — graceful fallback for ** not adjacent to /
|
|
149
|
-
// single * → .* — any characters (preserves existing behaviour)
|
|
150
|
-
// ? → . — single character wildcard
|
|
151
|
-
var T_TRAILING = '\x00TRAIL\x00';
|
|
152
|
-
var T_MIDDLE = '\x00MID\x00';
|
|
153
|
-
var T_DSTAR = '\x00DS\x00';
|
|
154
|
-
var T_STAR = '\x00ST\x00';
|
|
155
|
-
var T_QUEST = '\x00QU\x00';
|
|
156
|
-
var s = glob;
|
|
157
|
-
s = s.replace(/\/\*\*$/, T_TRAILING); // trailing /**
|
|
158
|
-
s = s.replace(/\/\*\*\//g, T_MIDDLE); // /**/ in the middle
|
|
159
|
-
s = s.replace(/\*\*/g, T_DSTAR); // bare ** (e.g. **.example.com)
|
|
160
|
-
s = s.replace(/\*/g, T_STAR); // single *
|
|
161
|
-
s = s.replace(/\?/g, T_QUEST); // ?
|
|
162
|
-
// Escape all remaining regex special characters.
|
|
163
|
-
s = s.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
164
|
-
// Expand tokens into their regex equivalents.
|
|
165
|
-
// Use split/join (not regex) to avoid the no-control-regex lint rule on the token strings.
|
|
166
|
-
s = s.split(T_TRAILING).join('(/.*)?'); // /** → optional /anything
|
|
167
|
-
s = s.split(T_MIDDLE).join('/(.*\\/)?'); // /**/ → /zero-or-more-segments/
|
|
168
|
-
s = s.split(T_DSTAR).join('.*'); // bare ** → .*
|
|
169
|
-
s = s.split(T_STAR).join('.*'); // * → .*
|
|
170
|
-
s = s.split(T_QUEST).join('.'); // ? → .
|
|
171
|
-
var regex = new RegExp("^".concat(s, "$"));
|
|
172
|
-
globRegexCache.set(glob, regex);
|
|
173
|
-
return regex;
|
|
174
|
-
};
|
|
175
21
|
export var validateUGCFilterRules = function (ugcFilterRules) {
|
|
176
22
|
// validate ugcFilterRules
|
|
177
23
|
if (!ugcFilterRules.every(function (rule) { return typeof rule.selector === 'string' && typeof rule.replacement === 'string'; })) {
|
|
@@ -183,7 +29,7 @@ export var validateUGCFilterRules = function (ugcFilterRules) {
|
|
|
183
29
|
}
|
|
184
30
|
};
|
|
185
31
|
export var getPageUrl = function (pageUrl, ugcFilterRules) {
|
|
186
|
-
var
|
|
32
|
+
var e_1, _a;
|
|
187
33
|
try {
|
|
188
34
|
// apply ugcFilterRules, order is important, first rule wins
|
|
189
35
|
for (var ugcFilterRules_1 = __values(ugcFilterRules), ugcFilterRules_1_1 = ugcFilterRules_1.next(); !ugcFilterRules_1_1.done; ugcFilterRules_1_1 = ugcFilterRules_1.next()) {
|
|
@@ -194,17 +40,17 @@ export var getPageUrl = function (pageUrl, ugcFilterRules) {
|
|
|
194
40
|
}
|
|
195
41
|
}
|
|
196
42
|
}
|
|
197
|
-
catch (
|
|
43
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
198
44
|
finally {
|
|
199
45
|
try {
|
|
200
46
|
if (ugcFilterRules_1_1 && !ugcFilterRules_1_1.done && (_a = ugcFilterRules_1.return)) _a.call(ugcFilterRules_1);
|
|
201
47
|
}
|
|
202
|
-
finally { if (
|
|
48
|
+
finally { if (e_1) throw e_1.error; }
|
|
203
49
|
}
|
|
204
50
|
return pageUrl;
|
|
205
51
|
};
|
|
206
52
|
export var getStorageSize = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
207
|
-
var globalScope, _a, usage, quota, usageDetails, totalStorageSize, percentOfQuota,
|
|
53
|
+
var globalScope, _a, usage, quota, usageDetails, totalStorageSize, percentOfQuota, e_2;
|
|
208
54
|
return __generator(this, function (_b) {
|
|
209
55
|
switch (_b.label) {
|
|
210
56
|
case 0:
|
|
@@ -219,7 +65,7 @@ export var getStorageSize = function () { return __awaiter(void 0, void 0, void
|
|
|
219
65
|
return [2 /*return*/, { totalStorageSize: totalStorageSize, percentOfQuota: percentOfQuota, usageDetails: JSON.stringify(usageDetails) }];
|
|
220
66
|
case 2: return [3 /*break*/, 4];
|
|
221
67
|
case 3:
|
|
222
|
-
|
|
68
|
+
e_2 = _b.sent();
|
|
223
69
|
return [3 /*break*/, 4];
|
|
224
70
|
case 4: return [2 /*return*/, { totalStorageSize: 0, percentOfQuota: 0, usageDetails: '' }];
|
|
225
71
|
}
|
package/lib/esm/helpers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"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","sourcesContent":["import { getGlobalScope } from '@amplitude/analytics-core';\nimport { DEFAULT_MASK_LEVEL, MaskLevel, PrivacyConfig, SessionReplayJoinedConfig, UGCFilterRule } from './config/types';\nimport { KB_SIZE, MASK_TEXT_CLASS, UNMASK_TEXT_CLASS } from './constants';\nimport { StorageData } from './typings/session-replay';\nimport { getInputType } from './utils/get-input-type';\nexport { getServerUrl } from './utils/server-url';\n\ntype ChromeStorageEstimate = {\n quota?: number;\n usage?: number;\n usageDetails?: { [key: string]: number };\n};\n\n/**\n * Light: Subset of inputs (sensitive types only — password, hidden, email, tel, cc-*)\n * Medium: All inputs (form fields), text is NOT masked\n * Conservative: All inputs and all texts\n */\nconst isMaskedForLevel = (elementType: 'input' | 'text', level: MaskLevel, element: HTMLElement | null): boolean => {\n switch (level) {\n case 'light': {\n if (elementType !== 'input') {\n return false;\n }\n\n const inputType = element ? getInputType(element) : '';\n /* istanbul ignore if */ // TODO(lew): For some reason it's impossible to test this.\n if (!inputType) {\n return false;\n }\n\n if (['password', 'hidden', 'email', 'tel'].includes(inputType)) {\n return true;\n }\n\n if ((element as HTMLInputElement).autocomplete.startsWith('cc-')) {\n return true;\n }\n\n return false;\n }\n case 'medium':\n return elementType === 'input';\n case 'conservative':\n return true;\n default:\n return isMaskedForLevel(elementType, DEFAULT_MASK_LEVEL, element);\n }\n};\n\n/**\n * Returns the effective mask level for a given URL by checking `urlMaskLevels`\n * (first match wins) and falling back to `defaultMaskLevel`.\n */\nexport const getEffectiveMaskLevel = (url: string | undefined, config: PrivacyConfig): MaskLevel => {\n if (url && config.urlMaskLevels) {\n for (const rule of config.urlMaskLevels) {\n if (globToRegex(rule.match).test(url)) {\n return rule.maskLevel;\n }\n }\n }\n return config.defaultMaskLevel ?? DEFAULT_MASK_LEVEL;\n};\n\n/**\n * Checks if the given element set to be masked by rrweb\n *\n * Priority is:\n * 1. [In code] Element/class based masking/unmasking <> [Config based] Selector based masking/unmasking\n * 2. Use app defaults\n */\nexport const isMasked = (\n elementType: 'input' | 'text',\n config: PrivacyConfig = { defaultMaskLevel: DEFAULT_MASK_LEVEL },\n element: HTMLElement | null,\n currentUrl?: string,\n): boolean => {\n if (element) {\n // Element or parent is explicitly instrumented in code to mask\n if (element.closest('.' + MASK_TEXT_CLASS)) {\n return true;\n }\n\n // Config has override for mask\n const shouldMask = (config.maskSelector ?? []).some((selector) => element.closest(selector));\n if (shouldMask) {\n return true;\n }\n\n // Code or config has override to unmask\n if (element.closest('.' + UNMASK_TEXT_CLASS)) {\n return false;\n }\n\n // Here we are probably sent an element, but we want to match if they have a\n // parent with an unmask selector.\n const shouldUnmask = (config.unmaskSelector ?? []).some((selector) => element.closest(selector));\n if (shouldUnmask) {\n return false;\n }\n }\n\n return isMaskedForLevel(elementType, getEffectiveMaskLevel(currentUrl, config), element);\n};\n\nexport const maskFn =\n (elementType: 'text' | 'input', config?: PrivacyConfig, getCurrentUrl?: () => string) =>\n (text: string, element: HTMLElement | null): string => {\n return isMasked(elementType, config, element, getCurrentUrl?.()) ? text.replace(/[^\\s]/g, '*') : text;\n };\n\nexport const maskAttributeFn = (config?: PrivacyConfig, getCurrentUrl?: () => string) => {\n return (key: string, value: string, element: HTMLElement): string => {\n // Never mask style — rrweb has a separate styleDiff path for attribute mutations\n // that reads directly from the DOM, bypassing maskAttributeFn.\n if (key === 'style') return value;\n\n // Short-circuit: only proceed if this attribute is in the allowlist.\n if (!(config?.maskAttributes ?? []).includes(key)) return value;\n\n // Use 'input' for form elements so that `medium` (which masks inputs but not text)\n // still masks attributes on inputs/selects/textareas. For non-form elements, use\n // 'text' so medium leaves them visible.\n const elementType = ['INPUT', 'SELECT', 'TEXTAREA'].includes(element.tagName) ? 'input' : 'text';\n return isMasked(elementType, config, element, getCurrentUrl?.()) ? value.replace(/[^\\s]/g, '*') : value;\n };\n};\n\nexport const getCurrentUrl = () => {\n const globalScope = getGlobalScope();\n return globalScope?.location ? globalScope.location.href : '';\n};\n\nexport const generateSessionReplayId = (sessionId: string | number, deviceId: string): string => {\n return `${deviceId}/${sessionId}`;\n};\n\nconst isValidGlobUrl = (globUrl: string): boolean => {\n if (typeof globUrl !== 'string' || globUrl.trim() === '') return false;\n const urlPattern = /^\\/|^https?:\\/\\/[^\\s]+$/;\n if (!urlPattern.test(globUrl)) return false;\n return true;\n};\n\nconst globRegexCache = new Map<string, RegExp>();\n\nconst globToRegex = (glob: string): RegExp => {\n const cached = globRegexCache.get(glob);\n if (cached) return cached;\n\n // Glob → regex conversion. Glob substitution must happen BEFORE regex escaping so that\n // the escaper does not corrupt the glob characters (e.g. turning `/**` into `/\\*\\*`).\n // Placeholder tokens use null bytes, which are illegal in HTTP URLs and therefore can\n // never collide with real pattern content.\n //\n // Substitution order (most-specific first):\n // trailing /** → (/.*)? — path with or without a trailing slash/subpath\n // middle /**/ → /(.*\\/)? — zero-or-more path segments (including zero)\n // bare ** → .* — graceful fallback for ** not adjacent to /\n // single * → .* — any characters (preserves existing behaviour)\n // ? → . — single character wildcard\n const T_TRAILING = '\\x00TRAIL\\x00';\n const T_MIDDLE = '\\x00MID\\x00';\n const T_DSTAR = '\\x00DS\\x00';\n const T_STAR = '\\x00ST\\x00';\n const T_QUEST = '\\x00QU\\x00';\n\n let s = glob;\n s = s.replace(/\\/\\*\\*$/, T_TRAILING); // trailing /**\n s = s.replace(/\\/\\*\\*\\//g, T_MIDDLE); // /**/ in the middle\n s = s.replace(/\\*\\*/g, T_DSTAR); // bare ** (e.g. **.example.com)\n s = s.replace(/\\*/g, T_STAR); // single *\n s = s.replace(/\\?/g, T_QUEST); // ?\n\n // Escape all remaining regex special characters.\n s = s.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n\n // Expand tokens into their regex equivalents.\n // Use split/join (not regex) to avoid the no-control-regex lint rule on the token strings.\n s = s.split(T_TRAILING).join('(/.*)?'); // /** → optional /anything\n s = s.split(T_MIDDLE).join('/(.*\\\\/)?'); // /**/ → /zero-or-more-segments/\n s = s.split(T_DSTAR).join('.*'); // bare ** → .*\n s = s.split(T_STAR).join('.*'); // * → .*\n s = s.split(T_QUEST).join('.'); // ? → .\n\n const regex = new RegExp(`^${s}$`);\n globRegexCache.set(glob, regex);\n return regex;\n};\n\nexport const validateUGCFilterRules = (ugcFilterRules: UGCFilterRule[]) => {\n // validate ugcFilterRules\n if (!ugcFilterRules.every((rule) => typeof rule.selector === 'string' && typeof rule.replacement === 'string')) {\n throw new Error('ugcFilterRules must be an array of objects with selector and replacement properties');\n }\n\n // validate ugcFilterRules are valid globs\n if (!ugcFilterRules.every((rule) => isValidGlobUrl(rule.selector))) {\n throw new Error('ugcFilterRules must be an array of objects with valid globs');\n }\n};\n\nexport const getPageUrl = (pageUrl: string, ugcFilterRules: UGCFilterRule[]) => {\n // apply ugcFilterRules, order is important, first rule wins\n for (const rule of ugcFilterRules) {\n const regex = globToRegex(rule.selector);\n\n if (regex.test(pageUrl)) {\n return pageUrl.replace(regex, rule.replacement);\n }\n }\n\n return pageUrl;\n};\n\nexport const getStorageSize = async (): Promise<StorageData> => {\n try {\n const globalScope = getGlobalScope();\n if (globalScope) {\n const { usage, quota, usageDetails }: ChromeStorageEstimate = await globalScope.navigator.storage.estimate();\n const totalStorageSize = usage ? Math.round(usage / KB_SIZE) : 0;\n const percentOfQuota = usage && quota ? Math.round((usage / quota + Number.EPSILON) * 1000) / 1000 : 0;\n return { totalStorageSize, percentOfQuota, usageDetails: JSON.stringify(usageDetails) };\n }\n } catch (e) {\n // swallow\n }\n return { totalStorageSize: 0, percentOfQuota: 0, usageDetails: '' };\n};\n\nexport const getDebugConfig = (config: SessionReplayJoinedConfig): SessionReplayJoinedConfig => {\n const debugConfig = {\n ...config,\n };\n const { apiKey } = debugConfig;\n debugConfig.apiKey = `****${apiKey.substring(apiKey.length - 4)}`;\n return debugConfig;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"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","sourcesContent":["import { getGlobalScope } from '@amplitude/analytics-core';\nimport { SessionReplayJoinedConfig, UGCFilterRule } from './config/types';\nimport { KB_SIZE } from './constants';\nimport { StorageData } from './typings/session-replay';\nimport { globToRegex } from '@amplitude/session-replay-dom-privacy';\nexport { getServerUrl } from './utils/server-url';\n\ntype ChromeStorageEstimate = {\n quota?: number;\n usage?: number;\n usageDetails?: { [key: string]: number };\n};\n\nexport const getCurrentUrl = () => {\n const globalScope = getGlobalScope();\n return globalScope?.location ? globalScope.location.href : '';\n};\n\nexport const generateSessionReplayId = (sessionId: string | number, deviceId: string): string => {\n return `${deviceId}/${sessionId}`;\n};\n\nconst isValidGlobUrl = (globUrl: string): boolean => {\n if (typeof globUrl !== 'string' || globUrl.trim() === '') return false;\n const urlPattern = /^\\/|^https?:\\/\\/[^\\s]+$/;\n if (!urlPattern.test(globUrl)) return false;\n return true;\n};\n\nexport const validateUGCFilterRules = (ugcFilterRules: UGCFilterRule[]) => {\n // validate ugcFilterRules\n if (!ugcFilterRules.every((rule) => typeof rule.selector === 'string' && typeof rule.replacement === 'string')) {\n throw new Error('ugcFilterRules must be an array of objects with selector and replacement properties');\n }\n\n // validate ugcFilterRules are valid globs\n if (!ugcFilterRules.every((rule) => isValidGlobUrl(rule.selector))) {\n throw new Error('ugcFilterRules must be an array of objects with valid globs');\n }\n};\n\nexport const getPageUrl = (pageUrl: string, ugcFilterRules: UGCFilterRule[]) => {\n // apply ugcFilterRules, order is important, first rule wins\n for (const rule of ugcFilterRules) {\n const regex = globToRegex(rule.selector);\n\n if (regex.test(pageUrl)) {\n return pageUrl.replace(regex, rule.replacement);\n }\n }\n\n return pageUrl;\n};\n\nexport const getStorageSize = async (): Promise<StorageData> => {\n try {\n const globalScope = getGlobalScope();\n if (globalScope) {\n const { usage, quota, usageDetails }: ChromeStorageEstimate = await globalScope.navigator.storage.estimate();\n const totalStorageSize = usage ? Math.round(usage / KB_SIZE) : 0;\n const percentOfQuota = usage && quota ? Math.round((usage / quota + Number.EPSILON) * 1000) / 1000 : 0;\n return { totalStorageSize, percentOfQuota, usageDetails: JSON.stringify(usageDetails) };\n }\n } catch (e) {\n // swallow\n }\n return { totalStorageSize: 0, percentOfQuota: 0, usageDetails: '' };\n};\n\nexport const getDebugConfig = (config: SessionReplayJoinedConfig): SessionReplayJoinedConfig => {\n const debugConfig = {\n ...config,\n };\n const { apiKey } = debugConfig;\n debugConfig.apiKey = `****${apiKey.substring(apiKey.length - 4)}`;\n return debugConfig;\n};\n"]}
|