@webpieces/http-client-node 0.4.789 → 0.4.791

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/http-client-node",
3
- "version": "0.4.789",
3
+ "version": "0.4.791",
4
4
  "description": "Server-side HTTP client for webpieces: inversify-wired, reads RequestContext directly, mints OIDC/shared-secret delivery auth, resolves Cloud Run URLs from a service name",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,10 +22,10 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@webpieces/core-context": "0.4.789",
26
- "@webpieces/core-util": "0.4.789",
27
- "@webpieces/gcp-identity": "0.4.789",
28
- "@webpieces/http-client-core": "0.4.789",
25
+ "@webpieces/core-context": "0.4.791",
26
+ "@webpieces/core-util": "0.4.791",
27
+ "@webpieces/gcp-identity": "0.4.791",
28
+ "@webpieces/http-client-core": "0.4.791",
29
29
  "inversify": "7.10.4",
30
30
  "reflect-metadata": "0.2.2"
31
31
  }
@@ -1,7 +1,7 @@
1
1
  import { DestinationTrust, RouteMetadata, Secrets } from '@webpieces/core-util';
2
2
  import { RequestContextHeaders } from '@webpieces/core-context';
3
3
  import { GcpOidc } from '@webpieces/gcp-identity';
4
- import { ApiPrototype, ByteReadableStream, ClientRequest, ClientFilterDefinition, ProxyClient, TranslatedFailure } from '@webpieces/http-client-core';
4
+ import { ApiPrototype, ByteReadableStream, ClientRequest, ClientFilterDefinition, ProxyClient } from '@webpieces/http-client-core';
5
5
  import { AddressResolver } from './AddressResolver';
6
6
  import { ClientConfig } from './ClientConfig';
7
7
  import { WebhookSignerCallback } from './WebhookSignerCallback';
@@ -87,54 +87,6 @@ export declare class NodeProxyClient extends ProxyClient {
87
87
  * from the call path — a logging backend stamps its own fields and never sees this.
88
88
  */
89
89
  private recordCall;
90
- /**
91
- * SERVER-TO-SERVER: a 4xx received from a dependency becomes THIS server's own 500.
92
- *
93
- * THE INVARIANT:
94
- *
95
- * A status received from a downstream dependency describes OUR request to it. It is never the
96
- * status we return to OUR caller. The server that answered 404 is correct; the server that
97
- * asked for a route that does not exist is broken, and must say so as a 500.
98
- *
99
- * Every 4xx is a CALLER-side defect on this hop: 404 = wrong path / wrong base URL / a dependency
100
- * that is not deployed yet, 400 = we sent a malformed request, 401/403 = our service credentials
101
- * or the callee's caller allow-list are wrong. None of them is an answer for whoever called US, and
102
- * relaying one lets an internal misconfiguration impersonate a legitimate response. That is not
103
- * hypothetical: a partner-facing Management API reported an EMPTY store estate for an org with six
104
- * live storefronts, because its dependency had not been promoted and Express served an HTML 404
105
- * which arrived here as `ApiNotFoundError` and went straight back out. A 500 would have been
106
- * loud, correct, and attributable to the one server that actually had the bug — which is the whole
107
- * point: only ONE server should be paged for this.
108
- *
109
- * DELIBERATELY 4xx ONLY. 5xx (502/503/504) already mean "the dependency is unavailable", which is
110
- * honest and useful outward, and 500 is already a 500. `ApiEndUserError` (266, a 2xx code carrying
111
- * user validation) and `ApiDependencyBackoffError` are not statuses about our request at all. All of
112
- * them pass through untouched.
113
- *
114
- * THE OPT-OUT IS `appRegistered`, not a config key. A thin proxy or gateway that genuinely wants to
115
- * relay a downstream status as its own registers a `ClientRegistry` error translation for it at
116
- * startup — one greppable line saying so out loud — and that translation wins here. Only the
117
- * framework's built-in default mapping gets wrapped. There is no flag, because a flag would make
118
- * the dangerous choice invisible in the code that suffers from it.
119
- *
120
- * The downstream diagnostic is NOT lost: the original error (which for the incident above names the
121
- * method, the status, the `text/html` content-type and a snippet of the body) is both quoted in the
122
- * message and kept as `cause`.
123
- *
124
- * How much "Downstream said:" is worth depends on WHO answered, and both halves are by design:
125
- * - a NON-webpieces answer (an lb's html 404, a proxy's plain-text 502) is described CLIENT-side by
126
- * `ResponseBodyReader.describeForeignBody`, so the full diagnostic is ours to quote — this is the
127
- * mealco incident's exact shape, and it is the case that mattered.
128
- * - a WEBPIECES peer deliberately sends only the generic reason phrase for its status (see
129
- * `ApiErrorHttpMapper` in http-server — only `ApiEndUserError`'s message is caller-facing), so this
130
- * reads "Downstream said: Not Found". That is correct and not a regression: the peer's real
131
- * message is in the PEER's log, correlated by request id, which is the only place it was ever
132
- * safe to read it.
133
- *
134
- * This whole string is an operator-facing message on an `ApiImplementationError`, so when THIS
135
- * server answers its own caller none of it goes on the wire — it goes to this server's log.
136
- */
137
- protected adaptDownstreamFailure(failure: TranslatedFailure, callId: string): Error;
138
90
  }
139
91
  /**
140
92
  * DI token for the `Provider<NodeProxyClient>` that hands out RPC clients — one per API contract.
@@ -224,65 +224,6 @@ let NodeProxyClient = class NodeProxyClient extends http_client_core_1.ProxyClie
224
224
  throw err;
225
225
  }
226
226
  }
227
- /**
228
- * SERVER-TO-SERVER: a 4xx received from a dependency becomes THIS server's own 500.
229
- *
230
- * THE INVARIANT:
231
- *
232
- * A status received from a downstream dependency describes OUR request to it. It is never the
233
- * status we return to OUR caller. The server that answered 404 is correct; the server that
234
- * asked for a route that does not exist is broken, and must say so as a 500.
235
- *
236
- * Every 4xx is a CALLER-side defect on this hop: 404 = wrong path / wrong base URL / a dependency
237
- * that is not deployed yet, 400 = we sent a malformed request, 401/403 = our service credentials
238
- * or the callee's caller allow-list are wrong. None of them is an answer for whoever called US, and
239
- * relaying one lets an internal misconfiguration impersonate a legitimate response. That is not
240
- * hypothetical: a partner-facing Management API reported an EMPTY store estate for an org with six
241
- * live storefronts, because its dependency had not been promoted and Express served an HTML 404
242
- * which arrived here as `ApiNotFoundError` and went straight back out. A 500 would have been
243
- * loud, correct, and attributable to the one server that actually had the bug — which is the whole
244
- * point: only ONE server should be paged for this.
245
- *
246
- * DELIBERATELY 4xx ONLY. 5xx (502/503/504) already mean "the dependency is unavailable", which is
247
- * honest and useful outward, and 500 is already a 500. `ApiEndUserError` (266, a 2xx code carrying
248
- * user validation) and `ApiDependencyBackoffError` are not statuses about our request at all. All of
249
- * them pass through untouched.
250
- *
251
- * THE OPT-OUT IS `appRegistered`, not a config key. A thin proxy or gateway that genuinely wants to
252
- * relay a downstream status as its own registers a `ClientRegistry` error translation for it at
253
- * startup — one greppable line saying so out loud — and that translation wins here. Only the
254
- * framework's built-in default mapping gets wrapped. There is no flag, because a flag would make
255
- * the dangerous choice invisible in the code that suffers from it.
256
- *
257
- * The downstream diagnostic is NOT lost: the original error (which for the incident above names the
258
- * method, the status, the `text/html` content-type and a snippet of the body) is both quoted in the
259
- * message and kept as `cause`.
260
- *
261
- * How much "Downstream said:" is worth depends on WHO answered, and both halves are by design:
262
- * - a NON-webpieces answer (an lb's html 404, a proxy's plain-text 502) is described CLIENT-side by
263
- * `ResponseBodyReader.describeForeignBody`, so the full diagnostic is ours to quote — this is the
264
- * mealco incident's exact shape, and it is the case that mattered.
265
- * - a WEBPIECES peer deliberately sends only the generic reason phrase for its status (see
266
- * `ApiErrorHttpMapper` in http-server — only `ApiEndUserError`'s message is caller-facing), so this
267
- * reads "Downstream said: Not Found". That is correct and not a regression: the peer's real
268
- * message is in the PEER's log, correlated by request id, which is the only place it was ever
269
- * safe to read it.
270
- *
271
- * This whole string is an operator-facing message on an `ApiImplementationError`, so when THIS
272
- * server answers its own caller none of it goes on the wire — it goes to this server's log.
273
- */
274
- adaptDownstreamFailure(failure, callId) {
275
- if (failure.appRegistered) {
276
- return failure.error;
277
- }
278
- if (failure.statusCode < 400 || failure.statusCode >= 500) {
279
- return failure.error;
280
- }
281
- return new core_util_1.ApiImplementationError(`${callId}: dependency answered HTTP ${failure.statusCode}. That status describes OUR ` +
282
- `request to it, not an answer for our caller, so this server owns it as a 500 — check the ` +
283
- `path, the base URL, whether the dependency is deployed, and our service credentials. ` +
284
- `Downstream said: ${failure.error.message}`, failure.error);
285
- }
286
227
  };
287
228
  exports.NodeProxyClient = NodeProxyClient;
288
229
  exports.NodeProxyClient = NodeProxyClient = tslib_1.__decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"NodeProxyClient.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-node/src/NodeProxyClient.ts"],"names":[],"mappings":";;;;AAAA,yCAA6C;AAC7C,6CAAmC;AACnC,yCAAoE;AACpE,2CAAqD;AACrD,6CAAuC;AACvC,oDAY8B;AAC9B,0DAKiC;AACjC,0DAAkD;AAClD,kEAOqC;AACrC,uDAAoD;AAEpD,iEAA8D;AAC9D,iEAA8D;AAC9D,6DAA0D;AAC1D,uDAAoD;AACpD,6CAA0C;AAC1C,mEAAyF;AAEzF;;;;;;GAMG;AACH,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAChC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAEnC;;;;;;;GAOG;AAEI,IAAM,eAAe,GAArB,MAAM,eAAgB,SAAQ,8BAAW;IAKQ;IAEd;IAEQ;IAGI;IAM7B;IAjBb,MAAM,CAAgB;IAE9B,YAEoD,OAA8B,EAE5C,OAAgB,EAER,eAAgC,EAG5B,OAAiB,EAM9C,aAAqC;QAEtD,kFAAkF;QAClF,2FAA2F;QAC3F,wDAAwD;QACxD,KAAK,CAAC,IAAI,0BAAc,CAAC,IAAI,2CAA4B,EAAE,CAAC,CAAC,CAAC;QAlBd,YAAO,GAAP,OAAO,CAAuB;QAE5C,YAAO,GAAP,OAAO,CAAS;QAER,oBAAe,GAAf,eAAe,CAAiB;QAG5B,YAAO,GAAP,OAAO,CAAU;QAM9C,kBAAa,GAAb,aAAa,CAAwB;IAM1D,CAAC;IAED;;;OAGG;IACH,IAAI,CACA,YAAkC,EAClC,MAAoB,EACpB,UAAoC;QAEpC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAC9C,CAAC;IAED;;;;;;;OAOG;IACgB,cAAc;QAC7B,OAAO,0BAAc,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;IAED,4FAA4F;IACzE,6BAA6B;QAC5C,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;OAGG;IACgB,sBAAsB,CACrC,OAAsB,EACtB,MAAmB,EACnB,IAAwB;QAExB,OAAO,IAAI,OAAO,CACd,CAAC,OAAqC,EAAE,MAA8B,EAAE,EAAE;YACtE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,oBAAY,CAAC,CAAC,CAAC,mBAAW,CAAC;YACpE,MAAM,QAAQ,GAAG,IAAI,CACjB,GAAG,EACH;gBACI,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,OAAO,CAAC,eAAe,EAAE;gBAClC,MAAM;aACT,EACD,CAAC,QAAyB,EAAQ,EAAE;gBAChC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;gBAC9B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC3D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;wBACvB,KAAK,MAAM,IAAI,IAAI,KAAK;4BAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBACzD,CAAC;yBAAM,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;wBAC7B,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;oBAC7B,CAAC;gBACL,CAAC;gBACD,MAAM,YAAY,GAAG,sBAAQ,CAAC,KAAK,CAAC,QAAQ,CAA+B,CAAC;gBAC5E,OAAO,CACH,IAAI,QAAQ,CAAC,YAAY,EAAE;oBACvB,MAAM,EAAE,QAAQ,CAAC,UAAU,IAAI,GAAG;oBAClC,UAAU,EAAE,QAAQ,CAAC,aAAa;oBAClC,OAAO;iBACV,CAAC,CACL,CAAC;YACN,CAAC,CACJ,CAAC;YACF,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC/B,+FAA+F;YAC/F,KAAK,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;gBAClE,MAAM,KAAK,GAAG,IAAA,mBAAO,EAAC,GAAG,CAAC,CAAC;gBAC3B,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC,CAAC,CAAC;QACP,CAAC,CACJ,CAAC;IACN,CAAC;IAEO,KAAK,CAAC,oBAAoB,CAC9B,IAAwB,EACxB,QAA2C;QAE3C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,8GAA8G;QAC9G,IAAI,CAAC;YACD,SAAS,CAAC;gBACN,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBACjC,IAAI,IAAI,CAAC,IAAI;oBAAE,MAAM;gBACrB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;oBAAE,MAAM,IAAA,kBAAI,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACnE,CAAC;YACD,QAAQ,CAAC,GAAG,EAAE,CAAC;QACnB,CAAC;gBAAS,CAAC;YACP,MAAM,CAAC,WAAW,EAAE,CAAC;QACzB,CAAC;IACL,CAAC;IAED;;;;;;;;OAQG;IACgB,aAAa;QAC5B,OAAO;YACH,IAAI,yCAAsB,CACtB,mBAAmB,EACnB,IAAI,iCAAe,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,eAAe,CAAC,CAC/D;YACD,IAAI,yCAAsB,CACtB,sBAAsB,EACtB,IAAI,uCAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,CACzE;SACJ,CAAC;IACN,CAAC;IAED;;;;;;;;;;OAUG;IACK,UAAU;QACd,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;YACjC,IAAI,MAAM,YAAY,2CAAoB,IAAI,MAAM,YAAY,2CAAoB,EAAE,CAAC;gBACnF,OAAO,MAAM,CAAC,UAAU,CAAC;YAC7B,CAAC;QACL,CAAC;QACD,OAAO,IAAI,uBAAU,EAAE,CAAC;IAC5B,CAAC;IAED;;;;;;;OAOG;IACgB,sBAAsB,CAAC,WAA6B;QACnE,OAAO,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,WAAW,CAAC,CAAC;IAC1D,CAAC;IAED;;;;OAIG;IACH,iFAAiF;IAC9D,KAAK,CAAC,OAAO,CAC5B,KAAoB,EACpB,UAAmB;IACnB,iFAAiF;IACjF,MAA8B;QAG9B,yFAAyF;QACzF,IAAI,CAAC,6BAAc,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACX,+EAA+E,CAClF,CAAC;QACN,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;QAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACZ,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IAChE,CAAC;IAED;;;;;OAKG;IACH,iFAAiF;IACzE,KAAK,CAAC,UAAU,CACpB,QAA0B,EAC1B,KAAoB,EACpB,UAAmB;IACnB,iFAAiF;IACjF,MAA8B;QAG9B,MAAM,WAAW,GAA2B,EAAE,CAAC;QAC/C,KAAK,MAAM,KAAK,IAAI,6BAAc,CAAC,cAAc,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC;YAC5D,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACrC,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,4BAAgB,CACjC,IAAI,CAAC,YAAY,EAAE,EACnB,KAAK,CAAC,UAAU,EAChB,CAAC,UAAU,CAAC,EACZ,WAAW,CACd,CAAC;QACF,QAAQ,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAEnC,4HAA4H;QAC5H,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;YAChE,QAAQ,CAAC,eAAe,GAAG,QAAQ,CAAC;YACpC,OAAO,QAAQ,CAAC;QACpB,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,mBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,QAAQ,CAAC,eAAe,GAAG,IAAI,yBAAa,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACxE,MAAM,GAAG,CAAC;QACd,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8CG;IACgB,sBAAsB,CAAC,OAA0B,EAAE,MAAc;QAChF,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;YACxB,OAAO,OAAO,CAAC,KAAK,CAAC;QACzB,CAAC;QACD,IAAI,OAAO,CAAC,UAAU,GAAG,GAAG,IAAI,OAAO,CAAC,UAAU,IAAI,GAAG,EAAE,CAAC;YACxD,OAAO,OAAO,CAAC,KAAK,CAAC;QACzB,CAAC;QACD,OAAO,IAAI,kCAAsB,CAC7B,GAAG,MAAM,8BAA8B,OAAO,CAAC,UAAU,8BAA8B;YACnF,2FAA2F;YAC3F,uFAAuF;YACvF,oBAAoB,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,EAC/C,OAAO,CAAC,KAAK,CAChB,CAAC;IACN,CAAC;CACJ,CAAA;AAjTY,0CAAe;0BAAf,eAAe;IAD3B,IAAA,wCAAyB,GAAE;IAMnB,mBAAA,IAAA,kBAAM,EAAC,oCAAqB,CAAC,CAAA;IAE7B,mBAAA,IAAA,kBAAM,EAAC,sBAAO,CAAC,CAAA;IAEf,mBAAA,IAAA,kBAAM,EAAC,iCAAe,CAAC,CAAA;IAGvB,mBAAA,IAAA,oBAAQ,GAAE,CAAA;IAAE,mBAAA,IAAA,kBAAM,EAAC,mBAAO,CAAC,CAAA;IAI3B,mBAAA,IAAA,oBAAQ,GAAE,CAAA;IACV,mBAAA,IAAA,kBAAM,EAAC,+CAAuB,CAAC,CAAA;6CAZyB,oCAAqB;QAEnC,sBAAO;QAES,iCAAe;QAGlB,mBAAO;QAM9B,6CAAqB;GAlBjD,eAAe,CAiT3B;AAED;;;;;;;GAOG;AACH,gGAAgG;AACnF,QAAA,0BAA0B,GAAG,MAAM,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC","sourcesContent":["import { inject, optional } from 'inversify';\nimport { once } from 'node:events';\nimport { request as httpRequest, IncomingMessage } from 'node:http';\nimport { request as httpsRequest } from 'node:https';\nimport { Readable } from 'node:stream';\nimport {\n ClientRegistry,\n DestinationTrust,\n ApiImplementationError,\n RecordedEndpoint,\n RecordedError,\n LogApiCallImpl,\n RouteMetadata,\n Secrets,\n SECRETS,\n TestCaseRecorder,\n toError,\n} from '@webpieces/core-util';\nimport {\n RequestContext,\n RequestContextApiCallContext,\n RequestContextHeaders,\n provideFrameworkTransient,\n} from '@webpieces/core-context';\nimport { GcpOidc } from '@webpieces/gcp-identity';\nimport {\n ApiPrototype,\n ByteReadableStream,\n ClientRequest,\n ClientFilterDefinition,\n ProxyClient,\n TranslatedFailure,\n} from '@webpieces/http-client-core';\nimport { AddressResolver } from './AddressResolver';\nimport { ClientConfig } from './ClientConfig';\nimport { ContextBaseUrlFilter } from './ContextBaseUrlFilter';\nimport { ContextFullUrlFilter } from './ContextFullUrlFilter';\nimport { OutboundAuthFilter } from './OutboundAuthFilter';\nimport { SsrfGuardFilter } from './SsrfGuardFilter';\nimport { SsrfPolicy } from './SsrfPolicy';\nimport { WEBHOOK_SIGNER_CALLBACK, WebhookSignerCallback } from './WebhookSignerCallback';\n\n/**\n * The two framework built-ins' priorities, RELATIVE TO EACH OTHER and to nothing else — they are\n * ordered beneath every app filter structurally, not by number (see `ProxyClient.initRoutes`).\n *\n * The guard is OUTSIDE the minter deliberately: a destination that is going to be refused must be\n * refused BEFORE a credential is minted for it, so a hostile URL never causes a token to exist.\n */\nconst SSRF_GUARD_PRIORITY = 900;\nconst OUTBOUND_AUTH_PRIORITY = 800;\n\n/**\n * The server-side {@link ProxyClient}. Everything a browser cannot do lives here: reading the\n * ambient RequestContext, minting OIDC tokens, holding shared secrets, and recording test cases.\n *\n * TRANSIENT on purpose. Every `createRpcClient(api, config)` needs its own instance, because `init()`\n * binds one instance to exactly one API contract and one target. {@link ProxyClientProvider} hands\n * them out — see its doc.\n */\n@provideFrameworkTransient()\nexport class NodeProxyClient extends ProxyClient {\n private config!: ClientConfig;\n\n constructor(\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- DI-resolved param; the esbuild/vitest path elides type-only imports (no design:paramtypes), so the explicit token is required\n @inject(RequestContextHeaders) private readonly headers: RequestContextHeaders,\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- DI-resolved param; the esbuild/vitest path elides type-only imports (no design:paramtypes), so the explicit token is required\n @inject(GcpOidc) private readonly gcpOidc: GcpOidc,\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- DI-resolved param; the esbuild/vitest path elides type-only imports (no design:paramtypes), so the explicit token is required\n @inject(AddressResolver) private readonly addressResolver: AddressResolver,\n // @optional: only @WpAuthSharedSecret endpoints need it; the client sends its bound value.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- DI-resolved param; the esbuild/vitest path elides type-only imports (no design:paramtypes), so the explicit token is required\n @optional() @inject(SECRETS) private readonly secrets?: Secrets,\n // @optional: only @WpAuthWebhook endpoints need it, and an unbound one makes them THROW\n // rather than deliver unsigned — see WebhookSignerCallback.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- DI-resolved param; the esbuild/vitest path elides type-only imports (no design:paramtypes), so the explicit token is required\n @optional()\n @inject(WEBHOOK_SIGNER_CALLBACK)\n private readonly webhookSigner?: WebhookSignerCallback,\n ) {\n // This package is node-only and already depends on core-context, so it builds the\n // RequestContext-backed ApiCallContext itself. No startup install, and therefore nothing a\n // non-webpieces host (plain NestJS/Express) can forget.\n super(new LogApiCallImpl(new RequestContextApiCallContext()));\n }\n\n /**\n * Bind this client to one API contract + target, with the app's outbound filters (url\n * rewriting, header editing, logging, per-call re-pointing).\n */\n init(\n apiPrototype: ApiPrototype<object>,\n config: ClientConfig,\n appFilters: ClientFilterDefinition[],\n ): void {\n this.config = config;\n this.initRoutes(apiPrototype, appFilters);\n }\n\n /**\n * The same chain every client runs — a ClientRegistry mapping, else the installed deriver — but\n * with NODE's fallback: THROW. A server has no \"own origin\" to fall back to the way a browser\n * does, so an unresolvable peer is a setup bug and must fail loudly (the error names the fixes).\n *\n * Resolved per call, never at construction, so building a client stays synchronous. Any metadata\n * read beneath a deriver is memoized process-wide, so only the first call pays.\n */\n protected override resolveBaseUrl(): Promise<string> {\n return ClientRegistry.resolve(this.config.svcName);\n }\n\n /** Node/undici supports a live ReadableStream body when fetch is given `duplex: 'half'`. */\n protected override supportsConcurrentDuplexFetch(): boolean {\n return true;\n }\n\n /**\n * Node's native HTTP stream is genuinely concurrent: unlike browser Fetch, an early response\n * does not cancel the still-open upload. The returned Web Response keeps parsing isomorphic.\n */\n protected override sendStreamingTransport(\n request: ClientRequest,\n signal: AbortSignal,\n body: ByteReadableStream,\n ): Promise<Response> {\n return new Promise<Response>(\n (resolve: (response: Response) => void, reject: (error: Error) => void) => {\n const url = new URL(request.url);\n const send = url.protocol === 'https:' ? httpsRequest : httpRequest;\n const outgoing = send(\n url,\n {\n method: 'POST',\n headers: request.headersAsRecord(),\n signal,\n },\n (incoming: IncomingMessage): void => {\n const headers = new Headers();\n for (const [name, value] of Object.entries(incoming.headers)) {\n if (Array.isArray(value)) {\n for (const item of value) headers.append(name, item);\n } else if (value !== undefined) {\n headers.set(name, value);\n }\n }\n const responseBody = Readable.toWeb(incoming) as ReadableStream<Uint8Array>;\n resolve(\n new Response(responseBody, {\n status: incoming.statusCode ?? 500,\n statusText: incoming.statusMessage,\n headers,\n }),\n );\n },\n );\n outgoing.once('error', reject);\n // webpieces-disable no-any-unknown -- a native socket/write rejection is normalized by toError\n void this.pumpStreamingRequest(body, outgoing).catch((err: unknown) => {\n const error = toError(err);\n outgoing.destroy(error);\n });\n },\n );\n }\n\n private async pumpStreamingRequest(\n body: ByteReadableStream,\n outgoing: import('node:http').ClientRequest,\n ): Promise<void> {\n const reader = body.getReader();\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- socket errors reject writer acknowledgements\n try {\n for (;;) {\n const part = await reader.read();\n if (part.done) break;\n if (!outgoing.write(part.value)) await once(outgoing, 'drain');\n }\n outgoing.end();\n } finally {\n reader.releaseLock();\n }\n }\n\n /**\n * The two framework built-ins, installed on EVERY client this package builds and ordered\n * beneath every app filter by {@link ProxyClient.initRoutes}.\n *\n * They are unconditional rather than opt-in because neither costs anything on the path that\n * does not need it: the SSRF guard steps aside when nothing re-pointed the request, and the\n * auth filter does nothing for a `@WpAuthPublic` endpoint. An app therefore cannot forget to install\n * the guard on the one client that takes runtime URLs — the ACT of re-pointing is what arms it.\n */\n protected override clientFilters(): ClientFilterDefinition[] {\n return [\n new ClientFilterDefinition(\n SSRF_GUARD_PRIORITY,\n new SsrfGuardFilter(this.ssrfPolicy(), this.addressResolver),\n ),\n new ClientFilterDefinition(\n OUTBOUND_AUTH_PRIORITY,\n new OutboundAuthFilter(this.gcpOidc, this.secrets, this.webhookSigner),\n ),\n ];\n }\n\n /**\n * WHICH policy the guard applies when something does re-point this client.\n *\n * Read off an installed {@link ContextBaseUrlFilter} or {@link ContextFullUrlFilter}, because\n * that filter is where an app says\n * \"this client may be re-pointed\", and the single legitimate relaxation\n * ({@link SsrfTestingPolicy}) belongs at the same construction site as that decision rather\n * than in a second place a reader has to correlate. No such filter — or one built with the\n * default — means {@link SsrfPolicy} (the strict one), so the safe answer is what an app gets by saying\n * nothing.\n */\n private ssrfPolicy(): SsrfPolicy {\n for (const definition of this.appFilters) {\n const filter = definition.filter;\n if (filter instanceof ContextBaseUrlFilter || filter instanceof ContextFullUrlFilter) {\n return filter.ssrfPolicy;\n }\n }\n return new SsrfPolicy();\n }\n\n /**\n * Straight from the RequestContext. Throws when there is no active request scope.\n *\n * `destination` rides through unchanged: this is the ONE client that can legitimately propagate a\n * verified identity, and it does so exactly when the callee will authenticate us (@WpAuthOidc /\n * @WpAuthSharedSecret). Calling a peer's @WpAuthPublic or @WpAuthJwt endpoint now omits `x-user-id` and\n * friends instead of shipping headers that endpoint's AuthFilter is obliged to reject.\n */\n protected override outboundContextHeaders(destination: DestinationTrust): Map<string, string> {\n return this.headers.buildOutboundHeaders(destination);\n }\n\n /**\n * Test-case recording hook (mirror of Java HttpsJsonClientInvokeHandler): if a recorder is\n * travelling in the magic context, capture this outbound call + its result so it becomes a mock\n * in the generated test. Absent a recorder this is exactly the base behavior.\n */\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n protected override async execute(\n route: RouteMetadata,\n requestDto: unknown,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n method: () => Promise<unknown>,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n ): Promise<unknown> {\n // Preserve the context precondition before logging, which also requires an active scope.\n if (!RequestContext.isActive()) {\n throw new Error(\n 'No active RequestContext. Run the client call inside RequestContext.run(...).',\n );\n }\n const recorder = this.headers.findRecorder();\n if (!recorder) {\n return super.execute(route, requestDto, method);\n }\n return this.recordCall(recorder, route, requestDto, method);\n }\n\n /**\n * Execute the call while recording it (args + masked ctx snapshot + result).\n *\n * The snapshot is a FIXTURE field, not a log line, so it is built here rather than handed down\n * from the call path — a logging backend stamps its own fields and never sees this.\n */\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n private async recordCall(\n recorder: TestCaseRecorder,\n route: RouteMetadata,\n requestDto: unknown,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n method: () => Promise<unknown>,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n ): Promise<unknown> {\n const ctxSnapshot: Record<string, string> = {};\n for (const entry of RequestContext.buildLogFields().entries()) {\n ctxSnapshot[entry[0]] = entry[1];\n }\n const recorded = new RecordedEndpoint(\n this.contractName(),\n route.methodName,\n [requestDto],\n ctxSnapshot,\n );\n recorder.addEndpointInfo(recorded);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- capture failure into the recording, then rethrow unchanged\n try {\n const response = await super.execute(route, requestDto, method);\n recorded.successResponse = response;\n return response;\n } catch (err: unknown) {\n const error = toError(err);\n recorded.failureResponse = new RecordedError(error.name, error.message);\n throw err;\n }\n }\n\n /**\n * SERVER-TO-SERVER: a 4xx received from a dependency becomes THIS server's own 500.\n *\n * THE INVARIANT:\n *\n * A status received from a downstream dependency describes OUR request to it. It is never the\n * status we return to OUR caller. The server that answered 404 is correct; the server that\n * asked for a route that does not exist is broken, and must say so as a 500.\n *\n * Every 4xx is a CALLER-side defect on this hop: 404 = wrong path / wrong base URL / a dependency\n * that is not deployed yet, 400 = we sent a malformed request, 401/403 = our service credentials\n * or the callee's caller allow-list are wrong. None of them is an answer for whoever called US, and\n * relaying one lets an internal misconfiguration impersonate a legitimate response. That is not\n * hypothetical: a partner-facing Management API reported an EMPTY store estate for an org with six\n * live storefronts, because its dependency had not been promoted and Express served an HTML 404\n * which arrived here as `ApiNotFoundError` and went straight back out. A 500 would have been\n * loud, correct, and attributable to the one server that actually had the bug — which is the whole\n * point: only ONE server should be paged for this.\n *\n * DELIBERATELY 4xx ONLY. 5xx (502/503/504) already mean \"the dependency is unavailable\", which is\n * honest and useful outward, and 500 is already a 500. `ApiEndUserError` (266, a 2xx code carrying\n * user validation) and `ApiDependencyBackoffError` are not statuses about our request at all. All of\n * them pass through untouched.\n *\n * THE OPT-OUT IS `appRegistered`, not a config key. A thin proxy or gateway that genuinely wants to\n * relay a downstream status as its own registers a `ClientRegistry` error translation for it at\n * startup — one greppable line saying so out loud — and that translation wins here. Only the\n * framework's built-in default mapping gets wrapped. There is no flag, because a flag would make\n * the dangerous choice invisible in the code that suffers from it.\n *\n * The downstream diagnostic is NOT lost: the original error (which for the incident above names the\n * method, the status, the `text/html` content-type and a snippet of the body) is both quoted in the\n * message and kept as `cause`.\n *\n * How much \"Downstream said:\" is worth depends on WHO answered, and both halves are by design:\n * - a NON-webpieces answer (an lb's html 404, a proxy's plain-text 502) is described CLIENT-side by\n * `ResponseBodyReader.describeForeignBody`, so the full diagnostic is ours to quote — this is the\n * mealco incident's exact shape, and it is the case that mattered.\n * - a WEBPIECES peer deliberately sends only the generic reason phrase for its status (see\n * `ApiErrorHttpMapper` in http-server — only `ApiEndUserError`'s message is caller-facing), so this\n * reads \"Downstream said: Not Found\". That is correct and not a regression: the peer's real\n * message is in the PEER's log, correlated by request id, which is the only place it was ever\n * safe to read it.\n *\n * This whole string is an operator-facing message on an `ApiImplementationError`, so when THIS\n * server answers its own caller none of it goes on the wire — it goes to this server's log.\n */\n protected override adaptDownstreamFailure(failure: TranslatedFailure, callId: string): Error {\n if (failure.appRegistered) {\n return failure.error;\n }\n if (failure.statusCode < 400 || failure.statusCode >= 500) {\n return failure.error;\n }\n return new ApiImplementationError(\n `${callId}: dependency answered HTTP ${failure.statusCode}. That status describes OUR ` +\n `request to it, not an answer for our caller, so this server owns it as a 500 — check the ` +\n `path, the base URL, whether the dependency is deployed, and our service credentials. ` +\n `Downstream said: ${failure.error.message}`,\n failure.error,\n );\n }\n}\n\n/**\n * DI token for the `Provider<NodeProxyClient>` that hands out RPC clients — one per API contract.\n * `Provider<T>` is erased at runtime, so it cannot be its own token; this Symbol names T.\n *\n * Because NodeProxyClient is bound TRANSIENT, every `get()` constructs a new one. (Were it bound\n * `@provideFrameworkSingleton`, the very same Provider would instead hand back one lazily-created\n * instance — the provider caches nothing, so the target's scope decides.)\n */\n// webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; the Symbol names T\nexport const NODE_PROXY_CLIENT_PROVIDER = Symbol.for('Provider<NodeProxyClient>');\n"]}
