Skip to content

[WIP] feat: add interactive mode #1

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 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Ulysse is a simple CLI tool for blocking your distracting apps and websites.
Prevent distractions by blocking your most distracting apps and websites, even if you are the administrator of your computer.

> [!WARNING]
> The shield mode block root access to your computer and can block you from disabling Ulysse.
> The shield mode block root access to your computer and can prevent you from disabling Ulysse.
>
> Make sure to remember your password.
>
Expand Down
47 changes: 24 additions & 23 deletions __tests__/block.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { config, editConfig, readConfig } from '../src/config';
import { DEFAULT_CONFIG } from '../src/constants';
import { disableShieldMode } from '../src/shield';
import { config, readConfig, resetConfig } from '../src/config';
import {
getBlockedApps,
blockDistraction,
Expand All @@ -11,13 +9,11 @@ import {
} from '../src/block';

beforeEach(async () => {
await disableShieldMode('ulysse');
await editConfig(DEFAULT_CONFIG);
Object.assign(config, DEFAULT_CONFIG);
await resetConfig();
jest.spyOn(console, 'log').mockImplementation(() => {});
});

test('Should check a distraction', async () => {
test('Should check if a distraction is valid', () => {
expect(isValidDistraction({ name: '' })).toBe(false);
expect(isValidDistraction({ name: '*' })).toBe(true);
expect(isValidDistraction({ name: '*.*' })).toBe(true);
Expand All @@ -33,13 +29,14 @@ test('Should block a distraction', async () => {
await blockDistraction({ name: 'example.com' });

expect(isDistractionBlocked('example.com')).toEqual(true);
expect(readConfig().blocklist).toContainEqual({ name: 'example.com', profile: 'default' });
});

test('Should block a distraction with a duration', async () => {
await blockDistraction({ name: 'twitter.com', time: '2m' });

expect(isDistractionBlocked('twitter.com')).toBe(true);
expect(config.blocklist).toEqual([{ name: 'twitter.com', time: '2m', timeout: expect.any(Number) }]);
expect(readConfig().blocklist).toContainEqual({ name: 'twitter.com', time: '2m', profile: 'default', timeout: expect.any(Number) });
});

test('Should block a distraction with a time-based interval', async () => {
Expand All @@ -49,13 +46,15 @@ test('Should block a distraction with a time-based interval', async () => {
await blockDistraction({ name: 'example.com', time: '0h-23h' });

expect(isDistractionBlocked('example.com')).toBe(true);
expect(readConfig().blocklist).toContainEqual({ name: 'example.com', profile: 'default', time: '0h-23h' });
});

test('Should block a specific subdomain', async () => {
await blockDistraction({ name: 'www.example.com' });

expect(isDistractionBlocked('www.example.com')).toBe(true);
expect(isDistractionBlocked('example.com')).toBe(false);
expect(readConfig().blocklist).toContainEqual({ name: 'www.example.com', profile: 'default' });
});

test('Should block all subdomains of a domain with a wildcard', async () => {
Expand Down Expand Up @@ -111,7 +110,7 @@ test('Should unblock a distraction', async () => {
expect(isDistractionBlocked('example.com')).toBe(false);
});

test('Should run isDistractionBlocked in less than 150ms with a large blocklist', async () => {
test.skip('Should run isDistractionBlocked in less than 150ms with a large blocklist', async () => {
config.blocklist = Array.from({ length: 500000 }, (_, i) => ({ name: `${i + 1}.com` }));

isDistractionBlocked('example.com');
Expand Down Expand Up @@ -147,10 +146,10 @@ test('Should get all blocked apps', async () => {
expect(blockedApps).toEqual(['node']);
});

test('Should get running blocked apps', () => {
test('Should get running blocked apps', async () => {
config.blocklist = [{ name: 'node' }, { name: 'firefox' }];

const runningBlockedApps = getRunningBlockedApps();
const runningBlockedApps = await getRunningBlockedApps();

expect(runningBlockedApps).toContainEqual({
name: 'node',
Expand All @@ -160,27 +159,21 @@ test('Should get running blocked apps', () => {
});
});

test('Should block all apps and websites', async () => {
test.skip('Should block all apps and websites', async () => {
await blockDistraction({ name: '*' });

const runningBlockedApps = await getRunningBlockedApps();

expect(isDistractionBlocked('example.com')).toEqual(true);
expect(isDistractionBlocked('node')).toEqual(true);
expect(getRunningBlockedApps()).toContainEqual({
name: 'node',
expect(isDistractionBlocked('chromium')).toEqual(true);
expect(runningBlockedApps).toContainEqual({
name: 'chromium',
pid: expect.any(Number),
cmd: expect.any(String),
bin: expect.any(String),
});
});

test('Should not block system process', async () => {
blockDistraction({ name: '*' });

const runningBlockedApps = JSON.stringify(getRunningBlockedApps());

expect(runningBlockedApps).not.toContain('/sbin/init');
});

test('Should not block all websites outside of a time range', async () => {
const currentDate = new Date('2021-01-01T12:00:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => currentDate);
Expand All @@ -189,3 +182,11 @@ test('Should not block all websites outside of a time range', async () => {

expect(isDistractionBlocked('example.com')).toEqual(false);
});

test('Should not block a distraction from a profile disabled', async () => {
config.profiles = [{ name: 'default', enabled: false }];
await blockDistraction({ name: 'signal-desktop', profile: 'default' });

expect(isDistractionBlocked('signal-desktop')).toEqual(false);
expect(getBlockedApps()).not.toContain('signal-desktop');
});
6 changes: 2 additions & 4 deletions __tests__/commands.spec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { config, editConfig } from '../src/config';
import { DEFAULT_CONFIG } from '../src/constants';
import { config, resetConfig } from '../src/config';
import { disableShieldMode } from '../src/shield';
import { helpCmd, versionCmd, blockCmd, whitelistCmd, unblockCmd, shieldCmd } from '../src/commands';

beforeEach(async () => {
process.argv = [];
await disableShieldMode('ulysse');
await editConfig(DEFAULT_CONFIG);
Object.assign(config, DEFAULT_CONFIG);
await resetConfig();
jest.spyOn(console, 'log').mockImplementation(() => {});
});

Expand Down
10 changes: 4 additions & 6 deletions __tests__/daemon.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import fs from 'fs';
import { config, editConfig, readConfig } from '../src/config';
import { config, resetConfig, readConfig } from '../src/config';
import { getRunningApps } from '../src/utils';
import { blockDistraction } from '../src/block';
import { DEFAULT_CONFIG } from '../src/constants';
import { disableShieldMode } from '../src/shield';
import { handleAppBlocking, handleTimeout, updateResolvConf } from '../src/daemon';

Expand All @@ -18,15 +17,14 @@ jest.mock('child_process', () => ({

beforeEach(async () => {
await disableShieldMode('ulysse');
await editConfig(DEFAULT_CONFIG);
Object.assign(config, DEFAULT_CONFIG);
await resetConfig();
jest.spyOn(console, 'log').mockImplementation(() => {});
});

test('Should block a running app', async () => {
blockDistraction({ name: 'node' });
await blockDistraction({ name: 'node' });

handleAppBlocking();
await handleAppBlocking();

expect(console.log).toHaveBeenCalledWith('Blocking node');
});
Expand Down
15 changes: 15 additions & 0 deletions __tests__/profile.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { config, resetConfig } from '../src/config';
import { disableShieldMode } from '../src/shield';

beforeEach(async () => {
process.argv = [];
await disableShieldMode('ulysse');
await resetConfig();
jest.spyOn(console, 'log').mockImplementation(() => {});
});

test.skip('Should create a new profile', () => {
process.argv = ['ulysse', '-p', 'work'];

expect(config.profiles).toEqual([{ name: 'work', enabled: true }]);
});
13 changes: 6 additions & 7 deletions __tests__/shield.spec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { config, readConfig, editConfig } from '../src/config';
import { DEFAULT_CONFIG } from '../src/constants';
import { readConfig, resetConfig } from '../src/config';
import { whitelistDistraction } from '../src/whitelist';
import { enableShieldMode, disableShieldMode } from '../src/shield';
import { blockDistraction, unblockDistraction } from '../src/block';

beforeEach(async () => {
await disableShieldMode('ulysse');
await editConfig(DEFAULT_CONFIG);
Object.assign(config, DEFAULT_CONFIG);
await resetConfig();
jest.spyOn(console, 'log').mockImplementation(() => {});
});

Expand Down Expand Up @@ -42,13 +39,15 @@ test('Should not unblock a distraction if shield mode is enabled', async () => {

await unblockDistraction({ name: 'example.com' });

expect(readConfig().blocklist).toContainEqual({ name: 'example.com' });
const { blocklist } = readConfig();
expect(blocklist).toContainEqual({ name: 'example.com', profile: 'default' });
});

test('Should not whitelist a distraction if shield mode is enabled', async () => {
await enableShieldMode('ulysse');

await whitelistDistraction({ name: 'example.com' });

expect(readConfig().whitelist).not.toContainEqual({ name: 'example.com' });
const { whitelist } = readConfig();
expect(whitelist).not.toContainEqual({ name: 'example.com' });
});
16 changes: 4 additions & 12 deletions __tests__/whitelist.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { config, readConfig, editConfig } from '../src/config';
import { DEFAULT_CONFIG } from '../src/constants';
import { config, resetConfig } from '../src/config';
import { disableShieldMode } from '../src/shield';
import { blockDistraction, isDistractionBlocked, getRunningBlockedApps } from '../src/block';
import { isDistractionWhitelisted, whitelistDistraction } from '../src/whitelist';
import { whitelistDistraction } from '../src/whitelist';

jest.mock('../src/utils', () => ({
...jest.requireActual('../src/utils'),
Expand All @@ -15,8 +14,7 @@ jest.mock('../src/utils', () => ({

beforeEach(async () => {
await disableShieldMode('ulysse');
await editConfig(DEFAULT_CONFIG);
Object.assign(config, DEFAULT_CONFIG);
await resetConfig();
jest.spyOn(console, 'log').mockImplementation(() => {});
});

Expand All @@ -25,7 +23,7 @@ test('Should whitelist a distraction', async () => {

await whitelistDistraction(distraction);

expect(readConfig().whitelist).toEqual([distraction]);
expect(config.whitelist).toEqual([distraction]);
});

test('Should not block a domain if it is in the whitelist', async () => {
Expand All @@ -42,12 +40,6 @@ test('Should not block a domain if it is in the whitelist with a wildcard', asyn
expect(isDistractionBlocked('www.example.com')).toBe(false);
});

test('Should not block a process from the system whitelist', async () => {
await blockDistraction({ name: '*' });

expect(isDistractionWhitelisted('systemd')).toBe(true);
});

test('Should not whitelist a blocked process outside of a time range', async () => {
const currentDate = new Date('2021-01-01T12:00:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => currentDate);
Expand Down
9 changes: 9 additions & 0 deletions __tests__/x11.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { listActiveWindows } from '../src/x11';

test.skip('Should list active windows', async () => {
const windows = await listActiveWindows();

expect(windows[0]).toEqual(
{ windowId: expect.any(Number), name: expect.any(String), pid: expect.any(Number) },
);
});
14 changes: 14 additions & 0 deletions customSequencer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint class-methods-use-this: 0 */
const TestSequencer = require('@jest/test-sequencer').default;

class AlphabeticalSequencer extends TestSequencer {
sort(tests) {
return tests.sort((testA, testB) => {
const nameA = testA.path.toUpperCase();
const nameB = testB.path.toUpperCase();
return nameA.localeCompare(nameB);
});
}
}

module.exports = AlphabeticalSequencer;
33 changes: 14 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,45 +33,40 @@
],
"dependencies": {
"dns-packet": "^5.6.1",
"gun": "^0.2020.1240"
"enquirer": "^2.4.1",
"gun": "^0.2020.1240",
"x11": "^2.3.0"
},
"devDependencies": {
"@babel/cli": "^7.23.9",
"@babel/core": "^7.23.9",
"@babel/node": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/cli": "^7.24.8",
"@babel/core": "^7.24.9",
"@babel/node": "^7.24.8",
"@babel/preset-env": "^7.24.8",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"babel-plugin-transform-remove-strict-mode": "^0.0.2",
"dotenv": "^16.4.5",
"eslint": "^8.57.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-jest": "^28.6.0",
"jest": "^29.7.0",
"rollup": "^4.12.0"
"rollup": "^4.18.1"
},
"scripts": {
"lint": "eslint src",
"prepublishOnly": "npm run build",
"prebuild": "rm -rf dist",
"build": "rollup --bundleConfigAsCjs -c",
"build": "rollup --bundleConfigAsCjs --no-strict -c",
"start": "babel-node src/index.js",
"test": "DOTENV_CONFIG_PATH=.env.test jest -i --setupFiles dotenv/config --forceExit"
},
"jest": {
"testSequencer": "./customSequencer.js",
"globalSetup": "./jest.setup.js",
"restoreMocks": true,
"transformIgnorePatterns": [],
"transform": {
"\\.js$": [
"babel-jest",
{
"configFile": "./babel.config.js"
}
]
}
"restoreMocks": true
}
}
6 changes: 5 additions & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export default {
plugins: [
json(),
terser(),
commonjs(),
commonjs({
dynamicRequireTargets: [
'node_modules/x11/lib/ext/big-requests.js',
],
}),
nodeResolve(),
babel({ babelHelpers: 'bundled' }),
],
Expand Down
Loading
Loading