mirror of
https://github.com/ByteAtATime/raycast-linux.git
synced 2025-08-31 03:07:23 +00:00
refactor(sidecar): create shared RPC module for async requests
This commit introduces a new `rpc.ts` module to centralize the asynchronous request/response logic between the sidecar and the main Tauri process. Previously, files like `clipboard.ts`, `environment.ts`, and `browserExtension.ts` each had their own duplicate implementation for managing pending requests and timeouts. By abstracting this boilerplate into a single RPC utility, we eliminate redundant code, reduce the surface area for bugs, and simplify the creation of future native API bridges.
This commit is contained in:
parent
06f823873c
commit
8218e39ae7
5 changed files with 67 additions and 164 deletions
|
@ -1,39 +1,4 @@
|
|||
import { writeOutput } from '../io';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
const pendingRequests = new Map<
|
||||
string,
|
||||
{ resolve: (value: unknown) => void; reject: (reason?: unknown) => void }
|
||||
>();
|
||||
|
||||
export function handleBrowserExtensionResponse(requestId: string, result: unknown, error?: string) {
|
||||
const promise = pendingRequests.get(requestId);
|
||||
if (promise) {
|
||||
if (error) {
|
||||
promise.reject(new Error(error));
|
||||
} else {
|
||||
promise.resolve(result);
|
||||
}
|
||||
pendingRequests.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
function sendRequest<T>(method: string, params: unknown): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestId = crypto.randomUUID();
|
||||
pendingRequests.set(requestId, { resolve: resolve as (value: unknown) => void, reject });
|
||||
writeOutput({
|
||||
type: 'browser-extension-request',
|
||||
payload: { requestId, method, params }
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (pendingRequests.has(requestId)) {
|
||||
pendingRequests.delete(requestId);
|
||||
reject(new Error(`Request for ${method} timed out`));
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
import { sendRequest } from './rpc';
|
||||
|
||||
type Tab = {
|
||||
active: boolean;
|
||||
|
@ -51,9 +16,13 @@ type RawTab = {
|
|||
active: boolean;
|
||||
};
|
||||
|
||||
const sendBrowserRequest = <T>(method: string, params: unknown) => {
|
||||
return sendRequest<T>('browser-extension-request', { method, params });
|
||||
};
|
||||
|
||||
export const BrowserExtensionAPI = {
|
||||
async getTabs(): Promise<Tab[]> {
|
||||
const result = await sendRequest<{ value: RawTab[] }>('getTabs', {});
|
||||
const result = await sendBrowserRequest<{ value: RawTab[] }>('getTabs', {});
|
||||
return result.value.map((tab) => ({
|
||||
id: tab.tabId,
|
||||
url: tab.url,
|
||||
|
@ -84,7 +53,7 @@ export const BrowserExtensionAPI = {
|
|||
params.tabId = options.tabId;
|
||||
}
|
||||
|
||||
const result = await sendRequest<{ value: string }>('getTab', params);
|
||||
const result = await sendBrowserRequest<{ value: string }>('getTab', params);
|
||||
return result.value;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { writeOutput } from '../io';
|
||||
import * as crypto from 'crypto';
|
||||
import { sendRequest } from './rpc';
|
||||
import type * as api from '@raycast/api';
|
||||
|
||||
type ClipboardContent = {
|
||||
|
@ -14,40 +13,6 @@ type ReadResult = {
|
|||
file?: string;
|
||||
};
|
||||
|
||||
const pendingRequests = new Map<
|
||||
string,
|
||||
{ resolve: (value: unknown) => void; reject: (reason?: unknown) => void }
|
||||
>();
|
||||
|
||||
function sendRequest<T>(type: string, payload: object): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestId = crypto.randomUUID();
|
||||
pendingRequests.set(requestId, { resolve: resolve as (value: unknown) => void, reject });
|
||||
writeOutput({
|
||||
type,
|
||||
payload: { requestId, ...payload }
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (pendingRequests.has(requestId)) {
|
||||
pendingRequests.delete(requestId);
|
||||
reject(new Error(`Request for ${type} timed out`));
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
export function handleClipboardResponse(requestId: string, result: unknown, error?: string) {
|
||||
const promise = pendingRequests.get(requestId);
|
||||
if (promise) {
|
||||
if (error) {
|
||||
promise.reject(new Error(error));
|
||||
} else {
|
||||
promise.resolve(result);
|
||||
}
|
||||
pendingRequests.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeContent(content: string | number | api.Clipboard.Content): ClipboardContent {
|
||||
if (typeof content === 'string' || typeof content === 'number') {
|
||||
return { text: String(content) };
|
||||
|
|
|
@ -4,7 +4,7 @@ import { writeOutput } from '../io';
|
|||
import type { Application } from './types';
|
||||
import { config } from '../config';
|
||||
import { browserExtensionState } from '../state';
|
||||
import * as crypto from 'crypto';
|
||||
import { sendRequest } from './rpc';
|
||||
|
||||
const supportPath = config.supportDir;
|
||||
try {
|
||||
|
@ -21,40 +21,6 @@ export interface FileSystemItem {
|
|||
|
||||
export const BrowserExtension = { name: 'BrowserExtension' };
|
||||
|
||||
const pendingSystemRequests = new Map<
|
||||
string,
|
||||
{ resolve: (value: unknown) => void; reject: (reason?: unknown) => void }
|
||||
>();
|
||||
|
||||
function sendSystemRequest<T>(type: string, payload: object = {}): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestId = crypto.randomUUID();
|
||||
pendingSystemRequests.set(requestId, { resolve, reject });
|
||||
writeOutput({
|
||||
type: `system-${type}`,
|
||||
payload: { requestId, ...payload }
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (pendingSystemRequests.has(requestId)) {
|
||||
pendingSystemRequests.delete(requestId);
|
||||
reject(new Error(`Request for ${type} timed out`));
|
||||
}
|
||||
}, 5000); // 5-second timeout
|
||||
});
|
||||
}
|
||||
|
||||
export function handleSystemResponse(requestId: string, result: unknown, error?: string) {
|
||||
const promise = pendingSystemRequests.get(requestId);
|
||||
if (promise) {
|
||||
if (error) {
|
||||
promise.reject(new Error(error));
|
||||
} else {
|
||||
promise.resolve(result);
|
||||
}
|
||||
pendingSystemRequests.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
export const environment = {
|
||||
appearance: 'dark' as const,
|
||||
assetsPath: config.assetsDir,
|
||||
|
@ -76,11 +42,11 @@ export const environment = {
|
|||
};
|
||||
|
||||
export async function getSelectedFinderItems(): Promise<FileSystemItem[]> {
|
||||
return sendSystemRequest<FileSystemItem[]>('get-selected-finder-items');
|
||||
return sendRequest<FileSystemItem[]>('system-get-selected-finder-items');
|
||||
}
|
||||
|
||||
export async function getSelectedText(): Promise<string> {
|
||||
return sendSystemRequest<string>('get-selected-text');
|
||||
return sendRequest<string>('system-get-selected-text');
|
||||
}
|
||||
|
||||
export async function open(target: string, application?: Application | string): Promise<void> {
|
||||
|
@ -103,22 +69,22 @@ export async function open(target: string, application?: Application | string):
|
|||
|
||||
export async function getApplications(path?: fs.PathLike): Promise<Application[]> {
|
||||
const pathString = path ? path.toString() : undefined;
|
||||
return sendSystemRequest<Application[]>('get-applications', { path: pathString });
|
||||
return sendRequest<Application[]>('system-get-applications', { path: pathString });
|
||||
}
|
||||
|
||||
export async function getDefaultApplication(path: fs.PathLike): Promise<Application> {
|
||||
return sendSystemRequest<Application>('get-default-application', { path: path.toString() });
|
||||
return sendRequest<Application>('system-get-default-application', { path: path.toString() });
|
||||
}
|
||||
|
||||
export async function getFrontmostApplication(): Promise<Application> {
|
||||
return sendSystemRequest<Application>('get-frontmost-application');
|
||||
return sendRequest<Application>('system-get-frontmost-application');
|
||||
}
|
||||
|
||||
export async function showInFinder(path: fs.PathLike): Promise<void> {
|
||||
return sendSystemRequest<void>('show-in-finder', { path: path.toString() });
|
||||
return sendRequest<void>('system-show-in-finder', { path: path.toString() });
|
||||
}
|
||||
|
||||
export async function trash(path: fs.PathLike | fs.PathLike[]): Promise<void> {
|
||||
const paths = (Array.isArray(path) ? path : [path]).map((p) => p.toString());
|
||||
return sendSystemRequest<void>('trash', { paths });
|
||||
return sendRequest<void>('system-trash', { paths });
|
||||
}
|
||||
|
|
38
sidecar/src/api/rpc.ts
Normal file
38
sidecar/src/api/rpc.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { writeOutput } from '../io';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
const pendingRequests = new Map<
|
||||
string,
|
||||
{ resolve: (value: unknown) => void; reject: (reason?: unknown) => void }
|
||||
>();
|
||||
|
||||
export function sendRequest<T>(type: string, payload: object = {}): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestId = crypto.randomUUID();
|
||||
pendingRequests.set(requestId, { resolve: resolve as (value: unknown) => void, reject });
|
||||
|
||||
writeOutput({
|
||||
type,
|
||||
payload: { requestId, ...payload }
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (pendingRequests.has(requestId)) {
|
||||
pendingRequests.delete(requestId);
|
||||
reject(new Error(`Request for ${type} timed out`));
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
export function handleResponse(requestId: string, result: unknown, error?: string) {
|
||||
const promise = pendingRequests.get(requestId);
|
||||
if (promise) {
|
||||
if (error) {
|
||||
promise.reject(new Error(error));
|
||||
} else {
|
||||
promise.resolve(result);
|
||||
}
|
||||
pendingRequests.delete(requestId);
|
||||
}
|
||||
}
|
|
@ -5,9 +5,7 @@ import { instances, navigationStack, toasts, browserExtensionState } from './sta
|
|||
import { batchedUpdates, updateContainer } from './reconciler';
|
||||
import { preferencesStore } from './preferences';
|
||||
import type { RaycastInstance } from './types';
|
||||
import { handleSystemResponse } from './api/environment';
|
||||
import { handleBrowserExtensionResponse } from './api/browserExtension';
|
||||
import { handleClipboardResponse } from './api/clipboard';
|
||||
import { handleResponse } from './api/rpc';
|
||||
import { handleOAuthResponse, handleTokenResponse } from './api/oauth';
|
||||
|
||||
process.on('unhandledRejection', (reason: unknown) => {
|
||||
|
@ -23,13 +21,22 @@ rl.on('line', (line) => {
|
|||
try {
|
||||
const command: { action: string; payload: unknown } = JSON.parse(line);
|
||||
|
||||
if (command.action.startsWith('system-') && command.action.endsWith('-response')) {
|
||||
const { requestId, result, error } = command.payload as {
|
||||
if (command.action.endsWith('-response')) {
|
||||
const { requestId, result, error, state, code } = command.payload as {
|
||||
requestId: string;
|
||||
result?: unknown;
|
||||
error?: string;
|
||||
state?: string;
|
||||
code?: string;
|
||||
};
|
||||
handleSystemResponse(requestId, result, error);
|
||||
|
||||
if (command.action === 'oauth-authorize-response') {
|
||||
handleOAuthResponse(state!, code!, state, error);
|
||||
} else if (command.action.startsWith('oauth-')) {
|
||||
handleTokenResponse(requestId, result, error);
|
||||
} else {
|
||||
handleResponse(requestId, result, error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -126,53 +133,11 @@ rl.on('line', (line) => {
|
|||
toast?.hide();
|
||||
break;
|
||||
}
|
||||
case 'browser-extension-response': {
|
||||
const { requestId, result, error } = command.payload as {
|
||||
requestId: string;
|
||||
result?: unknown;
|
||||
error?: string;
|
||||
};
|
||||
handleBrowserExtensionResponse(requestId, result, error);
|
||||
break;
|
||||
}
|
||||
case 'browser-extension-connection-status': {
|
||||
const { isConnected } = command.payload as { isConnected: boolean };
|
||||
browserExtensionState.isConnected = isConnected;
|
||||
break;
|
||||
}
|
||||
case 'oauth-authorize-response': {
|
||||
const { code, state, error } = command.payload as {
|
||||
code: string;
|
||||
state: string;
|
||||
error?: string;
|
||||
};
|
||||
handleOAuthResponse(state, code, state, error);
|
||||
break;
|
||||
}
|
||||
case 'oauth-get-tokens-response':
|
||||
case 'oauth-set-tokens-response':
|
||||
case 'oauth-remove-tokens-response': {
|
||||
const { requestId, result, error } = command.payload as {
|
||||
requestId: string;
|
||||
result?: unknown;
|
||||
error?: string;
|
||||
};
|
||||
handleTokenResponse(requestId, result, error);
|
||||
break;
|
||||
}
|
||||
case 'clipboard-read-text-response':
|
||||
case 'clipboard-read-response':
|
||||
case 'clipboard-copy-response':
|
||||
case 'clipboard-paste-response':
|
||||
case 'clipboard-clear-response': {
|
||||
const { requestId, result, error } = command.payload as {
|
||||
requestId: string;
|
||||
result?: unknown;
|
||||
error?: string;
|
||||
};
|
||||
handleClipboardResponse(requestId, result, error);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
writeLog(`Unknown command action: ${command.action}`);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue