@webpieces/ipc-bridge 0.4.760 → 0.4.761
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/package.json +2 -2
- package/src/IpcClientFactory.d.ts +2 -2
- package/src/IpcClientFactory.js +8 -9
- package/src/IpcClientFactory.js.map +1 -1
- package/src/IpcServerFactory.d.ts +3 -3
- package/src/IpcServerFactory.js +16 -18
- package/src/IpcServerFactory.js.map +1 -1
package/README.md
CHANGED
|
@@ -6,22 +6,23 @@
|
|
|
6
6
|
import { IpcClientFactory, IpcServerFactory } from '@webpieces/ipc-bridge';
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Declare a shared abstract API
|
|
9
|
+
Declare a shared abstract API with class-level `@WpInternal(apiId)` and one method-level `@WpIpcEndpoint(methodId)` per operation. The IDs are stable wire identities and survive minification. Methods accept exactly one non-null DTO and return a Promise. Add `{ kind: 'notification' }` when the operation is a notification; notifications still acknowledge failures rather than silently swallowing them. IPC intentionally passes parsed JSON shapes through without consumer-authored runtime schemas or DTO reconstruction.
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
|
+
import { MaskLog, WpInternal, WpIpcEndpoint } from '@webpieces/core-util/ipc';
|
|
13
|
+
|
|
14
|
+
@WpInternal('playback.v1')
|
|
12
15
|
abstract class PlaybackApi {
|
|
16
|
+
@MaskLog({ token: 'full' })
|
|
17
|
+
@WpIpcEndpoint('play.v1')
|
|
13
18
|
abstract play(request: PlayRequest): Promise<void>;
|
|
14
19
|
}
|
|
15
|
-
class PlaybackMethods {
|
|
16
|
-
readonly play = new IpcMethod('play.v1', new PlayRequestSchema(), new IpcVoidSchema(), new MaskSpec({}));
|
|
17
|
-
}
|
|
18
|
-
const playbackContract = new IpcContract('playback.v1', PlaybackApi, new PlaybackMethods());
|
|
19
20
|
const clients = new IpcClientFactory(connection, logging);
|
|
20
|
-
const playback = clients.createClient(
|
|
21
|
+
const playback = clients.createClient(PlaybackApi);
|
|
21
22
|
await playback.play(new PlayRequest('track-id'));
|
|
22
23
|
```
|
|
23
24
|
|
|
24
|
-
The
|
|
25
|
+
The DTO, IDs and metadata live in the shared API package. The names of the abstract class and controller are never sent on the wire. Proxy inspection, symbols and `then` do not send requests. Added JSON fields are naturally ignored by typed consumers; missing fields may fail in application logic.
|
|
25
26
|
|
|
26
27
|
Construct `IpcConnection` with an `IpcTransport` adapter and `IpcConnectionOptions`: a positive timeout, a connection-wide unique ID generator, a scheduler returning timer cancellation functions, and an error owner. The adapter sends strings and subscribes once to incoming strings, transport errors and close events. Its send Promise must reject on send failure; parse/message-error callbacks go to the failure subscriber. A vendor bus adapter must not expose vendor Node declarations through its public contract. Do not install a second vendor handler for every API.
|
|
27
28
|
|
|
@@ -41,13 +42,13 @@ The required compatibility build checks published declarations without Node/DOM
|
|
|
41
42
|
|
|
42
43
|
```typescript
|
|
43
44
|
const server = new IpcServerFactory(logging);
|
|
44
|
-
server.create(
|
|
45
|
-
server.create(
|
|
45
|
+
server.create(PlaybackApi, new NativePlaybackController(player));
|
|
46
|
+
server.create(NavigationApi, new NavigationController(router));
|
|
46
47
|
connection.setHandler(server.handle); // exactly once, before sending any calls
|
|
47
48
|
```
|
|
48
49
|
|
|
49
|
-
One connection has one dispatcher containing multiple APIs. Duplicate registrations fail. Unknown API/method IDs return canonical `EndpointNotFoundError`, distinct from a missing domain entity.
|
|
50
|
+
One connection has one dispatcher containing multiple APIs. Duplicate registrations fail. Unknown API/method IDs return canonical `EndpointNotFoundError`, distinct from a missing domain entity. Successful void is encoded explicitly as null. Receiver exceptions are logged then encoded with the same `ApiErrorCodec` used by the sender to reconstruct canonical errors.
|
|
50
51
|
|
|
51
|
-
`createScoped(
|
|
52
|
+
`createScoped(Api, scope)` accepts an `IpcControllerScope<T>` whose `create(context)` supplies the implementation for that invocation. Inject a `clients.withContext(context)` proxy into this controller when it calls back or invokes another API. Concurrent calls have separate context objects and do not overwrite a global async context.
|
|
52
53
|
|
|
53
54
|
A host may register receivers and create client proxies on both ends of the same connection. `IpcConnection` owns transport parse/send failures and disposal; see the connection and logging contracts above for the transport and logging contracts, error ownership, security responsibilities and downstream native lifecycle tests.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/ipc-bridge",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.761",
|
|
4
4
|
"description": "Portable typed duplex IPC client and server factories for browser, React Native and Node",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@webpieces/core-util": "0.4.
|
|
25
|
+
"@webpieces/core-util": "0.4.761"
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IpcCallContext, IpcConnection,
|
|
1
|
+
import { IpcCallContext, IpcConnection, IpcApiType, IpcLogging } from '@webpieces/core-util/ipc';
|
|
2
2
|
/** Typed proxies on one trusted duplex connection; no HTTP decorators or container required. */
|
|
3
3
|
export declare class IpcClientFactory {
|
|
4
4
|
private readonly connection;
|
|
@@ -7,5 +7,5 @@ export declare class IpcClientFactory {
|
|
|
7
7
|
constructor(connection: IpcConnection, logging: IpcLogging, parent?: IpcCallContext | undefined);
|
|
8
8
|
/** Explicit scope propagation is safe across concurrent async calls in browser and RN. */
|
|
9
9
|
withContext(context: IpcCallContext): IpcClientFactory;
|
|
10
|
-
createClient<T extends object>(
|
|
10
|
+
createClient<T extends object>(apiClass: IpcApiType<T>): T;
|
|
11
11
|
}
|
package/src/IpcClientFactory.js
CHANGED
|
@@ -17,23 +17,22 @@ class IpcClientFactory {
|
|
|
17
17
|
withContext(context) {
|
|
18
18
|
return new IpcClientFactory(this.connection, this.logging, context);
|
|
19
19
|
}
|
|
20
|
-
createClient(
|
|
20
|
+
createClient(apiClass) {
|
|
21
|
+
const apiId = (0, ipc_1.assertInternalApi)(apiClass);
|
|
22
|
+
const endpoints = (0, ipc_1.getIpcEndpoints)(apiClass);
|
|
21
23
|
// webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation
|
|
22
24
|
const methods = new Map();
|
|
23
|
-
for (const key of Object.
|
|
24
|
-
// webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation
|
|
25
|
-
const method = contract.methods[key];
|
|
25
|
+
for (const [key, methodId] of Object.entries(endpoints)) {
|
|
26
26
|
// webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation
|
|
27
27
|
methods.set(key, async (request) => {
|
|
28
28
|
const context = this.connection.newContext(this.parent);
|
|
29
|
-
return ipc_1.IpcCallLogger.execute(this.logging, context, 'client',
|
|
30
|
-
|
|
31
|
-
if (body === null || body === undefined)
|
|
29
|
+
return ipc_1.IpcCallLogger.execute(this.logging, context, 'client', apiId, methodId, (0, ipc_1.getIpcMaskSpec)(apiClass, key) ?? new ipc_1.MaskSpec({}), request, async () => {
|
|
30
|
+
if (request === null || request === undefined)
|
|
32
31
|
throw new errors_1.InternalError('IPC requests require one non-null DTO');
|
|
33
|
-
const reply = await this.connection.request(new ipc_1.IpcRequest(
|
|
32
|
+
const reply = await this.connection.request(new ipc_1.IpcRequest(apiId, methodId, context, request));
|
|
34
33
|
if (reply.type === 'failure')
|
|
35
34
|
throw errors_1.ApiErrorCodec.decode(reply.error);
|
|
36
|
-
return
|
|
35
|
+
return reply.body === null ? undefined : reply.body;
|
|
37
36
|
});
|
|
38
37
|
});
|
|
39
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IpcClientFactory.js","sourceRoot":"","sources":["../../../../../packages/core/ipc-bridge/src/IpcClientFactory.ts"],"names":[],"mappings":";;;AAAA,wDAA2E;AAC3E,
|
|
1
|
+
{"version":3,"file":"IpcClientFactory.js","sourceRoot":"","sources":["../../../../../packages/core/ipc-bridge/src/IpcClientFactory.ts"],"names":[],"mappings":";;;AAAA,wDAA2E;AAC3E,kDAWkC;AAElC,gGAAgG;AAChG,MAAa,gBAAgB;IAEJ;IACA;IACA;IAHrB,YACqB,UAAyB,EACzB,OAAmB,EACnB,MAAuB;QAFvB,eAAU,GAAV,UAAU,CAAe;QACzB,YAAO,GAAP,OAAO,CAAY;QACnB,WAAM,GAAN,MAAM,CAAiB;IACzC,CAAC;IAEJ,0FAA0F;IAC1F,WAAW,CAAC,OAAuB;QAC/B,OAAO,IAAI,gBAAgB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACxE,CAAC;IAED,YAAY,CAAmB,QAAuB;QAClD,MAAM,KAAK,GAAG,IAAA,uBAAiB,EAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,IAAA,qBAAe,EAAC,QAAQ,CAAC,CAAC;QAC5C,0IAA0I;QAC1I,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuD,CAAC;QAC/E,KAAK,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YACtD,0IAA0I;YAC1I,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,OAAgB,EAAoB,EAAE;gBAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACxD,OAAO,mBAAa,CAAC,OAAO,CACxB,IAAI,CAAC,OAAO,EACZ,OAAO,EACP,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,IAAA,oBAAc,EAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,IAAI,cAAQ,CAAC,EAAE,CAAC,EACjD,OAAO,EACP,KAAK,IAAI,EAAE;oBACP,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS;wBACzC,MAAM,IAAI,sBAAa,CAAC,uCAAuC,CAAC,CAAC;oBACrE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CACvC,IAAI,gBAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CACpD,CAAC;oBACF,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;wBAAE,MAAM,sBAAa,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;oBACtE,OAAO,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;gBACxD,CAAC,CACJ,CAAC;YACN,CAAC,CAAC,CAAC;QACP,CAAC;QACD,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAM,EAAE,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7E,CAAC;CACJ;AA3CD,4CA2CC;AAED,MAAM,eAAe;IAGI;IAFrB;IACI,0IAA0I;IACzH,OAAyE;QAAzE,YAAO,GAAP,OAAO,CAAkE;IAC3F,CAAC;IACJ,0IAA0I;IAC1I,GAAG,CAAC,OAAU,EAAE,GAAoB;QAChC,8FAA8F;QAC9F,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IACD,GAAG,CAAC,OAAU,EAAE,GAAoB;QAChC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;CACJ","sourcesContent":["import { ApiErrorCodec, InternalError } from '@webpieces/core-util/errors';\nimport {\n IpcCallContext,\n IpcConnection,\n IpcApiType,\n IpcLogging,\n IpcRequest,\n IpcCallLogger,\n assertInternalApi,\n getIpcEndpoints,\n getIpcMaskSpec,\n MaskSpec,\n} from '@webpieces/core-util/ipc';\n\n/** Typed proxies on one trusted duplex connection; no HTTP decorators or container required. */\nexport class IpcClientFactory {\n constructor(\n private readonly connection: IpcConnection,\n private readonly logging: IpcLogging,\n private readonly parent?: IpcCallContext,\n ) {}\n\n /** Explicit scope propagation is safe across concurrent async calls in browser and RN. */\n withContext(context: IpcCallContext): IpcClientFactory {\n return new IpcClientFactory(this.connection, this.logging, context);\n }\n\n createClient<T extends object>(apiClass: IpcApiType<T>): T {\n const apiId = assertInternalApi(apiClass);\n const endpoints = getIpcEndpoints(apiClass);\n // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation\n const methods = new Map<PropertyKey, (request: unknown) => Promise<unknown>>();\n for (const [key, methodId] of Object.entries(endpoints)) {\n // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation\n methods.set(key, async (request: unknown): Promise<unknown> => {\n const context = this.connection.newContext(this.parent);\n return IpcCallLogger.execute(\n this.logging,\n context,\n 'client',\n apiId,\n methodId,\n getIpcMaskSpec(apiClass, key) ?? new MaskSpec({}),\n request,\n async () => {\n if (request === null || request === undefined)\n throw new InternalError('IPC requests require one non-null DTO');\n const reply = await this.connection.request(\n new IpcRequest(apiId, methodId, context, request),\n );\n if (reply.type === 'failure') throw ApiErrorCodec.decode(reply.error);\n return reply.body === null ? undefined : reply.body;\n },\n );\n });\n }\n return new Proxy(Object.create(null) as T, new IpcProxyHandler(methods));\n }\n}\n\nclass IpcProxyHandler<T extends object> implements ProxyHandler<T> {\n constructor(\n // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation\n private readonly methods: ReadonlyMap<PropertyKey, (request: unknown) => Promise<unknown>>,\n ) {}\n // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation\n get(_target: T, key: string | symbol): unknown {\n // Promise assimilation, inspection, symbols and framework instrumentation aren't RPC methods.\n return this.methods.get(key);\n }\n has(_target: T, key: string | symbol): boolean {\n return this.methods.has(key);\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IpcCallContext,
|
|
1
|
+
import { IpcCallContext, IpcApiType, IpcLogging, IpcReply, IpcRequest } from '@webpieces/core-util/ipc';
|
|
2
2
|
/** Construct a receiver using explicit call context when it needs to make nested outbound calls. */
|
|
3
3
|
export interface IpcControllerScope<T> {
|
|
4
4
|
create(context: IpcCallContext): T;
|
|
@@ -8,8 +8,8 @@ export declare class IpcServerFactory {
|
|
|
8
8
|
private readonly logging;
|
|
9
9
|
private readonly registrations;
|
|
10
10
|
constructor(logging: IpcLogging);
|
|
11
|
-
create<T extends object>(
|
|
12
|
-
createScoped<T extends object>(
|
|
11
|
+
create<T extends object>(apiClass: IpcApiType<T>, controller: T): void;
|
|
12
|
+
createScoped<T extends object>(apiClass: IpcApiType<T>, scope: IpcControllerScope<T>): void;
|
|
13
13
|
private register;
|
|
14
14
|
/** Bootstrap passes this function to the ONE connection's setHandler before sending calls. */
|
|
15
15
|
readonly handle: (request: IpcRequest) => Promise<IpcReply>;
|
package/src/IpcServerFactory.js
CHANGED
|
@@ -17,36 +17,34 @@ class IpcServerFactory {
|
|
|
17
17
|
constructor(logging) {
|
|
18
18
|
this.logging = logging;
|
|
19
19
|
}
|
|
20
|
-
create(
|
|
21
|
-
this.register(
|
|
20
|
+
create(apiClass, controller) {
|
|
21
|
+
this.register(apiClass, () => controller);
|
|
22
22
|
}
|
|
23
|
-
createScoped(
|
|
24
|
-
this.register(
|
|
23
|
+
createScoped(apiClass, scope) {
|
|
24
|
+
this.register(apiClass, (context) => scope.create(context));
|
|
25
25
|
}
|
|
26
|
-
register(
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
register(apiClass, controller) {
|
|
27
|
+
const apiId = (0, ipc_1.assertInternalApi)(apiClass);
|
|
28
|
+
if (this.registrations.has(apiId))
|
|
29
|
+
throw new errors_1.InternalError(`Duplicate IPC API registration: ${apiId}`);
|
|
29
30
|
const methods = new Map();
|
|
30
|
-
for (const key of Object.
|
|
31
|
-
|
|
32
|
-
const method = contract.methods[key];
|
|
33
|
-
methods.set(method.id, new IpcRegistration(async (request) => {
|
|
31
|
+
for (const [key, methodId] of Object.entries((0, ipc_1.getIpcEndpoints)(apiClass))) {
|
|
32
|
+
methods.set(methodId, new IpcRegistration(async (request) => {
|
|
34
33
|
// Logging wraps validation and invocation; only handle() below encodes exceptions.
|
|
35
|
-
return ipc_1.IpcCallLogger.execute(this.logging, request.context, 'server',
|
|
36
|
-
|
|
37
|
-
if (body === null || body === undefined)
|
|
34
|
+
return ipc_1.IpcCallLogger.execute(this.logging, request.context, 'server', apiId, methodId, (0, ipc_1.getIpcMaskSpec)(apiClass, key) ?? new ipc_1.MaskSpec({}), request.body, async () => {
|
|
35
|
+
if (request.body === null || request.body === undefined)
|
|
38
36
|
throw new errors_1.InternalError('IPC requests require one non-null DTO');
|
|
39
37
|
const instance = controller(request.context);
|
|
40
38
|
const invoke = instance[key];
|
|
41
39
|
if (typeof invoke !== 'function')
|
|
42
|
-
throw new errors_1.InternalError(`IPC implementation is missing ${
|
|
40
|
+
throw new errors_1.InternalError(`IPC implementation is missing ${apiId}.${methodId}`);
|
|
43
41
|
const result = await // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation
|
|
44
|
-
invoke.call(instance, body);
|
|
45
|
-
return
|
|
42
|
+
invoke.call(instance, request.body);
|
|
43
|
+
return result;
|
|
46
44
|
});
|
|
47
45
|
}));
|
|
48
46
|
}
|
|
49
|
-
this.registrations.set(
|
|
47
|
+
this.registrations.set(apiId, methods);
|
|
50
48
|
}
|
|
51
49
|
/** Bootstrap passes this function to the ONE connection's setHandler before sending calls. */
|
|
52
50
|
handle = async (request) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IpcServerFactory.js","sourceRoot":"","sources":["../../../../../packages/core/ipc-bridge/src/IpcServerFactory.ts"],"names":[],"mappings":";;;AAAA,wDAAkG;AAClG,
|
|
1
|
+
{"version":3,"file":"IpcServerFactory.js","sourceRoot":"","sources":["../../../../../packages/core/ipc-bridge/src/IpcServerFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiErrorCodec, EndpointNotFoundError, InternalError } from '@webpieces/core-util/errors';\nimport {\n IpcCallContext,\n IpcApiType,\n IpcFailure,\n IpcLogging,\n IpcReply,\n IpcRequest,\n IpcSuccess,\n IpcCallLogger,\n toError,\n assertInternalApi,\n getIpcEndpoints,\n getIpcMaskSpec,\n MaskSpec,\n} from '@webpieces/core-util/ipc';\n\n/** Construct a receiver using explicit call context when it needs to make nested outbound calls. */\nexport interface IpcControllerScope<T> {\n create(context: IpcCallContext): T;\n}\nclass IpcRegistration {\n // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation\n constructor(readonly invoke: (request: IpcRequest) => Promise<unknown>) {}\n}\n\n/** One dispatcher hosts multiple explicitly registered APIs and never enumerates controller members. */\nexport class IpcServerFactory {\n private readonly registrations = new Map<string, Map<string, IpcRegistration>>();\n constructor(private readonly logging: IpcLogging) {}\n\n create<T extends object>(apiClass: IpcApiType<T>, controller: T): void {\n this.register(apiClass, () => controller);\n }\n\n createScoped<T extends object>(apiClass: IpcApiType<T>, scope: IpcControllerScope<T>): void {\n this.register(apiClass, (context: IpcCallContext) => scope.create(context));\n }\n\n private register<T extends object>(\n apiClass: IpcApiType<T>,\n controller: (context: IpcCallContext) => T,\n ): void {\n const apiId = assertInternalApi(apiClass);\n if (this.registrations.has(apiId))\n throw new InternalError(`Duplicate IPC API registration: ${apiId}`);\n const methods = new Map<string, IpcRegistration>();\n for (const [key, methodId] of Object.entries(getIpcEndpoints(apiClass))) {\n methods.set(\n methodId,\n new IpcRegistration(async (request) => {\n // Logging wraps validation and invocation; only handle() below encodes exceptions.\n return IpcCallLogger.execute(\n this.logging,\n request.context,\n 'server',\n apiId,\n methodId,\n getIpcMaskSpec(apiClass, key) ?? new MaskSpec({}),\n request.body,\n async () => {\n if (request.body === null || request.body === undefined)\n throw new InternalError('IPC requests require one non-null DTO');\n const instance = controller(request.context);\n const invoke = instance[key as keyof T];\n if (typeof invoke !== 'function')\n throw new InternalError(\n `IPC implementation is missing ${apiId}.${methodId}`,\n );\n const result =\n await // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation\n (invoke as (request: unknown) => Promise<unknown>).call(\n instance,\n request.body,\n );\n return result;\n },\n );\n }),\n );\n }\n this.registrations.set(apiId, methods);\n }\n\n /** Bootstrap passes this function to the ONE connection's setHandler before sending calls. */\n readonly handle = async (request: IpcRequest): Promise<IpcReply> => {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- receiver translates to a failure reply; the generated client reconstructs and throws it\n try {\n const method = this.registrations.get(request.apiId)?.get(request.methodId);\n if (!method) throw new EndpointNotFoundError('Unknown IPC API or method');\n const body = await method.invoke(request);\n return new IpcSuccess(request.context, body === undefined ? null : body);\n } catch (err: unknown) {\n const error = toError(err);\n return new IpcFailure(request.context, ApiErrorCodec.encode(error));\n }\n };\n}\n"]}
|