Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cb64521
Initial plan
Copilot Aug 22, 2025
fb5c90a
Add core DOM watcher infrastructure with action integration
Copilot Aug 22, 2025
33eb9cf
Add DevTools integration and messaging for DOM watcher
Copilot Aug 22, 2025
96e1b61
Complete DOM watcher implementation with UI and documentation
Copilot Aug 22, 2025
dfeaf47
Add sequential action restart modes for DOM watcher
Copilot Aug 27, 2025
849340b
Merge branch 'main' into copilot/fix-667
dharmesh-hemaram Aug 27, 2025
eb75504
Merge branch 'main' into copilot/fix-667
dharmesh-hemaram Aug 27, 2025
5307c51
fix: add lang attribute to html tag for accessibility
dharmesh-hemaram Aug 27, 2025
366a243
Update apps/acf-options-page/src/store/config/action/settings/action-…
dharmesh-hemaram Aug 28, 2025
08416a7
Update apps/acf-options-page/src/store/config/action/settings/action-…
dharmesh-hemaram Aug 28, 2025
89d4a6d
Merge branch 'main' into copilot/fix-667
dharmesh-hemaram Aug 28, 2025
2234b28
Update i18next to version 25.4.2 and remove isbot dependency from pac…
dharmesh-hemaram Aug 28, 2025
ac1dc21
still working
dharmesh-hemaram Aug 28, 2025
7ad38ab
feat: Add Watch Settings Modal and integrate with action configuration
dharmesh-hemaram Aug 28, 2025
20170c0
Merge branch 'main' into copilot/fix-667
dharmesh-hemaram Aug 28, 2025
f2f86f0
Move DOM watcher from action-level to configuration-level settings
Copilot Aug 28, 2025
6f236a3
refactor: Remove DOM watcher from action and batch processing, integr…
dharmesh-hemaram Aug 28, 2025
5638e1d
Fixed lint error
dharmesh-hemaram Aug 28, 2025
b386572
refactor: remove DOM watcher implementation and related DevTools inte…
dharmesh-hemaram Aug 29, 2025
6258183
fix: correct filtering logic in filterProcessedElements function
dharmesh-hemaram Aug 29, 2025
83e968b
fix: ensure elements are processed after filtering in start function
dharmesh-hemaram Aug 29, 2025
91fc60b
Refactor code structure for improved readability and maintainability
dharmesh-hemaram Aug 29, 2025
d09ab17
Fixed lint error
dharmesh-hemaram Aug 29, 2025
e085b30
fix: update UUID generation in config-watch tests and remove outdated…
dharmesh-hemaram Aug 29, 2025
34111f8
fix: add missing dependency for @firebase/util in package.json
dharmesh-hemaram Aug 29, 2025
23edcb8
fix: update SWC dependencies and remove unused @firebase/util dependency
dharmesh-hemaram Aug 29, 2025
d16cbcd
Implement feature X to enhance user experience and fix bug Y in module Z
dharmesh-hemaram Aug 29, 2025
ccc3b1c
Update packages/acf/common/src/lib/model/IWatch.ts
dharmesh-hemaram Aug 29, 2025
4a5f6a4
Update site/src/content/html/textarea.html
dharmesh-hemaram Aug 29, 2025
754885f
Update apps/acf-extension/src/content_scripts/util/dom-watch-manager.ts
dharmesh-hemaram Aug 29, 2025
5a249b7
Update apps/acf-options-page/src/modal/config-watch/watch-settings.tsx
dharmesh-hemaram Aug 29, 2025
bf30a8a
Fixed lint error
dharmesh-hemaram Aug 29, 2025
294ce4f
Remove Verdaccio config and update package-lock.json and package.json…
dharmesh-hemaram Aug 29, 2025
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
4 changes: 1 addition & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,15 @@ VITE_PUBLIC_NAME=__MSG_EXT_NAME_DEV__

