@amplitude/plugin-session-replay-browser 0.1.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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -0
  3. package/lib/cjs/constants.d.ts +5 -0
  4. package/lib/cjs/constants.d.ts.map +1 -0
  5. package/lib/cjs/constants.js +7 -0
  6. package/lib/cjs/constants.js.map +1 -0
  7. package/lib/cjs/helpers.d.ts +2 -0
  8. package/lib/cjs/helpers.d.ts.map +1 -0
  9. package/lib/cjs/helpers.js +12 -0
  10. package/lib/cjs/helpers.js.map +1 -0
  11. package/lib/cjs/index.d.ts +2 -0
  12. package/lib/cjs/index.d.ts.map +1 -0
  13. package/lib/cjs/index.js +6 -0
  14. package/lib/cjs/index.js.map +1 -0
  15. package/lib/cjs/messages.d.ts +5 -0
  16. package/lib/cjs/messages.d.ts.map +1 -0
  17. package/lib/cjs/messages.js +7 -0
  18. package/lib/cjs/messages.js.map +1 -0
  19. package/lib/cjs/session-replay.d.ts +3 -0
  20. package/lib/cjs/session-replay.d.ts.map +1 -0
  21. package/lib/cjs/session-replay.js +353 -0
  22. package/lib/cjs/session-replay.js.map +1 -0
  23. package/lib/cjs/typings/session-replay.d.ts +54 -0
  24. package/lib/cjs/typings/session-replay.d.ts.map +1 -0
  25. package/lib/cjs/typings/session-replay.js +2 -0
  26. package/lib/cjs/typings/session-replay.js.map +1 -0
  27. package/lib/esm/constants.d.ts +5 -0
  28. package/lib/esm/constants.d.ts.map +1 -0
  29. package/lib/esm/constants.js +5 -0
  30. package/lib/esm/constants.js.map +1 -0
  31. package/lib/esm/helpers.d.ts +2 -0
  32. package/lib/esm/helpers.d.ts.map +1 -0
  33. package/lib/esm/helpers.js +9 -0
  34. package/lib/esm/helpers.js.map +1 -0
  35. package/lib/esm/index.d.ts +2 -0
  36. package/lib/esm/index.d.ts.map +1 -0
  37. package/lib/esm/index.js +2 -0
  38. package/lib/esm/index.js.map +1 -0
  39. package/lib/esm/messages.d.ts +5 -0
  40. package/lib/esm/messages.d.ts.map +1 -0
  41. package/lib/esm/messages.js +5 -0
  42. package/lib/esm/messages.js.map +1 -0
  43. package/lib/esm/session-replay.d.ts +3 -0
  44. package/lib/esm/session-replay.d.ts.map +1 -0
  45. package/lib/esm/session-replay.js +350 -0
  46. package/lib/esm/session-replay.js.map +1 -0
  47. package/lib/esm/typings/session-replay.d.ts +54 -0
  48. package/lib/esm/typings/session-replay.d.ts.map +1 -0
  49. package/lib/esm/typings/session-replay.js +2 -0
  50. package/lib/esm/typings/session-replay.js.map +1 -0
  51. package/lib/scripts/amplitude-min.js +1 -0
  52. package/lib/scripts/amplitude-min.js.gz +0 -0
  53. package/lib/scripts/amplitude-min.umd.js +1 -0
  54. package/lib/scripts/amplitude-min.umd.js.gz +0 -0
  55. package/lib/scripts/constants.d.ts +5 -0
  56. package/lib/scripts/constants.d.ts.map +1 -0
  57. package/lib/scripts/helpers.d.ts +2 -0
  58. package/lib/scripts/helpers.d.ts.map +1 -0
  59. package/lib/scripts/index.d.ts +2 -0
  60. package/lib/scripts/index.d.ts.map +1 -0
  61. package/lib/scripts/messages.d.ts +5 -0
  62. package/lib/scripts/messages.d.ts.map +1 -0
  63. package/lib/scripts/session-replay.d.ts +3 -0
  64. package/lib/scripts/session-replay.d.ts.map +1 -0
  65. package/lib/scripts/typings/session-replay.d.ts +54 -0
  66. package/lib/scripts/typings/session-replay.d.ts.map +1 -0
  67. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Amplitude Analytics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ <p align="center">
