rrweb extension implementation (#1044)
* feat: add rrweb web-extension package * refactor: make the extension suitable for manifest v3 * update tsconfig.json * use version_name rather than recorder_version in manifest.json * update manifest.json * enable to keep recording after changing tabs * enable to record between tabs and urls * fix CI error * try to fix CI error * feat: add pause and resume buttons * feat: add a link to new session after recording * improve session list * refactor: migrate session storage from chrome local storage to indexedDB * feat: add pagination to session list * fix: multiple recorders are started after pausing and resuming process * fix: can't stop recording on firefox browser * update type import of 'eventWithTime' * fix CI error * doc: add readme * Apply suggestions from Justin's code review Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com> * refactor: make use of webNavigation API to implement recording consistent during page navigation * fix firefox compatibility issue and add title to pages * add mouseleave listener to enhance the recording liability * fix firefox compatibility issue and improve the experience of recording resume after closing tabs * update tsconfig * upgrade vite-plugin-web-extension config to fix some bugs on facebook web page * update import links * refactor: cross tab recording mechanism apply Justin's suggestion * refactor: slipt util/index.ts into multiple files * implement cross-origin iframe recording * fix: regression of issue: ShadowHost can't be a string (issue 941) * refactor shadow dom recording to make tests cover key code * Apply formatting changes * increase the node memory limitation to avoid CI failure * Create lovely-pears-cross.md * Apply formatting changes * Update packages/web-extension/package.json * Update .changeset/lovely-pears-cross.md * update change logs * delete duplicated property --------- Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>
This commit is contained in:
90
packages/web-extension/src/utils/storage.ts
Normal file
90
packages/web-extension/src/utils/storage.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { openDB } from 'idb';
|
||||
import { eventWithTime } from '@rrweb/types';
|
||||
import { Session } from '~/types';
|
||||
|
||||
/**
|
||||
* Storage related functions with indexedDB.
|
||||
*/
|
||||
|
||||
const EventStoreName = 'events';
|
||||
type EventData = {
|
||||
id: string;
|
||||
events: eventWithTime[];
|
||||
};
|
||||
|
||||
export async function getEventStore() {
|
||||
return openDB<EventData>(EventStoreName, 1, {
|
||||
upgrade(db) {
|
||||
db.createObjectStore(EventStoreName, {
|
||||
keyPath: 'id',
|
||||
autoIncrement: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getEvents(id: string) {
|
||||
const db = await getEventStore();
|
||||
const data = (await db.get(EventStoreName, id)) as EventData;
|
||||
return data.events;
|
||||
}
|
||||
|
||||
const SessionStoreName = 'sessions';
|
||||
export async function getSessionStore() {
|
||||
return openDB<Session>(SessionStoreName, 1, {
|
||||
upgrade(db) {
|
||||
// Create a store of objects
|
||||
db.createObjectStore(SessionStoreName, {
|
||||
// The 'id' property of the object will be the key.
|
||||
keyPath: 'id',
|
||||
// If it isn't explicitly set, create a value by auto incrementing.
|
||||
autoIncrement: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveSession(session: Session, events: eventWithTime[]) {
|
||||
const eventStore = await getEventStore();
|
||||
await eventStore.put(EventStoreName, { id: session.id, events });
|
||||
const store = await getSessionStore();
|
||||
await store.add(SessionStoreName, session);
|
||||
}
|
||||
|
||||
export async function getSession(id: string) {
|
||||
const store = await getSessionStore();
|
||||
return store.get(SessionStoreName, id) as Promise<Session>;
|
||||
}
|
||||
|
||||
export async function getAllSessions() {
|
||||
const store = await getSessionStore();
|
||||
const sessions = (await store.getAll(SessionStoreName)) as Session[];
|
||||
return sessions.sort((a, b) => b.createTimestamp - a.createTimestamp);
|
||||
}
|
||||
|
||||
export async function deleteSession(id: string) {
|
||||
const eventStore = await getEventStore();
|
||||
const sessionStore = await getSessionStore();
|
||||
await Promise.all([
|
||||
eventStore.delete(EventStoreName, id),
|
||||
sessionStore.delete(SessionStoreName, id),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function deleteSessions(ids: string[]) {
|
||||
const eventStore = await getEventStore();
|
||||
const sessionStore = await getSessionStore();
|
||||
const eventTransition = eventStore.transaction(EventStoreName, 'readwrite');
|
||||
const sessionTransition = sessionStore.transaction(
|
||||
SessionStoreName,
|
||||
'readwrite',
|
||||
);
|
||||
const promises = [];
|
||||
for (const id of ids) {
|
||||
promises.push(eventTransition.store.delete(id));
|
||||
promises.push(sessionTransition.store.delete(id));
|
||||
}
|
||||
await Promise.all(promises).then(() => {
|
||||
return Promise.all([eventTransition.done, sessionTransition.done]);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user