Skip to content

feat(#161): refactor string literals to typed enums #216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
Open
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -17,9 +17,11 @@ jobs:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: pnpm
@@ -28,4 +30,4 @@ jobs:
run: pnpm install --frozen-lockfile --prefer-offline

- name: Run ESLint
- run: pnpm lint
run: pnpm lint
6 changes: 5 additions & 1 deletion .github/workflows/prettier.yml
Original file line number Diff line number Diff line change
@@ -17,8 +17,10 @@ jobs:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
- uses: pnpm/action-setup@v4
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -33,3 +35,5 @@ jobs:
--ignore-path ./.prettierignore \
--no-error-on-unmatched-pattern \
'**/*.{js,jsx,ts,tsx,json}'


4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
**/dist
**/build
**/dist-zip
chrome-extension/manifest.js
chrome-extension
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@naaa760,
can we please revert these changes back?

chrome-extension/pre-build.tsconfig.tsbuildinfo
tsconfig.tsbuildinfo

@@ -19,7 +19,7 @@ tsconfig.tsbuildinfo
# etc
.DS_Store
.idea
**/.turbo


# compiled
**/tailwind-output.css
4 changes: 4 additions & 0 deletions .turbo/preferences/tui.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@naaa760,
this file will be regenerated at each startup.
So no need to modify it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for that.

Copy link
Author

@naaa760 naaa760 Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but i have not modify the .turbo folder at all.

"is_task_list_visible": true,
"active_task": null
}
119 changes: 61 additions & 58 deletions chrome-extension/src/background/index.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,21 @@ import 'webextension-polyfill';
import { v4 as uuidv4 } from 'uuid';

import { t } from '@extension/i18n';
import {
MessageType,
MessageAction,
CaptureState,
CaptureType,
RecordType,
RecordSource,
LogMethod,
InstallReason,
ContextType,
ContextMenuId,
ResponseStatus,
ImageFormat,
RequestProperty,
} from '@extension/shared';
import {
annotationsRedoStorage,
annotationsStorage,
@@ -23,7 +38,7 @@ chrome.tabs.onRemoved.addListener(async tabId => {

const captureTabId = await captureTabStorage.getCaptureTabId();
if (tabId === captureTabId) {
await captureStateStorage.setCaptureState('idle');
await captureStateStorage.setCaptureState(CaptureState.IDLE);
await captureTabStorage.setCaptureTabId(null);

annotationsStorage.setAnnotations([]);
@@ -33,7 +48,7 @@ chrome.tabs.onRemoved.addListener(async tabId => {
}
});

chrome.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
// If tab finished loading (refreshed), remove it from pending reload tabs
if (changeInfo.status === 'complete') {
const pendingTabIds = await pendingReloadTabsStorage.getAll();
@@ -49,12 +64,12 @@ chrome.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
captureTabStorage.getCaptureTabId(),
]);

if (!capturedTabId && state === 'unsaved') {
await captureStateStorage.setCaptureState('idle');
if (!capturedTabId && state === CaptureState.UNSAVED) {
await captureStateStorage.setCaptureState(CaptureState.IDLE);
}

if (tabId === capturedTabId) {
await captureStateStorage.setCaptureState('idle');
await captureStateStorage.setCaptureState(CaptureState.IDLE);
await captureTabStorage.setCaptureTabId(null);

annotationsStorage.setAnnotations([]);
@@ -66,71 +81,59 @@ chrome.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
* NOTE: Do Not Use async/await in onMessage listeners
*/
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'EXIT_CAPTURE') {
captureStateStorage.setCaptureState('idle');
if (message.type === MessageType.EXIT_CAPTURE) {
captureStateStorage.setCaptureState(CaptureState.IDLE);
captureTabStorage.setCaptureTabId(null);

annotationsStorage.setAnnotations([]);
annotationsRedoStorage.setAnnotations([]);
sendResponse({ status: 'success' });
sendResponse({ status: ResponseStatus.SUCCESS });
}

if (sender?.tab?.id) {
if (message.type === 'ADD_RECORD') {
if (message.type === MessageType.ADD_RECORD) {
// Merge fetch request data from content script
addOrMergeRecords(sender.tab.id, message.data);
sendResponse({ status: 'success' });
sendResponse({ status: ResponseStatus.SUCCESS });
}

if (message.type === 'GET_RECORDS') {
if (message.type === MessageType.GET_RECORDS) {
getRecords(sender.tab.id).then(records => sendResponse({ records }));
}
} else {
console.log('[Background] - Add Records: No sender id');
}

if (message.action === 'checkNativeCapture') {
if (message.action === MessageAction.CHECK_NATIVE_CAPTURE) {
sendResponse({ isAvailable: !!chrome.tabs?.captureVisibleTab });
}

if (message.action === 'captureVisibleTab') {
if (message.action === MessageAction.CAPTURE_VISIBLE_TAB) {
// Handle the async operation
chrome.tabs.captureVisibleTab(
null, // Current window
{ format: 'jpeg', quality: 100 },
dataUrl => {
if (chrome.runtime.lastError) {
console.error('Error capturing screenshot:', chrome.runtime.lastError);
sendResponse({ success: false, message: chrome.runtime.lastError.message });
} else {
sendResponse({ success: true, dataUrl });
}
},
);
chrome.tabs.captureVisibleTab({ format: ImageFormat.JPEG, quality: 100 }, dataUrl => {
if (chrome.runtime.lastError) {
console.error('Error capturing screenshot:', chrome.runtime.lastError);
sendResponse({ success: false, message: chrome.runtime.lastError.message });
} else {
sendResponse({ success: true, dataUrl });
}
});
}

return true; // Keep the connection open for async handling
});

chrome.runtime.onInstalled.addListener(async ({ reason }) => {
if (reason === 'install') {
/**
* Set unique identifier for the user
* to store reported bugs when no account
*/
if (reason === InstallReason.INSTALL) {
const userUuid = await userUUIDStorage.get();
if (!userUuid) await userUUIDStorage.update(uuidv4());

// Open a welcome page
// await chrome.tabs.create({ url: 'welcome.html' });
}

/**
* @todo
* find a better way to reload the tabs that are open when install/update happens.
* context: see issue: #24
*/
if (['install', 'update'].includes(reason)) {
if ([InstallReason.INSTALL, InstallReason.UPDATE].includes(reason as InstallReason)) {
chrome.tabs.query({}, tabs => {
tabs.forEach(tab => {
if (tab.id) {
@@ -142,43 +145,43 @@ chrome.runtime.onInstalled.addListener(async ({ reason }) => {

// Creates parent context menu item
chrome.contextMenus.create({
id: 'capture_parent',
id: ContextMenuId.CAPTURE_PARENT,
title: t('extensionName'),
contexts: ['all'],
contexts: [ContextType.ALL],
});

// Define the child options
const captureOptions = [
{ id: 'area', title: t('area') },
{ id: 'full-page', title: t('fullPage') },
{ id: 'viewport', title: t('viewport') },
{ id: CaptureType.AREA, title: t('area') },
{ id: CaptureType.FULL_PAGE, title: t('fullPage') },
{ id: CaptureType.VIEWPORT, title: t('viewport') },
];

captureOptions.forEach(({ id, title }) => {
chrome.contextMenus.create({
id,
parentId: 'capture_parent',
parentId: ContextMenuId.CAPTURE_PARENT,
title,
contexts: ['all'],
contexts: [ContextType.ALL],
});
});
});

chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (!tab?.id) return; //skip if tab is invalid

const type = info.menuItemId as 'area' | 'viewport' | 'full-page';
const type = info.menuItemId as CaptureType;

// Updates capture state and active tab
await captureStateStorage.setCaptureState('capturing');
await captureStateStorage.setCaptureState(CaptureState.CAPTURING);
await captureTabStorage.setCaptureTabId(tab.id);

// Sends message to contentScript to start capture
if (type) {
chrome.tabs.sendMessage(
tab.id,
{
action: 'START_SCREENSHOT',
action: MessageAction.START_SCREENSHOT,
payload: { type },
},
response => {
@@ -205,18 +208,18 @@ chrome.webRequest.onCompleted.addListener(
(request: chrome.webRequest.WebResponseCacheDetails) => {
const clonedRequest = structuredClone(request);
addOrMergeRecords(clonedRequest.tabId, {
recordType: 'network',
source: 'background',
recordType: RecordType.NETWORK,
source: RecordSource.BACKGROUND,
...clonedRequest,
});

if (clonedRequest.statusCode >= 400) {
addOrMergeRecords(clonedRequest.tabId, {
timestamp: Date.now(),
type: 'log',
recordType: 'console',
source: 'background',
method: 'error',
type: RecordType.CONSOLE,
recordType: RecordType.CONSOLE,
source: RecordSource.BACKGROUND,
method: LogMethod.ERROR,
args: [
`[${clonedRequest.type}] ${clonedRequest.method} ${clonedRequest.url} responded with status ${clonedRequest.statusCode}`,
clonedRequest,
@@ -236,24 +239,24 @@ chrome.webRequest.onCompleted.addListener(
chrome.webRequest.onBeforeRequest.addListener(
(request: chrome.webRequest.WebRequestBodyDetails) => {
addOrMergeRecords(request.tabId, {
recordType: 'network',
source: 'background',
recordType: RecordType.NETWORK,
source: RecordSource.BACKGROUND,
...structuredClone(request),
});
},
{ urls: ['<all_urls>'] },
['requestBody'],
[RequestProperty.REQUEST_BODY],
);

// Listener for onBeforeSendHeaders
chrome.webRequest.onBeforeSendHeaders.addListener(
(request: chrome.webRequest.WebRequestHeadersDetails) => {
addOrMergeRecords(request.tabId, {
recordType: 'network',
source: 'background',
recordType: RecordType.NETWORK,
source: RecordSource.BACKGROUND,
...structuredClone(request),
});
},
{ urls: ['<all_urls>'] },
['requestHeaders'],
[RequestProperty.REQUEST_HEADERS],
);
6 changes: 2 additions & 4 deletions chrome-extension/src/utils/manage-records.util.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { v4 as uuidv4 } from 'uuid';

import { deepRedactSensitiveInfo } from '@extension/shared';
import { deepRedactSensitiveInfo, RecordType } from '@extension/shared';

const restricted = ['https://api.briehq.com']; // 'extend.iife', 'kbmbnelnoppneadncmmkfikbcgmilbao' Note: it blocks the logs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see some differences between current changes in develop branch.
Can you pull the latests changes from develop branch?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@naaa760,
I still see the issue with missing changes that are not matching the develop branch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@naaa760,
can you please pull the latest changes from upstream develop branch?

const invalidRecord = (entity: string) => restricted.some(word => entity.includes(word));

const tabRecordsMap = new Map<number, Map<string, any>>();

export type RecordType = 'events' | 'network' | 'console' | 'cookies';

export interface Record {
recordType: RecordType;
url?: string;
@@ -50,7 +48,7 @@ export const addOrMergeRecords = async (tabId: number, record: Record | any): Pr
const uuid = uuidv4();

try {
if (record.recordType !== 'network') {
if (record.recordType !== RecordType.NETWORK) {
recordsMap.set(uuid, { uuid, ...deepRedactSensitiveInfo(record, tabUrl) });
return;
}
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -53,17 +53,17 @@
"dependencies": {
"html2canvas": "^1.4.1",
"react": "19.1.0",
"react-dom": "19.1.0"
"react-dom": "19.1.0",
"uuid": "^11.1.0"
},
"devDependencies": {
"prettier-plugin-tailwindcss": "^0.6.12",
"@eslint/compat": "^1.2.5",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.20.0",
"@types/chrome": "0.0.304",
"@types/eslint-plugin-jsx-a11y": "^6.10.0",
"@types/eslint__eslintrc": "^2.1.2",
"@types/eslint__js": "^8.42.3",
"@types/eslint-plugin-jsx-a11y": "^6.10.0",
"@types/node": "^22.14.1",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
@@ -92,6 +92,7 @@
"postcss": "^8.5.2",
"postcss-load-config": "^6.0.1",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.12",
"rimraf": "^6.0.1",
"run-script-os": "^1.1.6",
"tailwindcss": "^3.4.17",
4 changes: 3 additions & 1 deletion packages/shared/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Shared Package

This package contains code shared with other packages.

// This package contains code shared with other packages.
To use the code in the package, you need to add the following to the package.json file.


```json
{
"dependencies": {
11 changes: 11 additions & 0 deletions packages/shared/lib/constants/enums/capture-states.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export enum CaptureState {
IDLE = 'idle',
CAPTURING = 'capturing',
UNSAVED = 'unsaved',
}

export enum CaptureType {
AREA = 'area',
FULL_PAGE = 'full-page',
VIEWPORT = 'viewport',
}
27 changes: 27 additions & 0 deletions packages/shared/lib/constants/enums/chrome-extension.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export enum InstallReason {
INSTALL = 'install',
UPDATE = 'update',
}

export enum ContextType {
ALL = 'all',
}

export enum ContextMenuId {
CAPTURE_PARENT = 'capture_parent',
}

export enum ResponseStatus {
SUCCESS = 'success',
ERROR = 'error',
}

export enum ImageFormat {
JPEG = 'jpeg',
PNG = 'png',
}

export enum RequestProperty {
REQUEST_BODY = 'requestBody',
REQUEST_HEADERS = 'requestHeaders',
}
32 changes: 32 additions & 0 deletions packages/shared/lib/constants/enums/events.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export enum EventType {
NAVIGATE = 'Navigate',
DOM_CONTENT_LOADED = 'DOMContentLoaded',
PAGE_LOADED = 'PageLoaded',
TAB_HIDDEN = 'TabHidden',
TAB_VISIBLE = 'TabVisible',
INPUT_CHANGE = 'InputChange',
SELECT_OPTION = 'SelectOption',
RESIZE = 'Resize',
CAPTURE = 'capture',
}

export enum CustomEventType {
METADATA = 'metadata',
CLOSE_MODAL = 'CLOSE_MODAL',
DISPLAY_MODAL = 'DISPLAY_MODAL',
}

export enum EventDomain {
SCREENSHOT = 'screenshot',
}

export enum NavigationMethod {
PUSH_STATE = 'pushState',
REPLACE_STATE = 'replaceState',
POP_STATE = 'popstate',
}

export enum VisibilityState {
HIDDEN = 'hidden',
VISIBLE = 'visible',
}
18 changes: 18 additions & 0 deletions packages/shared/lib/constants/enums/index.ts
Original file line number Diff line number Diff line change
@@ -3,3 +3,21 @@ export { SlicePriority, SliceStatus, SliceType } from './slices/index.js';
export { AuthMethod } from './auth-method.enum.js';
export { Plan } from './plan.enum.js';
export { SubscriptionStatus } from './subscription-status.enum.js';

// Chrome Extension Enums
export { MessageType, MessageAction } from './message-types.enum.js';

export { CaptureState, CaptureType } from './capture-states.enum.js';

export { RecordType, RecordSource, LogMethod } from './record-types.enum.js';

export {
InstallReason,
ContextType,
ContextMenuId,
ResponseStatus,
ImageFormat,
RequestProperty,
} from './chrome-extension.enum.js';

export { EventType, CustomEventType, EventDomain, NavigationMethod, VisibilityState } from './events.enum.js';
13 changes: 13 additions & 0 deletions packages/shared/lib/constants/enums/message-types.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export enum MessageType {
EXIT_CAPTURE = 'EXIT_CAPTURE',
ADD_RECORD = 'ADD_RECORD',
GET_RECORDS = 'GET_RECORDS',
}

export enum MessageAction {
CHECK_NATIVE_CAPTURE = 'checkNativeCapture',
CAPTURE_VISIBLE_TAB = 'captureVisibleTab',
START_SCREENSHOT = 'START_SCREENSHOT',
EXIT_CAPTURE = 'EXIT_CAPTURE',
CLOSE_MODAL = 'CLOSE_MODAL',
}
21 changes: 21 additions & 0 deletions packages/shared/lib/constants/enums/record-types.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export enum RecordType {
EVENTS = 'events',
NETWORK = 'network',
CONSOLE = 'console',
COOKIES = 'cookies',
LOCAL_STORAGE = 'local-storage',
SESSION_STORAGE = 'session-storage',
}

export enum RecordSource {
BACKGROUND = 'background',
CONTENT_SCRIPT = 'content-script',
CLIENT = 'client',
}

export enum LogMethod {
ERROR = 'error',
WARN = 'warn',
INFO = 'info',
LOG = 'log',
}
58 changes: 32 additions & 26 deletions packages/vite-config/lib/withPageConfig.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import env, { IS_DEV, IS_PROD } from '@extension/env';
import { watchRebuildPlugin } from '@extension/hmr';
import react from '@vitejs/plugin-react-swc';
import deepmerge from 'deepmerge';
import type { UserConfig } from 'vite';
import { defineConfig } from 'vite';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
import type { UserConfig } from 'vite';

import env, { IS_DEV, IS_PROD } from '@extension/env';
import { watchRebuildPlugin } from '@extension/hmr';

export const watchOption = IS_DEV
? {
@@ -14,26 +14,32 @@ export const watchOption = IS_DEV
}
: undefined;

export const withPageConfig = (config: UserConfig) =>
defineConfig(
deepmerge(
{
define: {
'process.env': env,
},
base: '',
plugins: [react(), IS_DEV && watchRebuildPlugin({ refresh: true }), nodePolyfills()],
build: {
sourcemap: IS_DEV,
minify: IS_PROD,
reportCompressedSize: IS_PROD,
emptyOutDir: IS_PROD,
watch: watchOption,
rollupOptions: {
external: ['chrome'],
},
},
export const withPageConfig = (config: UserConfig) => {
const plugins: any[] = [react(), nodePolyfills()];

if (IS_DEV) {
plugins.push(watchRebuildPlugin({ refresh: true }));
}

const mergedConfig = {
define: {
'process.env': env,
},
base: '',
plugins: [...plugins, ...(config.plugins || [])],
build: {
sourcemap: IS_DEV,
minify: IS_PROD,
reportCompressedSize: IS_PROD,
emptyOutDir: IS_PROD,
watch: watchOption,
rollupOptions: {
external: ['chrome'],
...config.build?.rollupOptions,
},
config,
),
);
},
...config,
};

return defineConfig(mergedConfig as any);
};
6 changes: 4 additions & 2 deletions pages/content/src/capture/screen.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import html2canvas from 'html2canvas';

import { MessageType } from '@extension/shared';

const isFirefox = process.env.__FIREFOX__ === 'true';

let startX: number, startY: number;
@@ -251,7 +253,7 @@ const onKeyDown = (e: KeyboardEvent) => {
cleanup(); // Cleanup on ESC

// Notify Background on ESC
chrome.runtime.sendMessage({ type: 'EXIT_CAPTURE' });
chrome.runtime.sendMessage({ type: MessageType.EXIT_CAPTURE });
}
};

@@ -261,7 +263,7 @@ const onTouchStart = (e: TouchEvent) => {
startX = e.touches[0].pageX;
startY = e.touches[0].pageY;

createSelectionBox(startX, startY);
createSelectionBox();
e.preventDefault();
};

9 changes: 5 additions & 4 deletions pages/content/src/capture/screenshot.capture.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import html2canvas from 'html2canvas';

import { t } from '@extension/i18n';
import { MessageType, MessageAction } from '@extension/shared';

let lastPointerX = 0;
let lastPointerY = 0;
@@ -250,7 +251,7 @@ const onKeyDown = (e: KeyboardEvent) => {
cleanup(); // Cleanup on ESC

// Notify Background on ESC
chrome.runtime.sendMessage({ type: 'EXIT_CAPTURE' });
chrome.runtime.sendMessage({ type: MessageType.EXIT_CAPTURE });
}
};

@@ -359,7 +360,7 @@ const showInstructions = () => {

const captureTab = (): Promise<string> =>
new Promise((resolve, reject) => {
chrome.runtime.sendMessage({ action: 'captureVisibleTab' }, response => {
chrome.runtime.sendMessage({ action: MessageAction.CAPTURE_VISIBLE_TAB }, response => {
if (chrome.runtime.lastError) {
// Error from Chrome's runtime
console.log('chrome.runtime.lastError.message', chrome.runtime.lastError.message);
@@ -378,7 +379,7 @@ const captureTab = (): Promise<string> =>

const checkIfNativeCaptureAvailable = () =>
new Promise(resolve => {
chrome.runtime.sendMessage({ action: 'checkNativeCapture' }, response => {
chrome.runtime.sendMessage({ action: MessageAction.CHECK_NATIVE_CAPTURE }, response => {
resolve(response?.isAvailable || false);
});
});
@@ -483,7 +484,7 @@ const saveAndNotify = ({ secondary, primary }: { secondary: string; primary: str
*/
window.postMessage(
{
type: 'ADD_RECORD',
type: MessageType.ADD_RECORD,
payload: {
type: 'event',
event: 'capture',
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { MessageAction } from '@extension/shared';

import { cleanup, startScreenshotCapture } from '@src/capture';

export const addRuntimeEventListeners = () => {
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.action === 'START_SCREENSHOT') {
if (msg.action === MessageAction.START_SCREENSHOT) {
window.dispatchEvent(new CustomEvent('metadata'));

startScreenshotCapture(msg.payload);
}

if (msg.action === 'EXIT_CAPTURE') {
if (msg.action === MessageAction.EXIT_CAPTURE) {
cleanup();
}

if (msg.action === 'CLOSE_MODAL') {
if (msg.action === MessageAction.CLOSE_MODAL) {
window.dispatchEvent(new CustomEvent('CLOSE_MODAL'));
}
});
6 changes: 4 additions & 2 deletions pages/content/src/event-listeners/window.event-listeners.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { MessageType } from '@extension/shared';

export const addWindowEventListeners = () => {
/**
* If you're injecting JavaScript into the webpage (e.g., to override fetch), remember:
@@ -7,9 +9,9 @@ export const addWindowEventListeners = () => {
window.addEventListener('message', event => {
if (event.source !== window || !event.data.type) return;

if (event.data.type === 'ADD_RECORD') {
if (event.data.type === MessageType.ADD_RECORD) {
try {
chrome.runtime.sendMessage({ type: 'ADD_RECORD', data: event.data.payload });
chrome.runtime.sendMessage({ type: MessageType.ADD_RECORD, data: event.data.payload });
} catch (err) {
console.error('[sendMessage error]', chrome.runtime.id, err);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { safePostMessage } from '@extension/shared';
import { safePostMessage, MessageType, RecordType, RecordSource } from '@extension/shared';

interface Cookie {
key: string;
@@ -22,5 +22,10 @@ export const interceptCookies = () => {
return ac;
}, []);

safePostMessage('ADD_RECORD', { timestamp, recordType: 'cookies', source: 'client', items: cookies });
safePostMessage(MessageType.ADD_RECORD, {
timestamp,
recordType: RecordType.COOKIES,
source: RecordSource.CLIENT,
items: cookies,
});
};
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import { safePostMessage } from '@extension/shared';
import { safePostMessage, MessageType, RecordType, RecordSource } from '@extension/shared';

// Get all localStorage data
export const interceptLocalStorage = () => {
const timestamp = Date.now();
const localStorageData = [];
try {
const timestamp = Date.now();
const localStorageData: Record<string, string> = {};

for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (!key) continue; // Skip null keys
// Get all keys from localStorage
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key) {
localStorageData[key] = localStorage.getItem(key) || '';
}
}

const value = localStorage.getItem(key);
localStorageData.push({
key,
value,
// post message to background/content
safePostMessage(MessageType.ADD_RECORD, {
timestamp,
recordType: RecordType.LOCAL_STORAGE,
source: RecordSource.CLIENT,
items: localStorageData,
});
} catch (error) {
console.error('Error accessing localStorage:', error);
}

safePostMessage('ADD_RECORD', { timestamp, recordType: 'local-storage', source: 'client', items: localStorageData });
};
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import { safePostMessage } from '@extension/shared';
import { safePostMessage, MessageType, RecordType, RecordSource } from '@extension/shared';

// Get all sessionStorage data
export const interceptSessionStorage = () => {
const timestamp = Date.now();
const sessionStorageData = [];
try {
const timestamp = Date.now();
const sessionStorageData: Record<string, string> = {};

for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
if (!key) continue; // Skip null keys
// Get all keys from sessionStorage
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
if (key) {
sessionStorageData[key] = sessionStorage.getItem(key) || '';
}
}

const value = sessionStorage.getItem(key);
sessionStorageData.push({
key,
value,
// post message to background/content
safePostMessage(MessageType.ADD_RECORD, {
timestamp,
recordType: RecordType.SESSION_STORAGE,
source: RecordSource.CLIENT,
items: sessionStorageData,
});
} catch (error) {
console.error('Error accessing sessionStorage:', error);
}

safePostMessage('ADD_RECORD', {
timestamp,
recordType: 'session-storage',
source: 'client',
items: sessionStorageData,
});
};
46 changes: 35 additions & 11 deletions pages/content/src/interceptors/console/console.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { safePostMessage, safeStructuredClone } from '@extension/shared';
import { safePostMessage, safeStructuredClone, MessageType, RecordType, RecordSource } from '@extension/shared';

export const interceptConsole = () => {
const originalConsole = {
@@ -38,9 +38,9 @@ export const interceptConsole = () => {
const sanitizedArgs = args.map(sanitizeArg);

const logData: Record<string, any> = {
type: 'log',
recordType: 'console',
source: 'client',
type: RecordType.CONSOLE,
recordType: RecordType.CONSOLE,
source: RecordSource.CLIENT,
method,
timestamp,
args: sanitizedArgs,
@@ -55,16 +55,40 @@ export const interceptConsole = () => {
};
}

safePostMessage('ADD_RECORD', logData);
safePostMessage(MessageType.ADD_RECORD, logData);
} catch {
// Don't throw or break host page
}
};

['log', 'warn', 'error', 'info', 'debug', 'table'].forEach(method => {
console[method] = (...args: any[]) => {
captureLog(method, args);
originalConsole[method](...args);
};
});
// Override each console method individually to avoid type issues
console.log = (...args: unknown[]) => {
captureLog('log', args);
originalConsole.log(...args);
};

console.warn = (...args: unknown[]) => {
captureLog('warn', args);
originalConsole.warn(...args);
};

console.error = (...args: unknown[]) => {
captureLog('error', args);
originalConsole.error(...args);
};

console.info = (...args: unknown[]) => {
captureLog('info', args);
originalConsole.info(...args);
};

console.debug = (...args: unknown[]) => {
captureLog('debug', args);
originalConsole.debug(...args);
};

console.table = (...args: unknown[]) => {
captureLog('table', args);
originalConsole.table(...args);
};
};
18 changes: 9 additions & 9 deletions pages/content/src/interceptors/network/fetch.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { safePostMessage } from '@extension/shared';
import { safePostMessage, MessageType, RecordType, RecordSource, LogMethod } from '@extension/shared';

import { extractQueryParams } from '@src/utils';

@@ -96,20 +96,20 @@ export const interceptFetch = (): void => {
status: responseClone.status,
};

safePostMessage('ADD_RECORD', {
recordType: 'network',
source: 'client',
safePostMessage(MessageType.ADD_RECORD, {
recordType: RecordType.NETWORK,
source: RecordSource.CLIENT,
timestamp,
...payload,
});

if (responseClone.status >= 400) {
safePostMessage('ADD_RECORD', {
safePostMessage(MessageType.ADD_RECORD, {
timestamp,
type: 'log',
recordType: 'console',
source: 'client',
method: 'error',
type: RecordType.CONSOLE,
recordType: RecordType.CONSOLE,
source: RecordSource.CLIENT,
method: LogMethod.ERROR,
args: [`[Fetch] ${method} ${url} responded with status ${responseClone.status}`, payload],
stackTrace: {
parsed: 'interceptFetch',
30 changes: 16 additions & 14 deletions pages/content/src/interceptors/network/xhr.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { safePostMessage } from '@extension/shared';
import { safePostMessage, MessageType, RecordType, RecordSource, LogMethod } from '@extension/shared';

// Define interfaces for request details and payload
interface RequestDetails {
@@ -23,15 +23,17 @@ export const interceptXHR = (): void => {
this: ExtendedXMLHttpRequest,
method: string,
url: string | URL,
...rest: any[]
async: boolean = true,
username?: string | null,
password?: string | null,
): void {
this._requestDetails = {
method,
url: url.toString(),
requestStart: new Date().toISOString(),
requestBody: null,
};
originalOpen.apply(this, [method, url, ...rest]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@naaa760, can you please elaborate a bit on this change?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change refactors the XHR interceptor to use the new enums (RecordType, RecordSource, LogMethod) instead of hardcoded strings like 'network', 'client', and 'error' for better type safety.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I see the username, password has been added, why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

originalOpen.apply(this, [method, url, async, username, password]);
};

// Intercept XMLHttpRequest send method
@@ -45,7 +47,7 @@ export const interceptXHR = (): void => {

const originalOnReadyStateChange = this.onreadystatechange;

this.onreadystatechange = function (this: ExtendedXMLHttpRequest, ...args: any[]): void {
this.onreadystatechange = function (this: ExtendedXMLHttpRequest, ev: Event): void {
if (this.readyState === 4 && this._requestDetails) {
// Request completed
const endTime = new Date().toISOString();
@@ -93,20 +95,20 @@ export const interceptXHR = (): void => {
responseBody,
};

safePostMessage('ADD_RECORD', {
safePostMessage(MessageType.ADD_RECORD, {
recordType: RecordType.NETWORK,
source: RecordSource.CLIENT,
timestamp,
recordType: 'network',
source: 'client',
...payload,
});

if (this.status >= 400) {
safePostMessage('ADD_RECORD', {
type: 'log',
recordType: 'console',
source: 'client',
method: 'error',
timestamp: Date.now(),
safePostMessage(MessageType.ADD_RECORD, {
timestamp,
type: RecordType.CONSOLE,
recordType: RecordType.CONSOLE,
source: RecordSource.CLIENT,
method: LogMethod.ERROR,
args: [
`[XHR] ${this._requestDetails.method} ${this._requestDetails.url} responded with status ${this.status}`,
payload,
@@ -128,7 +130,7 @@ export const interceptXHR = (): void => {

// Call the original onreadystatechange handler if defined
if (originalOnReadyStateChange) {
originalOnReadyStateChange.apply(this, args);
originalOnReadyStateChange.call(this, ev);
}
};

3 changes: 3 additions & 0 deletions pnpm-lock.yaml