@webpieces/http-client-browser 0.4.699 → 0.4.701

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-browser",
3
- "version": "0.4.699",
3
+ "version": "0.4.701",
4
4
  "description": "Browser HTTP client for webpieces: DI-free (React or Angular), app-managed context store, no AsyncLocalStorage",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,7 +22,7 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@webpieces/core-util": "0.4.699",
26
- "@webpieces/http-client-core": "0.4.699"
25
+ "@webpieces/core-util": "0.4.701",
26
+ "@webpieces/http-client-core": "0.4.701"
27
27
  }
28
28
  }
@@ -25,7 +25,9 @@ class BrowserProxyClient extends http_client_core_1.ProxyClient {
25
25
  /** Bind this client to one API contract + base URL. */
26
26
  init(apiPrototype, config) {
27
27
  this.config = config;
28
- this.initRoutes(apiPrototype);
28
+ // The browser factory does not take app filters yet, so this client runs the framework
29
+ // built-ins only (today: none). The chain itself is isomorphic and ready for them.
30
+ this.initRoutes(apiPrototype, []);
29
31
  }
30
32
  /**
31
33
  * The same chain every client runs — a ClientRegistry mapping, else the installed deriver — but
@@ -1 +1 @@
1
- {"version":3,"file":"BrowserProxyClient.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-browser/src/BrowserProxyClient.ts"],"names":[],"mappings":";;;AAAA,oDAA6G;AAC7G,kEAA2G;AAI3G;;;;;;;;;GASG;AACH,MAAa,kBAAmB,SAAQ,8BAAW;IAI1B;IACA;IAJb,MAAM,CAAgB;IAE9B,YACqB,UAAsB,EACtB,iBAA4C;QAE7D,KAAK,EAAE,CAAC;QAHS,eAAU,GAAV,UAAU,CAAY;QACtB,sBAAiB,GAAjB,iBAAiB,CAA2B;IAGjE,CAAC;IAED,uDAAuD;IACvD,IAAI,CAAC,YAAkC,EAAE,MAAoB;QACzD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IAClC,CAAC;IAED;;;;;;;;;;OAUG;IACgB,KAAK,CAAC,cAAc;QACnC,OAAO,CAAC,MAAM,0BAAc,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACxE,CAAC;IAED;;;;;;OAMG;IACgB,sBAAsB,CAAC,WAA6B;QACnE,OAAO,IAAI,CAAC,UAAU,CAAC,oBAAoB,CAAC,WAAW,CAAC,CAAC;IAC7D,CAAC;IAED;;;OAGG;IACgB,cAAc,CAAC,KAAoB;QAClD,IAAI,CAAC,iBAAiB,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;IAEkB,YAAY,CAAC,KAAoB,EAAE,OAAuB;QACzE,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IAED;;;;;;;;;;;OAWG;IACgB,sBAAsB,CAAC,OAA0B,EAAE,OAAe;QACjF,OAAO,OAAO,CAAC,KAAK,CAAC;IACzB,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACgB,uBAAuB,CAAC,QAA8B,EAAE,UAAkB;QACzF,MAAM,IAAI,GAAG,QAAQ,EAAE,IAAI,CAAC;QAC5B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACrB,OAAO;QACX,CAAC;QACD,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YAChB,KAAK,QAAQ,CAAC;YACd,KAAK,KAAK,CAAC;YACX,KAAK,YAAY;gBACb,OAAO;YACX,KAAK,MAAM,CAAC;YACZ,KAAK,eAAe;gBAChB,MAAM,IAAI,KAAK,CACX,YAAY,UAAU,QAAQ,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,kBAAkB,eAAe;oBACnG,mFAAmF;oBACnF,8BAA8B,CACjC,CAAC;YACN,oFAAoF;YACpF,oFAAoF;YACpF,qFAAqF;YACrF,kDAAkD;YAClD,KAAK,SAAS;gBACV,MAAM,IAAI,KAAK,CACX,YAAY,UAAU,qBAAqB,IAAI,CAAC,IAAI,aAAa,IAAI,CAAC,IAAI,gBAAgB;oBAC1F,gBAAgB,IAAI,CAAC,IAAI,yEAAyE;oBAClG,mCAAmC,CACtC,CAAC;YACN,uFAAuF;YACvF,yFAAyF;YACzF,mFAAmF;YACnF,KAAK,QAAQ;gBACT,MAAM,IAAI,KAAK,CACX,YAAY,UAAU,oBAAoB,IAAI,CAAC,MAAM,yCAAyC;oBAC9F,+FAA+F;oBAC/F,+FAA+F,CAClG,CAAC;QACV,CAAC;IACL,CAAC;CACJ;AA5HD,gDA4HC","sourcesContent":["import { AuthMeta, ContextMgr, ClientRegistry, DestinationTrust, RouteMetadata } from '@webpieces/core-util';\nimport { ApiPrototype, ProxyClient, RequestOutcome, TranslatedFailure } from '@webpieces/http-client-core';\nimport { ClientConfig } from './ClientConfig';\nimport { RequestLifecycleListener } from './RequestLifecycleListener';\n\n/**\n * The browser {@link ProxyClient}. Reads context from the app-held store (via {@link ContextMgr}),\n * because a browser has no ambient request scope.\n *\n * It attaches NO outbound credential and does NO recording — both inherit the base's no-ops. A\n * browser cannot mint an OIDC token and must never hold a shared secret; the user's JWT travels as\n * an ordinary transferred context key, set on the store at login.\n *\n * This is the ONLY class in webpieces that names ContextMgr.\n */\nexport class BrowserProxyClient extends ProxyClient {\n private config!: ClientConfig;\n\n constructor(\n private readonly contextMgr: ContextMgr,\n private readonly lifecycleListener?: RequestLifecycleListener,\n ) {\n super();\n }\n\n /** Bind this client to one API contract + base URL. */\n init(apiPrototype: ApiPrototype<object>, config: ClientConfig): void {\n this.config = config;\n this.initRoutes(apiPrototype);\n }\n\n /**\n * The same chain every client runs — a ClientRegistry mapping, else the installed deriver — but\n * with the BROWSER's fallback: `''`, which makes the URL RELATIVE (`/auth/oauth`) and therefore\n * same-origin, by definition. A browser app almost always calls the backend that served it, so\n * that is the default, and an unregistered svcName must NEVER throw the way it used to — a\n * forgotten registration silently killed sign-in, the request never leaving the page.\n *\n * A mapping still wins, which is exactly how an Angular dev server on :4201 reaches its backend\n * on :8201, while the same bundle served BY that backend in prod registers nothing and goes\n * relative. No `window` access, so this stays SSR-safe and testable.\n */\n protected override async resolveBaseUrl(): Promise<string> {\n return (await ClientRegistry.tryResolve(this.config.svcName)) ?? '';\n }\n\n /**\n * `destination` is always the un-verifying kind here — {@link assertEndpointSupported} below\n * refuses to bind an @AuthOidc / @AuthSharedSecret contract, so every browser destination is\n * @AuthJwt or @Public — which means a browser never puts a trusted context key on the wire. It is\n * still threaded rather than short-circuited: the rule lives in ContextMgr, one place, for both\n * environments.\n */\n protected override outboundContextHeaders(destination: DestinationTrust): Map<string, string> {\n return this.contextMgr.buildOutboundHeaders(destination);\n }\n\n /**\n * Forward the call's lifecycle to the app's listener, if one was registered on the factory. The\n * optional chain makes both a no-op when no listener is present — the default browser case.\n */\n protected override onRequestStart(route: RouteMetadata): void {\n this.lifecycleListener?.onRequestStart(route);\n }\n\n protected override onRequestEnd(route: RouteMetadata, outcome: RequestOutcome): void {\n this.lifecycleListener?.onRequestEnd(route, outcome);\n }\n\n /**\n * Rethrow EXACTLY the exception the translator picked, unchanged.\n *\n * This is the browser half of the asymmetry documented on {@link ProxyClient.adaptDownstreamFailure}.\n * Here the client IS the end user's agent and the \"downstream\" is the app's own backend, so a 404\n * really does mean \"that thing does not exist\", a 401 really does mean \"sign in again\", and a 403\n * really does mean \"you may not\". Rewriting any of those to a 500 would delete the only signal the\n * UI has to act on.\n *\n * The server twin (`NodeProxyClient`) does the opposite for exactly the same reason: there the\n * downstream is a dependency, not the user's answer.\n */\n protected override adaptDownstreamFailure(failure: TranslatedFailure, _callId: string): Error {\n return failure.error;\n }\n\n /**\n * Reject a contract this browser cannot satisfy, at bind time rather than on the first call.\n * Both service-to-service modes need credentials only a server has: @AuthOidc needs a runtime\n * service account to mint a token, @AuthSharedSecret needs a secret no browser may ship.\n *\n * @AuthLocalOnly is deliberately NOT rejected: a browser calling a dev-only endpoint on the\n * developer's own server is the motivating case for that mode (shipping browser logs into the\n * server log). It needs no credential — the server refuses it off-local by not having the route.\n *\n * An exhaustive switch with NO `default`, like {@link DestinationTrust.forAuthMode} and\n * `AuthFilter.verifiesCaller`. This was a NEGATIVE allow-list (`kind !== 'oidc' && kind !==\n * 'shared-secret'`), which silently WAVED THROUGH any future AuthMode kind — the browser would\n * have bound a contract it cannot satisfy and failed on the first call instead of at bind time.\n * Adding `local-only` is what surfaced it: the third reader of the union should fail to compile\n * on a NEW kind for the same reason the other two do.\n */\n protected override assertEndpointSupported(authMeta: AuthMeta | undefined, methodName: string): void {\n const mode = authMeta?.mode;\n if (mode === undefined) {\n return;\n }\n switch (mode.kind) {\n case 'public':\n case 'jwt':\n case 'local-only':\n return;\n case 'oidc':\n case 'shared-secret':\n throw new Error(\n `Endpoint ${methodName} is @${mode.kind === 'oidc' ? 'AuthOidc' : 'AuthSharedSecret'} — a browser ` +\n `cannot hold service credentials. Call it server-side with ClientHttpFactory from ` +\n `@webpieces/http-client-node.`,\n );\n // @AuthWebhook is not \"a credential a browser lacks\" — it is an endpoint whose ONLY\n // legitimate caller is the outside vendor that signs the request in its own scheme.\n // NOTHING in this repo can call it, browser or server, so it is refused at bind time\n // rather than failing as a 401 on the first call.\n case 'webhook':\n throw new Error(\n `Endpoint ${methodName} is @AuthWebhook('${mode.name}') — only ${mode.name} can call it, ` +\n `because only ${mode.name} can produce the signature its WebhookAuthCallback verifies. It is not ` +\n `callable from a webpieces client.`,\n );\n // @AuthApiKey is the same shape of refusal for a different reason: the credential is a\n // CUSTOMER-held api key whose header names the app's ApiKeyHook chooses, so no webpieces\n // client knows what to send — and a browser must not ship a customer's key at all.\n case 'apikey':\n throw new Error(\n `Endpoint ${methodName} is @AuthApiKey('${mode.regime}') — its credential is a customer-held ` +\n `api key that a browser must never ship, and the header carrying it is the app's ApiKeyHook's ` +\n `choice, so no webpieces client can build the call. Only the partner holding the key calls it.`,\n );\n }\n }\n}\n"]}
1
+ {"version":3,"file":"BrowserProxyClient.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-browser/src/BrowserProxyClient.ts"],"names":[],"mappings":";;;AAAA,oDAA6G;AAC7G,kEAA2G;AAI3G;;;;;;;;;GASG;AACH,MAAa,kBAAmB,SAAQ,8BAAW;IAI1B;IACA;IAJb,MAAM,CAAgB;IAE9B,YACqB,UAAsB,EACtB,iBAA4C;QAE7D,KAAK,EAAE,CAAC;QAHS,eAAU,GAAV,UAAU,CAAY;QACtB,sBAAiB,GAAjB,iBAAiB,CAA2B;IAGjE,CAAC;IAED,uDAAuD;IACvD,IAAI,CAAC,YAAkC,EAAE,MAAoB;QACzD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,uFAAuF;QACvF,mFAAmF;QACnF,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC;IAED;;;;;;;;;;OAUG;IACgB,KAAK,CAAC,cAAc;QACnC,OAAO,CAAC,MAAM,0BAAc,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACxE,CAAC;IAED;;;;;;OAMG;IACgB,sBAAsB,CAAC,WAA6B;QACnE,OAAO,IAAI,CAAC,UAAU,CAAC,oBAAoB,CAAC,WAAW,CAAC,CAAC;IAC7D,CAAC;IAED;;;OAGG;IACgB,cAAc,CAAC,KAAoB;QAClD,IAAI,CAAC,iBAAiB,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;IAEkB,YAAY,CAAC,KAAoB,EAAE,OAAuB;QACzE,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IAED;;;;;;;;;;;OAWG;IACgB,sBAAsB,CAAC,OAA0B,EAAE,OAAe;QACjF,OAAO,OAAO,CAAC,KAAK,CAAC;IACzB,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACgB,uBAAuB,CAAC,QAA8B,EAAE,UAAkB;QACzF,MAAM,IAAI,GAAG,QAAQ,EAAE,IAAI,CAAC;QAC5B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACrB,OAAO;QACX,CAAC;QACD,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YAChB,KAAK,QAAQ,CAAC;YACd,KAAK,KAAK,CAAC;YACX,KAAK,YAAY;gBACb,OAAO;YACX,KAAK,MAAM,CAAC;YACZ,KAAK,eAAe;gBAChB,MAAM,IAAI,KAAK,CACX,YAAY,UAAU,QAAQ,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,kBAAkB,eAAe;oBACnG,mFAAmF;oBACnF,8BAA8B,CACjC,CAAC;YACN,oFAAoF;YACpF,oFAAoF;YACpF,qFAAqF;YACrF,kDAAkD;YAClD,KAAK,SAAS;gBACV,MAAM,IAAI,KAAK,CACX,YAAY,UAAU,qBAAqB,IAAI,CAAC,IAAI,aAAa,IAAI,CAAC,IAAI,gBAAgB;oBAC1F,gBAAgB,IAAI,CAAC,IAAI,yEAAyE;oBAClG,mCAAmC,CACtC,CAAC;YACN,uFAAuF;YACvF,yFAAyF;YACzF,mFAAmF;YACnF,KAAK,QAAQ;gBACT,MAAM,IAAI,KAAK,CACX,YAAY,UAAU,oBAAoB,IAAI,CAAC,MAAM,yCAAyC;oBAC9F,+FAA+F;oBAC/F,+FAA+F,CAClG,CAAC;QACV,CAAC;IACL,CAAC;CACJ;AA9HD,gDA8HC","sourcesContent":["import { AuthMeta, ContextMgr, ClientRegistry, DestinationTrust, RouteMetadata } from '@webpieces/core-util';\nimport { ApiPrototype, ProxyClient, RequestOutcome, TranslatedFailure } from '@webpieces/http-client-core';\nimport { ClientConfig } from './ClientConfig';\nimport { RequestLifecycleListener } from './RequestLifecycleListener';\n\n/**\n * The browser {@link ProxyClient}. Reads context from the app-held store (via {@link ContextMgr}),\n * because a browser has no ambient request scope.\n *\n * It attaches NO outbound credential and does NO recording — both inherit the base's no-ops. A\n * browser cannot mint an OIDC token and must never hold a shared secret; the user's JWT travels as\n * an ordinary transferred context key, set on the store at login.\n *\n * This is the ONLY class in webpieces that names ContextMgr.\n */\nexport class BrowserProxyClient extends ProxyClient {\n private config!: ClientConfig;\n\n constructor(\n private readonly contextMgr: ContextMgr,\n private readonly lifecycleListener?: RequestLifecycleListener,\n ) {\n super();\n }\n\n /** Bind this client to one API contract + base URL. */\n init(apiPrototype: ApiPrototype<object>, config: ClientConfig): void {\n this.config = config;\n // The browser factory does not take app filters yet, so this client runs the framework\n // built-ins only (today: none). The chain itself is isomorphic and ready for them.\n this.initRoutes(apiPrototype, []);\n }\n\n /**\n * The same chain every client runs — a ClientRegistry mapping, else the installed deriver — but\n * with the BROWSER's fallback: `''`, which makes the URL RELATIVE (`/auth/oauth`) and therefore\n * same-origin, by definition. A browser app almost always calls the backend that served it, so\n * that is the default, and an unregistered svcName must NEVER throw the way it used to — a\n * forgotten registration silently killed sign-in, the request never leaving the page.\n *\n * A mapping still wins, which is exactly how an Angular dev server on :4201 reaches its backend\n * on :8201, while the same bundle served BY that backend in prod registers nothing and goes\n * relative. No `window` access, so this stays SSR-safe and testable.\n */\n protected override async resolveBaseUrl(): Promise<string> {\n return (await ClientRegistry.tryResolve(this.config.svcName)) ?? '';\n }\n\n /**\n * `destination` is always the un-verifying kind here — {@link assertEndpointSupported} below\n * refuses to bind an @AuthOidc / @AuthSharedSecret contract, so every browser destination is\n * @AuthJwt or @Public — which means a browser never puts a trusted context key on the wire. It is\n * still threaded rather than short-circuited: the rule lives in ContextMgr, one place, for both\n * environments.\n */\n protected override outboundContextHeaders(destination: DestinationTrust): Map<string, string> {\n return this.contextMgr.buildOutboundHeaders(destination);\n }\n\n /**\n * Forward the call's lifecycle to the app's listener, if one was registered on the factory. The\n * optional chain makes both a no-op when no listener is present — the default browser case.\n */\n protected override onRequestStart(route: RouteMetadata): void {\n this.lifecycleListener?.onRequestStart(route);\n }\n\n protected override onRequestEnd(route: RouteMetadata, outcome: RequestOutcome): void {\n this.lifecycleListener?.onRequestEnd(route, outcome);\n }\n\n /**\n * Rethrow EXACTLY the exception the translator picked, unchanged.\n *\n * This is the browser half of the asymmetry documented on {@link ProxyClient.adaptDownstreamFailure}.\n * Here the client IS the end user's agent and the \"downstream\" is the app's own backend, so a 404\n * really does mean \"that thing does not exist\", a 401 really does mean \"sign in again\", and a 403\n * really does mean \"you may not\". Rewriting any of those to a 500 would delete the only signal the\n * UI has to act on.\n *\n * The server twin (`NodeProxyClient`) does the opposite for exactly the same reason: there the\n * downstream is a dependency, not the user's answer.\n */\n protected override adaptDownstreamFailure(failure: TranslatedFailure, _callId: string): Error {\n return failure.error;\n }\n\n /**\n * Reject a contract this browser cannot satisfy, at bind time rather than on the first call.\n * Both service-to-service modes need credentials only a server has: @AuthOidc needs a runtime\n * service account to mint a token, @AuthSharedSecret needs a secret no browser may ship.\n *\n * @AuthLocalOnly is deliberately NOT rejected: a browser calling a dev-only endpoint on the\n * developer's own server is the motivating case for that mode (shipping browser logs into the\n * server log). It needs no credential — the server refuses it off-local by not having the route.\n *\n * An exhaustive switch with NO `default`, like {@link DestinationTrust.forAuthMode} and\n * `AuthFilter.verifiesCaller`. This was a NEGATIVE allow-list (`kind !== 'oidc' && kind !==\n * 'shared-secret'`), which silently WAVED THROUGH any future AuthMode kind — the browser would\n * have bound a contract it cannot satisfy and failed on the first call instead of at bind time.\n * Adding `local-only` is what surfaced it: the third reader of the union should fail to compile\n * on a NEW kind for the same reason the other two do.\n */\n protected override assertEndpointSupported(authMeta: AuthMeta | undefined, methodName: string): void {\n const mode = authMeta?.mode;\n if (mode === undefined) {\n return;\n }\n switch (mode.kind) {\n case 'public':\n case 'jwt':\n case 'local-only':\n return;\n case 'oidc':\n case 'shared-secret':\n throw new Error(\n `Endpoint ${methodName} is @${mode.kind === 'oidc' ? 'AuthOidc' : 'AuthSharedSecret'} — a browser ` +\n `cannot hold service credentials. Call it server-side with ClientHttpFactory from ` +\n `@webpieces/http-client-node.`,\n );\n // @AuthWebhook is not \"a credential a browser lacks\" — it is an endpoint whose ONLY\n // legitimate caller is the outside vendor that signs the request in its own scheme.\n // NOTHING in this repo can call it, browser or server, so it is refused at bind time\n // rather than failing as a 401 on the first call.\n case 'webhook':\n throw new Error(\n `Endpoint ${methodName} is @AuthWebhook('${mode.name}') — only ${mode.name} can call it, ` +\n `because only ${mode.name} can produce the signature its WebhookAuthCallback verifies. It is not ` +\n `callable from a webpieces client.`,\n );\n // @AuthApiKey is the same shape of refusal for a different reason: the credential is a\n // CUSTOMER-held api key whose header names the app's ApiKeyHook chooses, so no webpieces\n // client knows what to send — and a browser must not ship a customer's key at all.\n case 'apikey':\n throw new Error(\n `Endpoint ${methodName} is @AuthApiKey('${mode.regime}') — its credential is a customer-held ` +\n `api key that a browser must never ship, and the header carrying it is the app's ApiKeyHook's ` +\n `choice, so no webpieces client can build the call. Only the partner holding the key calls it.`,\n );\n }\n }\n}\n"]}