@lvce-editor/renderer-process 30.57.1 → 30.57.2

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.
@@ -1,219 +1,229 @@
1
- const version = 1;
2
- const maxEventBytes = 750_000;
3
- const maxSessionBytes = 64 * 1024 * 1024;
4
- const bytes = (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
5
-
6
- const validateSession = (session) => {
7
- if (session?.version !== version || !Array.isArray(session.events) || session.events.length > 200_000 || bytes(session) > maxSessionBytes) {
8
- throw new Error('Unsupported or oversized session replay')
1
+ // ../session-replay-worker/src/protocol.ts
2
+ var version = 1;
3
+ var maxEventBytes = 75e4;
4
+ var maxSessionBytes = 64 * 1024 * 1024;
5
+ var bytes = (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
6
+ var validateSession = (value) => {
7
+ const session = value;
8
+ if (session?.version !== version || !Array.isArray(session.events) || session.events.length > 2e5 || bytes(session) > maxSessionBytes) {
9
+ throw new Error("Unsupported or oversized session replay");
9
10
  }
10
11
  let time = 0;
11
12
  for (const [sequence, event] of session.events.entries()) {
12
- if (event.sequence !== sequence || !Number.isFinite(event.timestamp) || event.timestamp < time || !['frame', 'message'].includes(event.type)) {
13
- throw new Error('Invalid session replay event sequence')
13
+ if (!event || event.sequence !== sequence || !Number.isFinite(event.timestamp) || event.timestamp < time || !["frame", "message"].includes(event.type)) {
14
+ throw new Error("Invalid session replay event sequence");
14
15
  }
15
- if (bytes(event) > maxEventBytes) throw new Error('Session replay event is too large')
16
+ if (bytes(event) > maxEventBytes) throw new Error("Session replay event is too large");
16
17
  time = event.timestamp;
17
18
  }
18
- return session
19
+ return session;
19
20
  };
20
-
21
- // Binary search makes dragging independent of the number of diagnostic messages.
22
- const loadContent = (session) => {
23
- validateSession(session);
24
- const frames = session.events.filter((event) => event.type === 'frame');
25
- if (!frames.length) throw new Error('This session has no visual frames')
21
+ var loadContent = (value) => {
22
+ const session = validateSession(value);
23
+ const frames = session.events.filter((event) => event.type === "frame");
24
+ if (frames.length === 0) throw new Error("This session has no visual frames");
26
25
  const duration = session.events.at(-1)?.timestamp || 0;
27
26
  const seek = (timestamp) => {
28
- const position = Math.max(0, Math.min(duration, Number(timestamp) || 0));
27
+ const position = Math.max(0, Math.min(duration, timestamp || 0));
29
28
  let low = 0;
30
29
  let high = frames.length;
31
30
  while (low < high) {
32
- const middle = (low + high) >>> 1;
31
+ const middle = low + high >>> 1;
33
32
  if (frames[middle].timestamp <= position) low = middle + 1;
34
33
  else high = middle;
35
34
  }
36
- return { position, duration, frame: frames[Math.max(0, low - 1)].data }
35
+ return { duration, frame: frames[Math.max(0, low - 1)].data, position };
37
36
  };
38
- return { duration, seek }
37
+ return { duration, seek };
39
38
  };
40
39
 
41
- const createRecorder = ({ storage, fetch: request = globalThis.fetch, now = () => performance.now() }) => {
40
+ // ../session-replay-worker/src/recorder.ts
41
+ var createRecorder = ({
42
+ fetch: request = globalThis.fetch,
43
+ now = () => performance.now(),
44
+ storage: storage2
45
+ }) => {
42
46
  let metadata;
43
47
  let options;
44
- let events = [];
45
- let pending = [];
48
+ const events = [];
49
+ const pending = [];
46
50
  let size = 0;
47
51
  let origin = 0;
48
52
  let remote;
49
53
  let uploadPromise;
50
- let lastError = '';
54
+ let lastError = "";
51
55
  let saved = 0;
52
56
  let uploaded = 0;
53
57
  const start = async (config) => {
54
- if (metadata) throw new Error('A session is already recording')
58
+ if (metadata) throw new Error("A session is already recording");
55
59
  options = config;
56
60
  origin = now();
57
- metadata = { id: crypto.randomUUID(), version, createdAt: new Date().toISOString() };
58
- if (options.local) await storage.save(metadata, []);
59
- return metadata.id
61
+ metadata = { createdAt: (/* @__PURE__ */ new Date()).toISOString(), id: crypto.randomUUID(), version };
62
+ if (options.local) await storage2.save(metadata, []);
63
+ return metadata.id;
60
64
  };
61
- const record = async (type, data) => {
62
- if (!metadata) return
63
- const event = { sequence: events.length, timestamp: Math.max(events.at(-1)?.timestamp || 0, now() - origin), type, data };
65
+ const record = async (...[type, data]) => {
66
+ if (!metadata) return;
67
+ const event = { data, sequence: events.length, timestamp: Math.max(events.at(-1)?.timestamp || 0, now() - origin), type };
64
68
  const eventSize = bytes(event);
65
69
  if (eventSize > maxEventBytes || size + eventSize > maxSessionBytes)
66
- throw new Error('Session replay storage limit reached; download this session and start a new one')
70
+ throw new Error("Session replay storage limit reached; download this session and start a new one");
67
71
  size += eventSize;
68
72
  events.push(event);
69
73
  if (options.upload) pending.push(event);
70
74
  if (options.local) {
71
- await storage.save(metadata, [event]);
75
+ await storage2.save(metadata, [event]);
72
76
  saved++;
73
77
  }
74
78
  };
75
79
  const doUpload = async () => {
76
- if (!options?.upload || !pending.length) return
80
+ if (!options?.upload || pending.length === 0) return;
77
81
  const endpoint = new URL(options.endpoint);
78
- if (!['https:', 'http:'].includes(endpoint.protocol)) throw new Error('Invalid session replay backend URL')
79
- const headers = { 'Content-Type': 'application/json', ...(options.token ? { Authorization: `Bearer ${options.token}` } : {}) };
82
+ if (!["https:", "http:"].includes(endpoint.protocol)) throw new Error("Invalid session replay backend URL");
83
+ const headers = { "Content-Type": "application/json", ...options.token && { Authorization: `Bearer ${options.token}` } };
80
84
  const send = async (url, body) => {
81
85
  const response = await request(url, {
82
- method: 'POST',
83
- credentials: 'include',
84
- headers,
85
86
  body: JSON.stringify(body),
86
- signal: AbortSignal.timeout(10_000),
87
+ credentials: "include",
88
+ headers,
89
+ method: "POST",
90
+ signal: AbortSignal.timeout(1e4)
87
91
  });
88
- if (!response.ok) throw new Error(`Session replay upload failed (${response.status})`)
89
- return response.json()
92
+ if (!response.ok) throw new Error(`Session replay upload failed (${response.status})`);
93
+ return response.json();
90
94
  };
91
- if (!remote) remote = await send(endpoint, { version, createdAt: metadata.createdAt });
92
- while (pending.length) {
95
+ if (!remote) remote = await send(endpoint, { createdAt: metadata.createdAt, version });
96
+ while (pending.length > 0) {
93
97
  const batch = [];
94
98
  let batchBytes = 0;
95
99
  for (const event of pending) {
96
100
  const length = bytes(event);
97
- if (batch.length && (batchBytes + length > 800_000 || batch.length >= 250)) break
101
+ if (batch.length > 0 && (batchBytes + length > 8e5 || batch.length >= 250)) break;
98
102
  batch.push(event);
99
103
  batchBytes += length;
100
104
  }
101
105
  const url = new URL(endpoint);
102
- url.pathname = `${url.pathname.replace(/\/$/, '')}/${encodeURIComponent(remote.id)}/events`;
103
- await send(url, { uploadToken: remote.uploadToken, events: batch });
106
+ url.pathname = `${url.pathname.replace(/\/$/, "")}/${encodeURIComponent(remote.id)}/events`;
107
+ await send(url, { events: batch, uploadToken: remote.uploadToken });
104
108
  pending.splice(0, batch.length);
105
109
  uploaded += batch.length;
106
110
  }
107
111
  };
108
112
  const flush = () => {
109
- uploadPromise ||= doUpload()
110
- .then(
111
- () => {
112
- lastError = '';
113
- },
114
- (error) => {
115
- lastError = error.message;
116
- throw error
117
- },
118
- )
119
- .finally(() => {
120
- uploadPromise = undefined;
121
- });
122
- return uploadPromise
113
+ uploadPromise ||= doUpload().then(
114
+ () => {
115
+ lastError = "";
116
+ },
117
+ (error) => {
118
+ lastError = error.message;
119
+ throw error;
120
+ }
121
+ ).finally(() => {
122
+ uploadPromise = void 0;
123
+ });
124
+ return uploadPromise;
123
125
  };
124
126
  return {
125
- start,
126
- record,
127
- flush,
128
127
  export: () => ({ ...metadata, events }),
129
- status: () => ({ id: metadata?.id, events: events.length, saved, uploaded, pending: pending.length, bytes: size, error: lastError }),
130
- }
128
+ flush,
129
+ record,
130
+ start,
131
+ status: () => ({
132
+ bytes: size,
133
+ error: lastError,
134
+ events: events.length,
135
+ id: metadata?.id,
136
+ pending: pending.length,
137
+ saved,
138
+ uploaded
139
+ })
140
+ };
131
141
  };
132
142
 
133
- const open = () =>
134
- new Promise((resolve, reject) => {
135
- const request = indexedDB.open('lvce-session-replays', 1);
136
- request.onupgradeneeded = () => {
137
- request.result.createObjectStore('sessions', { keyPath: 'id' });
138
- request.result.createObjectStore('events', { keyPath: ['sessionId', 'sequence'] });
139
- };
140
- request.onsuccess = () => resolve(request.result);
141
- request.onerror = () => reject(request.error);
142
- });
143
-
144
- const createStorage = async () => {
143
+ // ../session-replay-worker/src/storage.ts
144
+ var withoutSessionId = ({ sessionId, ...event }) => event;
145
+ var open = () => new Promise((resolve, reject) => {
146
+ const request = indexedDB.open("lvce-session-replays", 1);
147
+ request.onupgradeneeded = () => {
148
+ request.result.createObjectStore("sessions", { keyPath: "id" });
149
+ request.result.createObjectStore("events", { keyPath: ["sessionId", "sequence"] });
150
+ };
151
+ request.onsuccess = () => resolve(request.result);
152
+ request.onerror = () => reject(request.error || new Error("Replay storage open failed"));
153
+ });
154
+ var createStorage = async () => {
145
155
  const db = await open();
146
- const transaction = (stores, action) =>
147
- new Promise((resolve, reject) => {
148
- const tx = db.transaction(stores, 'readwrite');
149
- action(tx);
150
- tx.oncomplete = () => resolve();
151
- tx.onerror = tx.onabort = () => reject(tx.error || new Error('Replay storage transaction aborted'));
152
- });
156
+ const transaction = (stores, action) => new Promise((resolve, reject) => {
157
+ const tx = db.transaction(stores, "readwrite");
158
+ action(tx);
159
+ tx.oncomplete = () => resolve();
160
+ tx.onerror = tx.onabort = () => reject(tx.error || new Error("Replay storage transaction aborted"));
161
+ });
153
162
  return {
154
- save: (session, events) =>
155
- transaction(['sessions', 'events'], (tx) => {
156
- tx.objectStore('sessions').put(session);
157
- for (const event of events) tx.objectStore('events').put({ ...event, sessionId: session.id });
158
- }),
159
- read: (id) =>
160
- new Promise((resolve, reject) => {
161
- const tx = db.transaction(['sessions', 'events']);
162
- const metadata = tx.objectStore('sessions').get(id);
163
- const events = tx.objectStore('events').getAll(IDBKeyRange.bound([id, 0], [id, Number.MAX_SAFE_INTEGER]));
164
- tx.oncomplete = () =>
165
- metadata.result
166
- ? resolve({ ...metadata.result, events: events.result.map(({ sessionId, ...event }) => event) })
167
- : reject(new Error('Session replay not found'));
168
- tx.onerror = () => reject(tx.error);
169
- }),
170
163
  close: () => db.close(),
171
- }
164
+ read: (id) => new Promise((resolve, reject) => {
165
+ const tx = db.transaction(["sessions", "events"]);
166
+ const metadata = tx.objectStore("sessions").get(id);
167
+ const events = tx.objectStore("events").getAll(IDBKeyRange.bound([id, 0], [id, Number.MAX_SAFE_INTEGER]));
168
+ tx.oncomplete = () => metadata.result ? resolve({ ...metadata.result, events: events.result.map(withoutSessionId) }) : reject(new Error("Session replay not found"));
169
+ tx.onerror = () => reject(tx.error || new Error("Replay storage read failed"));
170
+ }),
171
+ save: (session, events) => transaction(["sessions", "events"], (tx) => {
172
+ tx.objectStore("sessions").put(session);
173
+ for (const event of events) tx.objectStore("events").put({ ...event, sessionId: session.id });
174
+ })
175
+ };
172
176
  };
173
177
 
174
- let storage;
175
- let recorder;
176
- let content;
177
- let queue = Promise.resolve();
178
- let timer;
179
- const getStorage = async () => (storage ||= await createStorage());
180
- const commands = {
181
- async start(options) {
182
- recorder = createRecorder({ storage: options.local ? await getStorage() : undefined });
183
- const id = await recorder.start(options);
184
- clearInterval(timer);
185
- timer = setInterval(() => recorder.flush().catch(() => {}), 2000);
186
- return id
187
- },
188
- record: (type, data) => recorder.record(type, data),
189
- flush: () => recorder.flush(),
178
+ // ../session-replay-worker/src/worker.ts
179
+ var storage;
180
+ var recorder;
181
+ var content;
182
+ var queue = Promise.resolve();
183
+ var timer;
184
+ var getStorage = async () => storage ||= await createStorage();
185
+ var commands = {
190
186
  export: () => recorder.export(),
191
- status: () => recorder.status(),
187
+ flush: () => recorder.flush(),
192
188
  async load(source) {
193
189
  let session;
194
- if (source.localId) session = await (await getStorage()).read(source.localId);
195
- else if (source.url) {
196
- const response = await fetch(source.url, { credentials: 'include', headers: { Accept: 'application/json' } });
197
- if (!response.ok) throw new Error(`Cannot load session replay (${response.status})`)
190
+ if ("localId" in source) {
191
+ const localStorage = await getStorage();
192
+ session = await localStorage.read(source.localId);
193
+ } else if ("url" in source) {
194
+ const response = await fetch(source.url, { credentials: "include", headers: { Accept: "application/json" } });
195
+ if (!response.ok) throw new Error(`Cannot load session replay (${response.status})`);
198
196
  session = await response.json();
199
- } else session = source.session;
197
+ } else ({ session } = source);
200
198
  content = loadContent(session);
201
- return content.seek(0)
199
+ return content.seek(0);
202
200
  },
201
+ record: (...params) => recorder.record(...params),
203
202
  seek: (timestamp) => content.seek(timestamp),
203
+ async start(options) {
204
+ recorder = createRecorder({ storage: options.local ? await getStorage() : void 0 });
205
+ const id = await recorder.start(options);
206
+ clearInterval(timer);
207
+ timer = setInterval(() => {
208
+ void recorder.flush().catch(() => {
209
+ });
210
+ }, 2e3);
211
+ return id;
212
+ },
213
+ status: () => recorder.status(),
204
214
  async stop() {
205
215
  clearInterval(timer);
206
216
  await recorder?.flush();
207
217
  storage?.close();
208
- },
218
+ }
209
219
  };
210
- self.onmessage = ({ data: { id, method, params = [] } }) => {
220
+ globalThis.onmessage = ({ data: { id, method, params = [] } }) => {
211
221
  queue = queue.then(async () => {
212
222
  try {
213
- if (!Object.hasOwn(commands, method)) throw new Error('Unknown session replay command')
214
- self.postMessage({ id, result: await commands[method](...params) });
223
+ if (!Object.hasOwn(commands, method)) throw new Error("Unknown session replay command");
224
+ globalThis.postMessage({ id, result: await commands[method](...params) });
215
225
  } catch (error) {
216
- self.postMessage({ id, error: error.message });
226
+ globalThis.postMessage({ error: error instanceof Error ? error.message : String(error), id });
217
227
  }
218
228
  });
219
229
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/renderer-process",
3
- "version": "30.57.1",
3
+ "version": "30.57.2",
4
4
  "keywords": [
5
5
  "lvce-editor",
6
6
  "renderer-process"