@amplitude/session-replay-browser
Advanced tools
Comparing version 1.8.0 to 1.9.0
@@ -32,6 +32,6 @@ var _this = this; | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
var config, remoteConfig, samplingConfig_1, privacyConfig, err_1, knownError, samplingConfig, remotePrivacyConfig, localPrivacyConfig, joinedPrivacyConfig, privacyConfigSelectorMap, selectorMap, _d, _e, _f, selector, selectorType; | ||
var e_1, _g; | ||
return tslib_1.__generator(this, function (_h) { | ||
switch (_h.label) { | ||
var config, remoteConfig, samplingConfig_1, privacyConfig, _d, err_1, knownError, samplingConfig, remotePrivacyConfig, localPrivacyConfig, joinedPrivacyConfig, privacyConfigSelectorMap, selectorMap, _e, _f, _g, selector, selectorType; | ||
var e_1, _h; | ||
return tslib_1.__generator(this, function (_j) { | ||
switch (_j.label) { | ||
case 0: | ||
@@ -44,5 +44,5 @@ config = tslib_1.__assign({}, this.localConfig); | ||
config.captureEnabled = true; | ||
_h.label = 1; | ||
_j.label = 1; | ||
case 1: | ||
_h.trys.push([1, 4, , 5]); | ||
_j.trys.push([1, 5, , 6]); | ||
if (!this.remoteConfigFetch) { | ||
@@ -53,6 +53,12 @@ return [2 /*return*/, config]; | ||
case 2: | ||
samplingConfig_1 = _h.sent(); | ||
samplingConfig_1 = _j.sent(); | ||
return [4 /*yield*/, this.remoteConfigFetch.getRemoteConfig('sessionReplay', 'sr_privacy_config', sessionId)]; | ||
case 3: | ||
privacyConfig = _h.sent(); | ||
privacyConfig = _j.sent(); | ||
// This is intentionally forced to only be set through the remote config. | ||
_d = config; | ||
return [4 /*yield*/, this.remoteConfigFetch.getRemoteConfig('sessionReplay', 'sr_interaction_config', sessionId)]; | ||
case 4: | ||
// This is intentionally forced to only be set through the remote config. | ||
_d.interactionConfig = _j.sent(); | ||
if (samplingConfig_1 || privacyConfig) { | ||
@@ -67,10 +73,10 @@ remoteConfig = {}; | ||
} | ||
return [3 /*break*/, 5]; | ||
case 4: | ||
err_1 = _h.sent(); | ||
return [3 /*break*/, 6]; | ||
case 5: | ||
err_1 = _j.sent(); | ||
knownError = err_1; | ||
this.localConfig.loggerProvider.warn(knownError.message); | ||
config.captureEnabled = true; | ||
return [3 /*break*/, 5]; | ||
case 5: | ||
return [3 /*break*/, 6]; | ||
case 6: | ||
if (!remoteConfig) { | ||
@@ -169,4 +175,4 @@ return [2 /*return*/, config]; | ||
try { | ||
for (_d = tslib_1.__values(Object.entries(selectorMap)), _e = _d.next(); !_e.done; _e = _d.next()) { | ||
_f = tslib_1.__read(_e.value, 2), selector = _f[0], selectorType = _f[1]; | ||
for (_e = tslib_1.__values(Object.entries(selectorMap)), _f = _e.next(); !_f.done; _f = _e.next()) { | ||
_g = tslib_1.__read(_f.value, 2), selector = _g[0], selectorType = _g[1]; | ||
if (selectorType === 'mask') { | ||
@@ -186,3 +192,3 @@ joinedPrivacyConfig.maskSelector.push(selector); | ||
try { | ||
if (_e && !_e.done && (_g = _d.return)) _g.call(_d); | ||
if (_f && !_f.done && (_h = _e.return)) _h.call(_e); | ||
} | ||
@@ -189,0 +195,0 @@ finally { if (e_1) throw e_1.error; } |
@@ -5,3 +5,3 @@ import { FetchTransport } from '@amplitude/analytics-client-common'; | ||
import { SessionReplayOptions } from '../typings/session-replay'; | ||
import { SessionReplayLocalConfig as ISessionReplayLocalConfig, PrivacyConfig } from './types'; | ||
import { SessionReplayLocalConfig as ISessionReplayLocalConfig, InteractionConfig, PrivacyConfig } from './types'; | ||
export declare const getDefaultConfig: () => { | ||
@@ -17,2 +17,3 @@ flushMaxRetries: number; | ||
privacyConfig?: PrivacyConfig; | ||
interactionConfig?: InteractionConfig; | ||
debugMode?: boolean; | ||
@@ -19,0 +20,0 @@ configEndpointUrl?: string; |
@@ -6,5 +6,10 @@ import { Config, LogLevel, Logger } from '@amplitude/analytics-types'; | ||
} | ||
export interface InteractionConfig { | ||
trackEveryNms?: number; | ||
enabled: boolean; | ||
} | ||
export type SessionReplayRemoteConfig = { | ||
sr_sampling_config?: SamplingConfig; | ||
sr_privacy_config?: PrivacyConfig; | ||
sr_interaction_config?: InteractionConfig; | ||
}; | ||
@@ -37,2 +42,3 @@ export interface SessionReplayRemoteConfigAPIResponse { | ||
captureEnabled?: boolean; | ||
interactionConfig?: InteractionConfig; | ||
} | ||
@@ -39,0 +45,0 @@ export interface SessionReplayRemoteConfigFetch { |
@@ -17,2 +17,4 @@ import { ServerZone } from '@amplitude/analytics-types'; | ||
export declare const MAX_EVENT_LIST_SIZE_IN_BYTES: number; | ||
export declare const INTERACTION_MIN_INTERVAL = 30000; | ||
export declare const INTERACTION_MAX_INTERVAL = 60000; | ||
export declare const MIN_INTERVAL = 500; | ||
@@ -19,0 +21,0 @@ export declare const MAX_INTERVAL: number; |
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.MAX_IDB_STORAGE_LENGTH = exports.MAX_INTERVAL = exports.MIN_INTERVAL = exports.MAX_EVENT_LIST_SIZE_IN_BYTES = exports.STORAGE_PREFIX = exports.SESSION_REPLAY_STAGING_URL = exports.SESSION_REPLAY_EU_URL = exports.SESSION_REPLAY_SERVER_URL = exports.UNMASK_TEXT_CLASS = exports.MASK_TEXT_CLASS = exports.BLOCK_CLASS = exports.SESSION_REPLAY_DEBUG_PROPERTY = exports.DEFAULT_SERVER_ZONE = exports.DEFAULT_SAMPLE_RATE = exports.DEFAULT_SESSION_END_EVENT = exports.DEFAULT_SESSION_START_EVENT = exports.DEFAULT_SESSION_REPLAY_PROPERTY = exports.DEFAULT_EVENT_PROPERTY_PREFIX = void 0; | ||
exports.MAX_IDB_STORAGE_LENGTH = exports.MAX_INTERVAL = exports.MIN_INTERVAL = exports.INTERACTION_MAX_INTERVAL = exports.INTERACTION_MIN_INTERVAL = exports.MAX_EVENT_LIST_SIZE_IN_BYTES = exports.STORAGE_PREFIX = exports.SESSION_REPLAY_STAGING_URL = exports.SESSION_REPLAY_EU_URL = exports.SESSION_REPLAY_SERVER_URL = exports.UNMASK_TEXT_CLASS = exports.MASK_TEXT_CLASS = exports.BLOCK_CLASS = exports.SESSION_REPLAY_DEBUG_PROPERTY = exports.DEFAULT_SERVER_ZONE = exports.DEFAULT_SAMPLE_RATE = exports.DEFAULT_SESSION_END_EVENT = exports.DEFAULT_SESSION_START_EVENT = exports.DEFAULT_SESSION_REPLAY_PROPERTY = exports.DEFAULT_EVENT_PROPERTY_PREFIX = void 0; | ||
var analytics_core_1 = require("@amplitude/analytics-core"); | ||
@@ -20,2 +20,4 @@ var analytics_types_1 = require("@amplitude/analytics-types"); | ||
exports.MAX_EVENT_LIST_SIZE_IN_BYTES = 1 * 1000000; // 1 MB | ||
exports.INTERACTION_MIN_INTERVAL = 30000; // 30 seconds | ||
exports.INTERACTION_MAX_INTERVAL = 60000; // 1 minute | ||
exports.MIN_INTERVAL = 500; // 500 ms | ||
@@ -22,0 +24,0 @@ exports.MAX_INTERVAL = 10 * 1000; // 10 seconds |
import { Logger as ILogger } from '@amplitude/analytics-types'; | ||
import { DBSchema, IDBPDatabase } from 'idb'; | ||
import { SessionReplayEventsIDBStore as AmplitudeSessionReplayEventsIDBStore, Events, SendingSequencesIDBInput } from '../typings/session-replay'; | ||
import { SessionReplayEventsIDBStore as AmplitudeSessionReplayEventsIDBStore, EventType, Events, SendingSequencesIDBInput } from '../typings/session-replay'; | ||
export declare const currentSequenceKey = "sessionCurrentSequence"; | ||
@@ -37,7 +37,11 @@ export declare const sequencesToSendKey = "sequencesToSend"; | ||
timeAtLastSplit: number | null; | ||
constructor({ loggerProvider, apiKey }: { | ||
private readonly minInterval; | ||
private readonly maxInterval; | ||
constructor({ loggerProvider, apiKey, minInterval, maxInterval, }: { | ||
loggerProvider: ILogger; | ||
apiKey: string; | ||
minInterval?: number; | ||
maxInterval?: number; | ||
}); | ||
initialize(sessionId?: number): Promise<void>; | ||
initialize(type: EventType, sessionId?: number): Promise<void>; | ||
/** | ||
@@ -65,7 +69,10 @@ * Determines whether to send the events list to the backend and start a new | ||
} | ||
export declare const createEventsIDBStore: ({ loggerProvider, apiKey, sessionId, }: { | ||
export declare const createEventsIDBStore: ({ loggerProvider, apiKey, sessionId, type, minInterval, maxInterval, }: { | ||
loggerProvider: ILogger; | ||
apiKey: string; | ||
type: EventType; | ||
minInterval?: number | undefined; | ||
maxInterval?: number | undefined; | ||
sessionId?: number | undefined; | ||
}) => Promise<SessionReplayEventsIDBStore>; | ||
//# sourceMappingURL=events-idb-store.d.ts.map |
@@ -93,7 +93,6 @@ var _this = this; | ||
function SessionReplayEventsIDBStore(_a) { | ||
var loggerProvider = _a.loggerProvider, apiKey = _a.apiKey; | ||
var loggerProvider = _a.loggerProvider, apiKey = _a.apiKey, minInterval = _a.minInterval, maxInterval = _a.maxInterval; | ||
var _this = this; | ||
this.storageKey = ''; | ||
this.maxPersistedEventsSize = constants_1.MAX_EVENT_LIST_SIZE_IN_BYTES; | ||
this.interval = constants_1.MIN_INTERVAL; | ||
this.timeAtLastSplit = null; | ||
@@ -112,4 +111,7 @@ /** | ||
} | ||
if (_this.timeAtLastSplit !== null && Date.now() - _this.timeAtLastSplit > _this.interval && events.length) { | ||
_this.interval = Math.min(constants_1.MAX_INTERVAL, _this.interval + constants_1.MIN_INTERVAL); | ||
if (_this.timeAtLastSplit !== null && | ||
_this.interval && | ||
Date.now() - _this.timeAtLastSplit > _this.interval && | ||
events.length) { | ||
_this.interval = Math.min(_this.maxInterval, _this.interval + _this.minInterval); | ||
_this.timeAtLastSplit = Date.now(); | ||
@@ -178,3 +180,8 @@ return true; | ||
case 0: | ||
_b.trys.push([0, 10, , 11]); | ||
if (this.interval === 0) { | ||
this.interval = this.minInterval; | ||
} | ||
_b.label = 1; | ||
case 1: | ||
_b.trys.push([1, 11, , 12]); | ||
tx = (_a = this.db) === null || _a === void 0 ? void 0 : _a.transaction(exports.currentSequenceKey, 'readwrite'); | ||
@@ -185,27 +192,27 @@ if (!tx) { | ||
return [4 /*yield*/, tx.store.get(sessionId)]; | ||
case 1: | ||
case 2: | ||
sequenceEvents = _b.sent(); | ||
if (!!sequenceEvents) return [3 /*break*/, 3]; | ||
if (!!sequenceEvents) return [3 /*break*/, 4]; | ||
return [4 /*yield*/, tx.store.put({ sessionId: sessionId, events: [event] })]; | ||
case 2: | ||
case 3: | ||
_b.sent(); | ||
return [2 /*return*/]; | ||
case 3: | ||
case 4: | ||
eventsToSend = void 0; | ||
if (!this.shouldSplitEventsList(sequenceEvents.events, event)) return [3 /*break*/, 5]; | ||
if (!this.shouldSplitEventsList(sequenceEvents.events, event)) return [3 /*break*/, 6]; | ||
eventsToSend = sequenceEvents.events; | ||
// set store to empty array | ||
return [4 /*yield*/, tx.store.put({ sessionId: sessionId, events: [event] })]; | ||
case 4: | ||
case 5: | ||
// set store to empty array | ||
_b.sent(); | ||
return [3 /*break*/, 7]; | ||
case 5: | ||
return [3 /*break*/, 8]; | ||
case 6: | ||
updatedEvents = sequenceEvents.events.concat(event); | ||
return [4 /*yield*/, tx.store.put({ sessionId: sessionId, events: updatedEvents })]; | ||
case 6: | ||
case 7: | ||
_b.sent(); | ||
_b.label = 7; | ||
case 7: return [4 /*yield*/, tx.done]; | ||
case 8: | ||
_b.label = 8; | ||
case 8: return [4 /*yield*/, tx.done]; | ||
case 9: | ||
_b.sent(); | ||
@@ -216,3 +223,3 @@ if (!eventsToSend) { | ||
return [4 /*yield*/, this.storeSendingEvents(sessionId, eventsToSend)]; | ||
case 9: | ||
case 10: | ||
sequenceId = _b.sent(); | ||
@@ -227,7 +234,7 @@ if (!sequenceId) { | ||
}]; | ||
case 10: | ||
case 11: | ||
e_3 = _b.sent(); | ||
this.loggerProvider.warn("".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_3)); | ||
return [3 /*break*/, 11]; | ||
case 11: return [2 /*return*/, undefined]; | ||
return [3 /*break*/, 12]; | ||
case 12: return [2 /*return*/, undefined]; | ||
} | ||
@@ -384,10 +391,14 @@ }); | ||
this.apiKey = apiKey; | ||
this.maxInterval = maxInterval !== null && maxInterval !== void 0 ? maxInterval : constants_1.MAX_INTERVAL; | ||
this.minInterval = minInterval !== null && minInterval !== void 0 ? minInterval : constants_1.MIN_INTERVAL; | ||
this.interval = 0; | ||
} | ||
SessionReplayEventsIDBStore.prototype.initialize = function (sessionId) { | ||
SessionReplayEventsIDBStore.prototype.initialize = function (type, sessionId) { | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
var dbName, _a; | ||
var dbSuffix, dbName, _a; | ||
return tslib_1.__generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
dbName = "".concat(this.apiKey.substring(0, 10), "_amp_session_replay_events"); | ||
dbSuffix = type === 'replay' ? '' : "_".concat(type); | ||
dbName = "".concat(this.apiKey.substring(0, 10), "_amp_session_replay_events").concat(dbSuffix); | ||
_a = this; | ||
@@ -410,3 +421,3 @@ return [4 /*yield*/, (0, exports.createStore)(dbName)]; | ||
var createEventsIDBStore = function (_a) { | ||
var loggerProvider = _a.loggerProvider, apiKey = _a.apiKey, sessionId = _a.sessionId; | ||
var loggerProvider = _a.loggerProvider, apiKey = _a.apiKey, sessionId = _a.sessionId, type = _a.type, minInterval = _a.minInterval, maxInterval = _a.maxInterval; | ||
return tslib_1.__awaiter(_this, void 0, void 0, function () { | ||
@@ -417,4 +428,4 @@ var eventsIDBStore; | ||
case 0: | ||
eventsIDBStore = new SessionReplayEventsIDBStore({ loggerProvider: loggerProvider, apiKey: apiKey }); | ||
return [4 /*yield*/, eventsIDBStore.initialize(sessionId)]; | ||
eventsIDBStore = new SessionReplayEventsIDBStore({ loggerProvider: loggerProvider, apiKey: apiKey, minInterval: minInterval, maxInterval: maxInterval }); | ||
return [4 /*yield*/, eventsIDBStore.initialize(type, sessionId)]; | ||
case 1: | ||
@@ -421,0 +432,0 @@ _b.sent(); |
@@ -1,7 +0,12 @@ | ||
import { SessionReplayEventsManager as AmplitudeSessionReplayEventsManager } from '../typings/session-replay'; | ||
import { SessionReplayEventsManager as AmplitudeSessionReplayEventsManager, EventType } from '../typings/session-replay'; | ||
import { SessionReplayJoinedConfig } from '../config/types'; | ||
export declare const createEventsManager: ({ config, sessionId, }: { | ||
import { PayloadBatcher } from '../track-destination'; | ||
export declare const createEventsManager: <Type extends EventType>({ config, sessionId, minInterval, maxInterval, type, payloadBatcher, }: { | ||
config: SessionReplayJoinedConfig; | ||
type: Type; | ||
minInterval?: number | undefined; | ||
maxInterval?: number | undefined; | ||
sessionId?: number | undefined; | ||
}) => Promise<AmplitudeSessionReplayEventsManager>; | ||
payloadBatcher?: PayloadBatcher | undefined; | ||
}) => Promise<AmplitudeSessionReplayEventsManager<Type, string>>; | ||
//# sourceMappingURL=events-manager.d.ts.map |
@@ -5,6 +5,6 @@ var _this = this; | ||
var tslib_1 = require("tslib"); | ||
var events_idb_store_1 = require("./events-idb-store"); | ||
var track_destination_1 = require("../track-destination"); | ||
var events_idb_store_1 = require("./events-idb-store"); | ||
var createEventsManager = function (_a) { | ||
var config = _a.config, sessionId = _a.sessionId; | ||
var config = _a.config, sessionId = _a.sessionId, minInterval = _a.minInterval, maxInterval = _a.maxInterval, type = _a.type, payloadBatcher = _a.payloadBatcher; | ||
return tslib_1.__awaiter(_this, void 0, void 0, function () { | ||
@@ -15,6 +15,3 @@ function flush(useRetry) { | ||
return tslib_1.__generator(this, function (_a) { | ||
if (trackDestination) { | ||
return [2 /*return*/, trackDestination.flush(useRetry)]; | ||
} | ||
return [2 /*return*/]; | ||
return [2 /*return*/, trackDestination.flush(useRetry)]; | ||
}); | ||
@@ -28,3 +25,3 @@ }); | ||
case 0: | ||
trackDestination = new track_destination_1.SessionReplayTrackDestination({ loggerProvider: config.loggerProvider }); | ||
trackDestination = new track_destination_1.SessionReplayTrackDestination({ loggerProvider: config.loggerProvider, payloadBatcher: payloadBatcher }); | ||
return [4 /*yield*/, (0, events_idb_store_1.createEventsIDBStore)({ | ||
@@ -34,2 +31,5 @@ loggerProvider: config.loggerProvider, | ||
sessionId: sessionId, | ||
minInterval: minInterval, | ||
maxInterval: maxInterval, | ||
type: type, | ||
})]; | ||
@@ -49,2 +49,3 @@ case 1: | ||
serverZone: config.serverZone, | ||
type: type, | ||
onComplete: eventsIDBStore.cleanUpSessionEventsStore.bind(eventsIDBStore), | ||
@@ -97,3 +98,3 @@ }); | ||
eventsIDBStore | ||
.addEventToCurrentSequence(sessionId, event) | ||
.addEventToCurrentSequence(sessionId, event.data) | ||
.then(function (sequenceToSend) { | ||
@@ -100,0 +101,0 @@ return (sequenceToSend && |
@@ -10,3 +10,3 @@ import { Logger as ILogger } from '@amplitude/analytics-types'; | ||
identifiers: ISessionIdentifiers | undefined; | ||
eventsManager: AmplitudeSessionReplayEventsManager | undefined; | ||
eventsManager?: AmplitudeSessionReplayEventsManager<'replay' | 'interaction', string>; | ||
loggerProvider: ILogger; | ||
@@ -36,5 +36,5 @@ recordCancelCallback: ReturnType<typeof record> | null; | ||
getSessionId(): number | undefined; | ||
flush(useRetry?: boolean): Promise<void>; | ||
flush(useRetry?: boolean): Promise<void | undefined>; | ||
shutdown(): void; | ||
} | ||
//# sourceMappingURL=session-replay.d.ts.map |
@@ -12,2 +12,4 @@ Object.defineProperty(exports, "__esModule", { value: true }); | ||
var identifiers_1 = require("./identifiers"); | ||
var click_1 = require("./hooks/click"); | ||
var multi_manager_1 = require("./events/multi-manager"); | ||
var SessionReplay = /** @class */ (function () { | ||
@@ -72,26 +74,44 @@ function SessionReplay() { | ||
SessionReplay.prototype._init = function (apiKey, options) { | ||
var _a, _b; | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
var _a, _b, _c, globalScope; | ||
return tslib_1.__generator(this, function (_d) { | ||
switch (_d.label) { | ||
var _c, _d, managers, rrwebEventManager, interactionEventManager, globalScope; | ||
return tslib_1.__generator(this, function (_e) { | ||
switch (_e.label) { | ||
case 0: | ||
this.loggerProvider = options.loggerProvider || new analytics_core_1.Logger(); | ||
this.identifiers = new identifiers_1.SessionIdentifiers({ sessionId: options.sessionId, deviceId: options.deviceId }); | ||
_a = this; | ||
_c = this; | ||
return [4 /*yield*/, (0, joined_config_1.createSessionReplayJoinedConfigGenerator)(apiKey, options)]; | ||
case 1: | ||
_a.joinedConfigGenerator = _d.sent(); | ||
_b = this; | ||
_c.joinedConfigGenerator = _e.sent(); | ||
_d = this; | ||
return [4 /*yield*/, this.joinedConfigGenerator.generateJoinedConfig(this.identifiers.sessionId)]; | ||
case 2: | ||
_b.config = _d.sent(); | ||
_d.config = _e.sent(); | ||
this.loggerProvider.debug(JSON.stringify({ name: 'session replay joined privacy config', privacyConfig: this.config.privacyConfig }, null, 2)); | ||
this.removeInvalidSelectors(); | ||
_c = this; | ||
managers = []; | ||
return [4 /*yield*/, (0, events_manager_1.createEventsManager)({ | ||
config: this.config, | ||
sessionId: this.identifiers.sessionId, | ||
type: 'replay', | ||
})]; | ||
case 3: | ||
_c.eventsManager = _d.sent(); | ||
rrwebEventManager = _e.sent(); | ||
managers.push({ name: 'replay', manager: rrwebEventManager }); | ||
if (!((_a = this.config.interactionConfig) === null || _a === void 0 ? void 0 : _a.enabled)) return [3 /*break*/, 5]; | ||
return [4 /*yield*/, (0, events_manager_1.createEventsManager)({ | ||
config: this.config, | ||
sessionId: this.identifiers.sessionId, | ||
type: 'interaction', | ||
minInterval: (_b = this.config.interactionConfig.trackEveryNms) !== null && _b !== void 0 ? _b : constants_1.INTERACTION_MIN_INTERVAL, | ||
maxInterval: constants_1.INTERACTION_MAX_INTERVAL, | ||
payloadBatcher: click_1.clickBatcher, | ||
})]; | ||
case 4: | ||
interactionEventManager = _e.sent(); | ||
managers.push({ name: 'interaction', manager: interactionEventManager }); | ||
_e.label = 5; | ||
case 5: | ||
this.eventsManager = new (multi_manager_1.MultiEventManager.bind.apply(multi_manager_1.MultiEventManager, tslib_1.__spreadArray([void 0], tslib_1.__read(managers), false)))(); | ||
this.loggerProvider.log('Installing @amplitude/session-replay-browser.'); | ||
@@ -196,7 +216,3 @@ globalScope = (0, analytics_client_common_1.getGlobalScope)(); | ||
} | ||
this.eventsManager && | ||
shouldSendStoredEvents && | ||
this.eventsManager.sendStoredEvents({ | ||
deviceId: deviceId, | ||
}); | ||
this.eventsManager && shouldSendStoredEvents && this.eventsManager.sendStoredEvents({ deviceId: deviceId }); | ||
this.recordEvents(); | ||
@@ -279,6 +295,16 @@ }; | ||
var deviceId = _this.getDeviceId(); | ||
deviceId && _this.eventsManager && _this.eventsManager.addEvent({ event: eventString, sessionId: sessionId, deviceId: deviceId }); | ||
_this.eventsManager && | ||
deviceId && | ||
_this.eventsManager.addEvent({ event: { type: 'replay', data: eventString }, sessionId: sessionId, deviceId: deviceId }); | ||
}, | ||
packFn: rrweb_1.pack, | ||
inlineStylesheet: this.config.shouldInlineStylesheet, | ||
hooks: { | ||
mouseInteraction: this.eventsManager && | ||
(0, click_1.clickHook)({ | ||
eventsManager: this.eventsManager, | ||
sessionId: sessionId, | ||
deviceIdFn: this.getDeviceId.bind(this), | ||
}), | ||
}, | ||
maskAllInputs: true, | ||
@@ -326,9 +352,7 @@ maskTextClass: constants_1.MASK_TEXT_CLASS, | ||
SessionReplay.prototype.flush = function (useRetry) { | ||
var _a; | ||
if (useRetry === void 0) { useRetry = false; } | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
return tslib_1.__generator(this, function (_a) { | ||
if (this.eventsManager) { | ||
return [2 /*return*/, this.eventsManager.flush(useRetry)]; | ||
} | ||
return [2 /*return*/]; | ||
return tslib_1.__generator(this, function (_b) { | ||
return [2 /*return*/, (_a = this.eventsManager) === null || _a === void 0 ? void 0 : _a.flush(useRetry)]; | ||
}); | ||
@@ -335,0 +359,0 @@ }); |
import { Logger as ILogger, ServerZone } from '@amplitude/analytics-types'; | ||
import { SessionReplayTrackDestination as AmplitudeSessionReplayTrackDestination, SessionReplayDestination, SessionReplayDestinationContext } from './typings/session-replay'; | ||
export type PayloadBatcher = ({ version, events }: { | ||
version: number; | ||
events: string[]; | ||
}) => { | ||
version: number; | ||
events: unknown[]; | ||
}; | ||
export declare class SessionReplayTrackDestination implements AmplitudeSessionReplayTrackDestination { | ||
@@ -8,5 +15,7 @@ loggerProvider: ILogger; | ||
private scheduled; | ||
payloadBatcher: PayloadBatcher; | ||
queue: SessionReplayDestinationContext[]; | ||
constructor({ loggerProvider }: { | ||
constructor({ loggerProvider, payloadBatcher }: { | ||
loggerProvider: ILogger; | ||
payloadBatcher?: PayloadBatcher; | ||
}); | ||
@@ -13,0 +22,0 @@ sendEventsList(destinationData: SessionReplayDestination): void; |
@@ -12,3 +12,3 @@ Object.defineProperty(exports, "__esModule", { value: true }); | ||
function SessionReplayTrackDestination(_a) { | ||
var loggerProvider = _a.loggerProvider; | ||
var loggerProvider = _a.loggerProvider, payloadBatcher = _a.payloadBatcher; | ||
this.storageKey = ''; | ||
@@ -19,2 +19,3 @@ this.retryTimeout = 1000; | ||
this.loggerProvider = loggerProvider; | ||
this.payloadBatcher = payloadBatcher ? payloadBatcher : function (payload) { return payload; }; | ||
} | ||
@@ -120,7 +121,12 @@ SessionReplayTrackDestination.prototype.sendEventsList = function (destinationData) { | ||
seq_number: "".concat(context.sequenceId), | ||
type: "".concat(context.type), | ||
}); | ||
payload = { | ||
payload = this.payloadBatcher({ | ||
version: 1, | ||
events: context.events, | ||
}; | ||
}); | ||
if (payload.events.length === 0) { | ||
this.completeRequest({ context: context }); | ||
return [2 /*return*/]; | ||
} | ||
_a.label = 1; | ||
@@ -127,0 +133,0 @@ case 1: |
import { AmplitudeReturn, ServerZone } from '@amplitude/analytics-types'; | ||
import { SessionReplayLocalConfig } from '../config/types'; | ||
export type Events = string[]; | ||
export type EventType = 'replay' | 'interaction'; | ||
export interface SessionReplayDestination { | ||
events: Events; | ||
sequenceId: number; | ||
type: EventType; | ||
sessionId: number; | ||
@@ -26,5 +28,12 @@ flushMaxRetries?: number; | ||
export interface SessionReplayEventsIDBStore { | ||
initialize(): Promise<void>; | ||
initialize(type: EventType): Promise<void>; | ||
getSequencesToSend(): Promise<SendingSequencesIDBReturn[] | undefined>; | ||
/** | ||
* Moves current sequence of events to long term storage and resets short term storage. | ||
*/ | ||
storeCurrentSequence(sessionId: number): Promise<SendingSequencesIDBInput | undefined>; | ||
/** | ||
* Adds events to the current IDB sequence. Returns events that should be | ||
* sent to the track destination right away if should split events is true. | ||
*/ | ||
addEventToCurrentSequence(sessionId: number, event: string): Promise<SendingSequencesIDBReturn | undefined>; | ||
@@ -51,14 +60,36 @@ storeSendingEvents(sessionId: number, events: Events): Promise<number | undefined>; | ||
export interface SessionReplayTrackDestination { | ||
/** | ||
* Enqueues events to be sent. | ||
*/ | ||
sendEventsList: (destinationData: SessionReplayDestination) => void; | ||
/** | ||
* Immediately sends queued events. | ||
*/ | ||
flush: (useRetry: boolean) => Promise<void>; | ||
} | ||
export interface SessionReplayEventsManager { | ||
export type EventsManagerWithType<EventType, EventDataType> = { | ||
name: EventType; | ||
manager: SessionReplayEventsManager<EventType, EventDataType>; | ||
}; | ||
export interface SessionReplayEventsManager<Type, Event> { | ||
/** | ||
* For each sequence stored in the long term indexed DB send immediately to the track destination. | ||
*/ | ||
sendStoredEvents({ deviceId }: { | ||
deviceId: string; | ||
}): Promise<void>; | ||
addEvent({ sessionId, event, deviceId }: { | ||
/** | ||
* Adds an event to the short term storage. If should split based on size or last sent, then send immediately. | ||
*/ | ||
addEvent({ sessionId, event, deviceId, }: { | ||
sessionId: number; | ||
event: string; | ||
event: { | ||
type: Type; | ||
data: Event; | ||
}; | ||
deviceId: string; | ||
}): void; | ||
/** | ||
* Move events in short term storage to long term storage and send immediately to the track destination. | ||
*/ | ||
sendCurrentSequenceEvents({ sessionId, deviceId }: { | ||
@@ -68,4 +99,7 @@ sessionId: number; | ||
}): void; | ||
/** | ||
* Flush the track destination queue immediately. This should invoke sends for all the events in the queue. | ||
*/ | ||
flush(useRetry?: boolean): Promise<void>; | ||
} | ||
//# sourceMappingURL=session-replay.d.ts.map |
@@ -29,6 +29,6 @@ import { __assign, __awaiter, __generator, __read, __values } from "tslib"; | ||
return __awaiter(this, void 0, void 0, function () { | ||
var config, remoteConfig, samplingConfig_1, privacyConfig, err_1, knownError, samplingConfig, remotePrivacyConfig, localPrivacyConfig, joinedPrivacyConfig, privacyConfigSelectorMap, selectorMap, _d, _e, _f, selector, selectorType; | ||
var e_1, _g; | ||
return __generator(this, function (_h) { | ||
switch (_h.label) { | ||
var config, remoteConfig, samplingConfig_1, privacyConfig, _d, err_1, knownError, samplingConfig, remotePrivacyConfig, localPrivacyConfig, joinedPrivacyConfig, privacyConfigSelectorMap, selectorMap, _e, _f, _g, selector, selectorType; | ||
var e_1, _h; | ||
return __generator(this, function (_j) { | ||
switch (_j.label) { | ||
case 0: | ||
@@ -41,5 +41,5 @@ config = __assign({}, this.localConfig); | ||
config.captureEnabled = true; | ||
_h.label = 1; | ||
_j.label = 1; | ||
case 1: | ||
_h.trys.push([1, 4, , 5]); | ||
_j.trys.push([1, 5, , 6]); | ||
if (!this.remoteConfigFetch) { | ||
@@ -50,6 +50,12 @@ return [2 /*return*/, config]; | ||
case 2: | ||
samplingConfig_1 = _h.sent(); | ||
samplingConfig_1 = _j.sent(); | ||
return [4 /*yield*/, this.remoteConfigFetch.getRemoteConfig('sessionReplay', 'sr_privacy_config', sessionId)]; | ||
case 3: | ||
privacyConfig = _h.sent(); | ||
privacyConfig = _j.sent(); | ||
// This is intentionally forced to only be set through the remote config. | ||
_d = config; | ||
return [4 /*yield*/, this.remoteConfigFetch.getRemoteConfig('sessionReplay', 'sr_interaction_config', sessionId)]; | ||
case 4: | ||
// This is intentionally forced to only be set through the remote config. | ||
_d.interactionConfig = _j.sent(); | ||
if (samplingConfig_1 || privacyConfig) { | ||
@@ -64,10 +70,10 @@ remoteConfig = {}; | ||
} | ||
return [3 /*break*/, 5]; | ||
case 4: | ||
err_1 = _h.sent(); | ||
return [3 /*break*/, 6]; | ||
case 5: | ||
err_1 = _j.sent(); | ||
knownError = err_1; | ||
this.localConfig.loggerProvider.warn(knownError.message); | ||
config.captureEnabled = true; | ||
return [3 /*break*/, 5]; | ||
case 5: | ||
return [3 /*break*/, 6]; | ||
case 6: | ||
if (!remoteConfig) { | ||
@@ -166,4 +172,4 @@ return [2 /*return*/, config]; | ||
try { | ||
for (_d = __values(Object.entries(selectorMap)), _e = _d.next(); !_e.done; _e = _d.next()) { | ||
_f = __read(_e.value, 2), selector = _f[0], selectorType = _f[1]; | ||
for (_e = __values(Object.entries(selectorMap)), _f = _e.next(); !_f.done; _f = _e.next()) { | ||
_g = __read(_f.value, 2), selector = _g[0], selectorType = _g[1]; | ||
if (selectorType === 'mask') { | ||
@@ -183,3 +189,3 @@ joinedPrivacyConfig.maskSelector.push(selector); | ||
try { | ||
if (_e && !_e.done && (_g = _d.return)) _g.call(_d); | ||
if (_f && !_f.done && (_h = _e.return)) _h.call(_e); | ||
} | ||
@@ -186,0 +192,0 @@ finally { if (e_1) throw e_1.error; } |
@@ -5,3 +5,3 @@ import { FetchTransport } from '@amplitude/analytics-client-common'; | ||
import { SessionReplayOptions } from '../typings/session-replay'; | ||
import { SessionReplayLocalConfig as ISessionReplayLocalConfig, PrivacyConfig } from './types'; | ||
import { SessionReplayLocalConfig as ISessionReplayLocalConfig, InteractionConfig, PrivacyConfig } from './types'; | ||
export declare const getDefaultConfig: () => { | ||
@@ -17,2 +17,3 @@ flushMaxRetries: number; | ||
privacyConfig?: PrivacyConfig; | ||
interactionConfig?: InteractionConfig; | ||
debugMode?: boolean; | ||
@@ -19,0 +20,0 @@ configEndpointUrl?: string; |
@@ -6,5 +6,10 @@ import { Config, LogLevel, Logger } from '@amplitude/analytics-types'; | ||
} | ||
export interface InteractionConfig { | ||
trackEveryNms?: number; | ||
enabled: boolean; | ||
} | ||
export type SessionReplayRemoteConfig = { | ||
sr_sampling_config?: SamplingConfig; | ||
sr_privacy_config?: PrivacyConfig; | ||
sr_interaction_config?: InteractionConfig; | ||
}; | ||
@@ -37,2 +42,3 @@ export interface SessionReplayRemoteConfigAPIResponse { | ||
captureEnabled?: boolean; | ||
interactionConfig?: InteractionConfig; | ||
} | ||
@@ -39,0 +45,0 @@ export interface SessionReplayRemoteConfigFetch { |
@@ -17,2 +17,4 @@ import { ServerZone } from '@amplitude/analytics-types'; | ||
export declare const MAX_EVENT_LIST_SIZE_IN_BYTES: number; | ||
export declare const INTERACTION_MIN_INTERVAL = 30000; | ||
export declare const INTERACTION_MAX_INTERVAL = 60000; | ||
export declare const MIN_INTERVAL = 500; | ||
@@ -19,0 +21,0 @@ export declare const MAX_INTERVAL: number; |
@@ -18,2 +18,4 @@ import { AMPLITUDE_PREFIX } from '@amplitude/analytics-core'; | ||
export var MAX_EVENT_LIST_SIZE_IN_BYTES = 1 * 1000000; // 1 MB | ||
export var INTERACTION_MIN_INTERVAL = 30000; // 30 seconds | ||
export var INTERACTION_MAX_INTERVAL = 60000; // 1 minute | ||
export var MIN_INTERVAL = 500; // 500 ms | ||
@@ -20,0 +22,0 @@ export var MAX_INTERVAL = 10 * 1000; // 10 seconds |
import { Logger as ILogger } from '@amplitude/analytics-types'; | ||
import { DBSchema, IDBPDatabase } from 'idb'; | ||
import { SessionReplayEventsIDBStore as AmplitudeSessionReplayEventsIDBStore, Events, SendingSequencesIDBInput } from '../typings/session-replay'; | ||
import { SessionReplayEventsIDBStore as AmplitudeSessionReplayEventsIDBStore, EventType, Events, SendingSequencesIDBInput } from '../typings/session-replay'; | ||
export declare const currentSequenceKey = "sessionCurrentSequence"; | ||
@@ -37,7 +37,11 @@ export declare const sequencesToSendKey = "sequencesToSend"; | ||
timeAtLastSplit: number | null; | ||
constructor({ loggerProvider, apiKey }: { | ||
private readonly minInterval; | ||
private readonly maxInterval; | ||
constructor({ loggerProvider, apiKey, minInterval, maxInterval, }: { | ||
loggerProvider: ILogger; | ||
apiKey: string; | ||
minInterval?: number; | ||
maxInterval?: number; | ||
}); | ||
initialize(sessionId?: number): Promise<void>; | ||
initialize(type: EventType, sessionId?: number): Promise<void>; | ||
/** | ||
@@ -65,7 +69,10 @@ * Determines whether to send the events list to the backend and start a new | ||
} | ||
export declare const createEventsIDBStore: ({ loggerProvider, apiKey, sessionId, }: { | ||
export declare const createEventsIDBStore: ({ loggerProvider, apiKey, sessionId, type, minInterval, maxInterval, }: { | ||
loggerProvider: ILogger; | ||
apiKey: string; | ||
type: EventType; | ||
minInterval?: number | undefined; | ||
maxInterval?: number | undefined; | ||
sessionId?: number | undefined; | ||
}) => Promise<SessionReplayEventsIDBStore>; | ||
//# sourceMappingURL=events-idb-store.d.ts.map |
@@ -87,7 +87,6 @@ import { __assign, __awaiter, __generator } from "tslib"; | ||
function SessionReplayEventsIDBStore(_a) { | ||
var loggerProvider = _a.loggerProvider, apiKey = _a.apiKey; | ||
var loggerProvider = _a.loggerProvider, apiKey = _a.apiKey, minInterval = _a.minInterval, maxInterval = _a.maxInterval; | ||
var _this = this; | ||
this.storageKey = ''; | ||
this.maxPersistedEventsSize = MAX_EVENT_LIST_SIZE_IN_BYTES; | ||
this.interval = MIN_INTERVAL; | ||
this.timeAtLastSplit = null; | ||
@@ -106,4 +105,7 @@ /** | ||
} | ||
if (_this.timeAtLastSplit !== null && Date.now() - _this.timeAtLastSplit > _this.interval && events.length) { | ||
_this.interval = Math.min(MAX_INTERVAL, _this.interval + MIN_INTERVAL); | ||
if (_this.timeAtLastSplit !== null && | ||
_this.interval && | ||
Date.now() - _this.timeAtLastSplit > _this.interval && | ||
events.length) { | ||
_this.interval = Math.min(_this.maxInterval, _this.interval + _this.minInterval); | ||
_this.timeAtLastSplit = Date.now(); | ||
@@ -172,3 +174,8 @@ return true; | ||
case 0: | ||
_b.trys.push([0, 10, , 11]); | ||
if (this.interval === 0) { | ||
this.interval = this.minInterval; | ||
} | ||
_b.label = 1; | ||
case 1: | ||
_b.trys.push([1, 11, , 12]); | ||
tx = (_a = this.db) === null || _a === void 0 ? void 0 : _a.transaction(currentSequenceKey, 'readwrite'); | ||
@@ -179,27 +186,27 @@ if (!tx) { | ||
return [4 /*yield*/, tx.store.get(sessionId)]; | ||
case 1: | ||
case 2: | ||
sequenceEvents = _b.sent(); | ||
if (!!sequenceEvents) return [3 /*break*/, 3]; | ||
if (!!sequenceEvents) return [3 /*break*/, 4]; | ||
return [4 /*yield*/, tx.store.put({ sessionId: sessionId, events: [event] })]; | ||
case 2: | ||
case 3: | ||
_b.sent(); | ||
return [2 /*return*/]; | ||
case 3: | ||
case 4: | ||
eventsToSend = void 0; | ||
if (!this.shouldSplitEventsList(sequenceEvents.events, event)) return [3 /*break*/, 5]; | ||
if (!this.shouldSplitEventsList(sequenceEvents.events, event)) return [3 /*break*/, 6]; | ||
eventsToSend = sequenceEvents.events; | ||
// set store to empty array | ||
return [4 /*yield*/, tx.store.put({ sessionId: sessionId, events: [event] })]; | ||
case 4: | ||
case 5: | ||
// set store to empty array | ||
_b.sent(); | ||
return [3 /*break*/, 7]; | ||
case 5: | ||
return [3 /*break*/, 8]; | ||
case 6: | ||
updatedEvents = sequenceEvents.events.concat(event); | ||
return [4 /*yield*/, tx.store.put({ sessionId: sessionId, events: updatedEvents })]; | ||
case 6: | ||
case 7: | ||
_b.sent(); | ||
_b.label = 7; | ||
case 7: return [4 /*yield*/, tx.done]; | ||
case 8: | ||
_b.label = 8; | ||
case 8: return [4 /*yield*/, tx.done]; | ||
case 9: | ||
_b.sent(); | ||
@@ -210,3 +217,3 @@ if (!eventsToSend) { | ||
return [4 /*yield*/, this.storeSendingEvents(sessionId, eventsToSend)]; | ||
case 9: | ||
case 10: | ||
sequenceId = _b.sent(); | ||
@@ -221,7 +228,7 @@ if (!sequenceId) { | ||
}]; | ||
case 10: | ||
case 11: | ||
e_3 = _b.sent(); | ||
this.loggerProvider.warn("".concat(STORAGE_FAILURE, ": ").concat(e_3)); | ||
return [3 /*break*/, 11]; | ||
case 11: return [2 /*return*/, undefined]; | ||
return [3 /*break*/, 12]; | ||
case 12: return [2 /*return*/, undefined]; | ||
} | ||
@@ -378,10 +385,14 @@ }); | ||
this.apiKey = apiKey; | ||
this.maxInterval = maxInterval !== null && maxInterval !== void 0 ? maxInterval : MAX_INTERVAL; | ||
this.minInterval = minInterval !== null && minInterval !== void 0 ? minInterval : MIN_INTERVAL; | ||
this.interval = 0; | ||
} | ||
SessionReplayEventsIDBStore.prototype.initialize = function (sessionId) { | ||
SessionReplayEventsIDBStore.prototype.initialize = function (type, sessionId) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var dbName, _a; | ||
var dbSuffix, dbName, _a; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
dbName = "".concat(this.apiKey.substring(0, 10), "_amp_session_replay_events"); | ||
dbSuffix = type === 'replay' ? '' : "_".concat(type); | ||
dbName = "".concat(this.apiKey.substring(0, 10), "_amp_session_replay_events").concat(dbSuffix); | ||
_a = this; | ||
@@ -404,3 +415,3 @@ return [4 /*yield*/, createStore(dbName)]; | ||
export var createEventsIDBStore = function (_a) { | ||
var loggerProvider = _a.loggerProvider, apiKey = _a.apiKey, sessionId = _a.sessionId; | ||
var loggerProvider = _a.loggerProvider, apiKey = _a.apiKey, sessionId = _a.sessionId, type = _a.type, minInterval = _a.minInterval, maxInterval = _a.maxInterval; | ||
return __awaiter(void 0, void 0, void 0, function () { | ||
@@ -411,4 +422,4 @@ var eventsIDBStore; | ||
case 0: | ||
eventsIDBStore = new SessionReplayEventsIDBStore({ loggerProvider: loggerProvider, apiKey: apiKey }); | ||
return [4 /*yield*/, eventsIDBStore.initialize(sessionId)]; | ||
eventsIDBStore = new SessionReplayEventsIDBStore({ loggerProvider: loggerProvider, apiKey: apiKey, minInterval: minInterval, maxInterval: maxInterval }); | ||
return [4 /*yield*/, eventsIDBStore.initialize(type, sessionId)]; | ||
case 1: | ||
@@ -415,0 +426,0 @@ _b.sent(); |
@@ -1,7 +0,12 @@ | ||
import { SessionReplayEventsManager as AmplitudeSessionReplayEventsManager } from '../typings/session-replay'; | ||
import { SessionReplayEventsManager as AmplitudeSessionReplayEventsManager, EventType } from '../typings/session-replay'; | ||
import { SessionReplayJoinedConfig } from '../config/types'; | ||
export declare const createEventsManager: ({ config, sessionId, }: { | ||
import { PayloadBatcher } from '../track-destination'; | ||
export declare const createEventsManager: <Type extends EventType>({ config, sessionId, minInterval, maxInterval, type, payloadBatcher, }: { | ||
config: SessionReplayJoinedConfig; | ||
type: Type; | ||
minInterval?: number | undefined; | ||
maxInterval?: number | undefined; | ||
sessionId?: number | undefined; | ||
}) => Promise<AmplitudeSessionReplayEventsManager>; | ||
payloadBatcher?: PayloadBatcher | undefined; | ||
}) => Promise<AmplitudeSessionReplayEventsManager<Type, string>>; | ||
//# sourceMappingURL=events-manager.d.ts.map |
import { __awaiter, __generator } from "tslib"; | ||
import { createEventsIDBStore } from './events-idb-store'; | ||
import { SessionReplayTrackDestination } from '../track-destination'; | ||
import { createEventsIDBStore } from './events-idb-store'; | ||
export var createEventsManager = function (_a) { | ||
var config = _a.config, sessionId = _a.sessionId; | ||
var config = _a.config, sessionId = _a.sessionId, minInterval = _a.minInterval, maxInterval = _a.maxInterval, type = _a.type, payloadBatcher = _a.payloadBatcher; | ||
return __awaiter(void 0, void 0, void 0, function () { | ||
@@ -11,6 +11,3 @@ function flush(useRetry) { | ||
return __generator(this, function (_a) { | ||
if (trackDestination) { | ||
return [2 /*return*/, trackDestination.flush(useRetry)]; | ||
} | ||
return [2 /*return*/]; | ||
return [2 /*return*/, trackDestination.flush(useRetry)]; | ||
}); | ||
@@ -23,3 +20,3 @@ }); | ||
case 0: | ||
trackDestination = new SessionReplayTrackDestination({ loggerProvider: config.loggerProvider }); | ||
trackDestination = new SessionReplayTrackDestination({ loggerProvider: config.loggerProvider, payloadBatcher: payloadBatcher }); | ||
return [4 /*yield*/, createEventsIDBStore({ | ||
@@ -29,2 +26,5 @@ loggerProvider: config.loggerProvider, | ||
sessionId: sessionId, | ||
minInterval: minInterval, | ||
maxInterval: maxInterval, | ||
type: type, | ||
})]; | ||
@@ -44,2 +44,3 @@ case 1: | ||
serverZone: config.serverZone, | ||
type: type, | ||
onComplete: eventsIDBStore.cleanUpSessionEventsStore.bind(eventsIDBStore), | ||
@@ -92,3 +93,3 @@ }); | ||
eventsIDBStore | ||
.addEventToCurrentSequence(sessionId, event) | ||
.addEventToCurrentSequence(sessionId, event.data) | ||
.then(function (sequenceToSend) { | ||
@@ -95,0 +96,0 @@ return (sequenceToSend && |
@@ -10,3 +10,3 @@ import { Logger as ILogger } from '@amplitude/analytics-types'; | ||
identifiers: ISessionIdentifiers | undefined; | ||
eventsManager: AmplitudeSessionReplayEventsManager | undefined; | ||
eventsManager?: AmplitudeSessionReplayEventsManager<'replay' | 'interaction', string>; | ||
loggerProvider: ILogger; | ||
@@ -36,5 +36,5 @@ recordCancelCallback: ReturnType<typeof record> | null; | ||
getSessionId(): number | undefined; | ||
flush(useRetry?: boolean): Promise<void>; | ||
flush(useRetry?: boolean): Promise<void | undefined>; | ||
shutdown(): void; | ||
} | ||
//# sourceMappingURL=session-replay.d.ts.map |
@@ -1,2 +0,2 @@ | ||
import { __awaiter, __generator } from "tslib"; | ||
import { __awaiter, __generator, __read, __spreadArray } from "tslib"; | ||
import { getAnalyticsConnector, getGlobalScope } from '@amplitude/analytics-client-common'; | ||
@@ -6,6 +6,8 @@ import { Logger, returnWrapper } from '@amplitude/analytics-core'; | ||
import { createSessionReplayJoinedConfigGenerator } from './config/joined-config'; | ||
import { BLOCK_CLASS, DEFAULT_SESSION_REPLAY_PROPERTY, MASK_TEXT_CLASS, SESSION_REPLAY_DEBUG_PROPERTY, } from './constants'; | ||
import { BLOCK_CLASS, DEFAULT_SESSION_REPLAY_PROPERTY, INTERACTION_MAX_INTERVAL, INTERACTION_MIN_INTERVAL, MASK_TEXT_CLASS, SESSION_REPLAY_DEBUG_PROPERTY, } from './constants'; | ||
import { createEventsManager } from './events/events-manager'; | ||
import { generateHashCode, isSessionInSample, maskFn } from './helpers'; | ||
import { SessionIdentifiers } from './identifiers'; | ||
import { clickBatcher, clickHook } from './hooks/click'; | ||
import { MultiEventManager } from './events/multi-manager'; | ||
var SessionReplay = /** @class */ (function () { | ||
@@ -70,26 +72,44 @@ function SessionReplay() { | ||
SessionReplay.prototype._init = function (apiKey, options) { | ||
var _a, _b; | ||
return __awaiter(this, void 0, void 0, function () { | ||
var _a, _b, _c, globalScope; | ||
return __generator(this, function (_d) { | ||
switch (_d.label) { | ||
var _c, _d, managers, rrwebEventManager, interactionEventManager, globalScope; | ||
return __generator(this, function (_e) { | ||
switch (_e.label) { | ||
case 0: | ||
this.loggerProvider = options.loggerProvider || new Logger(); | ||
this.identifiers = new SessionIdentifiers({ sessionId: options.sessionId, deviceId: options.deviceId }); | ||
_a = this; | ||
_c = this; | ||
return [4 /*yield*/, createSessionReplayJoinedConfigGenerator(apiKey, options)]; | ||
case 1: | ||
_a.joinedConfigGenerator = _d.sent(); | ||
_b = this; | ||
_c.joinedConfigGenerator = _e.sent(); | ||
_d = this; | ||
return [4 /*yield*/, this.joinedConfigGenerator.generateJoinedConfig(this.identifiers.sessionId)]; | ||
case 2: | ||
_b.config = _d.sent(); | ||
_d.config = _e.sent(); | ||
this.loggerProvider.debug(JSON.stringify({ name: 'session replay joined privacy config', privacyConfig: this.config.privacyConfig }, null, 2)); | ||
this.removeInvalidSelectors(); | ||
_c = this; | ||
managers = []; | ||
return [4 /*yield*/, createEventsManager({ | ||
config: this.config, | ||
sessionId: this.identifiers.sessionId, | ||
type: 'replay', | ||
})]; | ||
case 3: | ||
_c.eventsManager = _d.sent(); | ||
rrwebEventManager = _e.sent(); | ||
managers.push({ name: 'replay', manager: rrwebEventManager }); | ||
if (!((_a = this.config.interactionConfig) === null || _a === void 0 ? void 0 : _a.enabled)) return [3 /*break*/, 5]; | ||
return [4 /*yield*/, createEventsManager({ | ||
config: this.config, | ||
sessionId: this.identifiers.sessionId, | ||
type: 'interaction', | ||
minInterval: (_b = this.config.interactionConfig.trackEveryNms) !== null && _b !== void 0 ? _b : INTERACTION_MIN_INTERVAL, | ||
maxInterval: INTERACTION_MAX_INTERVAL, | ||
payloadBatcher: clickBatcher, | ||
})]; | ||
case 4: | ||
interactionEventManager = _e.sent(); | ||
managers.push({ name: 'interaction', manager: interactionEventManager }); | ||
_e.label = 5; | ||
case 5: | ||
this.eventsManager = new (MultiEventManager.bind.apply(MultiEventManager, __spreadArray([void 0], __read(managers), false)))(); | ||
this.loggerProvider.log('Installing @amplitude/session-replay-browser.'); | ||
@@ -194,7 +214,3 @@ globalScope = getGlobalScope(); | ||
} | ||
this.eventsManager && | ||
shouldSendStoredEvents && | ||
this.eventsManager.sendStoredEvents({ | ||
deviceId: deviceId, | ||
}); | ||
this.eventsManager && shouldSendStoredEvents && this.eventsManager.sendStoredEvents({ deviceId: deviceId }); | ||
this.recordEvents(); | ||
@@ -277,6 +293,16 @@ }; | ||
var deviceId = _this.getDeviceId(); | ||
deviceId && _this.eventsManager && _this.eventsManager.addEvent({ event: eventString, sessionId: sessionId, deviceId: deviceId }); | ||
_this.eventsManager && | ||
deviceId && | ||
_this.eventsManager.addEvent({ event: { type: 'replay', data: eventString }, sessionId: sessionId, deviceId: deviceId }); | ||
}, | ||
packFn: pack, | ||
inlineStylesheet: this.config.shouldInlineStylesheet, | ||
hooks: { | ||
mouseInteraction: this.eventsManager && | ||
clickHook({ | ||
eventsManager: this.eventsManager, | ||
sessionId: sessionId, | ||
deviceIdFn: this.getDeviceId.bind(this), | ||
}), | ||
}, | ||
maskAllInputs: true, | ||
@@ -324,9 +350,7 @@ maskTextClass: MASK_TEXT_CLASS, | ||
SessionReplay.prototype.flush = function (useRetry) { | ||
var _a; | ||
if (useRetry === void 0) { useRetry = false; } | ||
return __awaiter(this, void 0, void 0, function () { | ||
return __generator(this, function (_a) { | ||
if (this.eventsManager) { | ||
return [2 /*return*/, this.eventsManager.flush(useRetry)]; | ||
} | ||
return [2 /*return*/]; | ||
return __generator(this, function (_b) { | ||
return [2 /*return*/, (_a = this.eventsManager) === null || _a === void 0 ? void 0 : _a.flush(useRetry)]; | ||
}); | ||
@@ -333,0 +357,0 @@ }); |
import { Logger as ILogger, ServerZone } from '@amplitude/analytics-types'; | ||
import { SessionReplayTrackDestination as AmplitudeSessionReplayTrackDestination, SessionReplayDestination, SessionReplayDestinationContext } from './typings/session-replay'; | ||
export type PayloadBatcher = ({ version, events }: { | ||
version: number; | ||
events: string[]; | ||
}) => { | ||
version: number; | ||
events: unknown[]; | ||
}; | ||
export declare class SessionReplayTrackDestination implements AmplitudeSessionReplayTrackDestination { | ||
@@ -8,5 +15,7 @@ loggerProvider: ILogger; | ||
private scheduled; | ||
payloadBatcher: PayloadBatcher; | ||
queue: SessionReplayDestinationContext[]; | ||
constructor({ loggerProvider }: { | ||
constructor({ loggerProvider, payloadBatcher }: { | ||
loggerProvider: ILogger; | ||
payloadBatcher?: PayloadBatcher; | ||
}); | ||
@@ -13,0 +22,0 @@ sendEventsList(destinationData: SessionReplayDestination): void; |
@@ -10,3 +10,3 @@ import { __assign, __awaiter, __generator } from "tslib"; | ||
function SessionReplayTrackDestination(_a) { | ||
var loggerProvider = _a.loggerProvider; | ||
var loggerProvider = _a.loggerProvider, payloadBatcher = _a.payloadBatcher; | ||
this.storageKey = ''; | ||
@@ -17,2 +17,3 @@ this.retryTimeout = 1000; | ||
this.loggerProvider = loggerProvider; | ||
this.payloadBatcher = payloadBatcher ? payloadBatcher : function (payload) { return payload; }; | ||
} | ||
@@ -118,7 +119,12 @@ SessionReplayTrackDestination.prototype.sendEventsList = function (destinationData) { | ||
seq_number: "".concat(context.sequenceId), | ||
type: "".concat(context.type), | ||
}); | ||
payload = { | ||
payload = this.payloadBatcher({ | ||
version: 1, | ||
events: context.events, | ||
}; | ||
}); | ||
if (payload.events.length === 0) { | ||
this.completeRequest({ context: context }); | ||
return [2 /*return*/]; | ||
} | ||
_a.label = 1; | ||
@@ -125,0 +131,0 @@ case 1: |
import { AmplitudeReturn, ServerZone } from '@amplitude/analytics-types'; | ||
import { SessionReplayLocalConfig } from '../config/types'; | ||
export type Events = string[]; | ||
export type EventType = 'replay' | 'interaction'; | ||
export interface SessionReplayDestination { | ||
events: Events; | ||
sequenceId: number; | ||
type: EventType; | ||
sessionId: number; | ||
@@ -26,5 +28,12 @@ flushMaxRetries?: number; | ||
export interface SessionReplayEventsIDBStore { | ||
initialize(): Promise<void>; | ||
initialize(type: EventType): Promise<void>; | ||
getSequencesToSend(): Promise<SendingSequencesIDBReturn[] | undefined>; | ||
/** | ||
* Moves current sequence of events to long term storage and resets short term storage. | ||
*/ | ||
storeCurrentSequence(sessionId: number): Promise<SendingSequencesIDBInput | undefined>; | ||
/** | ||
* Adds events to the current IDB sequence. Returns events that should be | ||
* sent to the track destination right away if should split events is true. | ||
*/ | ||
addEventToCurrentSequence(sessionId: number, event: string): Promise<SendingSequencesIDBReturn | undefined>; | ||
@@ -51,14 +60,36 @@ storeSendingEvents(sessionId: number, events: Events): Promise<number | undefined>; | ||
export interface SessionReplayTrackDestination { | ||
/** | ||
* Enqueues events to be sent. | ||
*/ | ||
sendEventsList: (destinationData: SessionReplayDestination) => void; | ||
/** | ||
* Immediately sends queued events. | ||
*/ | ||
flush: (useRetry: boolean) => Promise<void>; | ||
} | ||
export interface SessionReplayEventsManager { | ||
export type EventsManagerWithType<EventType, EventDataType> = { | ||
name: EventType; | ||
manager: SessionReplayEventsManager<EventType, EventDataType>; | ||
}; | ||
export interface SessionReplayEventsManager<Type, Event> { | ||
/** | ||
* For each sequence stored in the long term indexed DB send immediately to the track destination. | ||
*/ | ||
sendStoredEvents({ deviceId }: { | ||
deviceId: string; | ||
}): Promise<void>; | ||
addEvent({ sessionId, event, deviceId }: { | ||
/** | ||
* Adds an event to the short term storage. If should split based on size or last sent, then send immediately. | ||
*/ | ||
addEvent({ sessionId, event, deviceId, }: { | ||
sessionId: number; | ||
event: string; | ||
event: { | ||
type: Type; | ||
data: Event; | ||
}; | ||
deviceId: string; | ||
}): void; | ||
/** | ||
* Move events in short term storage to long term storage and send immediately to the track destination. | ||
*/ | ||
sendCurrentSequenceEvents({ sessionId, deviceId }: { | ||
@@ -68,4 +99,7 @@ sessionId: number; | ||
}): void; | ||
/** | ||
* Flush the track destination queue immediately. This should invoke sends for all the events in the queue. | ||
*/ | ||
flush(useRetry?: boolean): Promise<void>; | ||
} | ||
//# sourceMappingURL=session-replay.d.ts.map |
@@ -5,3 +5,3 @@ import { FetchTransport } from '@amplitude/analytics-client-common'; | ||
import { SessionReplayOptions } from '../typings/session-replay'; | ||
import { SessionReplayLocalConfig as ISessionReplayLocalConfig, PrivacyConfig } from './types'; | ||
import { SessionReplayLocalConfig as ISessionReplayLocalConfig, InteractionConfig, PrivacyConfig } from './types'; | ||
export declare const getDefaultConfig: () => { | ||
@@ -17,2 +17,3 @@ flushMaxRetries: number; | ||
privacyConfig?: PrivacyConfig; | ||
interactionConfig?: InteractionConfig; | ||
debugMode?: boolean; | ||
@@ -19,0 +20,0 @@ configEndpointUrl?: string; |
@@ -6,5 +6,10 @@ import { Config, LogLevel, Logger } from '@amplitude/analytics-types'; | ||
} | ||
export interface InteractionConfig { | ||
trackEveryNms?: number; | ||
enabled: boolean; | ||
} | ||
export type SessionReplayRemoteConfig = { | ||
sr_sampling_config?: SamplingConfig; | ||
sr_privacy_config?: PrivacyConfig; | ||
sr_interaction_config?: InteractionConfig; | ||
}; | ||
@@ -37,2 +42,3 @@ export interface SessionReplayRemoteConfigAPIResponse { | ||
captureEnabled?: boolean; | ||
interactionConfig?: InteractionConfig; | ||
} | ||
@@ -39,0 +45,0 @@ export interface SessionReplayRemoteConfigFetch { |
@@ -17,2 +17,4 @@ import { ServerZone } from '@amplitude/analytics-types'; | ||
export declare const MAX_EVENT_LIST_SIZE_IN_BYTES: number; | ||
export declare const INTERACTION_MIN_INTERVAL = 30000; | ||
export declare const INTERACTION_MAX_INTERVAL = 60000; | ||
export declare const MIN_INTERVAL = 500; | ||
@@ -19,0 +21,0 @@ export declare const MAX_INTERVAL: number; |
import { Logger as ILogger } from '@amplitude/analytics-types'; | ||
import { DBSchema, IDBPDatabase } from 'idb'; | ||
import { SessionReplayEventsIDBStore as AmplitudeSessionReplayEventsIDBStore, Events, SendingSequencesIDBInput } from '../typings/session-replay'; | ||
import { SessionReplayEventsIDBStore as AmplitudeSessionReplayEventsIDBStore, EventType, Events, SendingSequencesIDBInput } from '../typings/session-replay'; | ||
export declare const currentSequenceKey = "sessionCurrentSequence"; | ||
@@ -37,7 +37,11 @@ export declare const sequencesToSendKey = "sequencesToSend"; | ||
timeAtLastSplit: number | null; | ||
constructor({ loggerProvider, apiKey }: { | ||
private readonly minInterval; | ||
private readonly maxInterval; | ||
constructor({ loggerProvider, apiKey, minInterval, maxInterval, }: { | ||
loggerProvider: ILogger; | ||
apiKey: string; | ||
minInterval?: number; | ||
maxInterval?: number; | ||
}); | ||
initialize(sessionId?: number): Promise<void>; | ||
initialize(type: EventType, sessionId?: number): Promise<void>; | ||
/** | ||
@@ -65,7 +69,10 @@ * Determines whether to send the events list to the backend and start a new | ||
} | ||
export declare const createEventsIDBStore: ({ loggerProvider, apiKey, sessionId, }: { | ||
export declare const createEventsIDBStore: ({ loggerProvider, apiKey, sessionId, type, minInterval, maxInterval, }: { | ||
loggerProvider: ILogger; | ||
apiKey: string; | ||
type: EventType; | ||
minInterval?: number | undefined; | ||
maxInterval?: number | undefined; | ||
sessionId?: number | undefined; | ||
}) => Promise<SessionReplayEventsIDBStore>; | ||
//# sourceMappingURL=events-idb-store.d.ts.map |
@@ -1,7 +0,12 @@ | ||
import { SessionReplayEventsManager as AmplitudeSessionReplayEventsManager } from '../typings/session-replay'; | ||
import { SessionReplayEventsManager as AmplitudeSessionReplayEventsManager, EventType } from '../typings/session-replay'; | ||
import { SessionReplayJoinedConfig } from '../config/types'; | ||
export declare const createEventsManager: ({ config, sessionId, }: { | ||
import { PayloadBatcher } from '../track-destination'; | ||
export declare const createEventsManager: <Type extends EventType>({ config, sessionId, minInterval, maxInterval, type, payloadBatcher, }: { | ||
config: SessionReplayJoinedConfig; | ||
type: Type; | ||
minInterval?: number | undefined; | ||
maxInterval?: number | undefined; | ||
sessionId?: number | undefined; | ||
}) => Promise<AmplitudeSessionReplayEventsManager>; | ||
payloadBatcher?: PayloadBatcher | undefined; | ||
}) => Promise<AmplitudeSessionReplayEventsManager<Type, string>>; | ||
//# sourceMappingURL=events-manager.d.ts.map |
@@ -10,3 +10,3 @@ import { Logger as ILogger } from '@amplitude/analytics-types'; | ||
identifiers: ISessionIdentifiers | undefined; | ||
eventsManager: AmplitudeSessionReplayEventsManager | undefined; | ||
eventsManager?: AmplitudeSessionReplayEventsManager<'replay' | 'interaction', string>; | ||
loggerProvider: ILogger; | ||
@@ -36,5 +36,5 @@ recordCancelCallback: ReturnType<typeof record> | null; | ||
getSessionId(): number | undefined; | ||
flush(useRetry?: boolean): Promise<void>; | ||
flush(useRetry?: boolean): Promise<void | undefined>; | ||
shutdown(): void; | ||
} | ||
//# sourceMappingURL=session-replay.d.ts.map |
import { Logger as ILogger, ServerZone } from '@amplitude/analytics-types'; | ||
import { SessionReplayTrackDestination as AmplitudeSessionReplayTrackDestination, SessionReplayDestination, SessionReplayDestinationContext } from './typings/session-replay'; | ||
export type PayloadBatcher = ({ version, events }: { | ||
version: number; | ||
events: string[]; | ||
}) => { | ||
version: number; | ||
events: unknown[]; | ||
}; | ||
export declare class SessionReplayTrackDestination implements AmplitudeSessionReplayTrackDestination { | ||
@@ -8,5 +15,7 @@ loggerProvider: ILogger; | ||
private scheduled; | ||
payloadBatcher: PayloadBatcher; | ||
queue: SessionReplayDestinationContext[]; | ||
constructor({ loggerProvider }: { | ||
constructor({ loggerProvider, payloadBatcher }: { | ||
loggerProvider: ILogger; | ||
payloadBatcher?: PayloadBatcher; | ||
}); | ||
@@ -13,0 +22,0 @@ sendEventsList(destinationData: SessionReplayDestination): void; |
import { AmplitudeReturn, ServerZone } from '@amplitude/analytics-types'; | ||
import { SessionReplayLocalConfig } from '../config/types'; | ||
export type Events = string[]; | ||
export type EventType = 'replay' | 'interaction'; | ||
export interface SessionReplayDestination { | ||
events: Events; | ||
sequenceId: number; | ||
type: EventType; | ||
sessionId: number; | ||
@@ -26,5 +28,12 @@ flushMaxRetries?: number; | ||
export interface SessionReplayEventsIDBStore { | ||
initialize(): Promise<void>; | ||
initialize(type: EventType): Promise<void>; | ||
getSequencesToSend(): Promise<SendingSequencesIDBReturn[] | undefined>; | ||
/** | ||
* Moves current sequence of events to long term storage and resets short term storage. | ||
*/ | ||
storeCurrentSequence(sessionId: number): Promise<SendingSequencesIDBInput | undefined>; | ||
/** | ||
* Adds events to the current IDB sequence. Returns events that should be | ||
* sent to the track destination right away if should split events is true. | ||
*/ | ||
addEventToCurrentSequence(sessionId: number, event: string): Promise<SendingSequencesIDBReturn | undefined>; | ||
@@ -51,14 +60,36 @@ storeSendingEvents(sessionId: number, events: Events): Promise<number | undefined>; | ||
export interface SessionReplayTrackDestination { | ||
/** | ||
* Enqueues events to be sent. | ||
*/ | ||
sendEventsList: (destinationData: SessionReplayDestination) => void; | ||
/** | ||
* Immediately sends queued events. | ||
*/ | ||
flush: (useRetry: boolean) => Promise<void>; | ||
} | ||
export interface SessionReplayEventsManager { | ||
export type EventsManagerWithType<EventType, EventDataType> = { | ||
name: EventType; | ||
manager: SessionReplayEventsManager<EventType, EventDataType>; | ||
}; | ||
export interface SessionReplayEventsManager<Type, Event> { | ||
/** | ||
* For each sequence stored in the long term indexed DB send immediately to the track destination. | ||
*/ | ||
sendStoredEvents({ deviceId }: { | ||
deviceId: string; | ||
}): Promise<void>; | ||
addEvent({ sessionId, event, deviceId }: { | ||
/** | ||
* Adds an event to the short term storage. If should split based on size or last sent, then send immediately. | ||
*/ | ||
addEvent({ sessionId, event, deviceId, }: { | ||
sessionId: number; | ||
event: string; | ||
event: { | ||
type: Type; | ||
data: Event; | ||
}; | ||
deviceId: string; | ||
}): void; | ||
/** | ||
* Move events in short term storage to long term storage and send immediately to the track destination. | ||
*/ | ||
sendCurrentSequenceEvents({ sessionId, deviceId }: { | ||
@@ -68,4 +99,7 @@ sessionId: number; | ||
}): void; | ||
/** | ||
* Flush the track destination queue immediately. This should invoke sends for all the events in the queue. | ||
*/ | ||
flush(useRetry?: boolean): Promise<void>; | ||
} | ||
//# sourceMappingURL=session-replay.d.ts.map |
{ | ||
"name": "@amplitude/session-replay-browser", | ||
"version": "1.8.0", | ||
"version": "1.9.0", | ||
"description": "", | ||
@@ -31,2 +31,3 @@ "author": "Amplitude Inc", | ||
"lint:prettier": "prettier --check \"{src,test}/**/*.ts\"", | ||
"postinstall": "rimraf ../../node_modules/@medv/finder/finder.ts # This is required until the package is fixed upstream", | ||
"publish": "node ../../scripts/publish/upload-to-s3.js", | ||
@@ -47,2 +48,3 @@ "test": "jest", | ||
"@amplitude/rrweb": "^2.0.0-alpha.14", | ||
"@medv/finder": "^3.2.0", | ||
"idb": "^8.0.0", | ||
@@ -52,2 +54,4 @@ "tslib": "^2.4.1" | ||
"devDependencies": { | ||
"@babel/core": "^7.24.7", | ||
"@babel/plugin-transform-modules-commonjs": "^7.24.7", | ||
"@rollup/plugin-commonjs": "^23.0.4", | ||
@@ -57,2 +61,3 @@ "@rollup/plugin-node-resolve": "^15.0.1", | ||
"@ungap/structured-clone": "^1.2.0", | ||
"babel-jest": "^29.7.0", | ||
"fake-indexeddb": "4.0.2", | ||
@@ -67,3 +72,3 @@ "rollup": "^2.79.1", | ||
], | ||
"gitHead": "0fb81b5ab9736d9761912b85f4b86931f4d0f2a5" | ||
"gitHead": "b90360189f0fd3a2687755da6e2d3db534bd909a" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
863663
187
5794
8
12
1
+ Added@medv/finder@^3.2.0
+ Added@medv/finder@3.2.0(transitive)