Skip to content

ZynclaveTech/react-native-grpcx

Repository files navigation

@vync/grpcx

A React Native/Expo module for gRPC with full streaming support (unary, client streaming, server streaming, and bidirectional streaming).

Features

  • All Streaming Types: Unary, Server Streaming, Client Streaming, and Bidirectional Streaming
  • Runtime Proto Loading: No build-time code generation required
  • Dynamic Service Discovery: Automatically discovers services and methods from proto files
  • TypeScript Support: Full type definitions for all APIs
  • Native Performance: Uses native gRPC libraries (gRPC-Java, gRPC-Swift)
  • Connection Management: Automatic channel pooling and reconnection
  • Metadata Support: Custom headers and trailers
  • Error Handling: Proper gRPC status codes mapped to JavaScript errors

Installation

npm install @vync/grpcx
# or
yarn add @vync/grpcx

iOS Setup

cd ios && pod install

Android Setup

No additional steps required - Gradle dependencies are automatically handled.

Prerequisites

Proto Descriptor Files

This library requires proto descriptor files (.desc) alongside your .proto files. Generate them using protoc:

protoc --descriptor_set_out=service.desc --include_imports service.proto

Include both .proto and .desc files in your app bundle.

Quick Start

import { GrpcClient } from '@vync/grpcx';

// Create a client
const client = new GrpcClient({
  host: 'api.example.com',
  port: 50051,
  protoPath: '/path/to/service.proto',
  useTls: true,
});

// Load the proto file
await client.load();

// Make a unary call
const response = await client.call(
  'mypackage.MyService/MyMethod',
  { field1: 'value1', field2: 42 }
);

console.log(response);

// Clean up
await client.close();

Usage Examples

Unary Calls

Single request, single response:

const response = await client.call(
  'mypackage.MyService/MyMethod',
  { field1: 'value1', field2: 42 },
  {
    metadata: { 'authorization': 'Bearer token' },
    timeout: 5000,
  }
);

console.log(response);

Server Streaming

Single request, stream of responses:

const stream = client.serverStream(
  'mypackage.MyService/StreamMethod',
  { query: 'search term' }
);

stream.on('data', (data) => {
  console.log('Received:', data);
});

stream.on('error', (error) => {
  console.error('Stream error:', error);
});

stream.on('end', () => {
  console.log('Stream completed');
});

// Cancel the stream if needed
// await stream.cancel();

Client Streaming

Stream of requests, single response:

const stream = client.clientStream('mypackage.MyService/UploadMethod');

// Listen for the final response
stream.on('data', (response) => {
  console.log('Final response:', response);
});

stream.on('error', (error) => {
  console.error('Stream error:', error);
});

stream.on('end', () => {
  console.log('Stream completed');
});

// Send multiple messages
await stream.write({ chunk: 1, data: '...' });
await stream.write({ chunk: 2, data: '...' });
await stream.write({ chunk: 3, data: '...' });

// Close the stream (triggers server to send final response)
await stream.end();

Bidirectional Streaming

Stream of requests and responses:

const stream = client.bidiStream('mypackage.MyService/ChatMethod');

// Listen for responses
stream.on('data', (message) => {
  console.log('Received:', message);
});

stream.on('error', (error) => {
  console.error('Stream error:', error);
});

stream.on('end', () => {
  console.log('Stream ended');
});

// Send messages
await stream.write({ message: 'Hello' });
await stream.write({ message: 'World' });

// Close when done
await stream.end();

Service Discovery

// Get all available services
const services = client.getServices();
console.log(services);

// Get a specific service
const service = client.getService('mypackage.MyService');
console.log(service.methods);

// Get a specific method
const method = client.getMethod('mypackage.MyService/MyMethod');
console.log(method.type); // UNARY, SERVER_STREAMING, etc.

API Reference

GrpcClient

Constructor

new GrpcClient(config: GrpcClientConfig)

Config Options:

Option Type Default Description
host string required Server hostname
port number 443 Server port
protoPath string required Path to .proto file in app bundle
useTls boolean true Use TLS/SSL
timeout number 30000 Default timeout in milliseconds
maxRetries number 3 Max retry attempts
metadata object {} Default metadata for all calls

Methods

load(): Promise<void>

Load and parse the proto file. Must be called before making any RPC calls.

call<TRequest, TResponse>(method: string, request: TRequest, options?: CallOptions): Promise<TResponse>

Make a unary call.

Parameters:

  • method - Full method name (e.g., 'package.Service/Method')
  • request - Request object
  • options - Optional call options (metadata, timeout)

Returns: Promise resolving to response object

serverStream<TRequest>(method: string, request: TRequest, options?: CallOptions): ServerStream

Create a server streaming call.

Parameters:

  • method - Full method name
  • request - Request object
  • options - Optional call options

Returns: ServerStream instance

clientStream<TResponse>(method: string, options?: CallOptions): ClientStream

Create a client streaming call.

Parameters:

  • method - Full method name
  • options - Optional call options

Returns: ClientStream instance

bidiStream(method: string, options?: CallOptions): BidiStream

Create a bidirectional streaming call.

Parameters:

  • method - Full method name
  • options - Optional call options

Returns: BidiStream instance

getServices(): GrpcServiceDescriptor[]

Get all available services from the loaded proto.

Returns: Array of service descriptors

getService(serviceName: string): GrpcServiceDescriptor | undefined

Get a specific service descriptor.

Parameters:

  • serviceName - Full service name

Returns: Service descriptor or undefined

getMethod(fullMethodName: string): GrpcMethodDescriptor | undefined

Get a specific method descriptor.

Parameters:

  • fullMethodName - Full method name (e.g., 'package.Service/Method')

Returns: Method descriptor or undefined

close(): Promise<void>

Close the client and cleanup all resources.

GrpcStream

Base class for all stream types. Extends EventEmitter.

Events

  • data: Emitted when data is received
  • error: Emitted on error
  • end: Emitted when stream completes
  • metadata: Emitted when metadata is received
  • status: Emitted with gRPC status

Methods

write(data: any): Promise<void>

Write data to the stream (client/bidi streams only).

end(): Promise<void>

End the stream (half-close).

cancel(): Promise<void>

Cancel the stream.

getId(): string

Get the stream ID.

TypeScript Support

Full TypeScript definitions are included. All types are exported from the main package:

import {
  GrpcClient,
  GrpcClientConfig,
  GrpcMethodType,
  GrpcStatusCode,
  GrpcError,
  ServerStream,
  ClientStream,
  BidiStream,
} from '@vync/grpcx';

Error Handling

Errors follow gRPC status codes:

import { GrpcStatusCode } from '@vync/grpcx';

try {
  const response = await client.call('MyService/MyMethod', request);
} catch (error) {
  if (error.code === GrpcStatusCode.UNAUTHENTICATED) {
    // Handle auth error
  } else if (error.code === GrpcStatusCode.NOT_FOUND) {
    // Handle not found
  }
  console.error(error.message);
}

gRPC Status Codes

Code Name Description
0 OK Success
1 CANCELLED Operation cancelled
2 UNKNOWN Unknown error
3 INVALID_ARGUMENT Invalid argument
4 DEADLINE_EXCEEDED Deadline exceeded
5 NOT_FOUND Not found
7 PERMISSION_DENIED Permission denied
14 UNAVAILABLE Service unavailable
16 UNAUTHENTICATED Unauthenticated

Advanced Usage

Custom Metadata

Send custom headers with any call:

const response = await client.call(
  'Service/Method',
  request,
  {
    metadata: {
      'authorization': 'Bearer token',
      'x-custom-header': 'value'
    }
  }
);

Timeout Configuration

Set custom timeouts per call:

const response = await client.call(
  'Service/Method',
  request,
  {
    timeout: 5000 // 5 seconds
  }
);

Connection Management

The client automatically manages gRPC channels with:

  • Connection pooling
  • Automatic reconnection
  • Keep-alive (30s interval)
  • Proper cleanup on close

File Organization

Place proto files in your app bundle:

app/
  assets/
    protos/
      auth.proto
      auth.desc
      feed.proto
      feed.desc

Reference them in your code:

import * as FileSystem from 'expo-file-system';

const client = new GrpcClient({
  host: 'api.example.com',
  port: 50051,
  protoPath: `${FileSystem.documentDirectory}protos/auth.proto`,
  useTls: true,
});

Complete Example

import { GrpcClient } from '@vync/grpcx';
import * as FileSystem from 'expo-file-system';

async function example() {
  // Create client
  const client = new GrpcClient({
    host: 'grpc.example.com',
    port: 50051,
    protoPath: `${FileSystem.documentDirectory}protos/service.proto`,
    useTls: true,
  });

  try {
    // Load proto
    await client.load();
    console.log('Services:', client.getServices());

    // Unary call
    const user = await client.call('users.UserService/GetUser', { id: 123 });
    console.log('User:', user);

    // Server streaming
    const stream = client.serverStream('users.UserService/StreamUsers', {});

    stream.on('data', (user) => {
      console.log('User:', user);
    });

    stream.on('end', () => {
      console.log('All users received');
    });

    stream.on('error', (error) => {
      console.error('Stream error:', error);
    });

  } catch (error) {
    console.error('Error:', error);
  } finally {
    await client.close();
  }
}

Platform Support

  • iOS: 15.1+
  • Android: API 24+ (Android 7.0+)
  • React Native: 0.70+
  • Expo: SDK 50+

Dependencies

Runtime Dependencies

None! This is a zero-dependency package (peer dependencies only).

Peer Dependencies

  • expo
  • react
  • react-native

Native Dependencies

Android:

  • gRPC-Java 1.65.0
  • Protobuf-Java 3.25.3

iOS:

  • gRPC-Swift 1.23.0
  • SwiftProtobuf 1.27.0

Troubleshooting

Proto files not found

Ensure proto and desc files are in your app bundle and the path is correct:

// Check file exists
const fileInfo = await FileSystem.getInfoAsync(protoPath);
console.log('File exists:', fileInfo.exists);

Connection refused

For development:

  • iOS Simulator: Use localhost
  • Android Emulator: Use 10.0.2.2 instead of localhost
  • Physical devices: Use your computer's IP address
const client = new GrpcClient({
  // For Android Emulator
  host: '10.0.2.2',
  port: 50051,
  useTls: false, // For development
});

Descriptor errors

Ensure you include imports when generating descriptors:

protoc --descriptor_set_out=service.desc --include_imports service.proto

Build errors on iOS

Run pod install:

cd ios && pod install

Build errors on Android

Clean and rebuild:

cd android && ./gradlew clean
cd .. && npx expo run:android

Development

Building from source

# Clone the repository
git clone https://github.com/soorajpandey/grpcx.git
cd grpcx

# Install dependencies
npm install

# Build
npm run build

# Run example
cd example
npm install
npx expo run:ios

Running tests

npm test

Contributing

Contributions are welcome! Please read the contributing guidelines before submitting a PR.

Areas for contribution

  • Additional documentation and examples
  • Unit and integration tests
  • Web support (gRPC-Web)
  • Interceptor support
  • Advanced retry policies
  • Performance optimizations

Resources

License

MIT

Author

Sooraj Pandey

Changelog

0.1.0 (Initial Release)

  • Complete gRPC client implementation
  • All 4 streaming types supported
  • Runtime proto loading
  • TypeScript support
  • iOS and Android support
  • Connection management
  • Error handling

Acknowledgments

  • Built with Expo Modules API
  • Uses gRPC-Java and gRPC-Swift
  • Inspired by the need for native gRPC support in React Native

Support

For issues, questions, or feature requests:


Made with care for the React Native community.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors