mirror of
https://github.com/RPCSX/rpcsx-ui.git
synced 2026-01-31 01:05:23 +01:00
extension-host: implement mobile support
use interfaces to create external components and launchers fixed typo in readme
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
- `npm install`
|
||||
- `npm run build:web`
|
||||
- `npm run dev:ui`
|
||||
- `npm run dev:wev:server`
|
||||
- `npm run dev:web:server`
|
||||
|
||||
|
||||
## Build Guide for Android
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -42,6 +42,7 @@
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-native": "0.79.6",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
"react-native-gesture-handler": "~2.24.0",
|
||||
"react-native-reanimated": "~3.17.4",
|
||||
"react-native-safe-area-context": "^5.4.0",
|
||||
@@ -14155,6 +14156,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-device-info": {
|
||||
"version": "14.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-14.0.4.tgz",
|
||||
"integrity": "sha512-NX0wMAknSDBeFnEnSFQ8kkAcQrFHrG4Cl0mVjoD+0++iaKrOupiGpBXqs8xR0SeJyPC5zpdPl4h/SaBGly6UxA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-edge-to-edge": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-edge-to-edge/-/react-native-edge-to-edge-1.6.0.tgz",
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"expo-constants": "~17.1.7",
|
||||
"expo-dev-client": "^5.2.4",
|
||||
"expo-document-picker": "^13.1.6",
|
||||
"expo-file-system": "~18.1.11",
|
||||
"expo-font": "~13.3.2",
|
||||
"expo-haptics": "~14.1.4",
|
||||
"expo-image": "~2.4.0",
|
||||
@@ -60,13 +61,13 @@
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-native": "0.79.6",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
"react-native-gesture-handler": "~2.24.0",
|
||||
"react-native-reanimated": "~3.17.4",
|
||||
"react-native-safe-area-context": "^5.4.0",
|
||||
"react-native-screens": "~4.11.1",
|
||||
"react-native-web": "^0.20.0",
|
||||
"react-native-webview": "13.13.5",
|
||||
"expo-file-system": "~18.1.11"
|
||||
"react-native-webview": "13.13.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/metro-config": "~0.20.0",
|
||||
|
||||
@@ -711,17 +711,17 @@ type ${uLabel}Interface = {
|
||||
${"methods" in iface ? iface.methods && Object.keys(iface.methods).map(method => {
|
||||
const methodTypeLabel = generateComponentLabelName(component, `${name}-${method}`, true);
|
||||
if ("params" in (iface.methods as any)[method]) {
|
||||
return ` ${generateLabelName(method, false)}(caller: Component, request: ${methodTypeLabel}Request): ${methodTypeLabel}Response | Promise<${methodTypeLabel}Response>`;
|
||||
return ` ${generateLabelName(method, false)}(caller: ComponentRef, request: ${methodTypeLabel}Request): ${methodTypeLabel}Response | Promise<${methodTypeLabel}Response>`;
|
||||
} else {
|
||||
return ` ${generateLabelName(method, false)}(caller: Component): ${methodTypeLabel}Response | Promise<${methodTypeLabel}Response>`;
|
||||
return ` ${generateLabelName(method, false)}(caller: ComponentRef): ${methodTypeLabel}Response | Promise<${methodTypeLabel}Response>`;
|
||||
}
|
||||
}).join("\n") : ""}
|
||||
${"notifications" in iface ? iface.notifications && Object.keys(iface.notifications).map(notification => {
|
||||
const methodTypeLabel = generateComponentLabelName(component, `${name}-${notification}`, true);
|
||||
if ("params" in (iface.notifications as any)[notification]) {
|
||||
return ` ${generateLabelName(notification, false)}(caller: Component, request: ${methodTypeLabel}Request): void | Promise<void>;`
|
||||
return ` ${generateLabelName(notification, false)}(caller: ComponentRef, request: ${methodTypeLabel}Request): void | Promise<void>;`
|
||||
} else {
|
||||
return ` ${generateLabelName(notification, false)}(caller: Component): void | Promise<void>;`;
|
||||
return ` ${generateLabelName(notification, false)}(caller: ComponentRef): void | Promise<void>;`;
|
||||
}
|
||||
}).join("\n") : ""}
|
||||
};`
|
||||
@@ -849,7 +849,7 @@ export class ${uLabel}ComponentObject implements ComponentObject {
|
||||
|
||||
constructor(public impl: ${uLabel}Interface, public name: string) {}
|
||||
|
||||
call(caller: Component, method: string, params: Json | undefined): Promise<Json | void> | Json | void {
|
||||
call(caller: ComponentRef, method: string, params: Json | undefined): Promise<Json | void> | Json | void {
|
||||
void caller, params;
|
||||
|
||||
switch (method) {
|
||||
@@ -866,7 +866,7 @@ ${"methods" in iface ? iface.methods && Object.keys(iface.methods).map(method =>
|
||||
}
|
||||
}
|
||||
|
||||
notify(caller: Component, method: string, params: Json | undefined): void | Promise<void> {
|
||||
notify(caller: ComponentRef, method: string, params: Json | undefined): void | Promise<void> {
|
||||
void caller, params;
|
||||
|
||||
switch (method) {
|
||||
@@ -897,12 +897,12 @@ ${"methods" in iface ? iface.methods && Object.keys(iface.methods).map(method =>
|
||||
const methodTypeLabel = generateComponentLabelName(component, `${name}-${method}`, true);
|
||||
if ("params" in (iface.methods as any)[method]) {
|
||||
return ` async ${generateLabelName(method, false)}(request: ${methodTypeLabel}Request): Promise<${methodTypeLabel}Response> {
|
||||
return (await ${component == "core" ? "" : "core."}objectCall({ object: this.id, method: "${method}", params: request})).result as any;
|
||||
return await ${component == "core" ? "" : "core."}objectCall({ object: this.id, method: "${method}", params: request}) as any;
|
||||
}
|
||||
`
|
||||
} else {
|
||||
return ` async ${generateLabelName(method, false)}(): Promise<${methodTypeLabel}Response> {
|
||||
return (await ${component == "core" ? "" : "core."}objectCall({ object: this.id, method: "${method}", params: {}})).result as any;
|
||||
return await ${component == "core" ? "" : "core."}objectCall({ object: this.id, method: "${method}", params: {}}) as any;
|
||||
}
|
||||
`
|
||||
}
|
||||
@@ -1019,7 +1019,7 @@ export function onAny${uLabel}Created(handler: () => Promise<void> | void) {
|
||||
`;
|
||||
}
|
||||
|
||||
generateView(component: string, _path: string, name: string) {
|
||||
generateView(_component: string, _path: string, name: string) {
|
||||
this.viewBody += `
|
||||
export function push${name}View(target: Window, params: ${name}Props) {
|
||||
return target.pushView("${name}", params);
|
||||
@@ -1045,8 +1045,8 @@ type ComponentObject = {
|
||||
typeId: string;
|
||||
name: string;
|
||||
impl: object;
|
||||
call(caller: Component, method: string, params: Json | undefined): Promise<Json | void> | Json | void;
|
||||
notify(caller: Component, method: string, params: Json | undefined): void | Promise<void>;
|
||||
call(caller: ComponentRef, method: string, params: Json | undefined): Promise<Json | void> | Json | void;
|
||||
notify(caller: ComponentRef, method: string, params: Json | undefined): void | Promise<void>;
|
||||
dispose(): void | Promise<void>;
|
||||
};
|
||||
|
||||
@@ -1074,24 +1074,28 @@ class ServerPrivateApiGenerator implements ContributionGenerator {
|
||||
generateEvent(component: string, event: object, name: string) {
|
||||
const label = generateComponentLabelName(component, name, true);
|
||||
if (Object.keys(event).length == 0) {
|
||||
this.body += `
|
||||
export function send${label}Event(receiver: Component) {
|
||||
this.body += `export function on${label}(handler: () => Promise<void> | void) {
|
||||
return thisComponent().onEvent(thisComponent(), "${name}", handler as any);
|
||||
}
|
||||
export function send${label}Event(receiver: ComponentRef) {
|
||||
return receiver.sendEvent("${name}");
|
||||
}
|
||||
export function emit${label}Event() {
|
||||
return thisComponent().emitEvent("${name}");
|
||||
}
|
||||
\n`;
|
||||
return;
|
||||
`;
|
||||
} else {
|
||||
this.body += `export function on${label}(handler: (event: ${label}Event) => Promise<void> | void) {
|
||||
return thisComponent().onEvent(thisComponent(), "${name}", handler as any);
|
||||
}
|
||||
|
||||
this.body += `
|
||||
export function send${label}Event(receiver: Component, params: ${label}Event) {
|
||||
export function send${label}Event(receiver: ComponentRef, params: ${label}Event) {
|
||||
return receiver.sendEvent("${name}", params);
|
||||
}
|
||||
export function emit${label}Event(params: ${label}Event) {
|
||||
return thisComponent().emitEvent("${name}", params);
|
||||
}\n`;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
generateMethod(component: string, method: object, name: string) {
|
||||
@@ -1111,7 +1115,7 @@ export function emit${label}Event(params: ${label}Event) {
|
||||
const label = generateComponentLabelName(component, name, false);
|
||||
const uLabel = generateComponentLabelName(component, name, true);
|
||||
this.body += `
|
||||
export async function call${uLabel}(caller: Component, params: ${uLabel}Request): Promise<${uLabel}Response> {
|
||||
export async function call${uLabel}(caller: ComponentRef, params: ${uLabel}Request): Promise<${uLabel}Response> {
|
||||
return impl.${method.handler}(caller, params);
|
||||
}
|
||||
|
||||
@@ -1140,7 +1144,7 @@ export async function ${label}(params: ${uLabel}Request): Promise<${uLabel}Respo
|
||||
const label = generateComponentLabelName(component, name, false);
|
||||
const uLabel = generateComponentLabelName(component, name, true);
|
||||
this.body += `
|
||||
export async function notify${uLabel}(caller: Component, params: ${uLabel}Request) {
|
||||
export async function notify${uLabel}(caller: ComponentRef, params: ${uLabel}Request) {
|
||||
impl.${notification.handler}(caller, params);
|
||||
}
|
||||
export async function ${label}(params: ${uLabel}Request) {
|
||||
@@ -1184,18 +1188,15 @@ export async function create${uLabel}Object<Impl extends ${uLabel}Interface, Par
|
||||
objects[id] = new ${uLabel}ComponentObject(impl, name);
|
||||
return impl;
|
||||
}
|
||||
`;
|
||||
if (component != 'core') {
|
||||
this.interfaceBody += `
|
||||
export function on${uLabel}Created(handler: (object: ${uLabel}) => Promise<void> | void) {
|
||||
return core.onObjectCreated((params) => {
|
||||
return ${component == "core" ? "" : "core."}onObjectCreated((params) => {
|
||||
if (params.interface == "${component}/${name}") {
|
||||
handler(new ${uLabel}(params.object));
|
||||
}
|
||||
});
|
||||
}
|
||||
export function onAny${uLabel}Created(handler: () => Promise<void> | void) {
|
||||
return core.onObjectCreated((params) => {
|
||||
return ${component == "core" ? "" : "core."}onObjectCreated((params) => {
|
||||
if (params.interface == "${component}/${name}") {
|
||||
handler();
|
||||
}
|
||||
@@ -1203,7 +1204,6 @@ export function onAny${uLabel}Created(handler: () => Promise<void> | void) {
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
toString(): string {
|
||||
@@ -1212,6 +1212,7 @@ ${this.callBody.length > 0 || this.notifyBody.length > 0 ? 'import * as impl fro
|
||||
import { createError } from "$core/Error";
|
||||
import { thisComponent } from "$/component-info";
|
||||
import * as core from "$core";
|
||||
import { isJsonObject } from "$core/Json";
|
||||
${this.viewBody && "import { Window } from '$core/Window';"}
|
||||
export { thisComponent } from "$/component-info";
|
||||
|
||||
@@ -1221,8 +1222,8 @@ type ComponentObject = {
|
||||
typeId: string;
|
||||
name: string;
|
||||
impl: object;
|
||||
call(caller: Component, method: string, params: Json | undefined): Promise<Json | void> | Json | void;
|
||||
notify(caller: Component, method: string, params: Json | undefined): void | Promise<void>;
|
||||
call(caller: ComponentRef, method: string, params: Json | undefined): Promise<Json | void> | Json | void;
|
||||
notify(caller: ComponentRef, method: string, params: Json | undefined): void | Promise<void>;
|
||||
dispose(): void | Promise<void>;
|
||||
};
|
||||
|
||||
@@ -1244,13 +1245,13 @@ export function ownObjects() {
|
||||
return Object.values(objects);
|
||||
}
|
||||
|
||||
export async function call(caller: Component, method: string, params: Json | undefined): Promise<Json | void> {
|
||||
export async function call(caller: ComponentRef, method: string, params: Json | undefined): Promise<Json | void> {
|
||||
void caller, params;
|
||||
|
||||
switch (method) {
|
||||
${this.callBody}
|
||||
case "$/object/call":
|
||||
if (params && typeof params.method == "string" && typeof params.object == 'number') {
|
||||
if (params && isJsonObject(params) && typeof params.method == "string" && typeof params.object == 'number') {
|
||||
if (params.object in objects) {
|
||||
return objects[params.object].call(caller, params.method,
|
||||
"params" in params ? params.params as any : undefined
|
||||
@@ -1267,13 +1268,13 @@ ${this.callBody}
|
||||
}
|
||||
}
|
||||
|
||||
export async function notify(caller: Component, method: string, params: Json | undefined) {
|
||||
export async function notify(caller: ComponentRef, method: string, params: Json | undefined) {
|
||||
void caller, params;
|
||||
|
||||
switch (method) {
|
||||
${this.notifyBody}
|
||||
case "$/object/notify":
|
||||
if (params && typeof params.notification == "string" && typeof params.object == 'number') {
|
||||
if (params && isJsonObject(params) && typeof params.notification == "string" && typeof params.object == 'number') {
|
||||
if (params.object in objects) {
|
||||
return objects[params.object].notify(caller, params.notification,
|
||||
"params" in params ? params.params as any : undefined
|
||||
@@ -1801,11 +1802,11 @@ class ${componentLabel}ComponentImpl implements IComponentImpl {
|
||||
}
|
||||
}
|
||||
|
||||
async call(caller: Component, method: string, params?: Json) {
|
||||
async call(caller: ComponentRef, method: string, params?: Json) {
|
||||
return await api.call(caller, method, params);
|
||||
}
|
||||
|
||||
async notify(caller: Component, notification: string, params?: Json) {
|
||||
async notify(caller: ComponentRef, notification: string, params?: Json) {
|
||||
await api.notify(caller, notification, params);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -308,43 +308,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"extension/load": {
|
||||
"handler": "loadExtension",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extension/unload": {
|
||||
"handler": "unloadExtension",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extension/install": {
|
||||
"handler": "installExtension",
|
||||
"params": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"returns": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extension/remove": {
|
||||
"handler": "removeExtension",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings/set": {
|
||||
"handler": "handleSettingsSet",
|
||||
"params": {
|
||||
@@ -452,11 +415,22 @@
|
||||
"type": "json"
|
||||
}
|
||||
},
|
||||
"returns": {
|
||||
"result": {
|
||||
"returns": "json"
|
||||
},
|
||||
"component-call": {
|
||||
"handler": "handleCall",
|
||||
"params": {
|
||||
"caller": {
|
||||
"type": "string"
|
||||
},
|
||||
"method": {
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"type": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"returns": "json"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -481,6 +455,20 @@
|
||||
"type": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"component-notify": {
|
||||
"handler": "handleNotify",
|
||||
"params": {
|
||||
"caller": {
|
||||
"type": "string"
|
||||
},
|
||||
"notification": {
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"type": "json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"events": {
|
||||
@@ -504,14 +492,101 @@
|
||||
"interfaces": {
|
||||
"external-component": {
|
||||
"methods": {
|
||||
"call": {
|
||||
"params": "json",
|
||||
"object-call": {
|
||||
"params": {
|
||||
"object": {
|
||||
"type": "number"
|
||||
},
|
||||
"method": {
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"type": "json"
|
||||
}
|
||||
},
|
||||
"returns": "json"
|
||||
},
|
||||
"call": {
|
||||
"params": {
|
||||
"method": {
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"type": "json"
|
||||
}
|
||||
},
|
||||
"returns": "json"
|
||||
},
|
||||
"initialize": {
|
||||
},
|
||||
"activate": {
|
||||
"params": {
|
||||
"settings": {
|
||||
"type": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deactivate": {},
|
||||
"dispose": {},
|
||||
"get-pid": {
|
||||
"returns": "number"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notify": {
|
||||
"params": "json"
|
||||
"params": {
|
||||
"method": {
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"type": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object-notify": {
|
||||
"params": {
|
||||
"object": {
|
||||
"type": "number"
|
||||
},
|
||||
"method": {
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"type": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object-destroy": {
|
||||
"params": {
|
||||
"object": {
|
||||
"type": "number"
|
||||
},
|
||||
"interface-name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"launcher": {
|
||||
"methods": {
|
||||
"launch": {
|
||||
"params": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"item-type": "string"
|
||||
},
|
||||
"manifest": {
|
||||
"type": "extension-info"
|
||||
},
|
||||
"launcher-params": {
|
||||
"type": "json-object"
|
||||
}
|
||||
},
|
||||
"returns": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
rpcsx-ui/src/core/lib/Component.d.ts
vendored
2
rpcsx-ui/src/core/lib/Component.d.ts
vendored
@@ -17,7 +17,7 @@ declare global {
|
||||
|
||||
export type ComponentId = string;
|
||||
|
||||
export type Component = {
|
||||
export type ComponentRef = {
|
||||
getId(): ComponentId;
|
||||
onClose(listener: () => void | Promise<void>): IDisposable;
|
||||
sendEvent(event: string, params?: any): void;
|
||||
|
||||
11
rpcsx-ui/src/core/lib/NativeTarget.ts
Normal file
11
rpcsx-ui/src/core/lib/NativeTarget.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Target } from "./Target";
|
||||
import { Platform } from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
export function getDeviceArchitecture() {
|
||||
const abis = DeviceInfo.supportedAbisSync();
|
||||
const abi = (abis && abis.length > 0) ? abis[0] : 'unknown';
|
||||
return abi.includes("arm64") || abi.includes("aarch64") ? "aarch64" : "x64";
|
||||
}
|
||||
|
||||
export const NativeTarget = new Target("elf", getDeviceArchitecture(), Platform.OS);
|
||||
3
rpcsx-ui/src/core/lib/NativeTarget.web.ts
Normal file
3
rpcsx-ui/src/core/lib/NativeTarget.web.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Target } from "./Target";
|
||||
|
||||
export const NativeTarget = new Target(process.platform === "win32" ? "pe" : "elf", process.arch, process.platform);
|
||||
@@ -14,13 +14,8 @@ export class Target {
|
||||
return new Target(parts[0], parts[1], parts[2]);
|
||||
}
|
||||
|
||||
static native() {
|
||||
return nativeTarget;
|
||||
}
|
||||
|
||||
format(): string {
|
||||
return this.fileFormat + "-" + this.arch + "-" + this.platform;
|
||||
}
|
||||
}
|
||||
|
||||
const nativeTarget = new Target("none", "none", "none");
|
||||
@@ -8,7 +8,7 @@ export function onComponentActivation(component: ComponentInstance) {
|
||||
const impl = component.getImpl();
|
||||
|
||||
const createRendererComponent = (webContents: Electron.WebContents) => {
|
||||
const rendererComponent: Component = {
|
||||
const rendererComponent: ComponentRef = {
|
||||
getId: () => ":renderer",
|
||||
onClose: (listener) => {
|
||||
const wrapped = async () => {
|
||||
|
||||
@@ -20,8 +20,11 @@ export type IComponentImpl = IDisposable & {
|
||||
initialize(eventEmitter: (event: string, params: Json) => void): void | Promise<void>;
|
||||
activate(context: ComponentContext, settings: Json, signal?: AbortSignal): void | Promise<void>;
|
||||
deactivate(context: ComponentContext): void | Promise<void>;
|
||||
call?(caller: Component, method: string, params: Json | undefined): Promise<Json | void>;
|
||||
notify?(caller: Component, notification: string, params: Json | undefined): Promise<void>;
|
||||
call?(caller: ComponentRef, method: string, params: Json | undefined): Promise<Json | void>;
|
||||
notify?(caller: ComponentRef, notification: string, params: Json | undefined): Promise<void>;
|
||||
objectCall?(caller: ComponentRef, object: number, method: string, params: Json | undefined): Promise<Json | void>;
|
||||
objectNotify?(caller: ComponentRef, object: number, notification: string, params: Json | undefined): Promise<void>;
|
||||
objectDestroy?(caller: ComponentRef, object: number, interfaceName: string): Promise<void>;
|
||||
getPid?(): number;
|
||||
}
|
||||
|
||||
@@ -50,7 +53,7 @@ export class ComponentInstance implements ComponentContext {
|
||||
this.externalEventEmitter[`${sender.getId()}/${event}`]?.emit(params);
|
||||
}
|
||||
|
||||
private createCallerView(caller: ComponentInstance): Component {
|
||||
private createCallerView(caller: ComponentInstance): ComponentRef {
|
||||
return {
|
||||
getId: () => caller.getId(),
|
||||
onClose: (listener) => caller.onEvent(this, deactivateEvent, listener),
|
||||
@@ -219,6 +222,10 @@ export class ComponentInstance implements ComponentContext {
|
||||
throw createError(ErrorCode.InvalidParams, `${caller.getId()}: component ${this.getName()} has no interface method support`);
|
||||
}
|
||||
|
||||
if (this.impl.objectCall) {
|
||||
return this.impl.objectCall(this.createCallerView(caller), objectId, method, params);
|
||||
}
|
||||
|
||||
return await this.impl.call(this.createCallerView(caller), `$/object/call`, {
|
||||
object: objectId,
|
||||
method,
|
||||
@@ -235,6 +242,10 @@ export class ComponentInstance implements ComponentContext {
|
||||
throw createError(ErrorCode.InvalidParams, `${caller.getId()}: component ${this.getName()} has no interface support`);
|
||||
}
|
||||
|
||||
if (this.impl.objectNotify) {
|
||||
return this.impl.objectNotify(this.createCallerView(caller), objectId, notification, params);
|
||||
}
|
||||
|
||||
return await this.impl.notify(this.createCallerView(caller), `$/object/notify`, {
|
||||
object: objectId,
|
||||
notification,
|
||||
@@ -251,6 +262,10 @@ export class ComponentInstance implements ComponentContext {
|
||||
throw createError(ErrorCode.InvalidParams, `${caller.getId()}: component ${this.getName()} has no interface support`);
|
||||
}
|
||||
|
||||
if (this.impl.objectDestroy) {
|
||||
return this.impl.objectDestroy(this.createCallerView(caller), objectId, interfaceName);
|
||||
}
|
||||
|
||||
return await this.impl.notify(this.createCallerView(caller), `$/object/destroy`, {
|
||||
objectId,
|
||||
interface: interfaceName
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
|
||||
// FIXME: remove this import
|
||||
import type { Readable, Writable } from 'stream';
|
||||
import { Target } from "./Target";
|
||||
|
||||
export type Process = {
|
||||
stdin: Writable;
|
||||
stdout: Readable;
|
||||
stderr: Readable;
|
||||
kill: (signal: number | NodeJS.Signals) => void;
|
||||
on: (event: 'close' | 'exit', listener: (...args: any[]) => void) => void;
|
||||
once: (event: 'close' | 'exit', listener: (...args: any[]) => void) => void;
|
||||
off: (event: 'close' | 'exit', listener: (...args: any[]) => void) => void;
|
||||
getPid(): number;
|
||||
}
|
||||
|
||||
export type LaunchParams = {
|
||||
launcherRequirements: object;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export type Launcher = {
|
||||
launch: (path: string, args: string[], params: LaunchParams) => Promise<Process> | Process;
|
||||
}
|
||||
|
||||
const launcherStorage: {
|
||||
[key: string]: Launcher
|
||||
} = {};
|
||||
|
||||
export function addLauncher(target: Target, launcher: Launcher) {
|
||||
launcherStorage[target.format()] = launcher;
|
||||
}
|
||||
|
||||
export function deleteLauncher(target: Target) {
|
||||
delete launcherStorage[target.format()];
|
||||
}
|
||||
|
||||
export function getLauncher(target: Target | string) {
|
||||
if (target instanceof Target) {
|
||||
target = target.format();
|
||||
}
|
||||
|
||||
if (target in launcherStorage) {
|
||||
return launcherStorage[target];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getLauncherList() {
|
||||
return Object.keys(launcherStorage);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createError } from "lib/Error";
|
||||
import { ComponentInstance, findComponentById } from "./ComponentInstance";
|
||||
import { ComponentInstance, findComponentById, registerComponent, unregisterComponent } from "./ComponentInstance";
|
||||
import * as self from '$';
|
||||
|
||||
let nextObjectId = 0;
|
||||
@@ -22,6 +22,7 @@ export function unregisterInterface(component: ComponentInstance, interfaceName:
|
||||
|
||||
iface.forEach(async objectId => {
|
||||
const instance = objects[objectId];
|
||||
if (instance) {
|
||||
delete objects[objectId];
|
||||
|
||||
const component = findComponentById(instance.owner);
|
||||
@@ -29,12 +30,13 @@ export function unregisterInterface(component: ComponentInstance, interfaceName:
|
||||
try {
|
||||
await component?.objectDestroy(component, instance.interfaceName, objectId);
|
||||
} catch { }
|
||||
}
|
||||
});
|
||||
|
||||
delete interfaceObjects[id];
|
||||
}
|
||||
|
||||
export function createObject(caller: Component, objectName: string, interfaceName: string) {
|
||||
export async function createObject(caller: ComponentRef, objectName: string, interfaceName: string) {
|
||||
if (!(interfaceName in interfaceObjects)) {
|
||||
throw createError(ErrorCode.InvalidParams, `Unknown interface ${interfaceName}`);
|
||||
}
|
||||
@@ -49,10 +51,46 @@ export function createObject(caller: Component, objectName: string, interfaceNam
|
||||
interfaceObjects[interfaceName].add(objectId);
|
||||
caller.onClose(() => destroyObject(caller, objectId));
|
||||
self.emitObjectCreatedEvent({ interface: interfaceName, object: objectId });
|
||||
|
||||
if (interfaceName == "core/external-component") {
|
||||
const externalComponent = self.toExternalComponent(objectId);
|
||||
const pid = 0; // FIXME: await externalComponent.getPid();
|
||||
registerComponent({ name: objectName, version: "0.0.1" }, {
|
||||
initialize: (): void | Promise<void> => {
|
||||
return externalComponent.initialize();
|
||||
},
|
||||
activate: (_context: ComponentContext, settings: Json, _signal?: AbortSignal): void | Promise<void> => {
|
||||
return externalComponent.activate({ settings });
|
||||
},
|
||||
deactivate: (_context: ComponentContext) => {
|
||||
return externalComponent.deactivate();
|
||||
},
|
||||
call: (_caller: ComponentRef, method: string, params: Json | undefined) => {
|
||||
return externalComponent.call({ method, params: params ?? null });
|
||||
},
|
||||
notify: async (_caller: ComponentRef, notification: string, params: Json | undefined) => {
|
||||
await externalComponent.notify({ method: notification, params: params ?? null });
|
||||
},
|
||||
objectCall: (_caller: ComponentRef, object: number, method: string, params: Json | undefined) => {
|
||||
return externalComponent.objectCall({ object, method, params: params ?? null });
|
||||
},
|
||||
objectNotify: async (_caller: ComponentRef, object: number, notification: string, params: Json | undefined) => {
|
||||
await externalComponent.objectNotify({ object, method: notification, params: params ?? null });
|
||||
},
|
||||
objectDestroy: async (_caller: ComponentRef, object: number, interfaceName: string) => {
|
||||
await externalComponent.objectDestroy({ object, interfaceName });
|
||||
},
|
||||
getPid: () => pid,
|
||||
dispose: async () => {
|
||||
await externalComponent.dispose();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return objectId;
|
||||
}
|
||||
|
||||
export function destroyObject(caller: Component, objectId: number) {
|
||||
export async function destroyObject(caller: ComponentRef, objectId: number) {
|
||||
const instance = objects[objectId];
|
||||
|
||||
if (!instance) {
|
||||
@@ -63,6 +101,12 @@ export function destroyObject(caller: Component, objectId: number) {
|
||||
throw createError(ErrorCode.InvalidRequest, `${caller.getId()}: Cannot destroy object '${instance.objectName}' created by component '${instance.owner}'`);
|
||||
}
|
||||
|
||||
if (instance.interfaceName == "core/external-component") {
|
||||
try {
|
||||
await unregisterComponent(instance.objectName);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
delete objects[objectId];
|
||||
interfaceObjects[instance.interfaceName]?.delete(objectId);
|
||||
}
|
||||
@@ -95,9 +139,13 @@ export function getName(objectId: number) {
|
||||
return objectInstance.objectName;
|
||||
}
|
||||
|
||||
export function call(caller: Component, objectId: number, method: string, params: Json) {
|
||||
export function call(caller: ComponentRef, objectId: number, method: string, params: Json) {
|
||||
const instance = objects[objectId];
|
||||
|
||||
if (!instance) {
|
||||
throw createError(ErrorCode.InvalidRequest, `Failed to find instance ${objectId}`);
|
||||
}
|
||||
|
||||
const callerComponent = findComponentById(caller.getId());
|
||||
if (!callerComponent) {
|
||||
throw createError(ErrorCode.InvalidRequest, "Cannot find caller component");
|
||||
@@ -111,9 +159,13 @@ export function call(caller: Component, objectId: number, method: string, params
|
||||
return component.objectCall(callerComponent, objectId, method, params);
|
||||
}
|
||||
|
||||
export function notify(caller: Component, objectId: number, notification: string, params: Json) {
|
||||
export function notify(caller: ComponentRef, objectId: number, notification: string, params: Json) {
|
||||
const instance = objects[objectId];
|
||||
|
||||
if (!instance) {
|
||||
throw createError(ErrorCode.InvalidRequest, `Failed to find instance ${objectId}`);
|
||||
}
|
||||
|
||||
const callerComponent = findComponentById(caller.getId());
|
||||
if (!callerComponent) {
|
||||
throw createError(ErrorCode.InvalidRequest, "Cannot find caller component");
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
export class Target {
|
||||
constructor(
|
||||
public fileFormat: string,
|
||||
public arch: string,
|
||||
public platform: string,
|
||||
) { }
|
||||
|
||||
static parse(triple: string) {
|
||||
const parts = triple.split('-');
|
||||
if (parts.length != 3) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new Target(parts[0], parts[1], parts[2]);
|
||||
}
|
||||
|
||||
static native() {
|
||||
return nativeTarget;
|
||||
}
|
||||
|
||||
format(): string {
|
||||
return this.fileFormat + "-" + this.arch + "-" + this.platform;
|
||||
}
|
||||
}
|
||||
|
||||
const nativeTarget = new Target(process.platform === "win32" ? "pe" : "elf", process.arch, process.platform);
|
||||
@@ -1,37 +0,0 @@
|
||||
import { createError } from '$/Error';
|
||||
|
||||
import { NativeModule, requireNativeModule } from 'expo';
|
||||
|
||||
declare class ExtensionLoaderModule extends NativeModule {
|
||||
loadExtension(path: string): Promise<number>;
|
||||
unloadExtension(id: number): Promise<void>;
|
||||
call(extension: number, method: string, params: string): Promise<string>;
|
||||
notify(extension: number, notification: string, params: string): Promise<void>;
|
||||
sendResponse(methodId: number, body: string): Promise<void>;
|
||||
}
|
||||
|
||||
const nativeLoader = requireNativeModule<ExtensionLoaderModule>('ExtensionLoader');
|
||||
|
||||
const loadedExtensions: Record<string, number> = {};
|
||||
|
||||
export async function loadExtension(request: ExtensionLoadRequest): Promise<ExtensionLoadResponse> {
|
||||
const nativeId = await nativeLoader.loadExtension(request.id);
|
||||
loadedExtensions[request.id] = nativeId;
|
||||
}
|
||||
|
||||
export async function unloadExtension(request: ExtensionUnloadRequest): Promise<ExtensionUnloadResponse> {
|
||||
const nativeId = loadedExtensions[request.id];
|
||||
if (nativeId === undefined) {
|
||||
throw createError(ErrorCode.InvalidRequest, `extension ${request.id} is not loaded`);
|
||||
}
|
||||
|
||||
await nativeLoader.unloadExtension(nativeId);
|
||||
}
|
||||
|
||||
export async function installExtension(_request: ExtensionInstallRequest): Promise<ExtensionInstallResponse> {
|
||||
throw createError(ErrorCode.InvalidRequest);
|
||||
}
|
||||
|
||||
export async function removeExtension(_request: ExtensionRemoveRequest): Promise<ExtensionRemoveResponse> {
|
||||
throw createError(ErrorCode.InvalidRequest);
|
||||
}
|
||||
@@ -4,14 +4,11 @@ import { ComponentInstance, findComponentById, getActivatedComponentList, getCom
|
||||
import * as instance from './ComponentInstance';
|
||||
import * as settings from './Settings';
|
||||
import { Schema, SchemaError, SchemaObject, validateObject } from 'lib/Schema';
|
||||
import * as extensionApi from './extension-api';
|
||||
import { registerBuiltinLaunchers } from './registerBuiltinLaunchers';
|
||||
import { initialize } from './initialize';
|
||||
import * as objects from './Objects';
|
||||
import { initializeRenderer } from './initialize-renderer';
|
||||
|
||||
initialize();
|
||||
registerBuiltinLaunchers();
|
||||
|
||||
export async function activate() {
|
||||
try {
|
||||
@@ -73,7 +70,7 @@ export async function deactivate() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function activateComponent(_caller: Component, request: ComponentActivateRequest): Promise<ComponentActivateResponse> {
|
||||
export async function activateComponent(_caller: ComponentRef, request: ComponentActivateRequest): Promise<ComponentActivateResponse> {
|
||||
const component = findComponentById(request.id);
|
||||
if (!component) {
|
||||
throw createError(ErrorCode.InvalidParams, `component ${request.id} not found`);
|
||||
@@ -82,7 +79,7 @@ export async function activateComponent(_caller: Component, request: ComponentAc
|
||||
await component.activate();
|
||||
}
|
||||
|
||||
export async function deactivateComponent(_caller: Component, request: ComponentDeactivateRequest): Promise<ComponentDeactivateResponse> {
|
||||
export async function deactivateComponent(_caller: ComponentRef, request: ComponentDeactivateRequest): Promise<ComponentDeactivateResponse> {
|
||||
const component = findComponentById(request.id);
|
||||
if (!component) {
|
||||
throw createError(ErrorCode.InvalidParams, `component ${request.id} not found`);
|
||||
@@ -91,22 +88,6 @@ export async function deactivateComponent(_caller: Component, request: Component
|
||||
await component.deactivate();
|
||||
}
|
||||
|
||||
export async function loadExtension(_caller: Component, request: ExtensionLoadRequest): Promise<ExtensionLoadResponse> {
|
||||
return extensionApi.loadExtension(request);
|
||||
}
|
||||
|
||||
export async function unloadExtension(_caller: Component, request: ExtensionUnloadRequest): Promise<ExtensionUnloadResponse> {
|
||||
return extensionApi.unloadExtension(request);
|
||||
}
|
||||
|
||||
export async function installExtension(_caller: Component, request: ExtensionInstallRequest): Promise<ExtensionInstallResponse> {
|
||||
return extensionApi.installExtension(request);
|
||||
}
|
||||
|
||||
export async function removeExtension(_caller: Component, request: ExtensionRemoveRequest): Promise<ExtensionRemoveResponse> {
|
||||
return extensionApi.removeExtension(request);
|
||||
}
|
||||
|
||||
function getComponentInstanceSettings(instance: ComponentInstance) {
|
||||
const schema = instance.getContribution("settings");
|
||||
if (!schema) {
|
||||
@@ -123,7 +104,7 @@ function getComponentInstanceSettings(instance: ComponentInstance) {
|
||||
};
|
||||
}
|
||||
|
||||
function getComponentSettings(component: Component) {
|
||||
function getComponentSettings(component: ComponentRef) {
|
||||
if (component.getId() == ":renderer") {
|
||||
// for renderer collect settings for all activated components
|
||||
|
||||
@@ -178,7 +159,7 @@ function getObjectMember(object: any, path: string[]) {
|
||||
|
||||
}
|
||||
|
||||
export async function handleSettingsSet(caller: Component, request: SettingsSetRequest): Promise<SettingsSetResponse> {
|
||||
export async function handleSettingsSet(caller: ComponentRef, request: SettingsSetRequest): Promise<SettingsSetResponse> {
|
||||
const path = request.path.split("/");
|
||||
const name = path.pop();
|
||||
|
||||
@@ -212,53 +193,82 @@ export async function handleSettingsSet(caller: Component, request: SettingsSetR
|
||||
settings.save().catch(e => console.error("failed to save settings", e));
|
||||
}
|
||||
|
||||
export async function handleSettingsGet(caller: Component, request: SettingsGetRequest): Promise<SettingsGetResponse> {
|
||||
export async function handleSettingsGet(caller: ComponentRef, request: SettingsGetRequest): Promise<SettingsGetResponse> {
|
||||
const { settings, schema } = getComponentSettings(caller);
|
||||
const path = request.path.split("/");
|
||||
|
||||
return { value: getObjectMember(settings, path), schema: getObjectMember(schema, path) };
|
||||
}
|
||||
|
||||
export async function shutdown(caller: Component, _request: ShutdownRequest): Promise<ShutdownResponse> {
|
||||
export async function shutdown(caller: ComponentRef, _request: ShutdownRequest): Promise<ShutdownResponse> {
|
||||
console.warn(`shutdown invoked by ${caller.getId()}`);
|
||||
await instance.uninitializeComponent(self.thisComponent().getManifest());
|
||||
}
|
||||
|
||||
export async function handleObjectCreate(caller: Component, request: ObjectCreateRequest): Promise<ObjectCreateResponse> {
|
||||
export async function handleObjectCreate(caller: ComponentRef, request: ObjectCreateRequest): Promise<ObjectCreateResponse> {
|
||||
return {
|
||||
object: objects.createObject(caller, request.name, request.interface)
|
||||
object: await objects.createObject(caller, request.name, request.interface)
|
||||
};
|
||||
}
|
||||
|
||||
export async function handleObjectDestroy(caller: Component, request: ObjectDestroyRequest): Promise<ObjectDestroyResponse> {
|
||||
export async function handleObjectDestroy(caller: ComponentRef, request: ObjectDestroyRequest): Promise<ObjectDestroyResponse> {
|
||||
return objects.destroyObject(caller, request.object)
|
||||
}
|
||||
|
||||
export async function handleFindObject(_caller: Component, request: ObjectFindRequest): Promise<ObjectFindResponse> {
|
||||
export async function handleFindObject(_caller: ComponentRef, request: ObjectFindRequest): Promise<ObjectFindResponse> {
|
||||
return {
|
||||
object: objects.findObject(request.interfaceName, request.objectName)
|
||||
};
|
||||
}
|
||||
|
||||
export async function handleObjectGetName(_caller: Component, request: ObjectGetNameRequest): Promise<ObjectGetNameResponse> {
|
||||
export async function handleObjectGetName(_caller: ComponentRef, request: ObjectGetNameRequest): Promise<ObjectGetNameResponse> {
|
||||
return {
|
||||
name: objects.getName(request.object)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export async function handleObjectGetList(_caller: Component, request: ObjectGetListRequest): Promise<ObjectGetListResponse> {
|
||||
export async function handleObjectGetList(_caller: ComponentRef, request: ObjectGetListRequest): Promise<ObjectGetListResponse> {
|
||||
return {
|
||||
objects: objects.getObjectList(request.interface)
|
||||
};
|
||||
}
|
||||
|
||||
export async function handleObjectCall(caller: Component, request: ObjectCallRequest): Promise<ObjectCallResponse> {
|
||||
return {
|
||||
result: await objects.call(caller, request.object, request.method, request.params) ?? {}
|
||||
};
|
||||
export async function handleObjectCall(caller: ComponentRef, request: ObjectCallRequest): Promise<ObjectCallResponse> {
|
||||
return await objects.call(caller, request.object, request.method, request.params) ?? {}
|
||||
}
|
||||
|
||||
export async function handleObjectNotify(caller: Component, request: ObjectNotifyRequest) {
|
||||
export async function handleObjectNotify(caller: ComponentRef, request: ObjectNotifyRequest) {
|
||||
return objects.notify(caller, request.object, request.notification, request.params);
|
||||
}
|
||||
|
||||
export async function handleCall(_caller: ComponentRef, request: ComponentCallRequest): Promise<ComponentCallResponse> {
|
||||
const callerComponent = findComponentById(request.caller);
|
||||
if (!callerComponent) {
|
||||
throw createError(ErrorCode.InvalidRequest, `Cannot find caller component ${request.caller}`);
|
||||
}
|
||||
|
||||
const [componentName, ...method] = request.method.split("/");
|
||||
const component = findComponentById(componentName);
|
||||
|
||||
if (!component) {
|
||||
throw createError(ErrorCode.InvalidRequest, `Cannot find component ${componentName}`);
|
||||
}
|
||||
|
||||
return await component.call(callerComponent, method.join("/"), request.params) ?? {};
|
||||
}
|
||||
|
||||
export async function handleNotify(_caller: ComponentRef, request: ComponentNotifyRequest) {
|
||||
const callerComponent = findComponentById(request.caller);
|
||||
if (!callerComponent) {
|
||||
throw createError(ErrorCode.InvalidRequest, `Cannot find caller component ${request.caller}`);
|
||||
}
|
||||
|
||||
const [componentName, ...notification] = request.notification.split("/");
|
||||
const component = findComponentById(componentName);
|
||||
|
||||
if (!component) {
|
||||
throw createError(ErrorCode.InvalidRequest, `Cannot find component ${componentName}`);
|
||||
}
|
||||
|
||||
return component.notify(callerComponent, notification.join("/"), request.params);
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
|
||||
export function registerBuiltinLaunchers() {}
|
||||
@@ -1,132 +0,0 @@
|
||||
import { dirname } from "path";
|
||||
import { addLauncher, Launcher, LaunchParams, Process } from "./Launcher";
|
||||
import { Target } from "./Target";
|
||||
import { fork, spawn } from "child_process";
|
||||
import { Duplex } from "stream";
|
||||
import { EventEmitter } from "events";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const nativeLauncher: Launcher = {
|
||||
launch: async (path: string, args: string[], params: LaunchParams) => {
|
||||
path = fileURLToPath(path);
|
||||
const newProcess = spawn(path, args, {
|
||||
argv0: path,
|
||||
cwd: dirname(process.execPath),
|
||||
signal: params.signal,
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
newProcess.stdout.setEncoding('utf8');
|
||||
newProcess.stderr.setEncoding('utf8');
|
||||
const pid = newProcess.pid ?? 0;
|
||||
|
||||
const result: Process = {
|
||||
stdin: newProcess.stdin,
|
||||
stdout: newProcess.stdout,
|
||||
stderr: newProcess.stderr,
|
||||
|
||||
kill: (signal: number | NodeJS.Signals) => {
|
||||
newProcess.kill(signal);
|
||||
},
|
||||
on: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.on(event, handler);
|
||||
},
|
||||
once: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.once(event, handler);
|
||||
},
|
||||
off: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.off(event, handler);
|
||||
},
|
||||
getPid: () => pid
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
const nodeLauncher: Launcher = {
|
||||
launch: async (path: string, args: string[], params: LaunchParams) => {
|
||||
path = fileURLToPath(path);
|
||||
const newProcess = fork(path, args, {
|
||||
signal: params.signal,
|
||||
stdio: 'pipe',
|
||||
cwd: dirname(path),
|
||||
});
|
||||
|
||||
newProcess.stdout!.setEncoding('utf8');
|
||||
newProcess.stderr!.setEncoding('utf8');
|
||||
const pid = newProcess.pid ?? 0;
|
||||
|
||||
const result: Process = {
|
||||
stdin: newProcess.stdin!,
|
||||
stdout: newProcess.stdout!,
|
||||
stderr: newProcess.stderr!,
|
||||
|
||||
kill: (signal: number | NodeJS.Signals) => {
|
||||
newProcess.kill(signal);
|
||||
},
|
||||
on: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.on(event, handler);
|
||||
},
|
||||
once: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.once(event, handler);
|
||||
},
|
||||
off: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.off(event, handler);
|
||||
},
|
||||
getPid: () => pid
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
const inlineLauncher: Launcher = {
|
||||
launch: async (path: string, args: string[], params: LaunchParams) => {
|
||||
path = fileURLToPath(path);
|
||||
const imported = await import(path);
|
||||
|
||||
if (!("activate" in imported) || typeof imported.activate != 'function') {
|
||||
throw new Error(`${path}: invalid inline module`);
|
||||
}
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
const stdin = new Duplex();
|
||||
const stdout = new Duplex();
|
||||
const stderr = new Duplex();
|
||||
imported.activate(eventEmitter, args, params.launcherRequirements, stdin, stdout, stderr);
|
||||
|
||||
const result: Process = {
|
||||
stdin: stdin,
|
||||
stdout: stdout,
|
||||
stderr: stderr,
|
||||
|
||||
kill: (_signal: number | NodeJS.Signals) => {
|
||||
if (("deactivate" in imported) && typeof imported.deactivate == 'function') {
|
||||
imported.deactivate();
|
||||
}
|
||||
},
|
||||
on: (event: string, handler: (...args: any[]) => void) => {
|
||||
eventEmitter.on(event, handler);
|
||||
},
|
||||
once: (event: string, handler: (...args: any[]) => void) => {
|
||||
eventEmitter.once(event, handler);
|
||||
},
|
||||
off: (event: string, handler: (...args: any[]) => void) => {
|
||||
eventEmitter.off(event, handler);
|
||||
},
|
||||
getPid() {
|
||||
return 0;
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
export function registerBuiltinLaunchers() {
|
||||
addLauncher(Target.native(), nativeLauncher);
|
||||
addLauncher(new Target("js", "any", "node"), nodeLauncher);
|
||||
addLauncher(new Target("js", "any", "inline"), inlineLauncher);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as core from "$core";
|
||||
export class ExplorerComponent implements IDisposable {
|
||||
items: ExplorerItem[] = [];
|
||||
progressToItem: Record<number, ExplorerItem> = {};
|
||||
subscriptions: Record<number, Component> = {};
|
||||
subscriptions: Record<number, ComponentRef> = {};
|
||||
refreshAbortController = new AbortController();
|
||||
refreshImmediate: NodeJS.Immediate | undefined = undefined;
|
||||
describedLocations = new Set<string>();
|
||||
@@ -218,7 +218,7 @@ export class ExplorerComponent implements IDisposable {
|
||||
});
|
||||
}
|
||||
|
||||
async get(caller: Component, params: ExplorerGetRequest): Promise<ExplorerGetResponse> {
|
||||
async get(caller: ComponentRef, params: ExplorerGetRequest): Promise<ExplorerGetResponse> {
|
||||
// this.refresh();
|
||||
|
||||
const progressChannel = params.channel ?? (await progress.progressCreate({
|
||||
|
||||
@@ -14,7 +14,7 @@ export function deactivate() {
|
||||
component = undefined;
|
||||
}
|
||||
|
||||
export async function handleAdd(_caller: Component, params: ExplorerAddRequest) {
|
||||
export async function handleAdd(_caller: ComponentRef, params: ExplorerAddRequest) {
|
||||
if (!component) {
|
||||
throw createError(ErrorCode.InvalidRequest);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export async function handleAdd(_caller: Component, params: ExplorerAddRequest)
|
||||
return component.add(params);
|
||||
}
|
||||
|
||||
export function handleRemove(_caller: Component, params: ExplorerRemoveRequest) {
|
||||
export function handleRemove(_caller: ComponentRef, params: ExplorerRemoveRequest) {
|
||||
if (!component) {
|
||||
throw createError(ErrorCode.InvalidRequest);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ export function handleRemove(_caller: Component, params: ExplorerRemoveRequest)
|
||||
return component.remove(params);
|
||||
}
|
||||
|
||||
export async function handleGet(caller: Component, params: ExplorerGetRequest): Promise<ExplorerGetResponse> {
|
||||
export async function handleGet(caller: ComponentRef, params: ExplorerGetRequest): Promise<ExplorerGetResponse> {
|
||||
if (!component) {
|
||||
throw createError(ErrorCode.InvalidRequest);
|
||||
}
|
||||
|
||||
@@ -11,5 +11,46 @@
|
||||
{
|
||||
"name": "fs"
|
||||
}
|
||||
]
|
||||
],
|
||||
"contributions": {
|
||||
"methods": {
|
||||
"load": {
|
||||
"handler": "loadExtension",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"unload": {
|
||||
"handler": "unloadExtension",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"install": {
|
||||
"handler": "installExtension",
|
||||
"params": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"returns": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"remove": {
|
||||
"handler": "removeExtension",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
import * as path from '$/path';
|
||||
import * as path from '$core/path';
|
||||
import * as fs from '$fs';
|
||||
import { findComponent, findComponentById, unregisterComponent } from './ComponentInstance';
|
||||
import { createError } from 'lib/Error';
|
||||
import { getLauncher } from './Launcher';
|
||||
import { Extension } from './Extension';
|
||||
import * as core from '$core';
|
||||
import { createError } from '$core/Error';
|
||||
|
||||
export async function loadExtension(request: ExtensionLoadRequest): Promise<ExtensionLoadResponse> {
|
||||
if (findComponentById(request.id)) {
|
||||
export async function loadExtension(request: ExtensionHostLoadRequest): Promise<ExtensionHostLoadResponse> {
|
||||
try {
|
||||
const componentObject = await core.findExternalComponentObject(request.id);
|
||||
|
||||
if (componentObject) {
|
||||
return;
|
||||
}
|
||||
} catch { }
|
||||
|
||||
const localExtensionsPath = path.join(await fs.fsGetBuiltinResourcesLocation(undefined), "extensions");
|
||||
const extensionManifestLocation = path.join(localExtensionsPath, request.id, "extension.json");
|
||||
@@ -29,29 +31,29 @@ export async function loadExtension(request: ExtensionLoadRequest): Promise<Exte
|
||||
}
|
||||
})();
|
||||
|
||||
const launcher = getLauncher(manifest.launcher.type);
|
||||
const launcher = await core.findLauncherObject(manifest.launcher.type);
|
||||
if (launcher == null) {
|
||||
throw createError(ErrorCode.InternalError, `launcher ${manifest.launcher.type} not found`);
|
||||
}
|
||||
|
||||
const process = await (async () => {
|
||||
try {
|
||||
return launcher.launch(path.join(localExtensionsPath, request.id, manifest.executable), manifest.args ?? [], {
|
||||
launcherRequirements: manifest.launcher.requirements ?? {},
|
||||
await launcher.launch({
|
||||
path: path.join(localExtensionsPath, request.id, manifest.executable),
|
||||
args: manifest.args ?? [],
|
||||
manifest,
|
||||
launcherParams: manifest.launcher.requirements ?? {}
|
||||
});
|
||||
} catch {
|
||||
throw createError(ErrorCode.InternalError, `${request.id}: failed to spawn extension process`);
|
||||
} catch (e) {
|
||||
throw createError(ErrorCode.InternalError, `${request.id}: failed to spawn extension process: ${e}`);
|
||||
}
|
||||
})();
|
||||
|
||||
new Extension(manifest, process);
|
||||
}
|
||||
|
||||
export async function unloadExtension(request: ExtensionUnloadRequest): Promise<ExtensionUnloadResponse> {
|
||||
await unregisterComponent(request.id);
|
||||
export async function unloadExtension(request: ExtensionHostUnloadRequest): Promise<ExtensionHostUnloadResponse> {
|
||||
const componentObject = await core.findExternalComponentObject(request.id);
|
||||
await componentObject.destroy();
|
||||
}
|
||||
|
||||
export async function installExtension(request: ExtensionInstallRequest): Promise<ExtensionInstallResponse> {
|
||||
export async function installExtension(request: ExtensionHostInstallRequest): Promise<ExtensionHostInstallResponse> {
|
||||
// FIXME: unpack package
|
||||
const extensionManifestLocation = path.join(request.path, "extension.json");
|
||||
|
||||
@@ -71,15 +73,21 @@ export async function installExtension(request: ExtensionInstallRequest): Promis
|
||||
}
|
||||
})();
|
||||
|
||||
if (findComponent(manifest.name[0].text, manifest.version)) {
|
||||
throw createError(ErrorCode.InvalidRequest, `extension ${request.path} already installed`);
|
||||
}
|
||||
void manifest;
|
||||
|
||||
throw createError(ErrorCode.InternalError, "not implemented");
|
||||
}
|
||||
|
||||
export async function removeExtension(request: ExtensionRemoveRequest): Promise<ExtensionRemoveResponse> {
|
||||
if (findComponentById(request.id)) {
|
||||
export async function removeExtension(request: ExtensionHostRemoveRequest): Promise<ExtensionHostRemoveResponse> {
|
||||
const component = await (async () => {
|
||||
try {
|
||||
return await core.findExternalComponentObject(request.id);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
|
||||
if (component) {
|
||||
throw createError(ErrorCode.InvalidRequest, `extension ${request.id} in use`);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as core from "$core";
|
||||
import * as fs from '$fs';
|
||||
import * as path from '$core/path';
|
||||
import * as extensionApi from './extension-api';
|
||||
|
||||
export async function activateLocalExtensions(list: Set<string>) {
|
||||
try {
|
||||
@@ -17,7 +18,7 @@ export async function activateLocalExtensions(list: Set<string>) {
|
||||
}
|
||||
|
||||
try {
|
||||
await core.extensionLoad({ id: entry.name });
|
||||
await extensionApi.loadExtension({ id: entry.name });
|
||||
} catch (e) {
|
||||
console.error(`failed to load local extension ${entry.name}`, e);
|
||||
continue;
|
||||
@@ -28,7 +29,7 @@ export async function activateLocalExtensions(list: Set<string>) {
|
||||
} catch (e) {
|
||||
console.error(`failed to activate extension ${entry.name}`, e);
|
||||
try {
|
||||
await core.extensionUnload({ id: entry.name });
|
||||
await extensionApi.unloadExtension({ id: entry.name });
|
||||
} catch (e) {
|
||||
console.error(`failed to unload extension ${entry.name}`, e);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { activateLocalExtensions } from './extension-host';
|
||||
import * as core from "$core";
|
||||
import * as extensionApi from './extension-api';
|
||||
import { registerBuiltinLaunchers } from './registerBuiltinLaunchers';
|
||||
|
||||
const activatedExtensions = new Set<string>();
|
||||
|
||||
export function activate() {
|
||||
export async function activate() {
|
||||
await registerBuiltinLaunchers();
|
||||
return activateLocalExtensions(activatedExtensions);
|
||||
}
|
||||
|
||||
@@ -19,3 +22,18 @@ export async function deactivate() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadExtension(_caller: ComponentRef, request: ExtensionHostLoadRequest): Promise<ExtensionHostLoadResponse> {
|
||||
return extensionApi.loadExtension(request);
|
||||
}
|
||||
|
||||
export async function unloadExtension(_caller: ComponentRef, request: ExtensionHostUnloadRequest): Promise<ExtensionHostUnloadResponse> {
|
||||
return extensionApi.unloadExtension(request);
|
||||
}
|
||||
|
||||
export async function installExtension(_caller: ComponentRef, request: ExtensionHostInstallRequest): Promise<ExtensionHostInstallResponse> {
|
||||
return extensionApi.installExtension(request);
|
||||
}
|
||||
|
||||
export async function removeExtension(_caller: ComponentRef, request: ExtensionHostRemoveRequest): Promise<ExtensionHostRemoveResponse> {
|
||||
return extensionApi.removeExtension(request);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { NativeModule, requireNativeModule } from 'expo';
|
||||
import { NativeTarget } from '$core/NativeTarget';
|
||||
import * as core from "$core";
|
||||
import * as self from "$";
|
||||
|
||||
declare class ExtensionLoaderModule extends NativeModule {
|
||||
loadExtension(path: string): Promise<number>;
|
||||
unloadExtension(id: number): Promise<void>;
|
||||
call(extension: number, method: string, params: string): Promise<string>;
|
||||
notify(extension: number, notification: string, params: string): Promise<void>;
|
||||
sendResponse(methodId: number, body: string): Promise<void>;
|
||||
}
|
||||
|
||||
const nativeLoader = requireNativeModule<ExtensionLoaderModule>('ExtensionLoader');
|
||||
|
||||
class NativeProtocol implements ExternalComponentInterface {
|
||||
constructor(
|
||||
private objectId: number,
|
||||
private extensionId: number,
|
||||
public manifest: ExtensionInfo) {
|
||||
}
|
||||
|
||||
async activate(_caller: ComponentRef, request: ExternalComponentActivateRequest) {
|
||||
return JSON.parse(await nativeLoader.call(this.extensionId, "$/activate", JSON.stringify(request)));
|
||||
}
|
||||
|
||||
deactivate(_caller: ComponentRef): ExternalComponentDeactivateResponse | Promise<ExternalComponentDeactivateResponse> {
|
||||
return nativeLoader.notify(this.extensionId, "$/deactivate", "{}");
|
||||
}
|
||||
|
||||
async call(_caller: ComponentRef, request: ExternalComponentCallRequest) {
|
||||
return JSON.parse(await nativeLoader.call(this.extensionId, request.method, JSON.stringify(request.params)));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
nativeLoader.unloadExtension(this.extensionId);
|
||||
}
|
||||
|
||||
getPid() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
return JSON.parse(await nativeLoader.call(this.extensionId, "$/initialize", "{}"));
|
||||
}
|
||||
|
||||
notify(_caller: ComponentRef, request: ExternalComponentNotifyRequest): void | Promise<void> {
|
||||
return nativeLoader.notify(this.extensionId, request.method, JSON.stringify(request.params));
|
||||
}
|
||||
|
||||
async objectCall(_caller: ComponentRef, request: ExternalComponentObjectCallRequest) {
|
||||
return JSON.parse(await nativeLoader.call(this.extensionId, "$/object/activate", JSON.stringify(request)));
|
||||
}
|
||||
|
||||
objectDestroy(caller: ComponentRef, request: ExternalComponentObjectDestroyRequest): void | Promise<void> {
|
||||
return nativeLoader.notify(this.extensionId, "$/object/destroy", JSON.stringify(request));
|
||||
}
|
||||
|
||||
objectNotify(caller: ComponentRef, request: ExternalComponentObjectNotifyRequest): void | Promise<void> {
|
||||
return nativeLoader.notify(this.extensionId, "$/object/notify", JSON.stringify(request));
|
||||
}
|
||||
|
||||
getObjectId() {
|
||||
return this.objectId;
|
||||
}
|
||||
}
|
||||
|
||||
class InlineLauncher implements LauncherInterface {
|
||||
async launch(_caller: ComponentRef, request: LauncherLaunchRequest): Promise<LauncherLaunchResponse> {
|
||||
const extensionId = await nativeLoader.loadExtension(request.path);
|
||||
const protocol = await core.createExternalComponentObject(request.manifest.name[0].text, NativeProtocol, extensionId, request.manifest);
|
||||
|
||||
try {
|
||||
await protocol.initialize();
|
||||
return protocol.getObjectId();
|
||||
} catch (e) {
|
||||
self.destroyObject(protocol.getObjectId());
|
||||
nativeLoader.unloadExtension(extensionId);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function registerBuiltinLaunchers() {
|
||||
await core.createLauncherObject(NativeTarget.format(), InlineLauncher);
|
||||
}
|
||||
@@ -1,8 +1,24 @@
|
||||
import { Process } from './Launcher';
|
||||
|
||||
import { dirname } from "path";
|
||||
import { Target } from "$core/Target";
|
||||
import { NativeTarget } from "$core/NativeTarget";
|
||||
import { fork, spawn } from "child_process";
|
||||
import { Duplex, Readable, Writable } from "stream";
|
||||
import { EventEmitter } from "events";
|
||||
import { fileURLToPath } from "url";
|
||||
import * as self from "$";
|
||||
import * as core from "$core";
|
||||
import packageJson from '../../../../package.json' with { type: "json" };
|
||||
import { findComponent, getComponentId, registerComponent, uninitializeComponent, IComponentImpl } from './ComponentInstance';
|
||||
import { createError } from 'lib/Error';
|
||||
|
||||
type Process = {
|
||||
stdin: Writable;
|
||||
stdout: Readable;
|
||||
stderr: Readable;
|
||||
kill: (signal: number | NodeJS.Signals) => void;
|
||||
on: (event: 'close' | 'exit', listener: (...args: any[]) => void) => void;
|
||||
once: (event: 'close' | 'exit', listener: (...args: any[]) => void) => void;
|
||||
off: (event: 'close' | 'exit', listener: (...args: any[]) => void) => void;
|
||||
getPid(): number;
|
||||
}
|
||||
|
||||
type ResponseValue = void | object | null | number | string | boolean | [];
|
||||
type ResponseError = {
|
||||
@@ -13,14 +29,13 @@ type ResponseError = {
|
||||
type Response = ResponseValue | ResponseError | void;
|
||||
type ErrorHandler = (error: ResponseError) => void;
|
||||
|
||||
|
||||
const clientInfo: ClientInfo = Object.freeze({
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
capabilities: {}
|
||||
});
|
||||
|
||||
export class Extension implements IComponentImpl {
|
||||
class JsonRpcProtocol implements ExternalComponentInterface {
|
||||
private alive = true;
|
||||
private expectedResponses: {
|
||||
[key: number]: {
|
||||
@@ -39,23 +54,25 @@ export class Extension implements IComponentImpl {
|
||||
private componentManifest: ComponentManifest;
|
||||
|
||||
constructor(
|
||||
public readonly manifest: ExtensionInfo,
|
||||
public readonly extensionProcess: Process) {
|
||||
private objectId: number,
|
||||
public readonly extensionProcess: Process,
|
||||
public manifest: ExtensionInfo) {
|
||||
|
||||
this.componentManifest = {
|
||||
name: manifest.name[0].text,
|
||||
version: manifest.version,
|
||||
};
|
||||
|
||||
extensionProcess.stdout.on('data', (message: string) => {
|
||||
this.receive(message);
|
||||
});
|
||||
|
||||
extensionProcess.stderr.on('data', (message: string) => {
|
||||
this.debugLog(message);
|
||||
});
|
||||
|
||||
this.debugLog("Starting");
|
||||
|
||||
this.componentManifest = {
|
||||
...this.manifest,
|
||||
name: this.manifest.name[0].text
|
||||
};
|
||||
|
||||
extensionProcess.on('exit', () => {
|
||||
this.debugLog("Exit");
|
||||
this.alive = false;
|
||||
@@ -65,10 +82,8 @@ export class Extension implements IComponentImpl {
|
||||
clearTimeout(this.responseWatchdog);
|
||||
}
|
||||
|
||||
uninitializeComponent(this.componentManifest);
|
||||
// uninitializeComponent(this.componentManifest);
|
||||
});
|
||||
|
||||
registerComponent(this.componentManifest, this);
|
||||
}
|
||||
|
||||
getPid() {
|
||||
@@ -81,7 +96,7 @@ export class Extension implements IComponentImpl {
|
||||
if (line.length == 0) {
|
||||
continue;
|
||||
}
|
||||
process.stderr.write(`${date} [${this.manifest.name[0].text}-v${this.manifest.version}] ${line}\n`);
|
||||
process.stderr.write(`${date} [${this.componentManifest.name}-v${this.componentManifest.version}] ${line}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,31 +107,14 @@ export class Extension implements IComponentImpl {
|
||||
|
||||
const response = await this.callMethod<InitializeResponse>("$/initialize", request);
|
||||
|
||||
let isInvalid = false;
|
||||
|
||||
if (response.extension.name[0].text != this.manifest.name[0].text) {
|
||||
this.debugLog(`executable sends unexpected name ${response.extension.name}`);
|
||||
isInvalid = true;
|
||||
}
|
||||
|
||||
if (response.extension.version != this.manifest.version) {
|
||||
this.debugLog(`executable sends unexpected version ${response.extension.version}`);
|
||||
isInvalid = true;
|
||||
}
|
||||
|
||||
// FIXME: register contributions
|
||||
|
||||
if (isInvalid) {
|
||||
throw createError(ErrorCode.InvalidRequest, "Extension initialize request returns invalid name/version");
|
||||
}
|
||||
}
|
||||
|
||||
async activate(_context: ComponentContext, settings: JsonObject, signal?: AbortSignal | undefined) {
|
||||
const request: ActivateRequest = {
|
||||
settings
|
||||
this.componentManifest = {
|
||||
...response.extension,
|
||||
name: response.extension.name[0].text,
|
||||
};
|
||||
}
|
||||
|
||||
return await this.callMethod<ActivateResponse>("$/activate", request, signal);
|
||||
async activate(_caller: ComponentRef, request: ExternalComponentActivateRequest) {
|
||||
return await this.callMethod<ActivateResponse>("$/activate", request);
|
||||
}
|
||||
|
||||
async deactivate() {
|
||||
@@ -184,6 +182,18 @@ export class Extension implements IComponentImpl {
|
||||
}
|
||||
}
|
||||
|
||||
objectCall(_caller: ComponentRef, request: ExternalComponentObjectCallRequest): ExternalComponentObjectCallResponse | Promise<ExternalComponentObjectCallResponse> {
|
||||
return this.callMethod("$/object/call", request);
|
||||
}
|
||||
|
||||
objectDestroy(_caller: ComponentRef, request: ExternalComponentObjectDestroyRequest): void | Promise<void> {
|
||||
return this.sendNotify("$/object/destroy", request);
|
||||
}
|
||||
|
||||
objectNotify(_caller: ComponentRef, request: ExternalComponentObjectNotifyRequest): void | Promise<void> {
|
||||
return this.sendNotify("$/object/notify", request);
|
||||
}
|
||||
|
||||
async callMethod<R extends Response = Response>(method: string, params: object | [] | string | number | boolean | null = null, signal?: AbortSignal) {
|
||||
const id = this.nextMessageId++;
|
||||
const abortHandler = () => this.cancel({ id });
|
||||
@@ -221,16 +231,16 @@ export class Extension implements IComponentImpl {
|
||||
this.send({ jsonrpc: "2.0", notification, params });
|
||||
}
|
||||
|
||||
async call(_caller: Component, method: string, params?: Json): Promise<Json | void> {
|
||||
return this.callMethod(method, params);
|
||||
call(_caller: ComponentRef, params: ExternalComponentCallRequest): Promise<ExternalComponentCallResponse> {
|
||||
return this.callMethod(params.method, params.params);
|
||||
}
|
||||
|
||||
async notify(_caller: Component, notification: string, params: Json | undefined) {
|
||||
this.sendNotify(notification, params);
|
||||
notify(_caller: ComponentRef, params: ExternalComponentNotifyRequest) {
|
||||
return this.sendNotify(params.method, params.params);
|
||||
}
|
||||
|
||||
async cancel(request: CancelRequest) {
|
||||
await this.sendNotify("$/cancel", request);
|
||||
cancel(request: CancelRequest) {
|
||||
return this.sendNotify("$/cancel", request);
|
||||
}
|
||||
|
||||
onError(handler: ErrorHandler) {
|
||||
@@ -344,36 +354,20 @@ export class Extension implements IComponentImpl {
|
||||
}
|
||||
|
||||
if ("method" in message) {
|
||||
const componentMethod = message["method"] as string;
|
||||
const params = "params" in message ? message["params"] as JsonObject : undefined;
|
||||
const [componentName, ...method] = componentMethod.split("/");
|
||||
const component = findComponent(componentName);
|
||||
const self = findComponent(this.componentManifest.name);
|
||||
|
||||
if (!component || !self) {
|
||||
this.send({
|
||||
jsonrpc: "2.0", id, error: {
|
||||
code: ErrorCode.MethodNotFound,
|
||||
message: componentMethod
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
const method = message["method"] as string;
|
||||
const params = "params" in message ? message["params"] as JsonObject : null;
|
||||
|
||||
if (id !== null) {
|
||||
try {
|
||||
const result = await component.call(self, method.join("/"), params);
|
||||
const result = await core.componentCall({ caller: this.manifest.name[0].text, method, params });
|
||||
this.send({ jsonrpc: "2.0", id, result });
|
||||
} catch (error) {
|
||||
this.send({ jsonrpc: "2.0", id, error });
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
component.notify(self, method.join("/"), params);
|
||||
} catch (error) {
|
||||
core.componentNotify({ caller: this.manifest.name[0].text, notification: method, params }).catch(error => {
|
||||
this.send({ jsonrpc: "2.0", error });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -400,7 +394,7 @@ export class Extension implements IComponentImpl {
|
||||
if (requestDeadline <= now) {
|
||||
delete this.expectedResponses[request];
|
||||
this.debugLog(`wait for response timed out (request ${request})`);
|
||||
this.cancel({ id: parseInt(request) });
|
||||
this.cancel({ id: parseInt(request) }).catch(e => console.warn(`cancellation of request ${request} failed`, e));
|
||||
response.reject({ code: ErrorCode.TimedOut });
|
||||
} else if (nextDeadline < 0 || nextDeadline > requestDeadline) {
|
||||
nextDeadline = requestDeadline;
|
||||
@@ -419,8 +413,157 @@ export class Extension implements IComponentImpl {
|
||||
}
|
||||
}
|
||||
|
||||
getId() {
|
||||
return getComponentId(this.componentManifest);
|
||||
getObjectId() {
|
||||
return this.objectId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class NativeLauncher implements LauncherInterface {
|
||||
async launch(_caller: ComponentRef, request: LauncherLaunchRequest): Promise<LauncherLaunchResponse> {
|
||||
const path = fileURLToPath(request.path);
|
||||
const newProcess = spawn(path, request.args, {
|
||||
argv0: path,
|
||||
cwd: dirname(process.execPath),
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
newProcess.stdout.setEncoding('utf8');
|
||||
newProcess.stderr.setEncoding('utf8');
|
||||
const pid = newProcess.pid ?? 0;
|
||||
|
||||
const wrappedNewProcess: Process = {
|
||||
stdin: newProcess.stdin,
|
||||
stdout: newProcess.stdout,
|
||||
stderr: newProcess.stderr,
|
||||
|
||||
kill: (signal: number | NodeJS.Signals) => {
|
||||
newProcess.kill(signal);
|
||||
},
|
||||
on: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.on(event, handler);
|
||||
},
|
||||
once: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.once(event, handler);
|
||||
},
|
||||
off: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.off(event, handler);
|
||||
},
|
||||
getPid: () => pid
|
||||
};
|
||||
|
||||
const protocol = await core.createExternalComponentObject(request.manifest.name[0].text, JsonRpcProtocol, wrappedNewProcess, request.manifest);
|
||||
|
||||
try {
|
||||
await protocol.initialize();
|
||||
return protocol.getObjectId();
|
||||
} catch (e) {
|
||||
self.destroyObject(protocol.getObjectId());
|
||||
newProcess.kill("SIGKILL");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class NodeLauncher implements LauncherInterface {
|
||||
async launch(_caller: ComponentRef, request: LauncherLaunchRequest): Promise<LauncherLaunchResponse> {
|
||||
const path = fileURLToPath(request.path);
|
||||
const newProcess = fork(path, request.args, {
|
||||
stdio: 'pipe',
|
||||
cwd: dirname(path),
|
||||
});
|
||||
|
||||
newProcess.stdout!.setEncoding('utf8');
|
||||
newProcess.stderr!.setEncoding('utf8');
|
||||
const pid = newProcess.pid ?? 0;
|
||||
|
||||
const wrappedNewProcess: Process = {
|
||||
stdin: newProcess.stdin!,
|
||||
stdout: newProcess.stdout!,
|
||||
stderr: newProcess.stderr!,
|
||||
|
||||
kill: (signal: number | NodeJS.Signals) => {
|
||||
newProcess.kill(signal);
|
||||
},
|
||||
on: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.on(event, handler);
|
||||
},
|
||||
once: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.once(event, handler);
|
||||
},
|
||||
off: (event: string, handler: (...args: any[]) => void) => {
|
||||
newProcess.off(event, handler);
|
||||
},
|
||||
getPid: () => pid
|
||||
};
|
||||
|
||||
const protocol = await core.createExternalComponentObject(request.manifest.name[0].text, JsonRpcProtocol, wrappedNewProcess, request.manifest);
|
||||
|
||||
try {
|
||||
await protocol.initialize();
|
||||
return protocol.getObjectId();
|
||||
} catch (e) {
|
||||
self.destroyObject(protocol.getObjectId());
|
||||
newProcess.kill("SIGKILL");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class InlineLauncher implements LauncherInterface {
|
||||
async launch(_caller: ComponentRef, request: LauncherLaunchRequest): Promise<LauncherLaunchResponse> {
|
||||
const path = fileURLToPath(request.path);
|
||||
const imported = await import(path);
|
||||
|
||||
if (!("activate" in imported) || typeof imported.activate != 'function') {
|
||||
throw new Error(`${path}: invalid inline module`);
|
||||
}
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
const stdin = new Duplex();
|
||||
const stdout = new Duplex();
|
||||
const stderr = new Duplex();
|
||||
imported.activate(eventEmitter, request.args, request.launcherParams, stdin, stdout, stderr);
|
||||
|
||||
const wrappedProcess: Process = {
|
||||
stdin: stdin,
|
||||
stdout: stdout,
|
||||
stderr: stderr,
|
||||
|
||||
kill: (_signal: number | NodeJS.Signals) => {
|
||||
if (("deactivate" in imported) && typeof imported.deactivate == 'function') {
|
||||
imported.deactivate();
|
||||
}
|
||||
},
|
||||
on: (event: string, handler: (...args: any[]) => void) => {
|
||||
eventEmitter.on(event, handler);
|
||||
},
|
||||
once: (event: string, handler: (...args: any[]) => void) => {
|
||||
eventEmitter.once(event, handler);
|
||||
},
|
||||
off: (event: string, handler: (...args: any[]) => void) => {
|
||||
eventEmitter.off(event, handler);
|
||||
},
|
||||
getPid() {
|
||||
return 0;
|
||||
},
|
||||
};
|
||||
|
||||
const protocol = await core.createExternalComponentObject(request.manifest.name[0].text, JsonRpcProtocol, wrappedProcess, request.manifest);
|
||||
|
||||
try {
|
||||
await protocol.initialize();
|
||||
return protocol.getObjectId();
|
||||
} catch (e) {
|
||||
self.destroyObject(protocol.getObjectId());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export async function registerBuiltinLaunchers() {
|
||||
await core.createLauncherObject(NativeTarget.format(), NativeLauncher);
|
||||
await core.createLauncherObject(new Target("js", "any", "node").format(), NodeLauncher);
|
||||
await core.createLauncherObject(new Target("js", "any", "inline").format(), InlineLauncher);
|
||||
}
|
||||
|
||||
@@ -8,14 +8,14 @@ import * as core from '$core';
|
||||
class NativeFile implements FileInterface {
|
||||
constructor(private id: number, private handle: FileHandle) { }
|
||||
|
||||
close(_caller: Component) {
|
||||
close(_caller: ComponentRef) {
|
||||
core.objectDestroy({
|
||||
object: this.id
|
||||
});
|
||||
this.handle.close();
|
||||
}
|
||||
|
||||
read(_caller: Component, request: FsFileReadRequest): FsFileReadResponse {
|
||||
read(_caller: ComponentRef, request: FsFileReadRequest): FsFileReadResponse {
|
||||
this.handle.offset = request.offset;
|
||||
const result = this.handle.readBytes(request.size);
|
||||
|
||||
@@ -24,7 +24,7 @@ class NativeFile implements FileInterface {
|
||||
};
|
||||
}
|
||||
|
||||
write(_caller: Component, request: FsFileWriteRequest): FsFileWriteResponse {
|
||||
write(_caller: ComponentRef, request: FsFileWriteRequest): FsFileWriteResponse {
|
||||
this.handle.offset = request.offset;
|
||||
this.handle.writeBytes(Uint8Array.from(request.data));
|
||||
return request.data.length;
|
||||
@@ -36,7 +36,7 @@ class NativeFile implements FileInterface {
|
||||
}
|
||||
|
||||
class NativeFileSystem implements FileSystemInterface {
|
||||
async open(_caller: Component, request: FsFileSystemOpenRequest): Promise<FsFileSystemOpenResponse> {
|
||||
async open(_caller: ComponentRef, request: FsFileSystemOpenRequest): Promise<FsFileSystemOpenResponse> {
|
||||
try {
|
||||
const descriptor = new File(request.uri).open();
|
||||
const object = await self.createFileObject(request.uri, NativeFile, descriptor);
|
||||
@@ -46,7 +46,7 @@ class NativeFileSystem implements FileSystemInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async readToString(_caller: Component, request: FsFileSystemReadToStringRequest): Promise<FsFileSystemReadToStringResponse> {
|
||||
async readToString(_caller: ComponentRef, request: FsFileSystemReadToStringRequest): Promise<FsFileSystemReadToStringResponse> {
|
||||
try {
|
||||
return new File(request.uri).text();
|
||||
} catch (e) {
|
||||
@@ -54,7 +54,7 @@ class NativeFileSystem implements FileSystemInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async writeString(_caller: Component, request: FsFileSystemWriteStringRequest) {
|
||||
async writeString(_caller: ComponentRef, request: FsFileSystemWriteStringRequest) {
|
||||
try {
|
||||
return new File(request.uri).write(request.string);
|
||||
} catch (e) {
|
||||
@@ -62,7 +62,7 @@ class NativeFileSystem implements FileSystemInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async readDir(_caller: Component, request: FsFileSystemReadDirRequest): Promise<FsFileSystemReadDirResponse> {
|
||||
async readDir(_caller: ComponentRef, request: FsFileSystemReadDirRequest): Promise<FsFileSystemReadDirResponse> {
|
||||
try {
|
||||
const result = new Directory(request).list();
|
||||
|
||||
@@ -81,7 +81,7 @@ class NativeFileSystem implements FileSystemInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async stat(_caller: Component, request: FsFileSystemStatRequest): Promise<FsFileSystemStatResponse> {
|
||||
async stat(_caller: ComponentRef, request: FsFileSystemStatRequest): Promise<FsFileSystemStatResponse> {
|
||||
try {
|
||||
const result = new File(request);
|
||||
|
||||
@@ -103,50 +103,50 @@ export async function uninitialize() {
|
||||
await Promise.all(self.ownObjects().map(object => object.dispose()));
|
||||
}
|
||||
|
||||
export async function open(_caller: Component, request: FsOpenRequest): Promise<FsOpenResponse> {
|
||||
export async function open(_caller: ComponentRef, request: FsOpenRequest): Promise<FsOpenResponse> {
|
||||
const protocol = new URL(request.uri).protocol || "file:";
|
||||
|
||||
const object = await self.findFileSystemObject(protocol);
|
||||
return await object.open(request);
|
||||
}
|
||||
|
||||
export async function readToString(_caller: Component, request: FsReadToStringRequest): Promise<FsReadToStringResponse> {
|
||||
export async function readToString(_caller: ComponentRef, request: FsReadToStringRequest): Promise<FsReadToStringResponse> {
|
||||
const protocol = new URL(request.uri).protocol || "file:";
|
||||
|
||||
const object = await self.findFileSystemObject(protocol);
|
||||
return await object.readToString(request);
|
||||
}
|
||||
|
||||
export async function writeString(_caller: Component, request: FsWriteStringRequest): Promise<FsWriteStringResponse> {
|
||||
export async function writeString(_caller: ComponentRef, request: FsWriteStringRequest): Promise<FsWriteStringResponse> {
|
||||
const protocol = new URL(request.uri).protocol || "file:";
|
||||
|
||||
const object = await self.findFileSystemObject(protocol);
|
||||
return await object.writeString(request);
|
||||
}
|
||||
|
||||
export async function readDir(_caller: Component, request: FsReadDirRequest): Promise<FsReadDirResponse> {
|
||||
export async function readDir(_caller: ComponentRef, request: FsReadDirRequest): Promise<FsReadDirResponse> {
|
||||
const protocol = new URL(request).protocol || "file:";
|
||||
|
||||
const object = await self.findFileSystemObject(protocol);
|
||||
return await object.readDir(request);
|
||||
}
|
||||
|
||||
export async function stat(_caller: Component, request: FsStatRequest): Promise<FsStatResponse> {
|
||||
export async function stat(_caller: ComponentRef, request: FsStatRequest): Promise<FsStatResponse> {
|
||||
const protocol = new URL(request).protocol || "file:";
|
||||
|
||||
const object = await self.findFileSystemObject(protocol);
|
||||
return await object.stat(request);
|
||||
}
|
||||
|
||||
export function getBuiltinResourcesLocation(_caller: Component, _request: FsGetBuiltinResourcesLocationRequest): FsGetBuiltinResourcesLocationResponse {
|
||||
export function getBuiltinResourcesLocation(_caller: ComponentRef, _request: FsGetBuiltinResourcesLocationRequest): FsGetBuiltinResourcesLocationResponse {
|
||||
return Paths.document.uri;
|
||||
}
|
||||
|
||||
export function getConfigLocation(_caller: Component, _request: FsGetConfigLocationRequest): FsGetConfigLocationResponse {
|
||||
export function getConfigLocation(_caller: ComponentRef, _request: FsGetConfigLocationRequest): FsGetConfigLocationResponse {
|
||||
return Paths.document.uri;
|
||||
}
|
||||
|
||||
export async function openDirectorySelector(caller: Component, request: FsOpenDirectorySelectorRequest): Promise<FsOpenDirectorySelectorResponse> {
|
||||
export async function openDirectorySelector(caller: ComponentRef, request: FsOpenDirectorySelectorRequest): Promise<FsOpenDirectorySelectorResponse> {
|
||||
try {
|
||||
return (await pickDirectory({
|
||||
requestLongTermAccess: true
|
||||
|
||||
@@ -15,14 +15,14 @@ function parseUri(uri: string) {
|
||||
class NativeFile implements FileInterface {
|
||||
constructor(private id: number, private handle: nodeFs.FileHandle) { }
|
||||
|
||||
async close(_caller: Component) {
|
||||
async close(_caller: ComponentRef) {
|
||||
core.objectDestroy({
|
||||
object: this.id
|
||||
});
|
||||
await this.handle.close();
|
||||
}
|
||||
|
||||
async read(_caller: Component, request: FsFileReadRequest): Promise<FsFileReadResponse> {
|
||||
async read(_caller: ComponentRef, request: FsFileReadRequest): Promise<FsFileReadResponse> {
|
||||
const buffer = new Uint8Array(request.size);
|
||||
const result = await this.handle.read(buffer, 0, request.size, request.offset);
|
||||
|
||||
@@ -31,7 +31,7 @@ class NativeFile implements FileInterface {
|
||||
};
|
||||
}
|
||||
|
||||
async write(_caller: Component, request: FsFileWriteRequest): Promise<FsFileWriteResponse> {
|
||||
async write(_caller: ComponentRef, request: FsFileWriteRequest): Promise<FsFileWriteResponse> {
|
||||
const result = await this.handle.write(Uint8Array.from(request.data), {
|
||||
position: request.offset
|
||||
});
|
||||
@@ -82,7 +82,7 @@ function toFileType(fileType: WithFileType) {
|
||||
}
|
||||
|
||||
class NativeFileSystem implements FileSystemInterface {
|
||||
async open(_caller: Component, request: FsFileSystemOpenRequest): Promise<FsFileSystemOpenResponse> {
|
||||
async open(_caller: ComponentRef, request: FsFileSystemOpenRequest): Promise<FsFileSystemOpenResponse> {
|
||||
const filePath = parseUri(request.uri).pathname;
|
||||
|
||||
try {
|
||||
@@ -94,7 +94,7 @@ class NativeFileSystem implements FileSystemInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async readToString(_caller: Component, request: FsFileSystemReadToStringRequest): Promise<FsFileSystemReadToStringResponse> {
|
||||
async readToString(_caller: ComponentRef, request: FsFileSystemReadToStringRequest): Promise<FsFileSystemReadToStringResponse> {
|
||||
const filePath = parseUri(request.uri).pathname;
|
||||
|
||||
try {
|
||||
@@ -104,7 +104,7 @@ class NativeFileSystem implements FileSystemInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async writeString(_caller: Component, request: FsFileSystemWriteStringRequest) {
|
||||
async writeString(_caller: ComponentRef, request: FsFileSystemWriteStringRequest) {
|
||||
const filePath = parseUri(request.uri).pathname;
|
||||
|
||||
try {
|
||||
@@ -114,7 +114,7 @@ class NativeFileSystem implements FileSystemInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async readDir(_caller: Component, request: FsFileSystemReadDirRequest): Promise<FsFileSystemReadDirResponse> {
|
||||
async readDir(_caller: ComponentRef, request: FsFileSystemReadDirRequest): Promise<FsFileSystemReadDirResponse> {
|
||||
const path = parseUri(request).pathname;
|
||||
try {
|
||||
const result = await nodeFs.readdir(path, { withFileTypes: true });
|
||||
@@ -134,7 +134,7 @@ class NativeFileSystem implements FileSystemInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async stat(_caller: Component, request: FsFileSystemStatRequest): Promise<FsFileSystemStatResponse> {
|
||||
async stat(_caller: ComponentRef, request: FsFileSystemStatRequest): Promise<FsFileSystemStatResponse> {
|
||||
const path = parseUri(request).pathname;
|
||||
|
||||
try {
|
||||
@@ -158,42 +158,42 @@ export async function uninitialize() {
|
||||
await Promise.all(self.ownObjects().map(object => object.dispose()));
|
||||
}
|
||||
|
||||
export async function open(_caller: Component, request: FsOpenRequest): Promise<FsOpenResponse> {
|
||||
export async function open(_caller: ComponentRef, request: FsOpenRequest): Promise<FsOpenResponse> {
|
||||
const protocol = parseUri(request.uri).protocol || "file:";
|
||||
|
||||
const object = await self.findFileSystemObject(protocol);
|
||||
return await object.open(request);
|
||||
}
|
||||
|
||||
export async function readToString(_caller: Component, request: FsReadToStringRequest): Promise<FsReadToStringResponse> {
|
||||
export async function readToString(_caller: ComponentRef, request: FsReadToStringRequest): Promise<FsReadToStringResponse> {
|
||||
const protocol = parseUri(request.uri).protocol || "file:";
|
||||
|
||||
const object = await self.findFileSystemObject(protocol);
|
||||
return await object.readToString(request);
|
||||
}
|
||||
|
||||
export async function writeString(_caller: Component, request: FsWriteStringRequest): Promise<FsWriteStringResponse> {
|
||||
export async function writeString(_caller: ComponentRef, request: FsWriteStringRequest): Promise<FsWriteStringResponse> {
|
||||
const protocol = parseUri(request.uri).protocol || "file:";
|
||||
|
||||
const object = await self.findFileSystemObject(protocol);
|
||||
return await object.writeString(request);
|
||||
}
|
||||
|
||||
export async function readDir(_caller: Component, request: FsReadDirRequest): Promise<FsReadDirResponse> {
|
||||
export async function readDir(_caller: ComponentRef, request: FsReadDirRequest): Promise<FsReadDirResponse> {
|
||||
const protocol = parseUri(request).protocol || "file:";
|
||||
|
||||
const object = await self.findFileSystemObject(protocol);
|
||||
return await object.readDir(request);
|
||||
}
|
||||
|
||||
export async function stat(_caller: Component, request: FsStatRequest): Promise<FsStatResponse> {
|
||||
export async function stat(_caller: ComponentRef, request: FsStatRequest): Promise<FsStatResponse> {
|
||||
const protocol = parseUri(request).protocol || "file:";
|
||||
|
||||
const object = await self.findFileSystemObject(protocol);
|
||||
return await object.stat(request);
|
||||
}
|
||||
|
||||
export function getBuiltinResourcesLocation(_caller: Component, _request: FsGetBuiltinResourcesLocationRequest): FsGetBuiltinResourcesLocationResponse {
|
||||
export function getBuiltinResourcesLocation(_caller: ComponentRef, _request: FsGetBuiltinResourcesLocationRequest): FsGetBuiltinResourcesLocationResponse {
|
||||
if (app.isPackaged && "resourcesPath" in process && typeof process.resourcesPath == "string") {
|
||||
return pathToFileURL(process.resourcesPath).toString();
|
||||
}
|
||||
@@ -201,11 +201,11 @@ export function getBuiltinResourcesLocation(_caller: Component, _request: FsGetB
|
||||
return encodeURI(path.toURI(nodePath.resolve(import.meta.dirname, "..")));
|
||||
}
|
||||
|
||||
export function getConfigLocation(_caller: Component, _request: FsGetConfigLocationRequest): FsGetConfigLocationResponse {
|
||||
export function getConfigLocation(_caller: ComponentRef, _request: FsGetConfigLocationRequest): FsGetConfigLocationResponse {
|
||||
return encodeURI(path.toURI(nodePath.dirname(process.execPath)));
|
||||
}
|
||||
|
||||
export async function openDirectorySelector(caller: Component, request: FsOpenDirectorySelectorRequest): Promise<FsOpenDirectorySelectorResponse> {
|
||||
export async function openDirectorySelector(caller: ComponentRef, request: FsOpenDirectorySelectorRequest): Promise<FsOpenDirectorySelectorResponse> {
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: [
|
||||
'openDirectory',
|
||||
|
||||
@@ -7,33 +7,33 @@ export async function deactivate() {
|
||||
await fs.uninitialize();
|
||||
}
|
||||
|
||||
export async function handleOpen(caller: Component, request: FsOpenRequest) {
|
||||
export async function handleOpen(caller: ComponentRef, request: FsOpenRequest) {
|
||||
return fs.open(caller, request);
|
||||
}
|
||||
|
||||
export async function handleReadToString(caller: Component, request: FsReadToStringRequest) {
|
||||
export async function handleReadToString(caller: ComponentRef, request: FsReadToStringRequest) {
|
||||
return fs.readToString(caller, request);
|
||||
}
|
||||
export async function handleWriteString(caller: Component, request: FsWriteStringRequest) {
|
||||
export async function handleWriteString(caller: ComponentRef, request: FsWriteStringRequest) {
|
||||
return fs.writeString(caller, request);
|
||||
}
|
||||
|
||||
export async function handleReadDir(caller: Component, request: FsReadDirRequest) {
|
||||
export async function handleReadDir(caller: ComponentRef, request: FsReadDirRequest) {
|
||||
return fs.readDir(caller, request);
|
||||
}
|
||||
|
||||
export async function handleStat(caller: Component, request: FsStatRequest) {
|
||||
export async function handleStat(caller: ComponentRef, request: FsStatRequest) {
|
||||
return fs.stat(caller, request);
|
||||
}
|
||||
|
||||
export function handleGetBuiltinResourcesLocation(caller: Component, request: FsGetBuiltinResourcesLocationRequest) {
|
||||
export function handleGetBuiltinResourcesLocation(caller: ComponentRef, request: FsGetBuiltinResourcesLocationRequest) {
|
||||
return fs.getBuiltinResourcesLocation(caller, request);
|
||||
}
|
||||
|
||||
export function handleGetConfigLocation(caller: Component, request: FsGetConfigLocationRequest) {
|
||||
export function handleGetConfigLocation(caller: ComponentRef, request: FsGetConfigLocationRequest) {
|
||||
return fs.getConfigLocation(caller, request);
|
||||
}
|
||||
|
||||
export function handleOpenDirectorySelector(caller: Component, request: FsOpenDirectorySelectorRequest) {
|
||||
export function handleOpenDirectorySelector(caller: ComponentRef, request: FsOpenDirectorySelectorRequest) {
|
||||
return fs.openDirectorySelector(caller, request);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as github from './github';
|
||||
|
||||
export async function handleReleasesLatest(_caller: Component, params: GithubReleasesLatestRequest): Promise<GithubReleasesLatestResponse> {
|
||||
export async function handleReleasesLatest(_caller: ComponentRef, params: GithubReleasesLatestRequest): Promise<GithubReleasesLatestResponse> {
|
||||
return github.fetchReleasesLatest(params);
|
||||
}
|
||||
|
||||
export async function handleReleases(_caller: Component, params: GithubReleasesRequest): Promise<GithubReleasesResponse> {
|
||||
export async function handleReleases(_caller: ComponentRef, params: GithubReleasesRequest): Promise<GithubReleasesResponse> {
|
||||
return github.fetchReleases(params);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import * as api from '$';
|
||||
import { Disposable, IDisposable } from '$core/Disposable';
|
||||
|
||||
type ProgressInstance = Omit<ProgressValue, 'channel'> & {
|
||||
creator: Component;
|
||||
creator: ComponentRef;
|
||||
disposable?: IDisposable;
|
||||
};
|
||||
|
||||
let nextProgressChannel = 0;
|
||||
let channels: Record<number, ProgressInstance> = {};
|
||||
let subscriptions: Record<number, Set<Component>> = {};
|
||||
let subscriptions: Record<number, Set<ComponentRef>> = {};
|
||||
|
||||
export function deactivate() {
|
||||
nextProgressChannel = 0;
|
||||
@@ -30,7 +30,7 @@ export function deactivate() {
|
||||
subscriptions = {};
|
||||
}
|
||||
|
||||
export function progressCreate(source: Component, params: ProgressCreateRequest): ProgressCreateResponse {
|
||||
export function progressCreate(source: ComponentRef, params: ProgressCreateRequest): ProgressCreateResponse {
|
||||
const channel = nextProgressChannel++;
|
||||
|
||||
channels[channel] = {
|
||||
@@ -58,7 +58,7 @@ export function progressCreate(source: Component, params: ProgressCreateRequest)
|
||||
return { channel };
|
||||
}
|
||||
|
||||
export async function progressUpdate(caller: Component, params: ProgressUpdateRequest) {
|
||||
export async function progressUpdate(caller: ComponentRef, params: ProgressUpdateRequest) {
|
||||
const info = channels[params.channel];
|
||||
|
||||
if (!info) {
|
||||
@@ -108,7 +108,7 @@ export async function progressUpdate(caller: Component, params: ProgressUpdateRe
|
||||
}
|
||||
}
|
||||
|
||||
export function progressSubscribe(caller: Component, params: ProgressSubscribeRequest) {
|
||||
export function progressSubscribe(caller: ComponentRef, params: ProgressSubscribeRequest) {
|
||||
if (!(params.channel in channels)) {
|
||||
throw createError(ErrorCode.InvalidParams);
|
||||
}
|
||||
@@ -120,7 +120,7 @@ export function progressSubscribe(caller: Component, params: ProgressSubscribeRe
|
||||
api.sendProgressUpdateEvent(caller, { value: { channel: params.channel, ...info } });
|
||||
}
|
||||
|
||||
export function progressUnsubscribe(caller: Component, params: ProgressUnsubscribeRequest) {
|
||||
export function progressUnsubscribe(caller: ComponentRef, params: ProgressUnsubscribeRequest) {
|
||||
const channelSubscriptions = subscriptions[params.channel];
|
||||
if (!channelSubscriptions) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user