half way through refactor
This commit is contained in:
@@ -4,7 +4,7 @@ import { configLogout, setLoggingEnabled } from '../reducers/configReducer';
|
|||||||
import { userLogout } from '../reducers/currentUser';
|
import { userLogout } from '../reducers/currentUser';
|
||||||
import { ReduxSelector } from '../types/store';
|
import { ReduxSelector } from '../types/store';
|
||||||
import { User } from '../types/user';
|
import { User } from '../types/user';
|
||||||
import changeExtensionState from '../utils/changeExtensionState';
|
import changeExtensionState from '../utils/changeExtensionStatus';
|
||||||
|
|
||||||
export interface MainListProps {
|
export interface MainListProps {
|
||||||
loggingEnabled: boolean;
|
loggingEnabled: boolean;
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ export default function Options(): JSX.Element {
|
|||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiUrl: config.apiUrl,
|
apiUrl: config.apiUrl,
|
||||||
denyList: [],
|
denyList: [],
|
||||||
|
extensionStatus: 'allGood',
|
||||||
hostname: '',
|
hostname: '',
|
||||||
loading: false,
|
loading: false,
|
||||||
|
loggingEnabled: true,
|
||||||
loggingStyle: config.loggingStyle,
|
loggingStyle: config.loggingStyle,
|
||||||
loggingType: config.loggingType,
|
loggingType: config.loggingType,
|
||||||
socialMediaSites: config.socialMediaSites,
|
socialMediaSites: config.socialMediaSites,
|
||||||
@@ -54,7 +56,9 @@ export default function Options(): JSX.Element {
|
|||||||
apiKey: state.apiKey,
|
apiKey: state.apiKey,
|
||||||
apiUrl: state.apiUrl,
|
apiUrl: state.apiUrl,
|
||||||
denyList: state.denyList,
|
denyList: state.denyList,
|
||||||
|
extensionStatus: state.extensionStatus,
|
||||||
hostname: state.hostname,
|
hostname: state.hostname,
|
||||||
|
loggingEnabled: state.loggingEnabled,
|
||||||
loggingStyle: state.loggingStyle,
|
loggingStyle: state.loggingStyle,
|
||||||
loggingType: state.loggingType,
|
loggingType: state.loggingType,
|
||||||
socialMediaSites: state.socialMediaSites,
|
socialMediaSites: state.socialMediaSites,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import NavBar from './NavBar';
|
|||||||
|
|
||||||
export default function WakaTime(): JSX.Element {
|
export default function WakaTime(): JSX.Element {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [extensionState, setExtensionState] = useState('');
|
const [extensionStatus, setExtensionStatus] = useState('');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
apiKey: apiKeyFromRedux,
|
apiKey: apiKeyFromRedux,
|
||||||
@@ -21,8 +21,8 @@ export default function WakaTime(): JSX.Element {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
await fetchUserData(apiKeyFromRedux, dispatch);
|
await fetchUserData(apiKeyFromRedux, dispatch);
|
||||||
const items = await browser.storage.sync.get({ extensionState: '' });
|
const items = await browser.storage.sync.get({ extensionStatus: '' });
|
||||||
setExtensionState(items.extensionState as string);
|
setExtensionStatus(items.extensionStatus as string);
|
||||||
};
|
};
|
||||||
void fetchData();
|
void fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -32,7 +32,7 @@ export default function WakaTime(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<div className="py-4 px-2 pt-0">
|
<div className="py-4 px-2 pt-0">
|
||||||
<NavBar />
|
<NavBar />
|
||||||
{isApiKeyValid && extensionState === 'notSignedIn' && (
|
{isApiKeyValid && extensionStatus === 'notSignedIn' && (
|
||||||
<Alert
|
<Alert
|
||||||
type={config.alert.failure.type}
|
type={config.alert.failure.type}
|
||||||
text={'Invalid API key or API url'}
|
text={'Invalid API key or API url'}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import browser from 'webextension-polyfill';
|
|||||||
/**
|
/**
|
||||||
* Logging
|
* Logging
|
||||||
*/
|
*/
|
||||||
export type ApiStates = 'allGood' | 'notLogging' | 'notSignedIn' | 'ignored';
|
export type ExtensionStatus = 'allGood' | 'notLogging' | 'notSignedIn' | 'ignored';
|
||||||
/**
|
/**
|
||||||
* Supported logging style
|
* Supported logging style
|
||||||
*/
|
*/
|
||||||
@@ -90,7 +90,7 @@ export interface Config {
|
|||||||
name: string;
|
name: string;
|
||||||
nonTrackableSites: string[];
|
nonTrackableSites: string[];
|
||||||
socialMediaSites: string[];
|
socialMediaSites: string[];
|
||||||
states: ApiStates[];
|
states: ExtensionStatus[];
|
||||||
/**
|
/**
|
||||||
* Get stats from the wakatime api
|
* Get stats from the wakatime api
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
import { IDBPDatabase, openDB } from 'idb';
|
import { IDBPDatabase, openDB } from 'idb';
|
||||||
import moment from 'moment';
|
|
||||||
import browser, { Tabs } from 'webextension-polyfill';
|
import browser, { Tabs } from 'webextension-polyfill';
|
||||||
import { IS_EDGE, IS_FIREFOX, getOperatingSystem, isCodeReviewing } from '../utils';
|
|
||||||
import { getHeartbeatFromPage } from '../utils/heartbeat';
|
|
||||||
/* eslint-disable no-fallthrough */
|
/* eslint-disable no-fallthrough */
|
||||||
/* eslint-disable default-case */
|
/* eslint-disable default-case */
|
||||||
|
import moment from 'moment';
|
||||||
|
import { getOperatingSystem, isCodeReviewing } from '../utils';
|
||||||
|
import { changeExtensionStatus } from '../utils/changeExtensionStatus';
|
||||||
import getDomainFromUrl, { getDomain } from '../utils/getDomainFromUrl';
|
import getDomainFromUrl, { getDomain } from '../utils/getDomainFromUrl';
|
||||||
|
import { getSettings, Settings } from '../utils/settings';
|
||||||
|
|
||||||
import config from '../config/config';
|
import config, { ExtensionStatus } from '../config/config';
|
||||||
import { Heartbeat } from '../types/heartbeats';
|
import { Heartbeat } from '../types/heartbeats';
|
||||||
import { getApiKey } from '../utils/apiKey';
|
import { getApiKey } from '../utils/apiKey';
|
||||||
import changeExtensionState from '../utils/changeExtensionState';
|
|
||||||
import contains from '../utils/contains';
|
import contains from '../utils/contains';
|
||||||
|
import { getHeartbeatFromPage } from '../utils/heartbeat';
|
||||||
import { getLoggingType } from '../utils/logging';
|
import { getLoggingType } from '../utils/logging';
|
||||||
|
|
||||||
class WakaTimeCore {
|
class WakaTimeCore {
|
||||||
tabsWithDevtoolsOpen: Tabs.Tab[];
|
tabsWithDevtoolsOpen: Tabs.Tab[];
|
||||||
lastHeartbeat: Heartbeat | undefined;
|
lastHeartbeat: Heartbeat | undefined;
|
||||||
lastHeartbeatSentAt = 0;
|
lastHeartbeatSentAt = 0;
|
||||||
|
lastExtensionState: ExtensionStatus = 'allGood';
|
||||||
db: IDBPDatabase | undefined;
|
db: IDBPDatabase | undefined;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.tabsWithDevtoolsOpen = [];
|
this.tabsWithDevtoolsOpen = [];
|
||||||
@@ -55,47 +57,56 @@ class WakaTimeCore {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async processHeartbeat(heartbeat: Heartbeat) {
|
canSendHeartbeat(url: string, settings: Settings): boolean {
|
||||||
const apiKey = await getApiKey();
|
if (!settings.trackSocialMedia) {
|
||||||
if (!apiKey) return changeExtensionState('notLogging');
|
const domain = getDomain(url);
|
||||||
|
if (
|
||||||
|
settings.socialMediaSites.find((pattern) => {
|
||||||
|
const re = new RegExp(pattern.replace(/\*/g, '.*'));
|
||||||
|
return re.test(domain);
|
||||||
|
}) !== undefined
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.loggingStyle === 'deny') {
|
||||||
|
return (
|
||||||
|
settings.denyList.find((pattern) => {
|
||||||
|
const re = new RegExp(pattern.replace(/\*/g, '.*'));
|
||||||
|
return re.test(url);
|
||||||
|
}) == undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
settings.allowList.find((pattern) => {
|
||||||
|
const re = new RegExp(pattern.replace(/\*/g, '.*'));
|
||||||
|
return re.test(url);
|
||||||
|
}) !== undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleActivity(url: string, heartbeat: Heartbeat) {
|
||||||
|
const settings = await getSettings();
|
||||||
|
if (!settings.loggingEnabled) {
|
||||||
|
await changeExtensionStatus('notLogging');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.shouldSendHeartbeat(heartbeat)) return;
|
if (!this.shouldSendHeartbeat(heartbeat)) return;
|
||||||
|
|
||||||
if (items.loggingStyle == 'deny') {
|
if (!this.canSendHeartbeat(url, settings)) {
|
||||||
if (!contains(url, items.denyList as string)) {
|
await changeExtensionStatus('ignored');
|
||||||
await this.sendHeartbeat(
|
return;
|
||||||
{
|
}
|
||||||
branch: null,
|
|
||||||
hostname: items.hostname as string,
|
if (this.db) {
|
||||||
project,
|
// append heartbeat to queue
|
||||||
url,
|
await this.db.add('cacheHeartbeats', heartbeat);
|
||||||
},
|
|
||||||
apiKey,
|
if (settings.extensionStatus !== 'notSignedIn') {
|
||||||
payload,
|
await changeExtensionStatus('allGood');
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await changeExtensionState('ignored');
|
|
||||||
console.log(`${url} is on denyList.`);
|
|
||||||
}
|
}
|
||||||
} else if (items.loggingStyle == 'allow') {
|
|
||||||
const heartbeat = this.getHeartbeat(url, items.allowList as string);
|
|
||||||
if (heartbeat.url) {
|
|
||||||
await this.sendHeartbeat(
|
|
||||||
{
|
|
||||||
...heartbeat,
|
|
||||||
branch: null,
|
|
||||||
hostname: items.hostname as string,
|
|
||||||
project: heartbeat.project ?? project,
|
|
||||||
},
|
|
||||||
apiKey,
|
|
||||||
payload,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await changeExtensionState('ignored');
|
|
||||||
console.log(`${url} is not on allowList.`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw Error(`Unknown logging styel: ${items.loggingStyle}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +117,7 @@ class WakaTimeCore {
|
|||||||
async recordHeartbeat(html: string, payload: Record<string, unknown> = {}): Promise<void> {
|
async recordHeartbeat(html: string, payload: Record<string, unknown> = {}): Promise<void> {
|
||||||
const apiKey = await getApiKey();
|
const apiKey = await getApiKey();
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
return changeExtensionState('notLogging');
|
return changeExtensionStatus('notLogging');
|
||||||
}
|
}
|
||||||
const items = await browser.storage.sync.get({
|
const items = await browser.storage.sync.get({
|
||||||
allowList: '',
|
allowList: '',
|
||||||
@@ -118,14 +129,14 @@ class WakaTimeCore {
|
|||||||
trackSocialMedia: config.trackSocialMedia,
|
trackSocialMedia: config.trackSocialMedia,
|
||||||
});
|
});
|
||||||
if (items.loggingEnabled === true) {
|
if (items.loggingEnabled === true) {
|
||||||
await changeExtensionState('allGood');
|
await changeExtensionStatus('allGood');
|
||||||
|
|
||||||
let newState = '';
|
let newState = '';
|
||||||
// Detects we are running this code in the extension scope
|
// Detects we are running this code in the extension scope
|
||||||
if (browser.idle as browser.Idle.Static | undefined) {
|
if (browser.idle as browser.Idle.Static | undefined) {
|
||||||
newState = await browser.idle.queryState(config.detectionIntervalInSeconds);
|
newState = await browser.idle.queryState(config.detectionIntervalInSeconds);
|
||||||
if (newState !== 'active') {
|
if (newState !== 'active') {
|
||||||
return changeExtensionState('notLogging');
|
return changeExtensionStatus('notLogging');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +161,7 @@ class WakaTimeCore {
|
|||||||
const hostname = getDomain(url);
|
const hostname = getDomain(url);
|
||||||
if (!items.trackSocialMedia) {
|
if (!items.trackSocialMedia) {
|
||||||
if ((items.socialMediaSites as string[]).includes(hostname)) {
|
if ((items.socialMediaSites as string[]).includes(hostname)) {
|
||||||
return changeExtensionState('ignored');
|
return changeExtensionStatus('ignored');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +187,7 @@ class WakaTimeCore {
|
|||||||
payload,
|
payload,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await changeExtensionState('ignored');
|
await changeExtensionStatus('ignored');
|
||||||
console.log(`${url} is on denyList.`);
|
console.log(`${url} is on denyList.`);
|
||||||
}
|
}
|
||||||
} else if (items.loggingStyle == 'allow') {
|
} else if (items.loggingStyle == 'allow') {
|
||||||
@@ -193,7 +204,7 @@ class WakaTimeCore {
|
|||||||
payload,
|
payload,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await changeExtensionState('ignored');
|
await changeExtensionStatus('ignored');
|
||||||
console.log(`${url} is not on allowList.`);
|
console.log(`${url} is not on allowList.`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -374,7 +385,7 @@ class WakaTimeCore {
|
|||||||
await this.db.add('cacheHeartbeats', payload);
|
await this.db.add('cacheHeartbeats', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
await changeExtensionState('notSignedIn');
|
await changeExtensionStatus('notSignedIn');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +396,7 @@ class WakaTimeCore {
|
|||||||
async sendCachedHeartbeatsRequest(): Promise<void> {
|
async sendCachedHeartbeatsRequest(): Promise<void> {
|
||||||
const apiKey = await getApiKey();
|
const apiKey = await getApiKey();
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
return changeExtensionState('notLogging');
|
return changeExtensionStatus('notLogging');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.db) {
|
if (this.db) {
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import browser from 'webextension-polyfill';
|
|
||||||
import config from '../config/config';
|
|
||||||
import { IS_FIREFOX } from '.';
|
|
||||||
|
|
||||||
type ColorIconTypes = 'gray' | 'red' | 'white' | '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* It changes the extension icon color.
|
|
||||||
*/
|
|
||||||
export default async function changeExtensionIcon(color?: ColorIconTypes): Promise<void> {
|
|
||||||
let path;
|
|
||||||
if (color) {
|
|
||||||
path = `./graphics/wakatime-logo-38-${color}.png`;
|
|
||||||
} else {
|
|
||||||
const { theme } = await browser.storage.sync.get({
|
|
||||||
theme: config.theme,
|
|
||||||
});
|
|
||||||
path =
|
|
||||||
theme === config.theme
|
|
||||||
? './graphics/wakatime-logo-38.png'
|
|
||||||
: './graphics/wakatime-logo-38-white.png';
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
||||||
if (IS_FIREFOX && browser.browserAction) {
|
|
||||||
await browser.browserAction.setIcon({ path: path }); // Support for FF with manifest V2
|
|
||||||
} else if ((browser.action as browser.Action.Static | undefined) !== undefined) {
|
|
||||||
await browser.action.setIcon({ path: path }); // Support for Chrome with manifest V3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import browser from 'webextension-polyfill';
|
|
||||||
import config, { ApiStates } from '../config/config';
|
|
||||||
import changeExtensionIcon from './changeExtensionIcon';
|
|
||||||
import changeExtensionTooltip from './changeExtensionTooltip';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the current state of the extension.
|
|
||||||
*/
|
|
||||||
export default async function changeExtensionState(state: ApiStates): Promise<void> {
|
|
||||||
switch (state) {
|
|
||||||
case 'allGood':
|
|
||||||
await changeExtensionIcon(config.colors.allGood);
|
|
||||||
await changeExtensionTooltip(config.tooltips.allGood);
|
|
||||||
break;
|
|
||||||
case 'notLogging':
|
|
||||||
await changeExtensionIcon(config.colors.notLogging);
|
|
||||||
await changeExtensionTooltip(config.tooltips.notLogging);
|
|
||||||
break;
|
|
||||||
case 'notSignedIn':
|
|
||||||
await changeExtensionIcon(config.colors.notSignedIn);
|
|
||||||
await changeExtensionTooltip(config.tooltips.notSignedIn);
|
|
||||||
break;
|
|
||||||
case 'ignored':
|
|
||||||
await changeExtensionIcon(config.colors.notLogging);
|
|
||||||
await changeExtensionTooltip(config.tooltips.ignored);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
await browser.storage.sync.set({ extensionState: state });
|
|
||||||
}
|
|
||||||
76
src/utils/changeExtensionStatus.ts
Normal file
76
src/utils/changeExtensionStatus.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import browser from 'webextension-polyfill';
|
||||||
|
import config, { ExtensionStatus } from '../config/config';
|
||||||
|
import { IS_FIREFOX } from '.';
|
||||||
|
|
||||||
|
type ColorIconTypes = 'gray' | 'red' | 'white' | '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets status of the extension.
|
||||||
|
*/
|
||||||
|
export async function changeExtensionStatus(status: ExtensionStatus): Promise<void> {
|
||||||
|
switch (status) {
|
||||||
|
case 'allGood':
|
||||||
|
await changeExtensionIcon(config.colors.allGood);
|
||||||
|
await changeExtensionTooltip(config.tooltips.allGood);
|
||||||
|
break;
|
||||||
|
case 'notLogging':
|
||||||
|
await changeExtensionIcon(config.colors.notLogging);
|
||||||
|
await changeExtensionTooltip(config.tooltips.notLogging);
|
||||||
|
break;
|
||||||
|
case 'notSignedIn':
|
||||||
|
await changeExtensionIcon(config.colors.notSignedIn);
|
||||||
|
await changeExtensionTooltip(config.tooltips.notSignedIn);
|
||||||
|
break;
|
||||||
|
case 'ignored':
|
||||||
|
await changeExtensionIcon(config.colors.notLogging);
|
||||||
|
await changeExtensionTooltip(config.tooltips.ignored);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await browser.storage.sync.set({ extensionStatus: status });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the extension icon color.
|
||||||
|
*/
|
||||||
|
export async function changeExtensionIcon(color?: ColorIconTypes): Promise<void> {
|
||||||
|
let path;
|
||||||
|
if (color) {
|
||||||
|
path = `./graphics/wakatime-logo-38-${color}.png`;
|
||||||
|
} else {
|
||||||
|
const { theme } = await browser.storage.sync.get({
|
||||||
|
theme: config.theme,
|
||||||
|
});
|
||||||
|
path =
|
||||||
|
theme === config.theme
|
||||||
|
? './graphics/wakatime-logo-38.png'
|
||||||
|
: './graphics/wakatime-logo-38-white.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (IS_FIREFOX && browser.browserAction) {
|
||||||
|
await browser.browserAction.setIcon({ path: path }); // Support for FF with manifest V2
|
||||||
|
} else if ((browser.action as browser.Action.Static | undefined) !== undefined) {
|
||||||
|
await browser.action.setIcon({ path: path }); // Support for Chrome with manifest V3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It changes the extension title
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export default async function changeExtensionTooltip(text: string): Promise<void> {
|
||||||
|
if (text === '') {
|
||||||
|
text = config.name;
|
||||||
|
} else {
|
||||||
|
text = `${config.name} - ${text}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (IS_FIREFOX && browser.browserAction) {
|
||||||
|
await browser.browserAction.setTitle({ title: text }); // Support for FF with manifest V2
|
||||||
|
} else if ((browser.action as browser.Action.Static | undefined) !== undefined) {
|
||||||
|
await browser.action.setTitle({ title: text }); // Support for Chrome with manifest V3
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import browser from 'webextension-polyfill';
|
|
||||||
import config from '../config/config';
|
|
||||||
import { IS_FIREFOX } from '.';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* It changes the extension title
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export default async function changeExtensionTooltip(text: string): Promise<void> {
|
|
||||||
if (text === '') {
|
|
||||||
text = config.name;
|
|
||||||
} else {
|
|
||||||
text = `${config.name} - ${text}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
||||||
if (IS_FIREFOX && browser.browserAction) {
|
|
||||||
await browser.browserAction.setTitle({ title: text }); // Support for FF with manifest V2
|
|
||||||
} else if ((browser.action as browser.Action.Static | undefined) !== undefined) {
|
|
||||||
await browser.action.setTitle({ title: text }); // Support for Chrome with manifest V3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import browser from 'webextension-polyfill';
|
import browser from 'webextension-polyfill';
|
||||||
import config, { LoggingStyle, LoggingType, Theme } from '../config/config';
|
import config, { ExtensionStatus, LoggingStyle, LoggingType, Theme } from '../config/config';
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
allowList: string[];
|
allowList: string[];
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
denyList: string[];
|
denyList: string[];
|
||||||
|
extensionStatus: ExtensionStatus;
|
||||||
hostname: string;
|
hostname: string;
|
||||||
|
loggingEnabled: boolean;
|
||||||
loggingStyle: LoggingStyle;
|
loggingStyle: LoggingStyle;
|
||||||
loggingType: LoggingType;
|
loggingType: LoggingType;
|
||||||
socialMediaSites: string[];
|
socialMediaSites: string[];
|
||||||
@@ -57,7 +59,9 @@ export const getSettings = async (): Promise<Settings> => {
|
|||||||
apiKey: settings.apiKey,
|
apiKey: settings.apiKey,
|
||||||
apiUrl: settings.apiUrl,
|
apiUrl: settings.apiUrl,
|
||||||
denyList: settings.denyList,
|
denyList: settings.denyList,
|
||||||
|
extensionStatus: settings.extensionStatus,
|
||||||
hostname: settings.hostname,
|
hostname: settings.hostname,
|
||||||
|
loggingEnabled: settings.loggingEnabled,
|
||||||
loggingStyle: settings.loggingStyle,
|
loggingStyle: settings.loggingStyle,
|
||||||
loggingType: settings.loggingType,
|
loggingType: settings.loggingType,
|
||||||
socialMediaSites: settings.socialMediaSites,
|
socialMediaSites: settings.socialMediaSites,
|
||||||
|
|||||||
158
src/utils/sites.ts
Normal file
158
src/utils/sites.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import { parse } from 'node-html-parser';
|
||||||
|
|
||||||
|
type ProjectNameExtractor = (url: string, html: string) => string | null;
|
||||||
|
|
||||||
|
const GitHub: ProjectNameExtractor = (url: string, html: string): string | null => {
|
||||||
|
const { hostname } = new URL(url);
|
||||||
|
const match = url.match(/(?<=github\.(?:com|dev)\/[^/]+\/)([^/?#]+)/);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
if (hostname.endsWith('.com')) {
|
||||||
|
const root = parse(html);
|
||||||
|
const repoName = root
|
||||||
|
.querySelector('meta[name=octolytics-dimension-repository_nwo]')
|
||||||
|
?.getAttribute('content');
|
||||||
|
if (!repoName || repoName.split('/')[1] !== match[0]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const GitLab: ProjectNameExtractor = (url: string, html: string): string | null => {
|
||||||
|
const match = url.match(/(?<=gitlab\.com\/[^/]+\/)([^/?#]+)/);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const root = parse(html);
|
||||||
|
const repoName = root.querySelector('body')?.getAttribute('data-project-full-path');
|
||||||
|
if (!repoName || repoName.split('/')[1] !== match[0]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BitBucket: ProjectNameExtractor = (url: string, html: string): string | null => {
|
||||||
|
const match = url.match(/(?<=bitbucket\.org\/[^/]+\/)([^/?#]+)/);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const root = parse(html);
|
||||||
|
// this regex extracts the project name from the title
|
||||||
|
// eg. title: jhondoe / my-test-repo — Bitbucket
|
||||||
|
const match2 = root.querySelector('title')?.textContent.match(/(?<=\/\s)([^/\s]+)(?=\s—)/);
|
||||||
|
if (match2 && match2[0] === match[0]) {
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TravisCI: ProjectNameExtractor = (url: string, html: string): string | null => {
|
||||||
|
const match = url.match(/(?<=app\.travis-ci\.com\/[^/]+\/[^/]+\/)([^/?#]+)/);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const root = parse(html);
|
||||||
|
const projectName = root.querySelector('#ember737')?.textContent;
|
||||||
|
if (projectName === match[0]) {
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CircleCI: ProjectNameExtractor = (url: string, html: string): string | null => {
|
||||||
|
const projectPageMatch = url.match(
|
||||||
|
/(?<=app\.circleci\.com\/projects\/[^/]+\/[^/]+\/[^/]+\/)([^/?#]+)/,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (projectPageMatch) {
|
||||||
|
const root = parse(html);
|
||||||
|
const seconndBreadcrumbLabel = root.querySelector(
|
||||||
|
'#__next > div:nth-child(2) > div > div > main > div > header > div:nth-child(1) > ol > li:nth-child(2) > div > div > span',
|
||||||
|
)?.textContent;
|
||||||
|
const seconndBreadcrumbValue = root.querySelector(
|
||||||
|
'#__next > div:nth-child(2) > div > div > main > div > header > div:nth-child(1) > ol > li:nth-child(2) > div > span',
|
||||||
|
)?.textContent;
|
||||||
|
if (seconndBreadcrumbLabel === 'Project' && seconndBreadcrumbValue === projectPageMatch[0]) {
|
||||||
|
return projectPageMatch[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsPageMatch = url.match(
|
||||||
|
/(?<=app\.circleci\.com\/settings\/project\/[^/]+\/[^/]+\/)([^/?#]+)/,
|
||||||
|
);
|
||||||
|
if (settingsPageMatch) {
|
||||||
|
const root = parse(html);
|
||||||
|
const pageTitle = root.querySelector(
|
||||||
|
'#__next > div > div:nth-child(1) > header > div > div:nth-child(2) > h1',
|
||||||
|
)?.textContent;
|
||||||
|
const pageSubtitle = root.querySelector(
|
||||||
|
'#__next > div > div:nth-child(1) > header > div > div:nth-child(2) > div',
|
||||||
|
)?.textContent;
|
||||||
|
if (pageTitle === 'Project Settings' && pageSubtitle === settingsPageMatch[0]) {
|
||||||
|
return settingsPageMatch[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Vercel: ProjectNameExtractor = (url: string, html: string): string | null => {
|
||||||
|
const match = url.match(/(?<=vercel\.com\/[^/]+\/)([^/?#]+)/);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const root = parse(html);
|
||||||
|
// this regex extracts the project name from the title
|
||||||
|
// eg. title: test-website - Overview – Vercel
|
||||||
|
const match2 = root.querySelector('title')?.textContent.match(/^[^\s]+(?=\s-\s)/);
|
||||||
|
if (match2 && match2[0] === match[0]) {
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProjectNameExtractors: ProjectNameExtractor[] = [
|
||||||
|
GitHub,
|
||||||
|
GitLab,
|
||||||
|
BitBucket,
|
||||||
|
TravisCI,
|
||||||
|
CircleCI,
|
||||||
|
Vercel,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getHeartbeatFromPage = (): string | null => {
|
||||||
|
for (const projectNameExtractor of ProjectNameExtractors) {
|
||||||
|
const projectName = projectNameExtractor(url, html);
|
||||||
|
if (projectName) {
|
||||||
|
return projectName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CODE_REVIEW_URL_REG_LIST = [/github.com\/[^/]+\/[^/]+\/pull\/\d+\/files/];
|
||||||
|
|
||||||
|
export const isCodeReviewing = (url: string): boolean => {
|
||||||
|
for (const reg of CODE_REVIEW_URL_REG_LIST) {
|
||||||
|
if (url.match(reg)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getHtmlContentByTabId = async (tabId: number): Promise<string> => {
|
||||||
|
const response = (await browser.tabs.sendMessage(tabId, { message: 'get_html' })) as {
|
||||||
|
html: string;
|
||||||
|
};
|
||||||
|
return response.html;
|
||||||
|
};
|
||||||
@@ -7,7 +7,7 @@ import { setApiKey, setLoggingEnabled, setTotalTimeLoggedToday } from '../reduce
|
|||||||
import { setUser } from '../reducers/currentUser';
|
import { setUser } from '../reducers/currentUser';
|
||||||
import { GrandTotal, Summaries } from '../types/summaries';
|
import { GrandTotal, Summaries } from '../types/summaries';
|
||||||
import { ApiKeyPayload, AxiosUserResponse, User } from '../types/user';
|
import { ApiKeyPayload, AxiosUserResponse, User } from '../types/user';
|
||||||
import changeExtensionState from './changeExtensionState';
|
import changeExtensionState from './changeExtensionStatus';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the user is logged in.
|
* Checks if the user is logged in.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import chai from 'chai';
|
import chai from 'chai';
|
||||||
import changeExtensionState from '../../src/utils/changeExtensionState';
|
import changeExtensionState from '../../src/utils/changeExtensionStatus';
|
||||||
|
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user