mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(electron): move preload to infra (#3011)
This commit is contained in:
4
.github/actions/setup-node/action.yml
vendored
4
.github/actions/setup-node/action.yml
vendored
@@ -121,3 +121,7 @@ runs:
|
|||||||
run: node apps/electron/node_modules/electron/install.js
|
run: node apps/electron/node_modules/electron/install.js
|
||||||
env:
|
env:
|
||||||
ELECTRON_OVERRIDE_DIST_PATH: ./node_modules/.cache/electron
|
ELECTRON_OVERRIDE_DIST_PATH: ./node_modules/.cache/electron
|
||||||
|
|
||||||
|
- name: Build Infra
|
||||||
|
shell: bash
|
||||||
|
run: yarn run build:infra
|
||||||
|
|||||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -352,9 +352,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NATIVE_TEST: 'true'
|
NATIVE_TEST: 'true'
|
||||||
|
|
||||||
- name: Build Infra
|
|
||||||
run: yarn run build:infra
|
|
||||||
|
|
||||||
- name: Build Plugins
|
- name: Build Plugins
|
||||||
run: yarn run build:plugins
|
run: yarn run build:plugins
|
||||||
|
|
||||||
@@ -412,9 +409,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
electron-install: false
|
electron-install: false
|
||||||
|
|
||||||
- name: Build Infra
|
|
||||||
run: yarn run build:infra
|
|
||||||
|
|
||||||
- name: Unit Test
|
- name: Unit Test
|
||||||
run: yarn nx test:coverage @affine/monorepo
|
run: yarn nx test:coverage @affine/monorepo
|
||||||
|
|
||||||
|
|||||||
3
.github/workflows/nightly-build.yml
vendored
3
.github/workflows/nightly-build.yml
vendored
@@ -123,9 +123,6 @@ jobs:
|
|||||||
name: before-make-web-static
|
name: before-make-web-static
|
||||||
path: apps/electron/resources/web-static
|
path: apps/electron/resources/web-static
|
||||||
|
|
||||||
- name: Build Infra
|
|
||||||
run: yarn run build:infra
|
|
||||||
|
|
||||||
- name: Build Plugins
|
- name: Build Plugins
|
||||||
run: yarn run build:plugins
|
run: yarn run build:plugins
|
||||||
|
|
||||||
|
|||||||
3
.github/workflows/release-desktop-app.yml
vendored
3
.github/workflows/release-desktop-app.yml
vendored
@@ -123,9 +123,6 @@ jobs:
|
|||||||
name: before-make-web-static
|
name: before-make-web-static
|
||||||
path: apps/electron/resources/web-static
|
path: apps/electron/resources/web-static
|
||||||
|
|
||||||
- name: Build Infra
|
|
||||||
run: yarn run build:infra
|
|
||||||
|
|
||||||
- name: Build Plugins
|
- name: Build Plugins
|
||||||
run: yarn run build:plugins
|
run: yarn run build:plugins
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# check lockfile is up to date
|
# check lockfile is up to date
|
||||||
yarn install --mode=update-lockfile
|
yarn install --mode=update-lockfile
|
||||||
|
|
||||||
|
# build infra code
|
||||||
|
yarn -T run build:infra
|
||||||
|
|
||||||
# lint staged files
|
# lint staged files
|
||||||
yarn exec lint-staged
|
yarn exec lint-staged
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { RendererToHelper } from '@toeverything/infra/preload/electron';
|
||||||
import { AsyncCall } from 'async-call-rpc';
|
import { AsyncCall } from 'async-call-rpc';
|
||||||
|
|
||||||
import { events, handlers } from './exposed';
|
import { events, handlers } from './exposed';
|
||||||
@@ -30,7 +31,7 @@ function setupRendererConnection(rendererPort: Electron.MessagePortMain) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const rpc = AsyncCall<PeersAPIs.RendererToHelper>(
|
const rpc = AsyncCall<RendererToHelper>(
|
||||||
Object.fromEntries(flattenedHandlers),
|
Object.fromEntries(flattenedHandlers),
|
||||||
{
|
{
|
||||||
channel: {
|
channel: {
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
|
import type {
|
||||||
|
HelperToMain,
|
||||||
|
MainToHelper,
|
||||||
|
} from '@toeverything/infra/preload/electron';
|
||||||
import { AsyncCall } from 'async-call-rpc';
|
import { AsyncCall } from 'async-call-rpc';
|
||||||
|
|
||||||
import { getExposedMeta } from './exposed';
|
import { getExposedMeta } from './exposed';
|
||||||
|
|
||||||
const helperToMainServer: PeersAPIs.HelperToMain = {
|
const helperToMainServer: HelperToMain = {
|
||||||
getMeta: () => getExposedMeta(),
|
getMeta: () => getExposedMeta(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mainRPC = AsyncCall<PeersAPIs.MainToHelper>(helperToMainServer, {
|
export const mainRPC = AsyncCall<MainToHelper>(helperToMainServer, {
|
||||||
strict: {
|
strict: {
|
||||||
unknownMessage: false,
|
unknownMessage: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
HelperToMain,
|
||||||
|
MainToHelper,
|
||||||
|
} from '@toeverything/infra/preload/electron';
|
||||||
import { type _AsyncVersionOf, AsyncCall } from 'async-call-rpc';
|
import { type _AsyncVersionOf, AsyncCall } from 'async-call-rpc';
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
@@ -36,7 +40,7 @@ class HelperProcessManager {
|
|||||||
#process: UtilityProcess;
|
#process: UtilityProcess;
|
||||||
|
|
||||||
// a rpc server for the main process -> helper process
|
// a rpc server for the main process -> helper process
|
||||||
rpc?: _AsyncVersionOf<PeersAPIs.HelperToMain>;
|
rpc?: _AsyncVersionOf<HelperToMain>;
|
||||||
|
|
||||||
static instance = new HelperProcessManager();
|
static instance = new HelperProcessManager();
|
||||||
|
|
||||||
@@ -86,13 +90,13 @@ class HelperProcessManager {
|
|||||||
]);
|
]);
|
||||||
const appMethods = pickAndBind(app, ['getPath']);
|
const appMethods = pickAndBind(app, ['getPath']);
|
||||||
|
|
||||||
const mainToHelperServer: PeersAPIs.MainToHelper = {
|
const mainToHelperServer: MainToHelper = {
|
||||||
...dialogMethods,
|
...dialogMethods,
|
||||||
...shellMethods,
|
...shellMethods,
|
||||||
...appMethods,
|
...appMethods,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.rpc = AsyncCall<PeersAPIs.HelperToMain>(mainToHelperServer, {
|
this.rpc = AsyncCall<HelperToMain>(mainToHelperServer, {
|
||||||
strict: {
|
strict: {
|
||||||
// the channel is shared for other purposes as well so that we do not want to
|
// the channel is shared for other purposes as well so that we do not want to
|
||||||
// restrict to only JSONRPC messages
|
// restrict to only JSONRPC messages
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { contextBridge, ipcRenderer } from 'electron';
|
import { contextBridge, ipcRenderer } from 'electron';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const { appInfo, getAffineAPIs } = await import('./affine-apis');
|
const { appInfo, getElectronAPIs } = await import(
|
||||||
const { apis, events } = getAffineAPIs();
|
'@toeverything/infra/preload/electron'
|
||||||
|
);
|
||||||
|
const { apis, events } = getElectronAPIs();
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('appInfo', appInfo);
|
contextBridge.exposeInMainWorld('appInfo', appInfo);
|
||||||
contextBridge.exposeInMainWorld('apis', apis);
|
contextBridge.exposeInMainWorld('apis', apis);
|
||||||
|
|||||||
35
apps/electron/src/types.d.ts
vendored
35
apps/electron/src/types.d.ts
vendored
@@ -1,35 +0,0 @@
|
|||||||
declare namespace PeersAPIs {
|
|
||||||
import type { app, dialog, shell } from 'electron';
|
|
||||||
|
|
||||||
interface ExposedMeta {
|
|
||||||
handlers: [string, string[]][];
|
|
||||||
events: [string, string[]][];
|
|
||||||
}
|
|
||||||
|
|
||||||
// render <-> helper
|
|
||||||
interface RendererToHelper {
|
|
||||||
postEvent: (channel: string, ...args: any[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HelperToRenderer {
|
|
||||||
[key: string]: (...args: any[]) => Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper <-> main
|
|
||||||
interface HelperToMain {
|
|
||||||
getMeta: () => ExposedMeta;
|
|
||||||
}
|
|
||||||
|
|
||||||
type MainToHelper = Pick<
|
|
||||||
typeof dialog & typeof shell & typeof app,
|
|
||||||
| 'showOpenDialog'
|
|
||||||
| 'showSaveDialog'
|
|
||||||
| 'openExternal'
|
|
||||||
| 'showItemInFolder'
|
|
||||||
| 'getPath'
|
|
||||||
>;
|
|
||||||
|
|
||||||
// render <-> main
|
|
||||||
// these are handled via IPC
|
|
||||||
// TODO: fix type
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,50 @@
|
|||||||
{
|
{
|
||||||
"name": "@toeverything/infra",
|
"name": "@toeverything/infra",
|
||||||
"main": "./src/index.ts",
|
"type": "module",
|
||||||
"module": "./src/index.ts",
|
"module": "./dist/index.mjs",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": {
|
||||||
},
|
"types": "./dist/index.d.ts",
|
||||||
"publishConfig": {
|
"import": "./dist/index.js",
|
||||||
"module": "./dist/index.mjs",
|
"require": "./dist/index.cjs"
|
||||||
"main": "./dist/index.js",
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"import": "./dist/index.mjs",
|
|
||||||
"require": "./dist/index.js"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"files": [
|
"./core/*": {
|
||||||
"dist"
|
"types": "./dist/core/*.d.ts",
|
||||||
]
|
"import": "./dist/core/*.js",
|
||||||
|
"require": "./dist/core/*.cjs"
|
||||||
|
},
|
||||||
|
"./preload/*": {
|
||||||
|
"types": "./dist/preload/*.d.ts",
|
||||||
|
"import": "./dist/preload/*.js",
|
||||||
|
"require": "./dist/preload/*.cjs"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"dev": "vite build --watch"
|
"dev": "vite build --watch"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"async-call-rpc": "^6.3.1",
|
||||||
|
"electron": "link:../../apps/electron/node_modules/electron",
|
||||||
"vite": "^4.3.9",
|
"vite": "^4.3.9",
|
||||||
"vite-plugin-dts": "3.0.2"
|
"vite-plugin-dts": "3.0.2"
|
||||||
},
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"async-call-rpc": "*",
|
||||||
|
"electron": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"async-call-rpc": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"electron": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"version": "0.7.0-beta.0"
|
"version": "0.7.0-beta.0"
|
||||||
}
|
}
|
||||||
|
|||||||
3
packages/infra/preload/electron.d.ts
vendored
Normal file
3
packages/infra/preload/electron.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore
|
||||||
|
export * from '../dist/preload/electron';
|
||||||
3
packages/infra/preload/electron.js
Normal file
3
packages/infra/preload/electron.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/// <reference types="../dist/preload/electron.d.ts" />
|
||||||
|
export * from '../dist/preload/electron.js';
|
||||||
74
packages/infra/src/core/event-emitter.ts
Normal file
74
packages/infra/src/core/event-emitter.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Andy Wermke
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
export type EventMap = {
|
||||||
|
[key: string]: (...args: any[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe event emitter.
|
||||||
|
*
|
||||||
|
* Use it like this:
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* type MyEvents = {
|
||||||
|
* error: (error: Error) => void;
|
||||||
|
* message: (from: string, content: string) => void;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const myEmitter = new EventEmitter() as TypedEmitter<MyEvents>;
|
||||||
|
*
|
||||||
|
* myEmitter.emit("error", "x") // <- Will catch this type error;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Lifecycle:
|
||||||
|
* invoke -> handle -> emit -> on/once
|
||||||
|
*/
|
||||||
|
export interface TypedEventEmitter<Events extends EventMap> {
|
||||||
|
addListener<E extends keyof Events>(event: E, listener: Events[E]): this;
|
||||||
|
on<E extends keyof Events>(event: E, listener: Events[E]): this;
|
||||||
|
once<E extends keyof Events>(event: E, listener: Events[E]): this;
|
||||||
|
|
||||||
|
off<E extends keyof Events>(event: E, listener: Events[E]): this;
|
||||||
|
removeAllListeners<E extends keyof Events>(event?: E): this;
|
||||||
|
removeListener<E extends keyof Events>(event: E, listener: Events[E]): this;
|
||||||
|
|
||||||
|
emit<E extends keyof Events>(
|
||||||
|
event: E,
|
||||||
|
...args: Parameters<Events[E]>
|
||||||
|
): boolean;
|
||||||
|
// The sloppy `eventNames()` return type is to mitigate type incompatibilities - see #5
|
||||||
|
eventNames(): (keyof Events | string | symbol)[];
|
||||||
|
rawListeners<E extends keyof Events>(event: E): Events[E][];
|
||||||
|
listeners<E extends keyof Events>(event: E): Events[E][];
|
||||||
|
listenerCount<E extends keyof Events>(event: E): number;
|
||||||
|
|
||||||
|
handle<E extends keyof Events>(event: E, handler: Events[E]): this;
|
||||||
|
invoke<E extends keyof Events>(
|
||||||
|
event: E,
|
||||||
|
...args: Parameters<Events[E]>
|
||||||
|
): Promise<ReturnType<Events[E]>>;
|
||||||
|
|
||||||
|
getMaxListeners(): number;
|
||||||
|
setMaxListeners(maxListeners: number): this;
|
||||||
|
}
|
||||||
@@ -1,145 +1,51 @@
|
|||||||
export interface WorkspaceMeta {
|
import type {
|
||||||
id: string;
|
ClipboardHandlers,
|
||||||
mainDBPath: string;
|
DBHandlers,
|
||||||
secondaryDBPath?: string; // assume there will be only one
|
DebugHandlers,
|
||||||
}
|
DialogHandlers,
|
||||||
|
ExportHandlers,
|
||||||
export type PrimitiveHandlers = (...args: any[]) => Promise<any>;
|
UIHandlers,
|
||||||
type TODO = any;
|
UpdaterHandlers,
|
||||||
|
WorkspaceHandlers,
|
||||||
export abstract class HandlerManager<
|
} from './type';
|
||||||
Namespace extends string,
|
import { HandlerManager } from './type';
|
||||||
Handlers extends Record<string, PrimitiveHandlers>
|
|
||||||
> {
|
|
||||||
abstract readonly app: TODO;
|
|
||||||
abstract readonly namespace: Namespace;
|
|
||||||
abstract readonly handlers: Handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
type DBHandlers = {
|
|
||||||
getDocAsUpdates: (
|
|
||||||
workspaceId: string,
|
|
||||||
subdocId?: string
|
|
||||||
) => Promise<Uint8Array>;
|
|
||||||
applyDocUpdate: (
|
|
||||||
id: string,
|
|
||||||
update: Uint8Array,
|
|
||||||
subdocId?: string
|
|
||||||
) => Promise<void>;
|
|
||||||
addBlob: (
|
|
||||||
workspaceId: string,
|
|
||||||
key: string,
|
|
||||||
data: Uint8Array
|
|
||||||
) => Promise<void>;
|
|
||||||
getBlob: (workspaceId: string, key: string) => Promise<any>;
|
|
||||||
deleteBlob: (workspaceId: string, key: string) => Promise<void>;
|
|
||||||
getBlobKeys: (workspaceId: string) => Promise<any>;
|
|
||||||
getDefaultStorageLocation: () => Promise<string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class DBHandlerManager extends HandlerManager<
|
export abstract class DBHandlerManager extends HandlerManager<
|
||||||
'db',
|
'db',
|
||||||
DBHandlers
|
DBHandlers
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
type DebugHandlers = {
|
|
||||||
revealLogFile: () => Promise<string>;
|
|
||||||
logFilePath: () => Promise<string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class DebugHandlerManager extends HandlerManager<
|
export abstract class DebugHandlerManager extends HandlerManager<
|
||||||
'debug',
|
'debug',
|
||||||
DebugHandlers
|
DebugHandlers
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
type DialogHandlers = {
|
|
||||||
revealDBFile: (workspaceId: string) => Promise<any>;
|
|
||||||
loadDBFile: () => Promise<any>;
|
|
||||||
saveDBFileAs: (workspaceId: string) => Promise<any>;
|
|
||||||
moveDBFile: (workspaceId: string, dbFileLocation?: string) => Promise<any>;
|
|
||||||
selectDBFileLocation: () => Promise<any>;
|
|
||||||
setFakeDialogResult: (result: any) => Promise<any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class DialogHandlerManager extends HandlerManager<
|
export abstract class DialogHandlerManager extends HandlerManager<
|
||||||
'dialog',
|
'dialog',
|
||||||
DialogHandlers
|
DialogHandlers
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
type UIHandlers = {
|
|
||||||
handleThemeChange: (theme: 'system' | 'light' | 'dark') => Promise<any>;
|
|
||||||
handleSidebarVisibilityChange: (visible: boolean) => Promise<any>;
|
|
||||||
handleMinimizeApp: () => Promise<any>;
|
|
||||||
handleMaximizeApp: () => Promise<any>;
|
|
||||||
handleCloseApp: () => Promise<any>;
|
|
||||||
getGoogleOauthCode: () => Promise<any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class UIHandlerManager extends HandlerManager<
|
export abstract class UIHandlerManager extends HandlerManager<
|
||||||
'ui',
|
'ui',
|
||||||
UIHandlers
|
UIHandlers
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
type ClipboardHandlers = {
|
|
||||||
copyAsImageFromString: (dataURL: string) => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class ClipboardHandlerManager extends HandlerManager<
|
export abstract class ClipboardHandlerManager extends HandlerManager<
|
||||||
'clipboard',
|
'clipboard',
|
||||||
ClipboardHandlers
|
ClipboardHandlers
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
type ExportHandlers = {
|
|
||||||
savePDFFileAs: (title: string) => Promise<any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class ExportHandlerManager extends HandlerManager<
|
export abstract class ExportHandlerManager extends HandlerManager<
|
||||||
'export',
|
'export',
|
||||||
ExportHandlers
|
ExportHandlers
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
type UpdaterHandlers = {
|
|
||||||
currentVersion: () => Promise<any>;
|
|
||||||
quitAndInstall: () => Promise<any>;
|
|
||||||
checkForUpdatesAndNotify: () => Promise<any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class UpdaterHandlerManager extends HandlerManager<
|
export abstract class UpdaterHandlerManager extends HandlerManager<
|
||||||
'updater',
|
'updater',
|
||||||
UpdaterHandlers
|
UpdaterHandlers
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
type WorkspaceHandlers = {
|
|
||||||
list: () => Promise<[workspaceId: string, meta: WorkspaceMeta][]>;
|
|
||||||
delete: (id: string) => Promise<void>;
|
|
||||||
getMeta: (id: string) => Promise<WorkspaceMeta>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class WorkspaceHandlerManager extends HandlerManager<
|
export abstract class WorkspaceHandlerManager extends HandlerManager<
|
||||||
'workspace',
|
'workspace',
|
||||||
WorkspaceHandlers
|
WorkspaceHandlers
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
export type UnwrapManagerHandlerToServerSide<
|
|
||||||
ElectronEvent extends {
|
|
||||||
frameId: number;
|
|
||||||
processId: number;
|
|
||||||
},
|
|
||||||
Manager extends HandlerManager<string, Record<string, PrimitiveHandlers>>
|
|
||||||
> = {
|
|
||||||
[K in keyof Manager['handlers']]: Manager['handlers'][K] extends (
|
|
||||||
...args: infer Args
|
|
||||||
) => Promise<infer R>
|
|
||||||
? (event: ElectronEvent, ...args: Args) => Promise<R>
|
|
||||||
: never;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UnwrapManagerHandlerToClientSide<
|
|
||||||
Manager extends HandlerManager<string, Record<string, PrimitiveHandlers>>
|
|
||||||
> = {
|
|
||||||
[K in keyof Manager['handlers']]: Manager['handlers'][K] extends (
|
|
||||||
...args: infer Args
|
|
||||||
) => Promise<infer R>
|
|
||||||
? (...args: Args) => Promise<R>
|
|
||||||
: never;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from './handler';
|
export * from './handler';
|
||||||
|
export * from './type';
|
||||||
|
|||||||
@@ -1,14 +1,38 @@
|
|||||||
// NOTE: we will generate preload types from this file
|
// Please add modules to `external` in `rollupOptions` to avoid wrong bundling.
|
||||||
import { AsyncCall, type EventBasedChannel } from 'async-call-rpc';
|
import { AsyncCall, type EventBasedChannel } from 'async-call-rpc';
|
||||||
|
import type { app, dialog, shell } from 'electron';
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
type ExposedMeta = {
|
export interface ExposedMeta {
|
||||||
handlers: [namespace: string, handlerNames: string[]][];
|
handlers: [string, string[]][];
|
||||||
events: [namespace: string, eventNames: string[]][];
|
events: [string, string[]][];
|
||||||
};
|
}
|
||||||
|
|
||||||
export function getAffineAPIs() {
|
// render <-> helper
|
||||||
|
export interface RendererToHelper {
|
||||||
|
postEvent: (channel: string, ...args: any[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HelperToRenderer {
|
||||||
|
[key: string]: (...args: any[]) => Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper <-> main
|
||||||
|
export interface HelperToMain {
|
||||||
|
getMeta: () => ExposedMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MainToHelper = Pick<
|
||||||
|
typeof dialog & typeof shell & typeof app,
|
||||||
|
| 'showOpenDialog'
|
||||||
|
| 'showSaveDialog'
|
||||||
|
| 'openExternal'
|
||||||
|
| 'showItemInFolder'
|
||||||
|
| 'getPath'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function getElectronAPIs() {
|
||||||
const mainAPIs = getMainAPIs();
|
const mainAPIs = getMainAPIs();
|
||||||
const helperAPIs = getHelperAPIs();
|
const helperAPIs = getHelperAPIs();
|
||||||
|
|
||||||
@@ -126,13 +150,13 @@ function getHelperAPIs() {
|
|||||||
return val ? JSON.parse(val) : null;
|
return val ? JSON.parse(val) : null;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const rendererToHelperServer: PeersAPIs.RendererToHelper = {
|
const rendererToHelperServer: RendererToHelper = {
|
||||||
postEvent: (channel, ...args) => {
|
postEvent: (channel, ...args) => {
|
||||||
events$.next({ channel, args });
|
events$.next({ channel, args });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const rpc = AsyncCall<PeersAPIs.HelperToRenderer>(rendererToHelperServer, {
|
const rpc = AsyncCall<HelperToRenderer>(rendererToHelperServer, {
|
||||||
channel: helperPort$.then(helperPort =>
|
channel: helperPort$.then(helperPort =>
|
||||||
createMessagePortChannel(helperPort)
|
createMessagePortChannel(helperPort)
|
||||||
),
|
),
|
||||||
@@ -157,10 +181,10 @@ function getHelperAPIs() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setup = (meta: ExposedMeta) => {
|
const setup = (meta: ExposedMeta) => {
|
||||||
const { handlers: handlersMeta, events: eventsMeta } = meta;
|
const { handlers, events } = meta;
|
||||||
|
|
||||||
const helperHandlers = Object.fromEntries(
|
const helperHandlers = Object.fromEntries(
|
||||||
handlersMeta.map(([namespace, functionNames]) => {
|
handlers.map(([namespace, functionNames]) => {
|
||||||
return [
|
return [
|
||||||
namespace,
|
namespace,
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
@@ -173,7 +197,7 @@ function getHelperAPIs() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const helperEvents = Object.fromEntries(
|
const helperEvents = Object.fromEntries(
|
||||||
eventsMeta.map(([namespace, eventNames]) => {
|
events.map(([namespace, eventNames]) => {
|
||||||
return [
|
return [
|
||||||
namespace,
|
namespace,
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
162
packages/infra/src/type.ts
Normal file
162
packages/infra/src/type.ts
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import type { TypedEventEmitter } from './core/event-emitter';
|
||||||
|
|
||||||
|
export abstract class HandlerManager<
|
||||||
|
Namespace extends string,
|
||||||
|
Handlers extends Record<string, PrimitiveHandlers>
|
||||||
|
> {
|
||||||
|
static instance: HandlerManager<string, Record<string, PrimitiveHandlers>>;
|
||||||
|
private _app: App<Namespace, Handlers>;
|
||||||
|
private _namespace: Namespace;
|
||||||
|
private _handlers: Handlers;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
private _initialized = false;
|
||||||
|
|
||||||
|
registerHandlers(handlers: Handlers) {
|
||||||
|
if (this._initialized) {
|
||||||
|
throw new Error('Already initialized');
|
||||||
|
}
|
||||||
|
this._handlers = handlers;
|
||||||
|
for (const [name, handler] of Object.entries(this._handlers)) {
|
||||||
|
this._app.handle(`${this._namespace}:${name}`, (async (...args: any[]) =>
|
||||||
|
handler(...args)) as any);
|
||||||
|
}
|
||||||
|
this._initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeHandler<K extends keyof Handlers>(
|
||||||
|
name: K,
|
||||||
|
...args: Parameters<Handlers[K]>
|
||||||
|
): Promise<ReturnType<Handlers[K]>> {
|
||||||
|
return this._handlers[name](...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getInstance(): HandlerManager<
|
||||||
|
string,
|
||||||
|
Record<string, PrimitiveHandlers>
|
||||||
|
> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkspaceMeta {
|
||||||
|
id: string;
|
||||||
|
mainDBPath: string;
|
||||||
|
secondaryDBPath?: string; // assume there will be only one
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PrimitiveHandlers = (...args: any[]) => Promise<any>;
|
||||||
|
|
||||||
|
export type DBHandlers = {
|
||||||
|
getDocAsUpdates: (
|
||||||
|
workspaceId: string,
|
||||||
|
subdocId?: string
|
||||||
|
) => Promise<Uint8Array>;
|
||||||
|
applyDocUpdate: (
|
||||||
|
id: string,
|
||||||
|
update: Uint8Array,
|
||||||
|
subdocId?: string
|
||||||
|
) => Promise<void>;
|
||||||
|
addBlob: (
|
||||||
|
workspaceId: string,
|
||||||
|
key: string,
|
||||||
|
data: Uint8Array
|
||||||
|
) => Promise<void>;
|
||||||
|
getBlob: (workspaceId: string, key: string) => Promise<any>;
|
||||||
|
deleteBlob: (workspaceId: string, key: string) => Promise<void>;
|
||||||
|
getBlobKeys: (workspaceId: string) => Promise<any>;
|
||||||
|
getDefaultStorageLocation: () => Promise<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DebugHandlers = {
|
||||||
|
revealLogFile: () => Promise<string>;
|
||||||
|
logFilePath: () => Promise<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DialogHandlers = {
|
||||||
|
revealDBFile: (workspaceId: string) => Promise<any>;
|
||||||
|
loadDBFile: () => Promise<any>;
|
||||||
|
saveDBFileAs: (workspaceId: string) => Promise<any>;
|
||||||
|
moveDBFile: (workspaceId: string, dbFileLocation?: string) => Promise<any>;
|
||||||
|
selectDBFileLocation: () => Promise<any>;
|
||||||
|
setFakeDialogResult: (result: any) => Promise<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UIHandlers = {
|
||||||
|
handleThemeChange: (theme: 'system' | 'light' | 'dark') => Promise<any>;
|
||||||
|
handleSidebarVisibilityChange: (visible: boolean) => Promise<any>;
|
||||||
|
handleMinimizeApp: () => Promise<any>;
|
||||||
|
handleMaximizeApp: () => Promise<any>;
|
||||||
|
handleCloseApp: () => Promise<any>;
|
||||||
|
getGoogleOauthCode: () => Promise<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ClipboardHandlers = {
|
||||||
|
copyAsImageFromString: (dataURL: string) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExportHandlers = {
|
||||||
|
savePDFFileAs: (title: string) => Promise<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdaterHandlers = {
|
||||||
|
currentVersion: () => Promise<any>;
|
||||||
|
quitAndInstall: () => Promise<any>;
|
||||||
|
checkForUpdatesAndNotify: () => Promise<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceHandlers = {
|
||||||
|
list: () => Promise<[workspaceId: string, meta: WorkspaceMeta][]>;
|
||||||
|
delete: (id: string) => Promise<void>;
|
||||||
|
getMeta: (id: string) => Promise<WorkspaceMeta>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EventMap = DBHandlers &
|
||||||
|
DebugHandlers &
|
||||||
|
DialogHandlers &
|
||||||
|
UIHandlers &
|
||||||
|
ClipboardHandlers &
|
||||||
|
ExportHandlers &
|
||||||
|
UpdaterHandlers &
|
||||||
|
WorkspaceHandlers;
|
||||||
|
|
||||||
|
export type UnwrapManagerHandlerToServerSide<
|
||||||
|
ElectronEvent extends {
|
||||||
|
frameId: number;
|
||||||
|
processId: number;
|
||||||
|
},
|
||||||
|
Manager extends HandlerManager<string, Record<string, PrimitiveHandlers>>
|
||||||
|
> = Manager extends HandlerManager<infer _, infer Handlers>
|
||||||
|
? {
|
||||||
|
[K in keyof Handlers]: Handlers[K] extends (
|
||||||
|
...args: infer Args
|
||||||
|
) => Promise<infer R>
|
||||||
|
? (event: ElectronEvent, ...args: Args) => Promise<R>
|
||||||
|
: never;
|
||||||
|
}
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type UnwrapManagerHandlerToClientSide<
|
||||||
|
Manager extends HandlerManager<string, Record<string, PrimitiveHandlers>>
|
||||||
|
> = Manager extends HandlerManager<infer _, infer Handlers>
|
||||||
|
? {
|
||||||
|
[K in keyof Handlers]: Handlers[K] extends (
|
||||||
|
...args: infer Args
|
||||||
|
) => Promise<infer R>
|
||||||
|
? (...args: Args) => Promise<R>
|
||||||
|
: never;
|
||||||
|
}
|
||||||
|
: never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export type App<
|
||||||
|
Namespace extends string,
|
||||||
|
Handlers extends Record<string, PrimitiveHandlers>
|
||||||
|
> = TypedEventEmitter<{
|
||||||
|
[K in keyof Handlers as `${Namespace}:${K & string}`]: Handlers[K];
|
||||||
|
}>;
|
||||||
@@ -8,13 +8,19 @@ const root = fileURLToPath(new URL('.', import.meta.url));
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
build: {
|
build: {
|
||||||
|
minify: false,
|
||||||
lib: {
|
lib: {
|
||||||
entry: {
|
entry: {
|
||||||
index: resolve(root, 'src/index.ts'),
|
index: resolve(root, 'src/index.ts'),
|
||||||
|
'core/event-emitter': resolve(root, 'src/core/event-emitter.ts'),
|
||||||
|
'preload/electron': resolve(root, 'src/preload/electron.ts'),
|
||||||
},
|
},
|
||||||
formats: ['es', 'cjs'],
|
formats: ['es', 'cjs'],
|
||||||
name: 'AffineInfra',
|
name: 'AffineInfra',
|
||||||
},
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['electron', 'async-call-rpc', 'rxjs'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
dts({
|
dts({
|
||||||
|
|||||||
16
yarn.lock
16
yarn.lock
@@ -11436,8 +11436,18 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@toeverything/infra@workspace:packages/infra"
|
resolution: "@toeverything/infra@workspace:packages/infra"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
async-call-rpc: ^6.3.1
|
||||||
|
electron: "link:../../apps/electron/node_modules/electron"
|
||||||
vite: ^4.3.9
|
vite: ^4.3.9
|
||||||
vite-plugin-dts: 3.0.2
|
vite-plugin-dts: 3.0.2
|
||||||
|
peerDependencies:
|
||||||
|
async-call-rpc: "*"
|
||||||
|
electron: "*"
|
||||||
|
peerDependenciesMeta:
|
||||||
|
async-call-rpc:
|
||||||
|
optional: true
|
||||||
|
electron:
|
||||||
|
optional: true
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
@@ -17052,6 +17062,12 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"electron@link:../../apps/electron/node_modules/electron::locator=%40toeverything%2Finfra%40workspace%3Apackages%2Finfra":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "electron@link:../../apps/electron/node_modules/electron::locator=%40toeverything%2Finfra%40workspace%3Apackages%2Finfra"
|
||||||
|
languageName: node
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"electron@npm:^25.2.0":
|
"electron@npm:^25.2.0":
|
||||||
version: 25.2.0
|
version: 25.2.0
|
||||||
resolution: "electron@npm:25.2.0"
|
resolution: "electron@npm:25.2.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user