# ------------------- Extension
KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxO2GOH63WJB07mAFB2/pfkIYPg27J4Scwyv/Z1P/hl0iRB9kCSFBRrYkt+vX9CToZvE/MmwExxFWxhAXpvz0yQFmB8trUQxP/ubc6TePQQ5NiuE6bg+qlfLqaNPfNojJfZZSCe5HYSVEGvW1hxRhiXfRCy26NyRu/Y6D7CPCHfhkGDoGLExpVW1zEqfUcRPBh5cedlwFxFtAbd1OzmZnPllMOW+AMBP6GqlybkGuxpys4xt+32lVq3kb36LogdudQRe9geq9qKnk0tFNiqMe5QR04c0+IRuq3IGHLre+4peViG2w7bBgGY0r4ZxQ1/sOvoeh5XGdKPQKFF61Io5hqwIDAQAB
UNINSTALL_URL=https://forms.gle/49dAyTL1pLNEcvu76

# -------------------- Web App
INLINE_RUNTIME_CHUNK=false

# Extension
VITE_PUBLIC_CHROME_EXTENSION_ID=blfahkipmbikdeipdaogkjggbmgbippe
VITE_PUBLIC_EDGE_EXTENSION_ID=blfahkipmbikdeipdaogkjggbmgbippe

# i18n
VITE_PUBLIC_I18N=http://localhost:3000/locales
VITE_PUBLIC_I18N=/locales

# Firebase
VITE_PUBLIC_FIREBASE_API_KEY=
Expand Down
2 changes: 1 addition & 1 deletion .github/instructions/nx.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ applyTo: '**'

// This file is automatically generated by Nx Console

You are in an nx workspace using Nx 21.2.3 and npm as the package manager.
You are in an nx workspace using Nx 21.3.11 and npm as the package manager.

You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user:

Expand Down
45 changes: 0 additions & 45 deletions DISCUSSION.md

This file was deleted.

