{"_id":"@blorq/nice-grpc-web","name":"@blorq/nice-grpc-web","dist-tags":{"latest":"3.3.11"},"versions":{"3.3.11":{"name":"@blorq/nice-grpc-web","version":"3.3.11","description":"A Browser gRPC library that is nice to you","keywords":["grpc","grpc-web","promise","async-iterable","abort-controller","abort-signal","typescript"],"repository":{"type":"git","url":"git+https://github.com/blorq/nice-grpc.git"},"main":"lib/index.js","typings":"lib/index.d.ts","scripts":{"clean":"rimraf lib","test":"NODE_TLS_REJECT_UNAUTHORIZED=0 tsx ./run-specs-mocha.ts","test:local-browser":"wdio run ./wdio.conf.ts","test:local-browser-headless":"USE_HEADLESS_BROWSER=true wdio run ./wdio.conf.ts","test:browserstack":"USE_BROWSERSTACK=true wdio run ./wdio.conf.ts","build":"tsc -P tsconfig.build.json && cpr -f '\\.(ts|tsx|snap)$' src lib","prepublishOnly":"npm run clean && npm run build && npm test","prepare:grpcwebproxy":"path-exists grpcwebproxy || node scripts/download-grpcwebproxy.js","prepare:proto:grpc-web":"mkdirp ./fixtures/grpc-web && grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --js_out=import_style=commonjs,binary:./fixtures/grpc-web --ts_out=service=grpc-web:./fixtures/grpc-web -I fixtures fixtures/*.proto","prepare:proto:ts-proto":"mkdirp ./fixtures/ts-proto && grpc_tools_node_protoc --ts_proto_out=./fixtures/ts-proto --ts_proto_opt=outputServices=nice-grpc,outputServices=generic-definitions,outputJsonMethods=false,useExactTypes=false,esModuleInterop=true -I fixtures fixtures/*.proto","prepare:proto":"npm run prepare:proto:grpc-web && npm run prepare:proto:ts-proto","prepare":"npm run prepare:grpcwebproxy && npm run prepare:proto"},"author":{"name":"Daniel Lytkin","email":"aikoven@deeplay.io"},"license":"MIT","devDependencies":{"@improbable-eng/grpc-web":"^0.15.0","@tsconfig/recommended":"^1.0.13","@types/get-port":"^4.2.0","@types/google-protobuf":"^3.15.2","@types/tcp-port-used":"^1.0.0","@types/tmp":"^0.2.3","@types/ws":"^8.5.14","@wdio/browser-runner":"~8.39.1","@wdio/browserstack-service":"^9.2.6","@wdio/cli":"^9.21.1","@wdio/mocha-framework":"^9.0.8","@wdio/spec-reporter":"^9.0.7","assert-never":"^1.2.1","chromedriver":"^136.0.0","cpr":"^3.0.1","detect-browser":"^5.3.0","expect":"^30.3.0","get-port":"^5.1.1","glob":"^13.0.6","google-protobuf":"^3.17.3","grpc-tools":"^1.13.1","just-cartesian-product":"^4.2.0","mkdirp":"^3.0.1","mocha":"^11.7.5","path-exists-cli":"^2.0.0","request":"^2.88.2","selfsigned":"^2.1.1","string-env-interpolation":"^1.0.1","tcp-port-used":"^1.0.2","testcontainers":"^11.9.0","ts-proto":"^2.5.1","ts-protoc-gen":"^0.15.0","tsx":"^4.19.0","unzipper":"^0.12.1","ws":"^8.18.1"},"dependencies":{"abort-controller-x":"^0.5.0","isomorphic-ws":"^5.0.0","js-base64":"^3.7.2","nice-grpc-common":"^2.0.3"},"gitHead":"2d99637c0f24443d95be36295577dd3e294bfe11","_id":"@blorq/nice-grpc-web@3.3.11","bugs":{"url":"https://github.com/blorq/nice-grpc/issues"},"homepage":"https://github.com/blorq/nice-grpc#readme","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-5cMkzx/9ydSkHcLLPwLlF4CM3pW/KlfqY28n7Qzm9BCj3sY+m77TItkoznfJlhmtK/et8KRIt2uHeZIrVDDGuA==","shasum":"c1167e9b68baf47cdc1eb66da5fccc356337ea4d","tarball":"https://registry.npmjs.org/@blorq/nice-grpc-web/-/nice-grpc-web-3.3.11.tgz","fileCount":117,"unpackedSize":178767,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIGf9+1vkWBUllbVSOJQ0D/Izg2j+o8PnwxlSLSuig+bLAiEAuWdaA7F1kIm2scQJo602Nh1j51mwO7RHipjzZN/1gec="}]},"_npmUser":{"name":"crackcomm","email":"crackcomm@gmail.com"},"directories":{},"maintainers":[{"name":"crackcomm","email":"crackcomm@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nice-grpc-web_3.3.11_1785449191249_0.8744575182401932"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-30T22:06:31.029Z","3.3.11":"2026-07-30T22:06:31.411Z","modified":"2026-07-30T22:06:31.677Z"},"maintainers":[{"name":"crackcomm","email":"crackcomm@gmail.com"}],"description":"A Browser gRPC library that is nice to you","homepage":"https://github.com/blorq/nice-grpc#readme","keywords":["grpc","grpc-web","promise","async-iterable","abort-controller","abort-signal","typescript"],"repository":{"type":"git","url":"git+https://github.com/blorq/nice-grpc.git"},"author":{"name":"Daniel Lytkin","email":"aikoven@deeplay.io"},"bugs":{"url":"https://github.com/blorq/nice-grpc/issues"},"license":"MIT","readme":"# nice-grpc-web [![npm version][npm-image]][npm-url] <!-- omit in toc -->\n\nA Browser gRPC client library that is nice to you.\n\n- [Features](#features)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Compiling Protobuf files](#compiling-protobuf-files)\n    - [Using `ts-proto`](#using-ts-proto)\n    - [Using `google-protobuf`](#using-google-protobuf)\n  - [Preparing the server](#preparing-the-server)\n  - [Client](#client)\n    - [Call options](#call-options)\n    - [Channels](#channels)\n    - [Metadata](#metadata)\n    - [Errors](#errors)\n    - [Cancelling calls](#cancelling-calls)\n    - [Server streaming](#server-streaming)\n    - [Client streaming](#client-streaming)\n    - [Middleware](#middleware)\n      - [Example: Logging](#example-logging)\n- [Compatibility](#compatibility)\n\n## Features\n\n- Written in TypeScript for TypeScript.\n- Modern API that uses Promises and Async Iterables for streaming.\n- Easy cancellation propagation with\n  [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal).\n- Middleware support via concise API that uses Async Generators.\n\n## Installation\n\n```\nnpm install nice-grpc-web\n```\n\n## Usage\n\n### Compiling Protobuf files\n\nThe recommended way is to use\n[`ts-proto`](https://github.com/stephenh/ts-proto).\n\n#### Using `ts-proto`\n\nInstall necessary tools:\n\n```\nnpm install protobufjs long\nnpm install --save-dev grpc-tools ts-proto\n```\n\n> Use `ts-proto` version not older than `1.112.0`.\n\nGiven a Protobuf file `./proto/example.proto`, generate TypeScript code into\ndirectory `./compiled_proto`:\n\n```\n./node_modules/.bin/grpc_tools_node_protoc \\\n  --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \\\n  --ts_proto_out=./compiled_proto \\\n  --ts_proto_opt=env=browser,outputServices=nice-grpc,outputServices=generic-definitions,outputJsonMethods=false,useExactTypes=false \\\n  --proto_path=./proto \\\n  ./proto/example.proto\n```\n\n> You can omit the `--plugin` flag if you invoke this command via\n> [npm script](https://docs.npmjs.com/cli/v7/using-npm/scripts).\n\nWhen running on Windows command line, you may need to wrap the `ts_proto_opt`\nvalue with double quotes:\n\n```\n--ts_proto_opt=\"outputServices=nice-grpc,outputServices=generic-definitions,useExactTypes=false\"\n```\n\n#### Using `google-protobuf`\n\nInstall necessary tools:\n\n```\nnpm install google-protobuf\nnpm install --save-dev grpc-tools ts-protoc-gen @types/google-protobuf\n```\n\nGiven a Protobuf file `./proto/example.proto`, generate JS code and TypeScript\ndefinitions into directory `./compiled_proto`:\n\n```\n./node_modules/.bin/grpc_tools_node_protoc \\\n  --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \\\n  --js_out=import_style=commonjs,binary:./compiled_proto \\\n  --ts_out=service=grpc-web:./compiled_proto \\\n  --proto_path=./proto \\\n  ./proto/example.proto\n```\n\n### Preparing the server\n\nBrowsers can't talk directly to a gRPC server and require a specialized proxy.\n\nIt is recommended to use [Envoy proxy](https://www.envoyproxy.io/) with\n[`grpc_web` filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/grpc_web_filter).\nFor an example of how to configure Envoy, see the\n[config that we use in our tests](/packages/nice-grpc-web/test-server/envoy-tls.yaml).\n\nIn Kubernetes, use [Contour ingress controller](https://projectcontour.io/),\nwhich is based on Envoy and has `grpc_web` filter enabled by default.\n\nAnother option is to use [traefik](https://traefik.io/traefik/) with\n[`GrpcWeb` middleware](https://doc.traefik.io/traefik/master/middlewares/http/grpcweb/)\n(available in traefik 3.0.0-beta1).\n\nAnother option is to use\n[improbable-eng grpcwebproxy](https://github.com/improbable-eng/grpc-web/tree/master/go/grpcwebproxy)\nwhich is not recommended unless you require [Websocket transport](#channels).\nEven if you do, we advise you to use\n[`grpcwebproxy` binaries from our fork](https://github.com/aikoven/grpc-web/releases/tag/v0.0.1)\nwhich contain a few fixes.\n\ngRPC-Web is\n[supported natively](https://learn.microsoft.com/en-us/aspnet/core/grpc/grpcweb?view=aspnetcore-7.0)\nby ASP.NET Core.\n\nIn all cases, it is highly recommended to use `http2`, which in turn requires\n`https` in all browsers.\n\n### Client\n\nConsider the following Protobuf definition:\n\n```proto\nsyntax = \"proto3\";\n\npackage nice_grpc.example;\n\nservice ExampleService {\n  rpc ExampleUnaryMethod(ExampleRequest) returns (ExampleResponse) {};\n}\n\nmessage ExampleRequest {\n  // ...\n}\nmessage ExampleResponse {\n  // ...\n}\n```\n\nAfter compiling Protobuf file, we can create the client:\n\nWhen compiling Protobufs using `ts-proto`:\n\n```ts\nimport {createChannel, createClient} from 'nice-grpc-web';\nimport {ExampleServiceDefinition} from './compiled_proto/example';\nimport type {ExampleServiceClient} from './compiled_proto/example';\n\nconst channel = createChannel('http://localhost:8080');\n\nconst client: ExampleServiceClient = createClient(\n  ExampleServiceDefinition,\n  channel,\n);\n```\n\nWhen compiling Protobufs using `google-protobuf`:\n\n```ts\nimport {createChannel, createClient, Client} from 'nice-grpc';\nimport {\n  ExampleService,\n  IExampleService,\n} from './compiled_proto/example_grpc_pb';\n\nconst channel = createChannel('http://localhost:8080');\n\nconst client: Client<IExampleService> = createClient(ExampleService, channel);\n```\n\nFurther examples use `ts-proto`.\n\nCall the method:\n\n```ts\nconst response = await client.exampleUnaryMethod(request);\n```\n\nWith `ts-proto`, request is automatically wrapped with `fromPartial`.\n\n#### Call options\n\nEach client method accepts `CallOptions` as an optional second argument, that\nhas type:\n\n```ts\ntype CallOptions = {\n  /**\n   * Request metadata.\n   */\n  metadata?: Metadata;\n  /**\n   * Signal that cancels the call once aborted.\n   */\n  signal?: AbortSignal;\n  /**\n   * Called when header is received.\n   */\n  onHeader?(header: Metadata): void;\n  /**\n   * Called when trailer is received.\n   */\n  onTrailer?(trailer: Metadata): void;\n};\n```\n\nCall options may be augmented by [Middleware](#middleware).\n\nWhen creating a client, you may specify default call options per method, or for\nall methods. This doesn't make much sense for built-in options, but may do for\nmiddleware, for example,\n[nice-grpc-client-middleware-deadline](/packages/nice-grpc-client-middleware-deadline):\n\n```ts\nconst client = createClient(ExampleServiceDefinition, channel, {\n  '*': {\n    // applies for all methods\n    deadline: 30_000,\n  },\n  exampleUnaryMethod: {\n    // applies for single method\n    deadline: 10_000,\n  },\n});\n```\n\nTo add default metadata, instead use a middleware that merges it with the\nmetadata passed to the call:\n\n```ts\nconst token = '...';\n\nconst client = createClientFactory().use((call, options) =>\n  call.next(call.request, {\n    ...options,\n    metadata: Metadata(options.metadata).set(\n      'Authorization',\n      `Bearer ${token}`,\n    ),\n  }),\n);\n```\n\n#### Channels\n\nA channel is constructed from an address and optional transport. The following\nare equivalent:\n\n```ts\nimport {createChannel, FetchTransport} from 'nice-grpc-web';\n\ncreateChannel('https://example.com:8080');\ncreateChannel('https://example.com:8080', FetchTransport());\n```\n\nIf the port is omitted, it defaults to `80` for `http`, and `443` for `https`.\n\nA non-standard `WebsocketTransport` is also available, that only works with\n[improbable-eng grpcwebproxy](https://github.com/improbable-eng/grpc-web/tree/master/go/grpcwebproxy)\nand allows to overcome some limitations (see [Compatibility](#compatibility)).\nIt is still recommended to use `FetchTransport` whenever possible.\n\nTo support older NodeJS versions, we also provide `NodeHttpTransport` which is\nbased on `http` and `https` modules (see [Compatibility](#compatibility)).\n\n#### Metadata\n\nClient can send request metadata and receive response header and trailer:\n\n```ts\nimport {Metadata} from 'nice-grpc-web';\n\nconst response = await client.exampleUnaryMethod(request, {\n  metadata: Metadata({key: 'value'}),\n  onHeader(header: Metadata) {\n    // ...\n  },\n  onTrailer(trailer: Metadata) {\n    // ...\n  },\n});\n```\n\n> **Note** Most `fetch` implementations only receive response header when the\n> first chunk of the response body is received. This means that `onHeader` will\n> be called just before the response (or the first response message in case of\n> server streaming) is received, even if the server sends the header before\n> sending the response.\n\n#### Errors\n\nClient calls may throw gRPC errors represented as `ClientError`, that contain\nstatus code and description.\n\n```ts\nimport {ClientError, Status} from 'nice-grpc-web';\nimport {ExampleResponse} from './compiled_proto/example';\n\nlet response: ExampleResponse | null;\n\ntry {\n  response = await client.exampleUnaryMethod(request);\n} catch (error: unknown) {\n  if (error instanceof ClientError && error.code === Status.NOT_FOUND) {\n    response = null;\n  } else {\n    throw error;\n  }\n}\n```\n\n#### Cancelling calls\n\nA client call can be cancelled using\n[`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal).\n\n```ts\nimport {isAbortError} from 'abort-controller-x';\n\nconst abortController = new AbortController();\n\nclient\n  .exampleUnaryMethod(request, {\n    signal: abortController.signal,\n  })\n  .catch(error => {\n    if (isAbortError(error)) {\n      // aborted\n    } else {\n      throw error;\n    }\n  });\n\nabortController.abort();\n```\n\n#### Server streaming\n\nConsider the following Protobuf definition:\n\n```proto\nservice ExampleService {\n  rpc ExampleStreamingMethod(ExampleRequest)\n    returns (stream ExampleResponse) {};\n}\n```\n\nClient method returns an Async Iterable:\n\n```ts\nfor await (const response of client.exampleStreamingMethod(request)) {\n  // ...\n}\n```\n\n#### Client streaming\n\n> **Note** Most browsers don't support streaming request bodies. See\n> [Compatibility](#compatibility) for more details.\n\nGiven a client streaming method:\n\n```proto\nservice ExampleService {\n  rpc ExampleClientStreamingMethod(stream ExampleRequest)\n    returns (ExampleResponse) {};\n}\n```\n\nClient method expects an Async Iterable as its first argument:\n\n```ts\nimport {ExampleRequest, DeepPartial} from './compiled_proto/example';\n\nasync function* createRequest(): AsyncIterable<DeepPartial<ExampleRequest>> {\n  for (let i = 0; i < 10; i++) {\n    yield request;\n  }\n}\n\nconst response = await client.exampleClientStreamingMethod(createRequest());\n```\n\n#### Middleware\n\nClient middleware intercepts outgoing calls allowing to:\n\n- Execute any logic before and after reaching server\n- Modify request metadata\n- Look into request, response and response metadata\n- Send call multiple times for retries or hedging\n- Augment call options type to have own configuration\n\nClient middleware is defined as an Async Generator. The most basic no-op\nmiddleware looks like this:\n\n```ts\nimport {ClientMiddlewareCall, CallOptions} from 'nice-grpc-web';\n\nasync function* middleware<Request, Response>(\n  call: ClientMiddlewareCall<Request, Response>,\n  options: CallOptions,\n) {\n  return yield* call.next(call.request, options);\n}\n```\n\nFor unary and client streaming methods, the `call.next` generator yields no\nitems and returns a single response; for server streaming and bidirectional\nstreaming methods, it yields each response and returns void. By doing\n`return yield*` we cover both cases. To handle these cases separately, we can\nwrite a middleware as follows:\n\n```ts\nasync function* middleware<Request, Response>(\n  call: ClientMiddlewareCall<Request, Response>,\n  options: CallOptions,\n) {\n  if (!call.responseStream) {\n    const response = yield* call.next(call.request, options);\n\n    return response;\n  } else {\n    for await (const response of call.next(call.request, options)) {\n      yield response;\n    }\n\n    return;\n  }\n}\n```\n\nTo create a client with middleware, use a client factory:\n\n```ts\nimport {createClientFactory} from 'nice-grpc-web';\n\nconst client = createClientFactory()\n  .use(middleware1)\n  .use(middleware2)\n  .create(ExampleService, channel);\n```\n\nA middleware that is attached first, will be invoked last.\n\nYou can reuse a single factory to create multiple clients:\n\n```ts\nconst clientFactory = createClientFactory().use(middleware);\n\nconst client1 = clientFactory.create(Service1, channel1);\nconst client2 = clientFactory.create(Service2, channel2);\n```\n\nYou can also attach middleware per-client:\n\n```ts\nconst factory = createClientFactory().use(middlewareA);\n\nconst client1 = clientFactory.use(middlewareB).create(Service1, channel1);\nconst client2 = clientFactory.use(middlewareC).create(Service2, channel2);\n```\n\nIn the above example, `Service1` client gets `middlewareA` and `middlewareB`,\nand `Service2` client gets `middlewareA` and `middlewareC`.\n\n##### Example: Logging\n\nLog all calls:\n\n```ts\nimport {\n  ClientMiddlewareCall,\n  CallOptions,\n  ClientError,\n  Status,\n} from 'nice-grpc-web';\nimport {isAbortError} from 'abort-controller-x';\n\nasync function* loggingMiddleware<Request, Response>(\n  call: ClientMiddlewareCall<Request, Response>,\n  options: CallOptions,\n) {\n  const {path} = call.method;\n\n  console.log('Client call', path, 'start');\n\n  try {\n    const result = yield* call.next(call.request, options);\n\n    console.log('Client call', path, 'end: OK');\n\n    return result;\n  } catch (error) {\n    if (error instanceof ClientError) {\n      console.log(\n        'Client call',\n        path,\n        `end: ${Status[error.code]}: ${error.details}`,\n      );\n    } else if (isAbortError(error)) {\n      console.log('Client call', path, 'cancel');\n    } else {\n      console.log('Client call', path, `error: ${error?.stack}`);\n    }\n\n    throw error;\n  }\n}\n```\n\n## Compatibility\n\nThis library was tested against:\n\n- Chrome 71+\n- Firefox 73+\n- Safari 12.1+\n- Android 6+\n- iOS 10.3+\n- NodeJS 16.15+\n\nIt might work in older browsers as well.\n\nThe library's default `FetchTransport` requires\n[`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) to be\navailable globally and support for reading a `ReadableStream` from a `Response`\nbody. See [compatibility table](https://caniuse.com/mdn-api_response_body).\nThere is no polyfill for this, so this requirement defines the minimum browser\nversions. That said, the [Websocket transport with `grpcwebproxy`](#channels)\nshould work in even older browsers.\n\nGlobal\n[`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)\nis required. A [polyfill](https://www.npmjs.com/package/abort-controller) is\navailable.\n\nThis library works in NodeJS 18+ out of the box. It can also be used in NodeJS\n16.15 with the `--experimental-fetch` flag; also client streams require global\n`ReadableStream` constructor which can be added manually:\n\n```ts\nglobal.ReadableStream ??= require('stream/web').ReadableStream;\n```\n\nIt does **not** work with `node-fetch`, because it does not support\n`ReadableStream` in `Response` body.\n\nFor older NodeJS versions we provide `NodeHttpTransport` which is based on\n`http` and `https` modules.\n\nMost browsers do not support sending streams in `fetch` requests. This means\nthat [client streaming](#client-streaming) and bidirectional streaming will not\nwork. The only browser that supports client streams is Chrome 105+ (and other\nChromium-based browsers, see\n[compatibility table](https://caniuse.com/mdn-api_request_request_request_body_readablestream)),\nand only over `http2`, which in turn requires `https`. Client streams work in\nNodeJS native `fetch` implementation as well. Note, however, that `fetch`\nstreams are currently\n[half-duplex](https://github.com/whatwg/fetch/issues/1254), which means that any\nresponse data will be buffered until the request stream is sent until the end.\nThis unfortunately makes it impossible to use infinite bidirectional streaming.\nTo overcome this limitation, it is recommended to design your API to use only\nunary and server streaming methods. If you still need to use client streams in\nthe browser, you can use a [Websocket transport with `grpcwebproxy`](#channels).\n\nBrowser compatibility is tested with help of\n[BrowserStack](https://www.browserstack.com/).\n\n[npm-image]: https://badge.fury.io/js/nice-grpc-web.svg\n[npm-url]: https://badge.fury.io/js/nice-grpc-web\n","readmeFilename":"README.md","_rev":"1-1d747ca97cf3e08af7959e322e257238"}