mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
feat: add protocol handling to allow adding tasks from other apps
This commit is contained in:
parent
fa5e5325df
commit
cf1031c935
4 changed files with 153 additions and 0 deletions
115
electron/protocol-handler.ts
Normal file
115
electron/protocol-handler.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { App, BrowserWindow } from 'electron';
|
||||
import { log } from 'electron-log/main';
|
||||
import * as path from 'path';
|
||||
import { IPC } from './shared-with-frontend/ipc-events.const';
|
||||
|
||||
export const PROTOCOL_NAME = 'superproductivity';
|
||||
export const PROTOCOL_PREFIX = `${PROTOCOL_NAME}://`;
|
||||
|
||||
// Store pending URLs to process after window is ready
|
||||
let pendingUrls: string[] = [];
|
||||
|
||||
export const processProtocolUrl = (url: string, mainWin: BrowserWindow | null): void => {
|
||||
log('Processing protocol URL:', url);
|
||||
|
||||
// Only process after window is ready
|
||||
if (!mainWin || !mainWin.webContents) {
|
||||
log('Window not ready, deferring protocol URL processing');
|
||||
pendingUrls.push(url);
|
||||
|
||||
// Process any pending protocol URLs after window is created
|
||||
setTimeout(() => {
|
||||
processPendingProtocolUrls(mainWin);
|
||||
}, 10000);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
const action = urlObj.hostname;
|
||||
const pathParts = urlObj.pathname.split('/').filter(Boolean);
|
||||
|
||||
log('Protocol action:', action);
|
||||
log('Protocol path parts:', pathParts);
|
||||
|
||||
switch (action) {
|
||||
case 'create-task':
|
||||
if (pathParts.length > 0) {
|
||||
const taskTitle = decodeURIComponent(pathParts[0]);
|
||||
log('Creating task with title:', taskTitle);
|
||||
|
||||
// Send IPC message to create task
|
||||
if (mainWin && mainWin.webContents) {
|
||||
mainWin.webContents.send(IPC.ADD_TASK, { title: taskTitle });
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log('Unknown protocol action:', action);
|
||||
}
|
||||
} catch (error) {
|
||||
log('Error processing protocol URL:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const processPendingProtocolUrls = (mainWin: BrowserWindow): void => {
|
||||
if (pendingUrls.length > 0) {
|
||||
log(`Processing ${pendingUrls.length} pending protocol URLs`);
|
||||
const urls = [...pendingUrls];
|
||||
pendingUrls = [];
|
||||
urls.forEach((url) => processProtocolUrl(url, mainWin));
|
||||
}
|
||||
};
|
||||
|
||||
export const initializeProtocolHandling = (
|
||||
IS_DEV: boolean,
|
||||
appInstance: App,
|
||||
getMainWindow: () => BrowserWindow | null,
|
||||
): void => {
|
||||
// Register protocol handler
|
||||
if (IS_DEV && process.defaultApp) {
|
||||
if (process.argv.length >= 2) {
|
||||
appInstance.setAsDefaultProtocolClient(PROTOCOL_NAME, process.execPath, [
|
||||
path.resolve(process.argv[1]),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
appInstance.setAsDefaultProtocolClient(PROTOCOL_NAME);
|
||||
}
|
||||
|
||||
// Handle protocol on Windows/Linux via second instance
|
||||
appInstance.on('second-instance', (event, commandLine) => {
|
||||
const mainWin = getMainWindow();
|
||||
|
||||
// Someone tried to run a second instance, we should focus our window instead.
|
||||
if (mainWin) {
|
||||
if (mainWin.isMinimized()) mainWin.restore();
|
||||
mainWin.focus();
|
||||
}
|
||||
|
||||
// Handle protocol url from second instance
|
||||
const url = commandLine.find((arg) => arg.startsWith(PROTOCOL_PREFIX));
|
||||
if (url) {
|
||||
processProtocolUrl(url, mainWin);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle protocol on macOS
|
||||
appInstance.on('open-url', (event, url) => {
|
||||
if (url.startsWith(PROTOCOL_PREFIX)) {
|
||||
event.preventDefault();
|
||||
processProtocolUrl(url, getMainWindow());
|
||||
}
|
||||
});
|
||||
|
||||
// Handle protocol URL passed as command line argument for testing
|
||||
process.argv.forEach((val) => {
|
||||
if (val && val.startsWith(PROTOCOL_PREFIX)) {
|
||||
log('Protocol URL from command line:', val);
|
||||
// Process after app is ready
|
||||
appInstance.whenReady().then(() => {
|
||||
processProtocolUrl(val, getMainWindow());
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -23,6 +23,10 @@ import { quitApp, showOrFocus } from './various-shared';
|
|||
import { createWindow } from './main-window';
|
||||
import { IdleTimeHandler } from './idle-time-handler';
|
||||
import { destroyOverlayWindow } from './overlay-indicator/overlay-indicator';
|
||||
import {
|
||||
initializeProtocolHandling,
|
||||
processPendingProtocolUrls,
|
||||
} from './protocol-handler';
|
||||
|
||||
const ICONS_FOLDER = __dirname + '/assets/icons/';
|
||||
const IS_MAC = process.platform === 'darwin';
|
||||
|
|
@ -52,6 +56,16 @@ let mainWin: BrowserWindow;
|
|||
let idleTimeHandler: IdleTimeHandler;
|
||||
|
||||
export const startApp = (): void => {
|
||||
// Initialize protocol handling
|
||||
initializeProtocolHandling(IS_DEV, app, () => mainWin);
|
||||
|
||||
// Handle single instance lock
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
app.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
// LOAD IPC STUFF
|
||||
initIpcInterfaces();
|
||||
|
||||
|
|
@ -341,6 +355,11 @@ export const startApp = (): void => {
|
|||
quitApp,
|
||||
customUrl,
|
||||
});
|
||||
|
||||
// Process any pending protocol URLs after window is created
|
||||
setTimeout(() => {
|
||||
processPendingProtocolUrls(mainWin);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
||||
|
|
|
|||
|
|
@ -24,3 +24,7 @@ export const ipcResume$: Observable<unknown> = IS_ELECTRON
|
|||
export const ipcSuspend$: Observable<unknown> = IS_ELECTRON
|
||||
? ipcEvent$(IPC.SUSPEND).pipe()
|
||||
: EMPTY;
|
||||
|
||||
export const ipcAddTask$: Observable<{ title: string }> = IS_ELECTRON
|
||||
? ipcEvent$(IPC.ADD_TASK).pipe(map(([ev, data]: any) => data as { title: string }))
|
||||
: EMPTY;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import {
|
|||
unPauseFocusSession,
|
||||
} from '../../focus-mode/store/focus-mode.actions';
|
||||
import { IPC } from '../../../../../electron/shared-with-frontend/ipc-events.const';
|
||||
import { ipcAddTask$ } from '../../../core/ipc-events';
|
||||
import { TaskService } from '../task.service';
|
||||
|
||||
// TODO send message to electron when current task changes here
|
||||
|
||||
|
|
@ -29,6 +31,7 @@ export class TaskElectronEffects {
|
|||
private _configService = inject(GlobalConfigService);
|
||||
private _pomodoroService = inject(PomodoroService);
|
||||
private _focusModeService = inject(FocusModeService);
|
||||
private _taskService = inject(TaskService);
|
||||
|
||||
// -----------------------------------------------------------------------------------
|
||||
// NOTE: IS_ELECTRON checks not necessary, since we check before importing this module
|
||||
|
|
@ -153,4 +156,16 @@ export class TaskElectronEffects {
|
|||
),
|
||||
{ dispatch: false },
|
||||
);
|
||||
|
||||
handleAddTaskFromProtocol$ = createEffect(
|
||||
() =>
|
||||
ipcAddTask$.pipe(
|
||||
tap(({ title }) => {
|
||||
if (title) {
|
||||
this._taskService.add(title);
|
||||
}
|
||||
}),
|
||||
),
|
||||
{ dispatch: false },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue