mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2025-12-25 19:40:47 -08:00
fix: context isolation don't allow Observable passing
fixes https://github.com/electron/electron/issues/28176
This commit is contained in:
parent
e6adb1f0e5
commit
3ca09cf366
7 changed files with 95 additions and 14 deletions
|
|
@ -1,8 +1,8 @@
|
|||
import { Subscribable, Observer, TeardownLogic, Observable } from 'rxjs';
|
||||
import { Subscribable, Observer, TeardownLogic, Observable, isObservable } from 'rxjs';
|
||||
import { IpcRenderer, ipcRenderer, Event } from 'electron';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import Errio from 'errio';
|
||||
import { IpcProxyError } from './utils';
|
||||
import { getSubscriptionKey, IpcProxyError } from './utils';
|
||||
import { Request, RequestType, Response, ResponseType, ProxyDescriptor, ProxyPropertyType } from './common';
|
||||
|
||||
export type ObservableConstructor = new (subscribe: (obs: Observer<any>) => TeardownLogic) => Subscribable<any>;
|
||||
|
|
@ -20,10 +20,23 @@ export function createProxy<T>(descriptor: ProxyDescriptor, ObservableCtor: Obse
|
|||
);
|
||||
}
|
||||
|
||||
Object.defineProperty(result, propertyKey, {
|
||||
enumerable: true,
|
||||
get: memoize(() => getProperty(propertyType, propertyKey, descriptor.channel, ObservableCtor, transport)),
|
||||
});
|
||||
// fix https://github.com/electron/electron/issues/28176
|
||||
if (propertyType === ProxyPropertyType.Value$) {
|
||||
Object.defineProperty(result, getSubscriptionKey(propertyKey), {
|
||||
enumerable: true,
|
||||
get: memoize(() => (next: (value?: any) => void) => {
|
||||
const originalObservable = getProperty(propertyType, propertyKey, descriptor.channel, ObservableCtor, transport);
|
||||
if (isObservable(originalObservable)) {
|
||||
originalObservable.subscribe((value: any) => next(value));
|
||||
}
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
Object.defineProperty(result, propertyKey, {
|
||||
enumerable: true,
|
||||
get: memoize(() => getProperty(propertyType, propertyKey, descriptor.channel, ObservableCtor, transport)),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result as T;
|
||||
|
|
|
|||
27
src/helpers/electron-ipc-proxy/fixContextIsolation.ts
Normal file
27
src/helpers/electron-ipc-proxy/fixContextIsolation.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* fix https://github.com/electron/electron/issues/28176
|
||||
* We cannot pass Observable across contextBridge, so we have to add a hidden patch to the object on preload script, and use that patch to regenerate Observable on renderer side
|
||||
*/
|
||||
import { Observable } from 'rxjs';
|
||||
import { ProxyDescriptor, ProxyPropertyType } from './common';
|
||||
import { getSubscriptionKey } from './utils';
|
||||
|
||||
export function ipcProxyFixContextIsolation<T extends Record<string, any>>(service: T, descriptor: ProxyDescriptor): void {
|
||||
for (const key in descriptor.properties) {
|
||||
if (descriptor.properties[key] === ProxyPropertyType.Value$ && !(key in service) && getSubscriptionKey(key) in service) {
|
||||
// object is not extensible as contextBridge uses https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions
|
||||
// but we can still assign property to __proto__
|
||||
service.__proto__[key as keyof T] = new Observable((observer) => {
|
||||
service[getSubscriptionKey(key)]((value: any) => observer.next(value));
|
||||
}) as T[keyof T];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function fixContextIsolation(): void {
|
||||
const { descriptors, ...services } = window.service;
|
||||
for (const key in services) {
|
||||
const serviceName = key as Exclude<keyof typeof window.service, 'descriptors'>;
|
||||
ipcProxyFixContextIsolation(services[serviceName], descriptors[serviceName]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Observable, Subscription, isObservable } from 'rxjs';
|
||||
import { ipcMain, IpcMain, WebContents, IpcMainEvent } from 'electron';
|
||||
import Errio from 'errio';
|
||||
import { IpcProxyError, isFunction, isObservable } from './utils';
|
||||
import { IpcProxyError, isFunction } from './utils';
|
||||
import {
|
||||
Request,
|
||||
RequestType,
|
||||
|
|
@ -116,6 +116,9 @@ class ProxyServerHandler {
|
|||
if (!isObservable(obs)) {
|
||||
throw new IpcProxyError(`Remote property [${propKey}] is not an observable`);
|
||||
}
|
||||
if (typeof subscriptionId !== 'string') {
|
||||
throw new IpcProxyError(`subscriptionId [${subscriptionId}] is not a string`);
|
||||
}
|
||||
|
||||
this.doSubscribe(obs, subscriptionId, sender);
|
||||
}
|
||||
|
|
@ -133,6 +136,9 @@ class ProxyServerHandler {
|
|||
if (!isObservable(obs)) {
|
||||
throw new IpcProxyError(`Remote function [${propKey}] did not return an observable`);
|
||||
}
|
||||
if (typeof subscriptionId !== 'string') {
|
||||
throw new IpcProxyError(`subscriptionId [${subscriptionId}] is not a string`);
|
||||
}
|
||||
|
||||
this.doSubscribe(obs, subscriptionId, sender);
|
||||
}
|
||||
|
|
@ -148,8 +154,17 @@ class ProxyServerHandler {
|
|||
() => sender.send(subscriptionId, { type: ResponseType.Complete }),
|
||||
);
|
||||
|
||||
/* If the sender does not clean up after itself then we need to do it */
|
||||
sender.once('destroyed', () => this.doUnsubscribe(subscriptionId));
|
||||
/*
|
||||
* If the sender does not clean up after itself then we need to do it
|
||||
* This won't be called when webContent refresh by CMD+R, so beware this kind of memory leak.
|
||||
* But we will try to detect devtools-reload-page
|
||||
*/
|
||||
sender.once('destroyed', () => {
|
||||
this.doUnsubscribe(subscriptionId);
|
||||
});
|
||||
sender.once('devtools-reload-page', () => {
|
||||
this.doUnsubscribe(subscriptionId);
|
||||
});
|
||||
}
|
||||
|
||||
private handleUnsubscribe(request: UnsubscribeRequest): void {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { Observable } from 'rxjs';
|
||||
import Errio from 'errio';
|
||||
|
||||
/* Custom Error */
|
||||
|
|
@ -15,6 +14,11 @@ export function isFunction(value: any): value is Function {
|
|||
return value && typeof value === 'function';
|
||||
}
|
||||
|
||||
export function isObservable<T>(value: any): value is Observable<T> {
|
||||
return value && typeof value.subscribe === 'function';
|
||||
/**
|
||||
* Fix ContextIsolation
|
||||
* @param key original key
|
||||
* @returns
|
||||
*/
|
||||
export function getSubscriptionKey(key: string): string {
|
||||
return `${key}Subscribe`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,3 +60,22 @@ export const wikiGitWorkspace = createProxy<AsyncifyProxy<IWikiGitWorkspaceServi
|
|||
export const window = createProxy<AsyncifyProxy<IWindowService>>(WindowServiceIPCDescriptor);
|
||||
export const workspace = createProxy<AsyncifyProxy<IWorkspaceService>>(WorkspaceServiceIPCDescriptor);
|
||||
export const workspaceView = createProxy<AsyncifyProxy<IWorkspaceViewService>>(WorkspaceViewServiceIPCDescriptor);
|
||||
|
||||
export const descriptors = {
|
||||
auth: AuthenticationServiceIPCDescriptor,
|
||||
context: ContextServiceIPCDescriptor,
|
||||
git: GitServiceIPCDescriptor,
|
||||
menu: MenuServiceIPCDescriptor,
|
||||
native: NativeServiceIPCDescriptor,
|
||||
notification: NotificationServiceIPCDescriptor,
|
||||
preference: PreferenceServiceIPCDescriptor,
|
||||
systemPreference: SystemPreferenceServiceIPCDescriptor,
|
||||
theme: ThemeServiceIPCDescriptor,
|
||||
updater: UpdaterServiceIPCDescriptor,
|
||||
view: ViewServiceIPCDescriptor,
|
||||
wiki: WikiServiceIPCDescriptor,
|
||||
wikiGitWorkspace: WikiGitWorkspaceServiceIPCDescriptor,
|
||||
window: WindowServiceIPCDescriptor,
|
||||
workspace: WorkspaceServiceIPCDescriptor,
|
||||
workspaceView: WorkspaceViewServiceIPCDescriptor,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import path from 'path';
|
|||
import './common/i18n';
|
||||
import './common/authing-postmessage';
|
||||
import * as service from './common/services';
|
||||
import { MetaDataChannel, ViewChannel, ContextChannel, WindowChannel } from '@/constants/channels';
|
||||
import { MetaDataChannel, ViewChannel, WindowChannel } from '@/constants/channels';
|
||||
import { WindowNames, WindowMeta, IPossibleWindowMeta } from '@services/windows/WindowProperties';
|
||||
|
||||
const extraMetaJSONString = process.argv.pop() as string;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import { WindowNames, WindowMeta, IPreferenceWindowMeta } from '@services/window
|
|||
import 'typeface-roboto/index.css';
|
||||
|
||||
import { initI18N } from './i18n';
|
||||
import { fixContextIsolation } from './helpers/electron-ipc-proxy/fixContextIsolation';
|
||||
|
||||
fixContextIsolation();
|
||||
|
||||
const Main = React.lazy(async () => await import('./pages/Main'));
|
||||
const AboutPage = React.lazy(async () => await import('./pages/About'));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue