nggorpc
v1.3.0
Published
Angular client library for NgGoRPC, enabling high-performance, bidirectional gRPC over WebSockets.
Readme
NgGoRPC Client
Angular client library for NgGoRPC, enabling high-performance, bidirectional gRPC over WebSockets.
Overview
This library provides the client-side implementation of the NgGoRPC protocol. It handles:
- WebSocket connection management (reconnection, keep-alive)
- Protocol framing (serialization/deserialization)
- Stream multiplexing
- Integration with
ts-protogenerated code
Installation
This library is part of the NgGoRPC workspace. To use it in your Angular application within the workspace:
import { NgGoRpcClient } from '@nggorpc/client';Usage
1. Configuration
The client can be configured with NgGoRpcConfig:
export interface NgGoRpcConfig {
/** Keep-alive ping interval in milliseconds (default: 30000) */
pingInterval?: number;
/** Base delay for reconnection backoff in milliseconds (default: 1000) */
baseReconnectDelay?: number;
/** Maximum delay for reconnection backoff in milliseconds (default: 30000) */
maxReconnectDelay?: number;
/** Maximum frame size in bytes (default: 4194304 = 4MB) */
maxFrameSize?: number;
/** Enable debug logging (default: false) */
enableLogging?: boolean;
/** Returns the WebSocket subprotocols to offer, or null to offer none. */
subprotocols?: () => readonly string[] | null;
}subprotocols is a function, not an array, because it is evaluated on every connection
attempt — reconnects included. That is what lets a caller offer a rotating credential in the
subprotocol list (the bearer-token-over-WebSocket pattern: a constant the server selects back,
plus a short-lived session id it reads out of the offer). Returning null — or omitting the
option, which is what almost every consumer does — offers nothing and leaves the connection
exactly as it was before this option existed.
If the callback offers subprotocols and the server selects none of them, a conforming host
fails the handshake itself and the socket never opens — measured 2026-09-05: Chromium 152
("Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received") and ws 8.21.3
("Server sent no subprotocol"). NgGoRPC retries that with the configured backoff, because from
the page it is indistinguishable from an unreachable server or a rejected credential; it logs
handshake failed while offering N subprotocol(s) per attempt (the count, never the values).
So deploy the server side first: a client that offers before the server understands the
subprotocol does not fall back to anonymous, it fails to connect.
Should a socket nevertheless open with a selection that was not offered — impossible in a
conforming host, possible in a runtime that skips that step — NgGoRPC fails the connection
permanently instead of handing back an unauthenticated socket: connectionState$ goes
Disconnected (never Reconnecting), queued requests fail UNAVAILABLE, and the socket is
closed with code 4001.
negotiatedSubprotocol() returns the server's selection while a socket is open, '' when it
selected nothing, and null when no socket is open. The library enforces only that the selection
is one of the offered values; if it matters that the server echoed the constant and not the
credential, assert that with this accessor — the client is the only party that can see it.
The offered values are never logged, at any enableLogging setting.
2. Initialization
The NgGoRpcClient is designed to be instantiated manually (e.g., in a service or component) rather than provided as a global singleton service, giving you control over the connection lifecycle.
import { Injectable, NgZone, inject } from '@angular/core';
import { NgGoRpcClient } from '@nggorpc/client';
@Injectable({ providedIn: 'root' })
export class GrpcService {
private ngZone = inject(NgZone);
public client: NgGoRpcClient;
constructor() {
this.client = new NgGoRpcClient(this.ngZone, {
enableLogging: true,
pingInterval: 15000
});
// Connect to backend
this.client.connect('ws://localhost:8080/rpc');
}
}3. Making Requests
Use the client to create a transport for ts-proto generated services.
import { Component, inject } from '@angular/core';
import { MyServiceDefinition } from './generated/myservice'; // Generated by ts-proto
import { GrpcService } from './grpc.service';
@Component({ ... })
export class UserComponent {
private grpcService = inject(GrpcService);
private transport = this.grpcService.client.createTransport();
ngOnInit() {
// Unary call
this.transport.request(
MyServiceDefinition,
MyServiceDefinition.methods.getUser,
{ userId: '123' }
).subscribe(response => {
console.log('User:', response);
});
// Server-streaming call
this.transport.request(
MyServiceDefinition,
MyServiceDefinition.methods.streamEvents,
{ topic: 'updates' }
).subscribe({
next: event => console.log('Event:', event),
error: err => console.error('Error:', err),
complete: () => console.log('Stream closed')
});
// Client-streaming call: every message is sent as its own DATA frame and the
// last one carries EOS (the half-close the server's RecvMsg loop waits for).
// request() REFUSES a client-streaming method rather than sending a single
// frame the server would reject at the application layer.
this.transport.clientStream(
MyServiceDefinition,
MyServiceDefinition.methods.uploadFile,
[{ header: { name: 'a.png' } }, { chunk: firstChunk }, { chunk: secondChunk }]
).subscribe(summary => console.log('Uploaded:', summary));
}
}4. Authentication
You can set an authentication token (e.g., JWT) to be sent with every request:
this.client.setAuthToken('your-jwt-token');The token will be sent in the authorization header as Bearer <token>.
Connection Management
- connect(url, enableReconnection?): Connects to the WebSocket server.
- disconnect(): Closes the connection and stops reconnection attempts.
- isConnected(): Returns the current connection status.
- connectionState$: Observable that emits connection state changes (
Connected,Disconnected,Reconnecting).
Building
To build the library:
ng build client