1
+ {"version":3,"file":"NodeProxyClient.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-node/src/NodeProxyClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, optional } from 'inversify';\nimport { once } from 'node:events';\nimport { request as httpRequest, IncomingMessage } from 'node:http';\nimport { request as httpsRequest } from 'node:https';\nimport { Readable } from 'node:stream';\nimport {\n ClientRegistry,\n DestinationTrust,\n ApiImplementationError,\n RecordedEndpoint,\n RecordedError,\n LogApiCallImpl,\n RouteMetadata,\n Secrets,\n SECRETS,\n TestCaseRecorder,\n toError,\n} from '@webpieces/core-util';\nimport {\n RequestContext,\n RequestContextApiCallContext,\n RequestContextHeaders,\n provideFrameworkTransient,\n} from '@webpieces/core-context';\nimport { GcpOidc } from '@webpieces/gcp-identity';\nimport {\n ApiPrototype,\n ByteReadableStream,\n ClientRequest,\n ClientFilterDefinition,\n ProxyClient,\n} from '@webpieces/http-client-core';\nimport { AddressResolver } from './AddressResolver';\nimport { ClientConfig } from './ClientConfig';\nimport { ContextBaseUrlFilter } from './ContextBaseUrlFilter';\nimport { ContextFullUrlFilter } from './ContextFullUrlFilter';\nimport { OutboundAuthFilter } from './OutboundAuthFilter';\nimport { SsrfGuardFilter } from './SsrfGuardFilter';\nimport { SsrfPolicy } from './SsrfPolicy';\nimport { WEBHOOK_SIGNER_CALLBACK, WebhookSignerCallback } from './WebhookSignerCallback';\n\n/**\n * The two framework built-ins' priorities, RELATIVE TO EACH OTHER and to nothing else — they are\n * ordered beneath every app filter structurally, not by number (see `ProxyClient.initRoutes`).\n *\n * The guard is OUTSIDE the minter deliberately: a destination that is going to be refused must be\n * refused BEFORE a credential is minted for it, so a hostile URL never causes a token to exist.\n */\nconst SSRF_GUARD_PRIORITY = 900;\nconst OUTBOUND_AUTH_PRIORITY = 800;\n\n/**\n * The server-side {@link ProxyClient}. Everything a browser cannot do lives here: reading the\n * ambient RequestContext, minting OIDC tokens, holding shared secrets, and recording test cases.\n *\n * TRANSIENT on purpose. Every `createRpcClient(api, config)` needs its own instance, because `init()`\n * binds one instance to exactly one API contract and one target. {@link ProxyClientProvider} hands\n * them out — see its doc.\n */\n@provideFrameworkTransient()\nexport class NodeProxyClient extends ProxyClient {\n private config!: ClientConfig;\n\n constructor(\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- DI-resolved param; the esbuild/vitest path elides type-only imports (no design:paramtypes), so the explicit token is required\n @inject(RequestContextHeaders) private readonly headers: RequestContextHeaders,\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- DI-resolved param; the esbuild/vitest path elides type-only imports (no design:paramtypes), so the explicit token is required\n @inject(GcpOidc) private readonly gcpOidc: GcpOidc,\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- DI-resolved param; the esbuild/vitest path elides type-only imports (no design:paramtypes), so the explicit token is required\n @inject(AddressResolver) private readonly addressResolver: AddressResolver,\n // @optional: only @WpAuthSharedSecret endpoints need it; the client sends its bound value.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- DI-resolved param; the esbuild/vitest path elides type-only imports (no design:paramtypes), so the explicit token is required\n @optional() @inject(SECRETS) private readonly secrets?: Secrets,\n // @optional: only @WpAuthWebhook endpoints need it, and an unbound one makes them THROW\n // rather than deliver unsigned — see WebhookSignerCallback.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- DI-resolved param; the esbuild/vitest path elides type-only imports (no design:paramtypes), so the explicit token is required\n @optional()\n @inject(WEBHOOK_SIGNER_CALLBACK)\n private readonly webhookSigner?: WebhookSignerCallback,\n ) {\n // This package is node-only and already depends on core-context, so it builds the\n // RequestContext-backed ApiCallContext itself. No startup install, and therefore nothing a\n // non-webpieces host (plain NestJS/Express) can forget.\n super(new LogApiCallImpl(new RequestContextApiCallContext()));\n }\n\n /**\n * Bind this client to one API contract + target, with the app's outbound filters (url\n * rewriting, header editing, logging, per-call re-pointing).\n */\n init(\n apiPrototype: ApiPrototype<object>,\n config: ClientConfig,\n appFilters: ClientFilterDefinition[],\n ): void {\n this.config = config;\n this.initRoutes(apiPrototype, appFilters);\n }\n\n /**\n * The same chain every client runs — a ClientRegistry mapping, else the installed deriver — but\n * with NODE's fallback: THROW. A server has no \"own origin\" to fall back to the way a browser\n * does, so an unresolvable peer is a setup bug and must fail loudly (the error names the fixes).\n *\n * Resolved per call, never at construction, so building a client stays synchronous. Any metadata\n * read beneath a deriver is memoized process-wide, so only the first call pays.\n */\n protected override resolveBaseUrl(): Promise<string> {\n return ClientRegistry.resolve(this.config.svcName);\n }\n\n /** Node/undici supports a live ReadableStream body when fetch is given `duplex: 'half'`. */\n protected override supportsConcurrentDuplexFetch(): boolean {\n return true;\n }\n\n /**\n * Node's native HTTP stream is genuinely concurrent: unlike browser Fetch, an early response\n * does not cancel the still-open upload. The returned Web Response keeps parsing isomorphic.\n */\n protected override sendStreamingTransport(\n request: ClientRequest,\n signal: AbortSignal,\n body: ByteReadableStream,\n ): Promise<Response> {\n return new Promise<Response>(\n (resolve: (response: Response) => void, reject: (error: Error) => void) => {\n const url = new URL(request.url);\n const send = url.protocol === 'https:' ? httpsRequest : httpRequest;\n const outgoing = send(\n url,\n {\n method: 'POST',\n headers: request.headersAsRecord(),\n signal,\n },\n (incoming: IncomingMessage): void => {\n const headers = new Headers();\n for (const [name, value] of Object.entries(incoming.headers)) {\n if (Array.isArray(value)) {\n for (const item of value) headers.append(name, item);\n } else if (value !== undefined) {\n headers.set(name, value);\n }\n }\n const responseBody = Readable.toWeb(incoming) as ReadableStream<Uint8Array>;\n resolve(\n new Response(responseBody, {\n status: incoming.statusCode ?? 500,\n statusText: incoming.statusMessage,\n headers,\n }),\n );\n },\n );\n outgoing.once('error', reject);\n // webpieces-disable no-any-unknown -- a native socket/write rejection is normalized by toError\n void this.pumpStreamingRequest(body, outgoing).catch((err: unknown) => {\n const error = toError(err);\n outgoing.destroy(error);\n });\n },\n );\n }\n\n private async pumpStreamingRequest(\n body: ByteReadableStream,\n outgoing: import('node:http').ClientRequest,\n ): Promise<void> {\n const reader = body.getReader();\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- socket errors reject writer acknowledgements\n try {\n for (;;) {\n const part = await reader.read();\n if (part.done) break;\n if (!outgoing.write(part.value)) await once(outgoing, 'drain');\n }\n outgoing.end();\n } finally {\n reader.releaseLock();\n }\n }\n\n /**\n * The two framework built-ins, installed on EVERY client this package builds and ordered\n * beneath every app filter by {@link ProxyClient.initRoutes}.\n *\n * They are unconditional rather than opt-in because neither costs anything on the path that\n * does not need it: the SSRF guard steps aside when nothing re-pointed the request, and the\n * auth filter does nothing for a `@WpAuthPublic` endpoint. An app therefore cannot forget to install\n * the guard on the one client that takes runtime URLs — the ACT of re-pointing is what arms it.\n */\n protected override clientFilters(): ClientFilterDefinition[] {\n return [\n new ClientFilterDefinition(\n SSRF_GUARD_PRIORITY,\n new SsrfGuardFilter(this.ssrfPolicy(), this.addressResolver),\n ),\n new ClientFilterDefinition(\n OUTBOUND_AUTH_PRIORITY,\n new OutboundAuthFilter(this.gcpOidc, this.secrets, this.webhookSigner),\n ),\n ];\n }\n\n /**\n * WHICH policy the guard applies when something does re-point this client.\n *\n * Read off an installed {@link ContextBaseUrlFilter} or {@link ContextFullUrlFilter}, because\n * that filter is where an app says\n * \"this client may be re-pointed\", and the single legitimate relaxation\n * ({@link SsrfTestingPolicy}) belongs at the same construction site as that decision rather\n * than in a second place a reader has to correlate. No such filter — or one built with the\n * default — means {@link SsrfPolicy} (the strict one), so the safe answer is what an app gets by saying\n * nothing.\n */\n private ssrfPolicy(): SsrfPolicy {\n for (const definition of this.appFilters) {\n const filter = definition.filter;\n if (filter instanceof ContextBaseUrlFilter || filter instanceof ContextFullUrlFilter) {\n return filter.ssrfPolicy;\n }\n }\n return new SsrfPolicy();\n }\n\n /**\n * Straight from the RequestContext. Throws when there is no active request scope.\n *\n * `destination` rides through unchanged: this is the ONE client that can legitimately propagate a\n * verified identity, and it does so exactly when the callee will authenticate us (@WpAuthOidc /\n * @WpAuthSharedSecret). Calling a peer's @WpAuthPublic or @WpAuthJwt endpoint now omits `x-user-id` and\n * friends instead of shipping headers that endpoint's AuthFilter is obliged to reject.\n */\n protected override outboundContextHeaders(destination: DestinationTrust): Map<string, string> {\n return this.headers.buildOutboundHeaders(destination);\n }\n\n /**\n * Test-case recording hook (mirror of Java HttpsJsonClientInvokeHandler): if a recorder is\n * travelling in the magic context, capture this outbound call + its result so it becomes a mock\n * in the generated test. Absent a recorder this is exactly the base behavior.\n */\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n protected override async execute(\n route: RouteMetadata,\n requestDto: unknown,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n method: () => Promise<unknown>,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n ): Promise<unknown> {\n // Preserve the context precondition before logging, which also requires an active scope.\n if (!RequestContext.isActive()) {\n throw new Error(\n 'No active RequestContext. Run the client call inside RequestContext.run(...).',\n );\n }\n const recorder = this.headers.findRecorder();\n if (!recorder) {\n return super.execute(route, requestDto, method);\n }\n return this.recordCall(recorder, route, requestDto, method);\n }\n\n /**\n * Execute the call while recording it (args + masked ctx snapshot + result).\n *\n * The snapshot is a FIXTURE field, not a log line, so it is built here rather than handed down\n * from the call path — a logging backend stamps its own fields and never sees this.\n */\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n private async recordCall(\n recorder: TestCaseRecorder,\n route: RouteMetadata,\n requestDto: unknown,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n method: () => Promise<unknown>,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n ): Promise<unknown> {\n const ctxSnapshot: Record<string, string> = {};\n for (const entry of RequestContext.buildLogFields().entries()) {\n ctxSnapshot[entry[0]] = entry[1];\n }\n const recorded = new RecordedEndpoint(\n this.contractName(),\n route.methodName,\n [requestDto],\n ctxSnapshot,\n );\n recorder.addEndpointInfo(recorded);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- capture failure into the recording, then rethrow unchanged\n try {\n const response = await super.execute(route, requestDto, method);\n recorded.successResponse = response;\n return response;\n } catch (err: unknown) {\n const error = toError(err);\n recorded.failureResponse = new RecordedError(error.name, error.message);\n throw err;\n }\n }\n\n}\n\n/**\n * DI token for the `Provider<NodeProxyClient>` that hands out RPC clients — one per API contract.\n * `Provider<T>` is erased at runtime, so it cannot be its own token; this Symbol names T.\n *\n * Because NodeProxyClient is bound TRANSIENT, every `get()` constructs a new one. (Were it bound\n * `@provideFrameworkSingleton`, the very same Provider would instead hand back one lazily-created\n * instance — the provider caches nothing, so the target's scope decides.)\n */\n// webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; the Symbol names T\nexport const NODE_PROXY_CLIENT_PROVIDER = Symbol.for('Provider<NodeProxyClient>');\n"]}
package/src/index.d.ts CHANGED
@@ -37,7 +37,7 @@ export { SsrfRefusedError } from './SsrfRefusedError';
37
37
  export { OutboundAuthFilter } from './OutboundAuthFilter';
38
38
  export { InternalAddressRules } from './InternalAddressRules';
39
39
  export { AddressResolver, DnsAddressResolver } from './AddressResolver';
40
- export { ProxyClient, ClientErrorTranslator, TranslatedFailure } from '@webpieces/http-client-core';
40
+ export { ProxyClient, ClientErrorTranslator } from '@webpieces/http-client-core';
41
41
  export { ClientRequest, ClientFilterDefinition } from '@webpieces/http-client-core';
42
42
  export type { ClientFilter } from '@webpieces/http-client-core';
43
43
  export type { ApiPrototype } from '@webpieces/http-client-core';
package/src/index.js CHANGED
@@ -25,7 +25,7 @@
25
25
  * ```
26
26
  */
27
27
  Object.defineProperty(exports, "__esModule", { value: true });
28
- exports.StreamingCapabilityError = exports.ClientFilterDefinition = exports.ClientRequest = exports.TranslatedFailure = exports.ClientErrorTranslator = exports.ProxyClient = exports.DnsAddressResolver = exports.AddressResolver = exports.InternalAddressRules = exports.OutboundAuthFilter = exports.SsrfRefusedError = exports.SsrfGuardFilter = exports.SsrfTestingPolicy = exports.SsrfPolicy = exports.WEBHOOK_SIGNER_CALLBACK = exports.SignableRequest = exports.WebhookSignerCallback = exports.MissingWebhookSignerError = exports.MissingSharedSecretError = exports.MissingRuntimeBaseUrlError = exports.ContextFullUrlFilter = exports.ContextBaseUrlFilter = exports.ClientConfig = exports.NODE_PROXY_CLIENT_PROVIDER = exports.NodeProxyClient = exports.ClientHttpFactory = void 0;
28
+ exports.StreamingCapabilityError = exports.ClientFilterDefinition = exports.ClientRequest = exports.ClientErrorTranslator = exports.ProxyClient = exports.DnsAddressResolver = exports.AddressResolver = exports.InternalAddressRules = exports.OutboundAuthFilter = exports.SsrfRefusedError = exports.SsrfGuardFilter = exports.SsrfTestingPolicy = exports.SsrfPolicy = exports.WEBHOOK_SIGNER_CALLBACK = exports.SignableRequest = exports.WebhookSignerCallback = exports.MissingWebhookSignerError = exports.MissingSharedSecretError = exports.MissingRuntimeBaseUrlError = exports.ContextFullUrlFilter = exports.ContextBaseUrlFilter = exports.ClientConfig = exports.NODE_PROXY_CLIENT_PROVIDER = exports.NodeProxyClient = exports.ClientHttpFactory = void 0;
29
29
  var ClientHttpFactory_1 = require("./ClientHttpFactory");
30
30
  Object.defineProperty(exports, "ClientHttpFactory", { enumerable: true, get: function () { return ClientHttpFactory_1.ClientHttpFactory; } });
31
31
  var NodeProxyClient_1 = require("./NodeProxyClient");
@@ -75,7 +75,6 @@ Object.defineProperty(exports, "DnsAddressResolver", { enumerable: true, get: fu
75
75
  var http_client_core_1 = require("@webpieces/http-client-core");
76
76
  Object.defineProperty(exports, "ProxyClient", { enumerable: true, get: function () { return http_client_core_1.ProxyClient; } });
77
77
  Object.defineProperty(exports, "ClientErrorTranslator", { enumerable: true, get: function () { return http_client_core_1.ClientErrorTranslator; } });
78
- Object.defineProperty(exports, "TranslatedFailure", { enumerable: true, get: function () { return http_client_core_1.TranslatedFailure; } });
79
78
  var http_client_core_2 = require("@webpieces/http-client-core");
80
79
  Object.defineProperty(exports, "ClientRequest", { enumerable: true, get: function () { return http_client_core_2.ClientRequest; } });
81
80
  Object.defineProperty(exports, "ClientFilterDefinition", { enumerable: true, get: function () { return http_client_core_2.ClientFilterDefinition; } });
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-node/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;;;AAEH,yDAAwD;AAA/C,sHAAA,iBAAiB,OAAA;AAC1B,qDAAgF;AAAvE,kHAAA,eAAe,OAAA;AAAE,6HAAA,0BAA0B,OAAA;AACpD,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,kGAAkG;AAClG,4DAA4D;AAC5D,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,kGAAkG;AAClG,4EAA4E;AAC5E,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,2EAA0E;AAAjE,wIAAA,0BAA0B,OAAA;AAEnC,mGAAmG;AACnG,kGAAkG;AAClG,6DAA6D;AAC7D,2DAA2F;AAAlF,8HAAA,wBAAwB,OAAA;AAAE,+HAAA,yBAAyB,OAAA;AAE5D,qGAAqG;AACrG,qFAAqF;AACrF,iEAIiC;AAH7B,8HAAA,qBAAqB,OAAA;AACrB,wHAAA,eAAe,OAAA;AACf,gIAAA,uBAAuB,OAAA;AAG3B,qGAAqG;AACrG,+FAA+F;AAC/F,2CAA6D;AAApD,wGAAA,UAAU,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AACtC,qDAAoD;AAA3C,kHAAA,eAAe,OAAA;AACxB,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,2DAA0D;AAAjD,wHAAA,kBAAkB,OAAA;AAC3B,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,qDAAwE;AAA/D,kHAAA,eAAe,OAAA;AAAE,qHAAA,kBAAkB,OAAA;AAE5C,uEAAuE;AACvE,gEAAoG;AAA3F,+GAAA,WAAW,OAAA;AAAE,yHAAA,qBAAqB,OAAA;AAAE,qHAAA,iBAAiB,OAAA;AAC9D,gEAAoF;AAA3E,iHAAA,aAAa,OAAA;AAAE,0HAAA,sBAAsB,OAAA;AAG9C,gEAAuE;AAA9D,4HAAA,wBAAwB,OAAA","sourcesContent":["/**\n * @webpieces/http-client-node\n *\n * The SERVER-side HTTP client. Reads an API contract's decorators and generates type-safe HTTP\n * clients from it — the same contract the callee's controller implements.\n *\n * Node-only, so unlike @webpieces/http-client-browser it is fully inversify-wired and reads the\n * magic context straight out of the AsyncLocalStorage-backed RequestContext. There is no\n * ContextReader indirection, because a server has exactly one right answer, and a call made\n * OUTSIDE `RequestContext.run(...)` throws instead of silently dropping the trace.\n *\n * Usage:\n * ```typescript\n * import { ClientHttpFactory, ClientConfig } from '@webpieces/http-client-node';\n *\n * // inject the factory, then one client per contract\n * const server2 = factory.createRpcClient(Server2Api, new ClientConfig('server2'));\n * const response = await server2.fetchValue(req);\n *\n * // a client whose destination arrives per call: ONE filter, and nothing else changes\n * const partner = factory.createRpcClient(PartnerWebhookApi, new ClientConfig('partner-webhooks'), [\n * new ClientFilterDefinition(1000, new ContextFullUrlFilter()),\n * ]);\n * ```\n */\n\nexport { ClientHttpFactory } from './ClientHttpFactory';\nexport { NodeProxyClient, NODE_PROXY_CLIENT_PROVIDER } from './NodeProxyClient';\nexport { ClientConfig } from './ClientConfig';\n\n// Install this on the ONE client whose destination is data. Installing it IS the opt-in; a client\n// without it ignores an ambient OVERRIDE_BASE_URL entirely.\nexport { ContextBaseUrlFilter } from './ContextBaseUrlFilter';\n// The same opt-in for a COMPLETE stored url (host + path + query), sent verbatim with no contract\n// path appended — the usual partner-webhook shape. Reads OVERRIDE_FULL_URL.\nexport { ContextFullUrlFilter } from './ContextFullUrlFilter';\nexport { MissingRuntimeBaseUrlError } from './MissingRuntimeBaseUrlError';\n\n// The two ways outbound auth refuses to send: a binding a contract's auth mode requires is absent.\n// Typed, so a delivery worker tells 'we are misconfigured' from 'the partner registered something\n// hostile' (SsrfRefusedError) without matching message text.\nexport { MissingSharedSecretError, MissingWebhookSignerError } from './OutboundAuthErrors';\n\n// The OUTBOUND half of @WpAuthWebhook(name) — the mirror of http-routing's WebhookAuthCallback. Bind\n// one, or every outbound @WpAuthWebhook call throws rather than delivering unsigned.\nexport {\n WebhookSignerCallback,\n SignableRequest,\n WEBHOOK_SIGNER_CALLBACK,\n} from './WebhookSignerCallback';\n\n// The SSRF policy a re-pointed URL is judged under, and the refusal it produces. Automatic, armed by\n// the ACT of re-pointing; the ONLY way to relax it is naming SsrfTestingPolicy at a call site.\nexport { SsrfPolicy, SsrfTestingPolicy } from './SsrfPolicy';\nexport { SsrfGuardFilter } from './SsrfGuardFilter';\nexport { SsrfRefusedError } from './SsrfRefusedError';\nexport { OutboundAuthFilter } from './OutboundAuthFilter';\nexport { InternalAddressRules } from './InternalAddressRules';\nexport { AddressResolver, DnsAddressResolver } from './AddressResolver';\n\n// The isomorphic engine, re-exported so a server app needs one import.\nexport { ProxyClient, ClientErrorTranslator, TranslatedFailure } from '@webpieces/http-client-core';\nexport { ClientRequest, ClientFilterDefinition } from '@webpieces/http-client-core';\nexport type { ClientFilter } from '@webpieces/http-client-core';\nexport type { ApiPrototype } from '@webpieces/http-client-core';\nexport { StreamingCapabilityError } from '@webpieces/http-client-core';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-node/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;;;AAEH,yDAAwD;AAA/C,sHAAA,iBAAiB,OAAA;AAC1B,qDAAgF;AAAvE,kHAAA,eAAe,OAAA;AAAE,6HAAA,0BAA0B,OAAA;AACpD,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,kGAAkG;AAClG,4DAA4D;AAC5D,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,kGAAkG;AAClG,4EAA4E;AAC5E,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,2EAA0E;AAAjE,wIAAA,0BAA0B,OAAA;AAEnC,mGAAmG;AACnG,kGAAkG;AAClG,6DAA6D;AAC7D,2DAA2F;AAAlF,8HAAA,wBAAwB,OAAA;AAAE,+HAAA,yBAAyB,OAAA;AAE5D,qGAAqG;AACrG,qFAAqF;AACrF,iEAIiC;AAH7B,8HAAA,qBAAqB,OAAA;AACrB,wHAAA,eAAe,OAAA;AACf,gIAAA,uBAAuB,OAAA;AAG3B,qGAAqG;AACrG,+FAA+F;AAC/F,2CAA6D;AAApD,wGAAA,UAAU,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AACtC,qDAAoD;AAA3C,kHAAA,eAAe,OAAA;AACxB,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,2DAA0D;AAAjD,wHAAA,kBAAkB,OAAA;AAC3B,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,qDAAwE;AAA/D,kHAAA,eAAe,OAAA;AAAE,qHAAA,kBAAkB,OAAA;AAE5C,uEAAuE;AACvE,gEAAiF;AAAxE,+GAAA,WAAW,OAAA;AAAE,yHAAA,qBAAqB,OAAA;AAC3C,gEAAoF;AAA3E,iHAAA,aAAa,OAAA;AAAE,0HAAA,sBAAsB,OAAA;AAG9C,gEAAuE;AAA9D,4HAAA,wBAAwB,OAAA","sourcesContent":["/**\n * @webpieces/http-client-node\n *\n * The SERVER-side HTTP client. Reads an API contract's decorators and generates type-safe HTTP\n * clients from it — the same contract the callee's controller implements.\n *\n * Node-only, so unlike @webpieces/http-client-browser it is fully inversify-wired and reads the\n * magic context straight out of the AsyncLocalStorage-backed RequestContext. There is no\n * ContextReader indirection, because a server has exactly one right answer, and a call made\n * OUTSIDE `RequestContext.run(...)` throws instead of silently dropping the trace.\n *\n * Usage:\n * ```typescript\n * import { ClientHttpFactory, ClientConfig } from '@webpieces/http-client-node';\n *\n * // inject the factory, then one client per contract\n * const server2 = factory.createRpcClient(Server2Api, new ClientConfig('server2'));\n * const response = await server2.fetchValue(req);\n *\n * // a client whose destination arrives per call: ONE filter, and nothing else changes\n * const partner = factory.createRpcClient(PartnerWebhookApi, new ClientConfig('partner-webhooks'), [\n * new ClientFilterDefinition(1000, new ContextFullUrlFilter()),\n * ]);\n * ```\n */\n\nexport { ClientHttpFactory } from './ClientHttpFactory';\nexport { NodeProxyClient, NODE_PROXY_CLIENT_PROVIDER } from './NodeProxyClient';\nexport { ClientConfig } from './ClientConfig';\n\n// Install this on the ONE client whose destination is data. Installing it IS the opt-in; a client\n// without it ignores an ambient OVERRIDE_BASE_URL entirely.\nexport { ContextBaseUrlFilter } from './ContextBaseUrlFilter';\n// The same opt-in for a COMPLETE stored url (host + path + query), sent verbatim with no contract\n// path appended — the usual partner-webhook shape. Reads OVERRIDE_FULL_URL.\nexport { ContextFullUrlFilter } from './ContextFullUrlFilter';\nexport { MissingRuntimeBaseUrlError } from './MissingRuntimeBaseUrlError';\n\n// The two ways outbound auth refuses to send: a binding a contract's auth mode requires is absent.\n// Typed, so a delivery worker tells 'we are misconfigured' from 'the partner registered something\n// hostile' (SsrfRefusedError) without matching message text.\nexport { MissingSharedSecretError, MissingWebhookSignerError } from './OutboundAuthErrors';\n\n// The OUTBOUND half of @WpAuthWebhook(name) — the mirror of http-routing's WebhookAuthCallback. Bind\n// one, or every outbound @WpAuthWebhook call throws rather than delivering unsigned.\nexport {\n WebhookSignerCallback,\n SignableRequest,\n WEBHOOK_SIGNER_CALLBACK,\n} from './WebhookSignerCallback';\n\n// The SSRF policy a re-pointed URL is judged under, and the refusal it produces. Automatic, armed by\n// the ACT of re-pointing; the ONLY way to relax it is naming SsrfTestingPolicy at a call site.\nexport { SsrfPolicy, SsrfTestingPolicy } from './SsrfPolicy';\nexport { SsrfGuardFilter } from './SsrfGuardFilter';\nexport { SsrfRefusedError } from './SsrfRefusedError';\nexport { OutboundAuthFilter } from './OutboundAuthFilter';\nexport { InternalAddressRules } from './InternalAddressRules';\nexport { AddressResolver, DnsAddressResolver } from './AddressResolver';\n\n// The isomorphic engine, re-exported so a server app needs one import.\nexport { ProxyClient, ClientErrorTranslator } from '@webpieces/http-client-core';\nexport { ClientRequest, ClientFilterDefinition } from '@webpieces/http-client-core';\nexport type { ClientFilter } from '@webpieces/http-client-core';\nexport type { ApiPrototype } from '@webpieces/http-client-core';\nexport { StreamingCapabilityError } from '@webpieces/http-client-core';\n"]}