2
+ <a href="https://amplitude.com" target="_blank" align="center">
3
+ <img src="https://static.amplitude.com/lightning/46c85bfd91905de8047f1ee65c7c93d6fa9ee6ea/static/media/amplitude-logo-with-text.4fb9e463.svg" width="280">
4
+ </a>
5
+ <br />
6
+ </p>
7
+
8
+ # @amplitude/plugin-session-replay-browser
9
+
10
+ Official Browser SDK plugin for session replay
11
+
12
+ ## Installation
13
+
14
+ This package is published on NPM registry and is available to be installed using npm and yarn.
15
+
16
+ ```sh
17
+ # npm
18
+ npm install @amplitude/plugin-session-replay-browser
19
+
20
+ # yarn
21
+ yarn add @amplitude/plugin-session-replay-browser
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ This plugin works on top of Amplitude Browser SDK and adds session replay features to built-in features. To use this plugin, you need to install `@amplitude/analytics-browser` version `v1.0.0` or later.
27
+
28
+ ### 1. Import Amplitude packages
29
+
30
+ * `@amplitude/analytics-browser`
31
+ * `@amplitude/plugin-session-replay-browser`
32
+
33
+ ```typescript
34
+ import * as amplitude from '@amplitude/analytics-browser';
35
+ import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser';
36
+ ```
37
+
38
+ ### 2. Instantiate session replay plugin and install plugin to Amplitude SDK
39
+
40
+ The plugin must be registered with the amplitude instance via the following code:
41
+
42
+ ```typescript
43
+ amplitude.init(API_KEY);
44
+ const sessionReplayTracking = sessionReplayPlugin();
45
+ amplitude.add(sessionReplayTracking);
46
+ ```
@@ -0,0 +1,5 @@
1
+ export declare const DEFAULT_EVENT_PROPERTY_PREFIX = "[Amplitude]";
2
+ export declare const DEFAULT_SESSION_REPLAY_PROPERTY: string;
3
+ export declare const DEFAULT_SESSION_START_EVENT = "session_start";
4
+ export declare const DEFAULT_SESSION_END_EVENT = "session_end";
5
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,6BAA6B,gBAAgB,CAAC;AAE3D,eAAO,MAAM,+BAA+B,QAAsD,CAAC;AACnG,eAAO,MAAM,2BAA2B,kBAAkB,CAAC;AAC3D,eAAO,MAAM,yBAAyB,gBAAgB,CAAC"}
@@ -0,0 +1,7 @@
1
+ Object.defineProperty(exports, "__esModule", { value: true });
2
+ exports.DEFAULT_SESSION_END_EVENT = exports.DEFAULT_SESSION_START_EVENT = exports.DEFAULT_SESSION_REPLAY_PROPERTY = exports.DEFAULT_EVENT_PROPERTY_PREFIX = void 0;
3
+ exports.DEFAULT_EVENT_PROPERTY_PREFIX = '[Amplitude]';
4
+ exports.DEFAULT_SESSION_REPLAY_PROPERTY = "".concat(exports.DEFAULT_EVENT_PROPERTY_PREFIX, " Session Recorded");
5
+ exports.DEFAULT_SESSION_START_EVENT = 'session_start';
6
+ exports.DEFAULT_SESSION_END_EVENT = 'session_end';
7
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":";;AAAa,QAAA,6BAA6B,GAAG,aAAa,CAAC;AAE9C,QAAA,+BAA+B,GAAG,UAAG,qCAA6B,sBAAmB,CAAC;AACtF,QAAA,2BAA2B,GAAG,eAAe,CAAC;AAC9C,QAAA,yBAAyB,GAAG,aAAa,CAAC","sourcesContent":["export const DEFAULT_EVENT_PROPERTY_PREFIX = '[Amplitude]';\n\nexport const DEFAULT_SESSION_REPLAY_PROPERTY = `${DEFAULT_EVENT_PROPERTY_PREFIX} Session Recorded`;\nexport const DEFAULT_SESSION_START_EVENT = 'session_start';\nexport const DEFAULT_SESSION_END_EVENT = 'session_end';\n"]}
@@ -0,0 +1,2 @@
1
+ export declare const shouldSplitEventsList: (eventsList: string[], nextEventString: string, maxListSize: number) => boolean;
2
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,eAAgB,MAAM,EAAE,mBAAmB,MAAM,eAAe,MAAM,KAAG,OAO1G,CAAC"}
@@ -0,0 +1,12 @@
1
+ Object.defineProperty(exports, "__esModule", { value: true });
2
+ exports.shouldSplitEventsList = void 0;
3
+ var shouldSplitEventsList = function (eventsList, nextEventString, maxListSize) {
4
+ var sizeOfNextEvent = new Blob([nextEventString]).size;
5
+ var sizeOfEventsList = new Blob(eventsList).size;
6
+ if (sizeOfEventsList + sizeOfNextEvent >= maxListSize) {
7
+ return true;
8
+ }
9
+ return false;
10
+ };
11
+ exports.shouldSplitEventsList = shouldSplitEventsList;
12
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":";;AAAO,IAAM,qBAAqB,GAAG,UAAC,UAAoB,EAAE,eAAuB,EAAE,WAAmB;IACtG,IAAM,eAAe,GAAG,IAAI,IAAI,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC;IACzD,IAAM,gBAAgB,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;IACnD,IAAI,gBAAgB,GAAG,eAAe,IAAI,WAAW,EAAE;QACrD,OAAO,IAAI,CAAC;KACb;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAPW,QAAA,qBAAqB,yBAOhC","sourcesContent":["export const shouldSplitEventsList = (eventsList: string[], nextEventString: string, maxListSize: number): boolean => {\n const sizeOfNextEvent = new Blob([nextEventString]).size;\n const sizeOfEventsList = new Blob(eventsList).size;\n if (sizeOfEventsList + sizeOfNextEvent >= maxListSize) {\n return true;\n }\n return false;\n};\n"]}
@@ -0,0 +1,2 @@
1
+ export { sessionReplayPlugin as plugin, sessionReplayPlugin } from './session-replay';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,IAAI,MAAM,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,6 @@
1
+ Object.defineProperty(exports, "__esModule", { value: true });
2
+ exports.sessionReplayPlugin = exports.plugin = void 0;
3
+ var session_replay_1 = require("./session-replay");
4
+ Object.defineProperty(exports, "plugin", { enumerable: true, get: function () { return session_replay_1.sessionReplayPlugin; } });
5
+ Object.defineProperty(exports, "sessionReplayPlugin", { enumerable: true, get: function () { return session_replay_1.sessionReplayPlugin; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;AAAA,mDAAsF;AAA7E,wGAAA,mBAAmB,OAAU;AAAE,qHAAA,mBAAmB,OAAA","sourcesContent":["export { sessionReplayPlugin as plugin, sessionReplayPlugin } from './session-replay';\n"]}
@@ -0,0 +1,5 @@
1
+ export declare const SUCCESS_MESSAGE = "Session replay event batch tracked successfully";
2
+ export declare const UNEXPECTED_ERROR_MESSAGE = "Unexpected error occurred";
3
+ export declare const MAX_RETRIES_EXCEEDED_MESSAGE = "Session replay event batch rejected due to exceeded retry count";
4
+ export declare const STORAGE_FAILURE = "Failed to store session replay events in IndexedDB";
5
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/messages.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,oDAAoD,CAAC;AACjF,eAAO,MAAM,wBAAwB,8BAA8B,CAAC;AACpE,eAAO,MAAM,4BAA4B,oEAAoE,CAAC;AAC9G,eAAO,MAAM,eAAe,uDAAuD,CAAC"}
@@ -0,0 +1,7 @@
1
+ Object.defineProperty(exports, "__esModule", { value: true });
2
+ exports.STORAGE_FAILURE = exports.MAX_RETRIES_EXCEEDED_MESSAGE = exports.UNEXPECTED_ERROR_MESSAGE = exports.SUCCESS_MESSAGE = void 0;
3
+ exports.SUCCESS_MESSAGE = 'Session replay event batch tracked successfully';
4
+ exports.UNEXPECTED_ERROR_MESSAGE = 'Unexpected error occurred';
5
+ exports.MAX_RETRIES_EXCEEDED_MESSAGE = 'Session replay event batch rejected due to exceeded retry count';
6
+ exports.STORAGE_FAILURE = 'Failed to store session replay events in IndexedDB';
7
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/messages.ts"],"names":[],"mappings":";;AAAa,QAAA,eAAe,GAAG,iDAAiD,CAAC;AACpE,QAAA,wBAAwB,GAAG,2BAA2B,CAAC;AACvD,QAAA,4BAA4B,GAAG,iEAAiE,CAAC;AACjG,QAAA,eAAe,GAAG,oDAAoD,CAAC","sourcesContent":["export const SUCCESS_MESSAGE = 'Session replay event batch tracked successfully';\nexport const UNEXPECTED_ERROR_MESSAGE = 'Unexpected error occurred';\nexport const MAX_RETRIES_EXCEEDED_MESSAGE = 'Session replay event batch rejected due to exceeded retry count';\nexport const STORAGE_FAILURE = 'Failed to store session replay events in IndexedDB';\n"]}
@@ -0,0 +1,3 @@
1
+ import { SessionReplayPlugin } from './typings/session-replay';
2
+ export declare const sessionReplayPlugin: SessionReplayPlugin;
3
+ //# sourceMappingURL=session-replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-replay.d.ts","sourceRoot":"","sources":["../../src/session-replay.ts"],"names":[],"mappings":"AAOA,OAAO,EAKL,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAwRlC,eAAO,MAAM,mBAAmB,EAAE,mBAEjC,CAAC"}
@@ -0,0 +1,353 @@
1
+ Object.defineProperty(exports, "__esModule", { value: true });
2
+ exports.sessionReplayPlugin = void 0;
3
+ var tslib_1 = require("tslib");
4
+ var analytics_core_1 = require("@amplitude/analytics-core");
5
+ var analytics_types_1 = require("@amplitude/analytics-types");
6
+ var IDBKeyVal = tslib_1.__importStar(require("idb-keyval"));
7
+ var rrweb_1 = require("rrweb");
8
+ var constants_1 = require("./constants");
9
+ var helpers_1 = require("./helpers");
10
+ var messages_1 = require("./messages");
11
+ var SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/track';
12
+ var STORAGE_PREFIX = "".concat(analytics_core_1.AMPLITUDE_PREFIX, "_replay_unsent");
13
+ var PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
14
+ var MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
15
+ var SessionReplay = /** @class */ (function () {
16
+ function SessionReplay() {
17
+ this.name = '@amplitude/plugin-session-replay-browser';
18
+ this.type = analytics_types_1.PluginType.ENRICHMENT;
19
+ this.storageKey = '';
20
+ this.retryTimeout = 1000;
21
+ this.events = [];
22
+ this.currentSequenceId = 0;
23
+ this.scheduled = null;
24
+ this.queue = [];
25
+ this.stopRecordingEvents = null;
26
+ this.maxPersistedEventsSize = MAX_EVENT_LIST_SIZE_IN_BYTES;
27
+ }
28
+ SessionReplay.prototype.setup = function (config) {
29
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
30
+ return tslib_1.__generator(this, function (_a) {
31
+ config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
32
+ this.config = config;
33
+ this.storageKey = "".concat(STORAGE_PREFIX, "_").concat(this.config.apiKey.substring(0, 10));
34
+ void this.emptyStoreAndReset();
35
+ return [2 /*return*/];
36
+ });
37
+ });
38
+ };
39
+ SessionReplay.prototype.execute = function (event) {
40
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
41
+ var _a;
42
+ return tslib_1.__generator(this, function (_b) {
43
+ event.event_properties = tslib_1.__assign(tslib_1.__assign({}, event.event_properties), (_a = {}, _a[constants_1.DEFAULT_SESSION_REPLAY_PROPERTY] = true, _a));
44
+ if (event.event_type === constants_1.DEFAULT_SESSION_START_EVENT && this.stopRecordingEvents) {
45
+ this.stopRecordingEvents();
46
+ this.recordEvents();
47
+ }
48
+ else if (event.event_type === constants_1.DEFAULT_SESSION_END_EVENT) {
49
+ if (event.session_id) {
50
+ this.sendEventsList({
51
+ events: this.events,
52
+ sequenceId: this.currentSequenceId,
53
+ sessionId: event.session_id,
54
+ });
55
+ }
56
+ this.events = [];
57
+ this.currentSequenceId = 0;
58
+ }
59
+ return [2 /*return*/, Promise.resolve(event)];
60
+ });
61
+ });
62
+ };
63
+ SessionReplay.prototype.emptyStoreAndReset = function () {
64
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
65
+ var storedReplaySessions, sessionId, storedReplayEvents, currentSessionStoredEvents;
66
+ return tslib_1.__generator(this, function (_a) {
67
+ switch (_a.label) {
68
+ case 0: return [4 /*yield*/, this.getAllSessionEventsFromStore()];
69
+ case 1:
70
+ storedReplaySessions = _a.sent();
71
+ if (storedReplaySessions) {
72
+ for (sessionId in storedReplaySessions) {
73
+ storedReplayEvents = storedReplaySessions[sessionId];
74
+ if (storedReplayEvents.events.length) {
75
+ this.sendEventsList({
76
+ events: storedReplayEvents.events,
77
+ sequenceId: storedReplayEvents.sequenceId,
78
+ sessionId: parseInt(sessionId, 10),
79
+ });
80
+ }
81
+ }
82
+ this.events = [];
83
+ currentSessionStoredEvents = this.config.sessionId && storedReplaySessions[this.config.sessionId];
84
+ this.currentSequenceId = currentSessionStoredEvents ? currentSessionStoredEvents.sequenceId + 1 : 0;
85
+ void this.storeEventsForSession([], this.currentSequenceId);
86
+ this.recordEvents();
87
+ }
88
+ return [2 /*return*/];
89
+ }
90
+ });
91
+ });
92
+ };
93
+ SessionReplay.prototype.recordEvents = function () {
94
+ var _this = this;
95
+ this.stopRecordingEvents = (0, rrweb_1.record)({
96
+ emit: function (event) {
97
+ var eventString = JSON.stringify(event);
98
+ var shouldSplit = (0, helpers_1.shouldSplitEventsList)(_this.events, eventString, _this.maxPersistedEventsSize);
99
+ if (shouldSplit) {
100
+ _this.sendEventsList({
101
+ events: _this.events,
102
+ sequenceId: _this.currentSequenceId,
103
+ sessionId: _this.config.sessionId,
104
+ });
105
+ _this.events = [];
106
+ _this.currentSequenceId++;
107
+ }
108
+ _this.events.push(eventString);
109
+ void _this.storeEventsForSession(_this.events, _this.currentSequenceId);
110
+ },
111
+ packFn: rrweb_1.pack,
112
+ });
113
+ };
114
+ SessionReplay.prototype.sendEventsList = function (_a) {
115
+ var events = _a.events, sequenceId = _a.sequenceId, sessionId = _a.sessionId;
116
+ this.addToQueue({
117
+ events: events,
118
+ sequenceId: sequenceId,
119
+ attempts: 0,
120
+ timeout: 0,
121
+ sessionId: sessionId,
122
+ });
123
+ };
124
+ SessionReplay.prototype.addToQueue = function () {
125
+ var _this = this;
126
+ var list = [];
127
+ for (var _i = 0; _i < arguments.length; _i++) {
128
+ list[_i] = arguments[_i];
129
+ }
130
+ var tryable = list.filter(function (context) {
131
+ if (context.attempts < _this.config.flushMaxRetries) {
132
+ context.attempts += 1;
133
+ return true;
134
+ }
135
+ _this.completeRequest({
136
+ context: context,
137
+ err: "".concat(messages_1.MAX_RETRIES_EXCEEDED_MESSAGE, ", batch sequence id, ").concat(context.sequenceId),
138
+ });
139
+ return false;
140
+ });
141
+ tryable.forEach(function (context) {
142
+ _this.queue = _this.queue.concat(context);
143
+ if (context.timeout === 0) {
144
+ _this.schedule(_this.config.flushIntervalMillis);
145
+ return;
146
+ }
147
+ setTimeout(function () {
148
+ context.timeout = 0;
149
+ _this.schedule(0);
150
+ }, context.timeout);
151
+ });
152
+ };
153
+ SessionReplay.prototype.schedule = function (timeout) {
154
+ var _this = this;
155
+ if (this.scheduled)
156
+ return;
157
+ this.scheduled = setTimeout(function () {
158
+ void _this.flush(true).then(function () {
159
+ if (_this.queue.length > 0) {
160
+ _this.schedule(timeout);
161
+ }
162
+ });
163
+ }, timeout);
164
+ };
165
+ SessionReplay.prototype.flush = function (useRetry) {
166
+ if (useRetry === void 0) { useRetry = false; }
167
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
168
+ var list, later;
169
+ var _this = this;
170
+ return tslib_1.__generator(this, function (_a) {
171
+ switch (_a.label) {
172
+ case 0:
173
+ list = [];
174
+ later = [];
175
+ this.queue.forEach(function (context) { return (context.timeout === 0 ? list.push(context) : later.push(context)); });
176
+ this.queue = later;
177
+ if (this.scheduled) {
178
+ clearTimeout(this.scheduled);
179
+ this.scheduled = null;
180
+ }
181
+ return [4 /*yield*/, Promise.all(list.map(function (context) { return _this.send(context, useRetry); }))];
182
+ case 1:
183
+ _a.sent();
184
+ return [2 /*return*/];
185
+ }
186
+ });
187
+ });
188
+ };
189
+ SessionReplay.prototype.send = function (context, useRetry) {
190
+ if (useRetry === void 0) { useRetry = true; }
191
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
192
+ var payload, options, res, responseBody, e_1;
193
+ return tslib_1.__generator(this, function (_a) {
194
+ switch (_a.label) {
195
+ case 0:
196
+ payload = {
197
+ api_key: this.config.apiKey,
198
+ device_id: this.config.deviceId,
199
+ session_id: context.sessionId,
200
+ start_timestamp: context.sessionId,
201
+ events_batch: {
202
+ version: 1,
203
+ events: context.events,
204
+ seq_number: context.sequenceId,
205
+ },
206
+ };
207
+ _a.label = 1;
208
+ case 1:
209
+ _a.trys.push([1, 3, , 4]);
210
+ options = {
211
+ headers: {
212
+ 'Content-Type': 'application/json',
213
+ Accept: '*/*',
214
+ },
215
+ body: JSON.stringify(payload),
216
+ method: 'POST',
217
+ };
218
+ return [4 /*yield*/, fetch(SESSION_REPLAY_SERVER_URL, options)];
219
+ case 2:
220
+ res = _a.sent();
221
+ if (res === null) {
222
+ this.completeRequest({ context: context, err: messages_1.UNEXPECTED_ERROR_MESSAGE, removeEvents: false });
223
+ return [2 /*return*/];
224
+ }
225
+ if (!useRetry) {
226
+ responseBody = '';
227
+ try {
228
+ responseBody = JSON.stringify(res.body, null, 2);
229
+ }
230
+ catch (_b) {
231
+ // to avoid crash, but don't care about the error, add comment to avoid empty block lint error
232
+ }
233
+ this.completeRequest({ context: context, success: "".concat(res.status, ": ").concat(responseBody) });
234
+ }
235
+ else {
236
+ this.handleReponse(res.status, context);
237
+ }
238
+ return [3 /*break*/, 4];
239
+ case 3:
240
+ e_1 = _a.sent();
241
+ this.completeRequest({ context: context, err: e_1, removeEvents: false });
242
+ return [3 /*break*/, 4];
243
+ case 4: return [2 /*return*/];
244
+ }
245
+ });
246
+ });
247
+ };
248
+ SessionReplay.prototype.handleReponse = function (status, context) {
249
+ var parsedStatus = new analytics_core_1.BaseTransport().buildStatus(status);
250
+ switch (parsedStatus) {
251
+ case analytics_types_1.Status.Success:
252
+ this.handleSuccessResponse(context);
253
+ break;
254
+ default:
255
+ this.handleOtherResponse(context);
256
+ }
257
+ };
258
+ SessionReplay.prototype.handleSuccessResponse = function (context) {
259
+ this.completeRequest({ context: context, success: messages_1.SUCCESS_MESSAGE });
260
+ };
261
+ SessionReplay.prototype.handleOtherResponse = function (context) {
262
+ this.addToQueue(tslib_1.__assign(tslib_1.__assign({}, context), { timeout: context.attempts * this.retryTimeout }));
263
+ };
264
+ SessionReplay.prototype.getAllSessionEventsFromStore = function () {
265
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
266
+ var storedReplaySessionContexts, e_2;
267
+ return tslib_1.__generator(this, function (_a) {
268
+ switch (_a.label) {
269
+ case 0:
270
+ _a.trys.push([0, 2, , 3]);
271
+ return [4 /*yield*/, IDBKeyVal.get(this.storageKey)];
272
+ case 1:
273
+ storedReplaySessionContexts = _a.sent();
274
+ return [2 /*return*/, storedReplaySessionContexts];
275
+ case 2:
276
+ e_2 = _a.sent();
277
+ this.config.loggerProvider.error("".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_2));
278
+ return [3 /*break*/, 3];
279
+ case 3: return [2 /*return*/, undefined];
280
+ }
281
+ });
282
+ });
283
+ };
284
+ SessionReplay.prototype.storeEventsForSession = function (events, sequenceId) {
285
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
286
+ var e_3;
287
+ var _this = this;
288
+ return tslib_1.__generator(this, function (_a) {
289
+ switch (_a.label) {
290
+ case 0:
291
+ _a.trys.push([0, 2, , 3]);
292
+ return [4 /*yield*/, IDBKeyVal.update(this.storageKey, function (sessionMap) {
293
+ var _a;
294
+ return tslib_1.__assign(tslib_1.__assign({}, sessionMap), (_this.config.sessionId && (_a = {},
295
+ _a[_this.config.sessionId] = {
296
+ events: events,
297
+ sequenceId: sequenceId,
298
+ },
299
+ _a)));
300
+ })];
301
+ case 1:
302
+ _a.sent();
303
+ return [3 /*break*/, 3];
304
+ case 2:
305
+ e_3 = _a.sent();
306
+ this.config.loggerProvider.error("".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_3));
307
+ return [3 /*break*/, 3];
308
+ case 3: return [2 /*return*/];
309
+ }
310
+ });
311
+ });
312
+ };
313
+ SessionReplay.prototype.removeSessionEventsStore = function (sessionId) {
314
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
315
+ var e_4;
316
+ return tslib_1.__generator(this, function (_a) {
317
+ switch (_a.label) {
318
+ case 0:
319
+ _a.trys.push([0, 2, , 3]);
320
+ return [4 /*yield*/, IDBKeyVal.update(this.storageKey, function (sessionMap) {
321
+ if (sessionMap === void 0) { sessionMap = {}; }
322
+ delete sessionMap[sessionId];
323
+ return sessionMap;
324
+ })];
325
+ case 1:
326
+ _a.sent();
327
+ return [3 /*break*/, 3];
328
+ case 2:
329
+ e_4 = _a.sent();
330
+ this.config.loggerProvider.error("".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_4));
331
+ return [3 /*break*/, 3];
332
+ case 3: return [2 /*return*/];
333
+ }
334
+ });
335
+ });
336
+ };
337
+ SessionReplay.prototype.completeRequest = function (_a) {
338
+ var context = _a.context, err = _a.err, success = _a.success, _b = _a.removeEvents, removeEvents = _b === void 0 ? true : _b;
339
+ removeEvents && context.sessionId && this.removeSessionEventsStore(context.sessionId);
340
+ if (err) {
341
+ this.config.loggerProvider.error(err);
342
+ }
343
+ else if (success) {
344
+ this.config.loggerProvider.log(success);
345
+ }
346
+ };
347
+ return SessionReplay;
348
+ }());
349
+ var sessionReplayPlugin = function () {
350
+ return new SessionReplay();
351
+ };
352
+ exports.sessionReplayPlugin = sessionReplayPlugin;
353
+ //# sourceMappingURL=session-replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-replay.js","sourceRoot":"","sources":["../../src/session-replay.ts"],"names":[],"mappings":"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","sourcesContent":["import { AMPLITUDE_PREFIX, BaseTransport } from '@amplitude/analytics-core';\nimport { BrowserConfig, Event, PluginType, Status } from '@amplitude/analytics-types';\nimport * as IDBKeyVal from 'idb-keyval';\nimport { pack, record } from 'rrweb';\nimport { DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_REPLAY_PROPERTY, DEFAULT_SESSION_START_EVENT } from './constants';\nimport { shouldSplitEventsList } from './helpers';\nimport { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';\nimport {\n Events,\n IDBStore,\n SessionReplayContext,\n SessionReplayEnrichmentPlugin,\n SessionReplayPlugin,\n} from './typings/session-replay';\n\nconst SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/track';\nconst STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;\nconst PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events\nconst MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;\n\nclass SessionReplay implements SessionReplayEnrichmentPlugin {\n name = '@amplitude/plugin-session-replay-browser';\n type = PluginType.ENRICHMENT as const;\n // this.config is defined in setup() which will always be called first\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n config: BrowserConfig;\n storageKey = '';\n retryTimeout = 1000;\n events: Events = [];\n currentSequenceId = 0;\n private scheduled: ReturnType<typeof setTimeout> | null = null;\n queue: SessionReplayContext[] = [];\n stopRecordingEvents: ReturnType<typeof record> | null = null;\n maxPersistedEventsSize = MAX_EVENT_LIST_SIZE_IN_BYTES;\n\n async setup(config: BrowserConfig) {\n config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');\n\n this.config = config;\n this.storageKey = `${STORAGE_PREFIX}_${this.config.apiKey.substring(0, 10)}`;\n void this.emptyStoreAndReset();\n }\n\n async execute(event: Event) {\n event.event_properties = {\n ...event.event_properties,\n [DEFAULT_SESSION_REPLAY_PROPERTY]: true,\n };\n if (event.event_type === DEFAULT_SESSION_START_EVENT && this.stopRecordingEvents) {\n this.stopRecordingEvents();\n this.recordEvents();\n } else if (event.event_type === DEFAULT_SESSION_END_EVENT) {\n if (event.session_id) {\n this.sendEventsList({\n events: this.events,\n sequenceId: this.currentSequenceId,\n sessionId: event.session_id,\n });\n }\n this.events = [];\n this.currentSequenceId = 0;\n }\n return Promise.resolve(event);\n }\n\n async emptyStoreAndReset() {\n const storedReplaySessions = await this.getAllSessionEventsFromStore();\n if (storedReplaySessions) {\n for (const sessionId in storedReplaySessions) {\n const storedReplayEvents = storedReplaySessions[sessionId];\n if (storedReplayEvents.events.length) {\n this.sendEventsList({\n events: storedReplayEvents.events,\n sequenceId: storedReplayEvents.sequenceId,\n sessionId: parseInt(sessionId, 10),\n });\n }\n }\n this.events = [];\n const currentSessionStoredEvents = this.config.sessionId && storedReplaySessions[this.config.sessionId];\n this.currentSequenceId = currentSessionStoredEvents ? currentSessionStoredEvents.sequenceId + 1 : 0;\n void this.storeEventsForSession([], this.currentSequenceId);\n this.recordEvents();\n }\n }\n\n recordEvents() {\n this.stopRecordingEvents = record({\n emit: (event) => {\n const eventString = JSON.stringify(event);\n const shouldSplit = shouldSplitEventsList(this.events, eventString, this.maxPersistedEventsSize);\n if (shouldSplit) {\n this.sendEventsList({\n events: this.events,\n sequenceId: this.currentSequenceId,\n sessionId: this.config.sessionId as number,\n });\n this.events = [];\n this.currentSequenceId++;\n }\n this.events.push(eventString);\n void this.storeEventsForSession(this.events, this.currentSequenceId);\n },\n packFn: pack,\n });\n }\n\n sendEventsList({ events, sequenceId, sessionId }: { events: string[]; sequenceId: number; sessionId: number }) {\n this.addToQueue({\n events,\n sequenceId,\n attempts: 0,\n timeout: 0,\n sessionId,\n });\n }\n\n addToQueue(...list: SessionReplayContext[]) {\n const tryable = list.filter((context) => {\n if (context.attempts < this.config.flushMaxRetries) {\n context.attempts += 1;\n return true;\n }\n this.completeRequest({\n context,\n err: `${MAX_RETRIES_EXCEEDED_MESSAGE}, batch sequence id, ${context.sequenceId}`,\n });\n return false;\n });\n tryable.forEach((context) => {\n this.queue = this.queue.concat(context);\n if (context.timeout === 0) {\n this.schedule(this.config.flushIntervalMillis);\n return;\n }\n\n setTimeout(() => {\n context.timeout = 0;\n this.schedule(0);\n }, context.timeout);\n });\n }\n\n schedule(timeout: number) {\n if (this.scheduled) return;\n this.scheduled = setTimeout(() => {\n void this.flush(true).then(() => {\n if (this.queue.length > 0) {\n this.schedule(timeout);\n }\n });\n }, timeout);\n }\n\n async flush(useRetry = false) {\n const list: SessionReplayContext[] = [];\n const later: SessionReplayContext[] = [];\n this.queue.forEach((context) => (context.timeout === 0 ? list.push(context) : later.push(context)));\n this.queue = later;\n\n if (this.scheduled) {\n clearTimeout(this.scheduled);\n this.scheduled = null;\n }\n\n await Promise.all(list.map((context) => this.send(context, useRetry)));\n }\n\n async send(context: SessionReplayContext, useRetry = true) {\n const payload = {\n api_key: this.config.apiKey,\n device_id: this.config.deviceId,\n session_id: context.sessionId,\n start_timestamp: context.sessionId,\n events_batch: {\n version: 1,\n events: context.events,\n seq_number: context.sequenceId,\n },\n };\n try {\n const options: RequestInit = {\n headers: {\n 'Content-Type': 'application/json',\n Accept: '*/*',\n },\n body: JSON.stringify(payload),\n method: 'POST',\n };\n const res = await fetch(SESSION_REPLAY_SERVER_URL, options);\n if (res === null) {\n this.completeRequest({ context, err: UNEXPECTED_ERROR_MESSAGE, removeEvents: false });\n return;\n }\n if (!useRetry) {\n let responseBody = '';\n try {\n responseBody = JSON.stringify(res.body, null, 2);\n } catch {\n // to avoid crash, but don't care about the error, add comment to avoid empty block lint error\n }\n this.completeRequest({ context, success: `${res.status}: ${responseBody}` });\n } else {\n this.handleReponse(res.status, context);\n }\n } catch (e) {\n this.completeRequest({ context, err: e as string, removeEvents: false });\n }\n }\n\n handleReponse(status: number, context: SessionReplayContext) {\n const parsedStatus = new BaseTransport().buildStatus(status);\n switch (parsedStatus) {\n case Status.Success:\n this.handleSuccessResponse(context);\n break;\n default:\n this.handleOtherResponse(context);\n }\n }\n\n handleSuccessResponse(context: SessionReplayContext) {\n this.completeRequest({ context, success: SUCCESS_MESSAGE });\n }\n\n handleOtherResponse(context: SessionReplayContext) {\n this.addToQueue({\n ...context,\n timeout: context.attempts * this.retryTimeout,\n });\n }\n\n async getAllSessionEventsFromStore() {\n try {\n const storedReplaySessionContexts: IDBStore | undefined = await IDBKeyVal.get(this.storageKey);\n\n return storedReplaySessionContexts;\n } catch (e) {\n this.config.loggerProvider.error(`${STORAGE_FAILURE}: ${e as string}`);\n }\n return undefined;\n }\n\n async storeEventsForSession(events: Events, sequenceId: number) {\n try {\n await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {\n return {\n ...sessionMap,\n ...(this.config.sessionId && {\n [this.config.sessionId]: {\n events: events,\n sequenceId,\n },\n }),\n };\n });\n } catch (e) {\n this.config.loggerProvider.error(`${STORAGE_FAILURE}: ${e as string}`);\n }\n }\n\n async removeSessionEventsStore(sessionId: number) {\n try {\n await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore = {}): IDBStore => {\n delete sessionMap[sessionId];\n return sessionMap;\n });\n } catch (e) {\n this.config.loggerProvider.error(`${STORAGE_FAILURE}: ${e as string}`);\n }\n }\n\n completeRequest({\n context,\n err,\n success,\n removeEvents = true,\n }: {\n context: SessionReplayContext;\n err?: string;\n success?: string;\n removeEvents?: boolean;\n }) {\n removeEvents && context.sessionId && this.removeSessionEventsStore(context.sessionId);\n if (err) {\n this.config.loggerProvider.error(err);\n } else if (success) {\n this.config.loggerProvider.log(success);\n }\n }\n}\n\nexport const sessionReplayPlugin: SessionReplayPlugin = () => {\n return new SessionReplay();\n};\n"]}
@@ -0,0 +1,54 @@
1
+ import { BrowserClient, BrowserConfig, EnrichmentPlugin } from '@amplitude/analytics-types';
2
+ import { record } from 'rrweb';
3
+ export interface Options {
4
+ }
5
+ export type Events = string[];
6
+ export interface SessionReplayContext {
7
+ events: Events;
8
+ sequenceId: number;
9
+ attempts: number;
10
+ timeout: number;
11
+ sessionId: number;
12
+ }
13
+ export interface IDBStore {
14
+ [sessionId: number]: {
15
+ events: Events;
16
+ sequenceId: number;
17
+ };
18
+ }
19
+ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
20
+ config: BrowserConfig;
21
+ storageKey: string;
22
+ retryTimeout: number;
23
+ events: Events;
24
+ currentSequenceId: number;
25
+ queue: SessionReplayContext[];
26
+ stopRecordingEvents: ReturnType<typeof record> | null;
27
+ maxPersistedEventsSize: number;
28
+ emptyStoreAndReset: () => Promise<void>;
29
+ recordEvents: () => void;
30
+ sendEventsList: ({ events, sequenceId, sessionId, }: {
31
+ events: string[];
32
+ sequenceId: number;
33
+ sessionId: number;
34
+ }) => void;
35
+ addToQueue: (...list: SessionReplayContext[]) => void;
36
+ schedule: (timeout: number) => void;
37
+ flush: (useRetry?: boolean) => Promise<void>;
38
+ send: (context: SessionReplayContext, useRetry?: boolean) => Promise<void>;
39
+ completeRequest({ context, err, success, removeEvents, }: {
40
+ context: SessionReplayContext;
41
+ err?: string | undefined;
42
+ success?: string | undefined;
43
+ removeEvents?: boolean | undefined;
44
+ }): void;
45
+ getAllSessionEventsFromStore: () => Promise<IDBStore | undefined>;
46
+ storeEventsForSession: (events: Events, sequenceId: number) => Promise<void>;
47
+ removeSessionEventsStore: (sessionId: number) => Promise<void>;
48
+ }
49
+ export interface SessionReplayPlugin {
50
+ (client: BrowserClient, options?: Options): SessionReplayEnrichmentPlugin;
51
+ (options?: Options): SessionReplayEnrichmentPlugin;
52
+ }
53
+ export type SessionReplayPluginParameters = [BrowserClient, Options?] | [Options?];
54
+ //# sourceMappingURL=session-replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-replay.d.ts","sourceRoot":"","sources":["../../../src/typings/session-replay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC5F,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,OAAO;CAAG;AAE3B,MAAM,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;AAE9B,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,CAAC,SAAS,EAAE,MAAM,GAAG;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AACD,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACrE,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,mBAAmB,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,GAAG,IAAI,CAAC;IACtD,sBAAsB,EAAE,MAAM,CAAC;IAC/B,kBAAkB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,cAAc,EAAE,CAAC,EACf,MAAM,EACN,UAAU,EACV,SAAS,GACV,EAAE;QACD,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KACnB,KAAK,IAAI,CAAC;IACX,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,oBAAoB,EAAE,KAAK,IAAI,CAAC;IACtD,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,EAAE,CAAC,OAAO,EAAE,oBAAoB,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,eAAe,CAAC,EACd,OAAO,EACP,GAAG,EACH,OAAO,EACP,YAAY,GACb,EAAE;QACD,OAAO,EAAE,oBAAoB,CAAC;QAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACzB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC7B,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KACpC,GAAG,IAAI,CAAC;IACT,4BAA4B,EAAE,MAAM,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IAClE,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,wBAAwB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,mBAAmB;IAClC,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,6BAA6B,CAAC;IAC1E,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,6BAA6B,CAAC;CACpD;AAED,MAAM,MAAM,6BAA6B,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports, "__esModule", { value: true });
2
+ //# sourceMappingURL=session-replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-replay.js","sourceRoot":"","sources":["../../../src/typings/session-replay.ts"],"names":[],"mappings":"","sourcesContent":["import { BrowserClient, BrowserConfig, EnrichmentPlugin } from '@amplitude/analytics-types';\nimport { record } from 'rrweb';\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface Options {}\n\nexport type Events = string[];\n\nexport interface SessionReplayContext {\n events: Events;\n sequenceId: number;\n attempts: number;\n timeout: number;\n sessionId: number;\n}\n\nexport interface IDBStore {\n [sessionId: number]: {\n events: Events;\n sequenceId: number;\n };\n}\nexport interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {\n config: BrowserConfig;\n storageKey: string;\n retryTimeout: number;\n events: Events;\n currentSequenceId: number;\n queue: SessionReplayContext[];\n stopRecordingEvents: ReturnType<typeof record> | null;\n maxPersistedEventsSize: number;\n emptyStoreAndReset: () => Promise<void>;\n recordEvents: () => void;\n sendEventsList: ({\n events,\n sequenceId,\n sessionId,\n }: {\n events: string[];\n sequenceId: number;\n sessionId: number;\n }) => void;\n addToQueue: (...list: SessionReplayContext[]) => void;\n schedule: (timeout: number) => void;\n flush: (useRetry?: boolean) => Promise<void>;\n send: (context: SessionReplayContext, useRetry?: boolean) => Promise<void>;\n completeRequest({\n context,\n err,\n success,\n removeEvents,\n }: {\n context: SessionReplayContext;\n err?: string | undefined;\n success?: string | undefined;\n removeEvents?: boolean | undefined;\n }): void;\n getAllSessionEventsFromStore: () => Promise<IDBStore | undefined>;\n storeEventsForSession: (events: Events, sequenceId: number) => Promise<void>;\n removeSessionEventsStore: (sessionId: number) => Promise<void>;\n}\n\nexport interface SessionReplayPlugin {\n (client: BrowserClient, options?: Options): SessionReplayEnrichmentPlugin;\n (options?: Options): SessionReplayEnrichmentPlugin;\n}\n\nexport type SessionReplayPluginParameters = [BrowserClient, Options?] | [Options?];\n"]}
@@ -0,0 +1,5 @@
1
+ export declare const DEFAULT_EVENT_PROPERTY_PREFIX = "[Amplitude]";
2
+ export declare const DEFAULT_SESSION_REPLAY_PROPERTY: string;
3
+ export declare const DEFAULT_SESSION_START_EVENT = "session_start";
4
+ export declare const DEFAULT_SESSION_END_EVENT = "session_end";
5
+ //# sourceMappingURL=constants.d.ts.map