3 changes: 1 addition & 2 deletions apps/acf-extension/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
"cwd": "apps/acf-extension",
"args": ["--node-env=production", "--watch"]
},
"continuous": true,
"dependsOn": ["watch-deps"]
"continuous": true
}
}
}
2 changes: 1 addition & 1 deletion apps/acf-extension/src/content_scripts/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const ActionProcessor = (() => {
const process = async (action: IAction) => {
const elementFinder = await ACFValue.getValue(action.elementFinder);
const elements = await Common.start(elementFinder, action.settings);
if (elements === undefined) {
if (elements === undefined || elements.length === 0) {
throw EActionStatus.SKIPPED;
}
const value = action.value ? await ACFValue.getValue(action.value, action.settings) : action.value;
Expand Down
4 changes: 2 additions & 2 deletions apps/acf-extension/src/content_scripts/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ const Actions = (() => {
await statusBar.wait(action.initWait, STATUS_BAR_TYPE.ACTION_WAIT);
await AddonProcessor.check(action.addon, action.settings);
if (action.type === 'userscript') {
action.status = await UserScriptProcessor.start(action as IUserScript);
action.status = await UserScriptProcessor.start(action);
} else {
action.status = await ActionProcessor.start(action as IAction);
action.status = await ActionProcessor.start(action);
}
notify(action);
} catch (error) {
Expand Down
20 changes: 20 additions & 0 deletions apps/acf-extension/src/content_scripts/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SettingsStorage } from '@dhruv-techapps/acf-store';
import { ConfigError } from '@dhruv-techapps/core-common';
import { I18N_ERROR } from './i18n';
import { statusBar } from './status-bar';
import DomWatchManager from './util/dom-watch-manager';

const Common = (() => {
const retryFunc = async (retry?: number, retryInterval?: number | string) => {
Expand All @@ -15,6 +16,19 @@ const Common = (() => {
return false;
};

const setElementProcessed = (elements: Array<HTMLElement>) => {
elements.forEach((element) => {
element.setAttribute('data-acf-processed', 'true');
});
};

const filterProcessedElements = (elements: Array<HTMLElement>): Array<HTMLElement> => {
return elements.filter((element) => {
// Add your filtering logic here
return !element.hasAttribute('data-acf-processed');
});
};

const getElements = async (document: Document, elementFinder: string, retry: number, retryInterval: number | string): Promise<Array<HTMLElement> | undefined> => {
let elements: HTMLElement[] | undefined;
try {
Expand Down Expand Up @@ -132,6 +146,12 @@ const Common = (() => {
if (!elements || elements.length === 0) {
return checkRetryOption(retryOption, elementFinder, retryGoto);
}

if (DomWatchManager.getStatus().isActive && elements) {
elements = filterProcessedElements(elements);
}

setElementProcessed(elements);
return elements;
};

Expand Down
18 changes: 18 additions & 0 deletions apps/acf-extension/src/content_scripts/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import { GoogleAnalyticsService } from '@dhruv-techapps/shared-google-analytics'
import { GoogleSheetsCS } from '@dhruv-techapps/shared-google-sheets';
import { STATUS_BAR_TYPE } from '@dhruv-techapps/shared-status-bar';
import { scope } from '../common/instrument';
import Actions from './actions';
import BatchProcessor from './batch';
import Common from './common';
import { Hotkey } from './hotkey';
import { I18N_COMMON } from './i18n';
import { statusBar } from './status-bar';
import DomWatchManager from './util/dom-watch-manager';
import GoogleSheets from './util/google-sheets';

const CONFIG_I18N = {
Expand Down Expand Up @@ -45,6 +47,20 @@ const ConfigProcessor = (() => {
return events;
};

const InitializeDomWatcher = (config: IConfiguration) => {
// If watch settings are provided and enabled, set up DOM watcher for the entire configuration
if (config.watch?.watchEnabled) {
// Set up the sequence restart callback for DOM watcher
DomWatchManager.setSequenceRestartCallback(async () => {
console.debug(`Actions: Restarting entire action sequence due to DOM changes`);
await Actions.start(config.actions, window.ext.__batchRepeat + 1);
});

// Register the configuration-level DOM watcher after actions complete initially
DomWatchManager.registerConfiguration(config.watch);
}
};

const start = async (config: IConfiguration) => {
try {
window.ext.__sessionCount = new Session(config.id).getCount();
Expand All @@ -53,7 +69,9 @@ const ConfigProcessor = (() => {
}
const sheets = GoogleSheets.getSheets(config);
window.ext.__sheets = await new GoogleSheetsCS().getValues(sheets, config.spreadsheetId);
// Clear any existing DOM watchers before starting new actions
await BatchProcessor.start(config.actions, config.batch);
InitializeDomWatcher(config);
const { notifications } = await new SettingsStorage().getSettings();
if (notifications) {
const { onConfig, sound, discord } = notifications;
Expand Down
1 change: 1 addition & 0 deletions apps/acf-extension/src/content_scripts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ self.onerror = (...rest) => {

chrome.runtime.onMessage.addListener(async (message) => {
const { action, configId } = message;

if (action === RUNTIME_MESSAGE_ACF.RUN_CONFIG) {
try {
new ConfigStorage().getConfigById(configId).then(async (config) => {
Expand Down
175 changes: 175 additions & 0 deletions apps/acf-extension/src/content_scripts/util/dom-watch-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { IWatchSettings, defaultWatchSettings } from '@dhruv-techapps/acf-common';

interface DomWatchState {
isActive: boolean;
observer: MutationObserver | null;
watchSettings: IWatchSettings | null;
debounceTimeout: number | null;
currentUrl: string;
startTime: number;
sequenceRestartCallback?: () => Promise<void>;
}

const DomWatchManager = (() => {
const state: DomWatchState = {
isActive: false,
observer: null,
watchSettings: null,
debounceTimeout: null,
currentUrl: window.location.href,
startTime: 0,
sequenceRestartCallback: undefined
};

// Process added nodes and check if any match actions
const processAddedNodes = async (addedNodes: NodeList): Promise<void> => {
if (!state.watchSettings || !state.sequenceRestartCallback) {
return;
}

console.debug('DomWatchManager: Restarting action sequence due to DOM changes');
await state.sequenceRestartCallback();
};

// Debounced processing
const debounceProcessing = (processingFn: () => Promise<void>, delay: number): void => {
// Clear existing timeout
if (state.debounceTimeout) {
clearTimeout(state.debounceTimeout);
}

// Set new timeout
state.debounceTimeout = window.setTimeout(async () => {
try {
await processingFn();
} catch (error) {
console.error('DomWatchManager: Error in debounced processing:', error);
}
state.debounceTimeout = null;
}, delay);
};

// Check lifecycle stop conditions
const shouldStopWatching = (): boolean => {
if (!state.watchSettings?.lifecycleStopConditions) return false;

const { lifecycleStopConditions } = state.watchSettings;

// Check timeout
if (lifecycleStopConditions.timeout) {
const elapsed = Date.now() - state.startTime;
if (elapsed >= lifecycleStopConditions.timeout * 60 * 1000) {
// Convert mins to milliseconds
console.debug('DomWatchManager: Stopping due to timeout');
return true;
}
}

// Check URL change
if (lifecycleStopConditions.urlChange && state.currentUrl !== window.location.href) {
console.debug('DomWatchManager: Stopping due to URL change');
return true;
}

return false;
};

// Mutation observer callback
const handleMutations = (mutations: MutationRecord[]): void => {
if (!state.isActive || !state.watchSettings) {
return;
}

// Check if we should stop watching
if (shouldStopWatching()) {
return;
}

for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
// Debounce the processing to avoid excessive restarts
debounceProcessing(
() => processAddedNodes(mutation.addedNodes),
(state.watchSettings.debounce || 1) * 1000 // Convert seconds to milliseconds
);
}
}
};

// Initialize the mutation observer
const initializeObserver = (): void => {
if (state.observer || !state.watchSettings) {
return;
}

const watchRoot = state.watchSettings.watchRootSelector || 'body';
const rootElements = document.querySelectorAll<HTMLElement>(watchRoot) || document.body;

state.observer = new MutationObserver(handleMutations);
rootElements.forEach((rootElement) => {
state.observer?.observe(rootElement, {
childList: true,
subtree: true,
attributes: !!state.watchSettings?.watchAttributes, // track attributes
attributeFilter: state.watchSettings?.watchAttributes // optional optimization
});
});

console.debug(`DomWatchManager: Initialized observer on ${watchRoot}`);
};

// Register configuration-level DOM watching
const registerConfiguration = (watchSettings: IWatchSettings): void => {
if (!watchSettings.watchEnabled) {
return;
}

const mergedSettings: IWatchSettings = {
...defaultWatchSettings,
...watchSettings
};

state.watchSettings = mergedSettings;
state.startTime = Date.now();
if (!state.isActive) {
start();
}

console.debug(`DomWatchManager: Registered configuration-level DOM watching`);
};

// Start DOM watching
const start = (): void => {
if (state.isActive || !state.watchSettings) {
return;
}

state.isActive = true;
state.currentUrl = window.location.href;
initializeObserver();

console.debug('DomWatchManager: Started configuration-level DOM watching');
};

// Get current watch status
const getStatus = () => ({
isActive: state.isActive,
watchEnabled: state.watchSettings?.watchEnabled || false,
startTime: state.startTime,
settings: state.watchSettings
});

// Set the callback function for sequence restart
const setSequenceRestartCallback = (callback: () => Promise<void>): void => {
state.sequenceRestartCallback = callback;
};

return {
registerConfiguration,
setSequenceRestartCallback,
start,
getStatus
};
})();

export default DomWatchManager;
1 change: 1 addition & 0 deletions apps/acf-extension/tsconfig.tsbuildinfo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"fileNames":[],"fileInfos":[],"root":[],"options":{"composite":true,"declarationMap":true,"emitDeclarationOnly":true,"importHelpers":true,"module":99,"noEmitOnError":true,"noFallthroughCasesInSwitch":true,"noImplicitOverride":true,"noImplicitReturns":true,"noUnusedLocals":true,"skipLibCheck":true,"strict":true,"target":9},"version":"5.9.2"}
3 changes: 1 addition & 2 deletions apps/acf-options-page/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
{
"projects": ["acf-extension"],
"target": "serve"
},
"watch-deps"
}
]
}
}
Expand Down
Loading
Loading