@webpieces/ipc-bridge 0.4.880 → 0.4.882

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/ipc-bridge",
3
- "version": "0.4.880",
3
+ "version": "0.4.882",
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.880"
25
+ "@webpieces/core-util": "0.4.882"
26
26
  }
27
27
  }
@@ -16,4 +16,6 @@ export declare class IpcClientFactory {
16
16
  /** Explicit scope propagation is safe across concurrent async calls in browser and RN. */
17
17
  withContext(context: IpcCallContext): IpcClientFactory;
18
18
  createClient<T extends object>(apiClass: IpcApiType<T>): T;
19
+ /** One IPC request/reply, logged; the body of every generated IPC client method. */
20
+ private call;
19
21
  }
@@ -31,24 +31,40 @@ class IpcClientFactory {
31
31
  const endpoints = (0, ipc_1.getIpcEndpoints)(apiClass);
32
32
  // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation
33
33
  const methods = new Map();
34
+ const apiName = apiClass.name || apiId;
34
35
  for (const [key, methodId] of Object.entries(endpoints)) {
36
+ // Same call-site capture as the HTTP clients (#1175): synchronous on the CALLER's stack,
37
+ // trimmed at `clientMethod`, parked on the SAME rejected instance.
35
38
  // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation
36
- methods.set(key, async (request) => {
37
- const context = this.connection.newContext(this.parent);
38
- return ipc_1.IpcCallLogger.execute(this.logging, context, 'client', apiId, methodId, (0, ipc_1.getIpcMaskSpec)(apiClass, key) ?? new ipc_1.MaskSpec({}), request, async () => {
39
- if (request === null || request === undefined)
40
- throw new errors_1.ApiImplementationError('IPC requests require one non-null DTO');
41
- const reply = await this.connection.request(new ipc_1.IpcRequest(apiId, methodId, context, request));
42
- // EVERY reply passes the seam, success included, so an app can turn an
43
- // apparently-successful reply into a throw. `asserts reply is IpcSuccess`
44
- // is what leaves no `type === 'failure'` branch behind here.
45
- ipc_1.IpcClientErrorTranslator.throwIfFailure(reply, this.role);
46
- return reply.body === null ? undefined : reply.body;
39
+ const clientMethod = (request) => {
40
+ const callSite = errors_1.ApiCallSite.capture(apiName, key, clientMethod);
41
+ // webpieces-disable no-any-unknown -- a rejection value is genuinely unknown
42
+ return this.call(apiClass, apiId, methodId, key, request).catch((failure) => {
43
+ callSite.attachTo(failure);
44
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- rethrows the caller's own failure unchanged
45
+ throw failure;
47
46
  });
48
- });
47
+ };
48
+ methods.set(key, clientMethod);
49
49
  }
50
50
  return new Proxy(Object.create(null), new IpcProxyHandler(methods));
51
51
  }
52
+ /** One IPC request/reply, logged; the body of every generated IPC client method. */
53
+ async call(apiClass, apiId, methodId, key,
54
+ // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation
55
+ request) {
56
+ const context = this.connection.newContext(this.parent);
57
+ return ipc_1.IpcCallLogger.execute(this.logging, context, 'client', apiId, methodId, (0, ipc_1.getIpcMaskSpec)(apiClass, key) ?? new ipc_1.MaskSpec({}), request, async () => {
58
+ if (request === null || request === undefined)
59
+ throw new errors_1.ApiImplementationError('IPC requests require one non-null DTO');
60
+ const reply = await this.connection.request(new ipc_1.IpcRequest(apiId, methodId, context, request));
61
+ // EVERY reply passes the seam, success included, so an app can turn an
62
+ // apparently-successful reply into a throw. `asserts reply is IpcSuccess`
63
+ // is what leaves no `type === 'failure'` branch behind here.
64
+ ipc_1.IpcClientErrorTranslator.throwIfFailure(reply, this.role);
65
+ return reply.body === null ? undefined : reply.body;
66
+ });
67
+ }
52
68
  }
53
69
  exports.IpcClientFactory = IpcClientFactory;
54
70
  class IpcProxyHandler {
@@ -1 +1 @@
1
- {"version":3,"file":"IpcClientFactory.js","sourceRoot":"","sources":["../../../../../packages/core/ipc-bridge/src/IpcClientFactory.ts"],"names":[],"mappings":";;;AAAA,wDAAqE;AACrE,kDAakC;AAElC,gGAAgG;AAChG,MAAa,gBAAgB;IASJ;IACA;IACA;IACA;IAXrB;;;;;;OAMG;IACH,YACqB,UAAyB,EACzB,OAAmB,EACnB,IAAgB,EAChB,MAAuB;QAHvB,eAAU,GAAV,UAAU,CAAe;QACzB,YAAO,GAAP,OAAO,CAAY;QACnB,SAAI,GAAJ,IAAI,CAAY;QAChB,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,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnF,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,+BAAsB,CAC5B,uCAAuC,CAC1C,CAAC;oBACN,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CACvC,IAAI,gBAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CACpD,CAAC;oBACF,uEAAuE;oBACvE,0EAA0E;oBAC1E,6DAA6D;oBAC7D,8BAAwB,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;oBAC1D,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;AAxDD,4CAwDC;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 { ApiImplementationError } from '@webpieces/core-util/errors';\nimport {\n ClientRole,\n IpcCallContext,\n IpcClientErrorTranslator,\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 /**\n * @param role - WHO receives this factory's replies (see {@link ClientRole}). REQUIRED, with no\n * default (#1173): it decides what an `unauthorized` reply means. An end-user client (the\n * WebView or app shell acting for the person) decodes it as `ApiUnauthorizedError` (\"log in\n * again\"); a server decodes it as `ApiImplementationError`, because the credential it presented\n * was its own.\n */\n constructor(\n private readonly connection: IpcConnection,\n private readonly logging: IpcLogging,\n private readonly role: ClientRole,\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, this.role, 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 ApiImplementationError(\n 'IPC requests require one non-null DTO',\n );\n const reply = await this.connection.request(\n new IpcRequest(apiId, methodId, context, request),\n );\n // EVERY reply passes the seam, success included, so an app can turn an\n // apparently-successful reply into a throw. `asserts reply is IpcSuccess`\n // is what leaves no `type === 'failure'` branch behind here.\n IpcClientErrorTranslator.throwIfFailure(reply, this.role);\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
+ {"version":3,"file":"IpcClientFactory.js","sourceRoot":"","sources":["../../../../../packages/core/ipc-bridge/src/IpcClientFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiCallSite, ApiImplementationError } from '@webpieces/core-util/errors';\nimport {\n ClientRole,\n IpcCallContext,\n IpcClientErrorTranslator,\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 /**\n * @param role - WHO receives this factory's replies (see {@link ClientRole}). REQUIRED, with no\n * default (#1173): it decides what an `unauthorized` reply means. An end-user client (the\n * WebView or app shell acting for the person) decodes it as `ApiUnauthorizedError` (\"log in\n * again\"); a server decodes it as `ApiImplementationError`, because the credential it presented\n * was its own.\n */\n constructor(\n private readonly connection: IpcConnection,\n private readonly logging: IpcLogging,\n private readonly role: ClientRole,\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, this.role, 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 const apiName = apiClass.name || apiId;\n for (const [key, methodId] of Object.entries(endpoints)) {\n // Same call-site capture as the HTTP clients (#1175): synchronous on the CALLER's stack,\n // trimmed at `clientMethod`, parked on the SAME rejected instance.\n // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation\n const clientMethod = (request: unknown): Promise<unknown> => {\n const callSite = ApiCallSite.capture(apiName, key, clientMethod);\n // webpieces-disable no-any-unknown -- a rejection value is genuinely unknown\n return this.call(apiClass, apiId, methodId, key, request).catch((failure: unknown) => {\n callSite.attachTo(failure);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- rethrows the caller's own failure unchanged\n throw failure;\n });\n };\n methods.set(key, clientMethod);\n }\n return new Proxy(Object.create(null) as T, new IpcProxyHandler(methods));\n }\n\n /** One IPC request/reply, logged; the body of every generated IPC client method. */\n private async call<T extends object>(\n apiClass: IpcApiType<T>,\n apiId: string,\n methodId: string,\n key: string,\n // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation\n request: unknown,\n // webpieces-disable no-any-unknown -- untrusted IPC data is schema-validated; generic dispatch cannot assume a DTO type before validation\n ): 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 ApiImplementationError(\n 'IPC requests require one non-null DTO',\n );\n const reply = await this.connection.request(\n new IpcRequest(apiId, methodId, context, request),\n );\n // EVERY reply passes the seam, success included, so an app can turn an\n // apparently-successful reply into a throw. `asserts reply is IpcSuccess`\n // is what leaves no `type === 'failure'` branch behind here.\n IpcClientErrorTranslator.throwIfFailure(reply, this.role);\n return reply.body === null ? undefined : reply.body;\n },\n );\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"]}