Skip to content

Expo is not registering react-native.NativeModules.Grpc #15

@celso-alexandre

Description

@celso-alexandre

Hi @Mitch528,

What a great work, thanks for sharing it with us, really.

I'm really new to react native, and I'm trying to figure out why expo does not register "Grpc" as a "NativeModule".
I even tried to fork and very much simplify/debug, to no end.
If I directly call the native and copy the typescript wrapper (lol) it does the trick... somehow

I don't want to take too much of your time, but if you heard of anything, or has any suggestion for me to look up, I'm trying for some some days. If I can't figure out, maybe I will really keep the copied the typescript wrapper part, that is working for now

This never works (NativeModules.Grpc is not there, NativeModules is {} empty object, always)

import { GrpcClient } from '@mitch528/react-native-grpc';
import { NativeModules } from 'react-native';
import { AuthServiceClientImpl } from '../../proto/auth';

// The native function (IOS/Android) that actually make the gRPC calls.
// The library makes it available under the name `Grpc`.
const Grpc = NativeModules.Grpc;

if (!Grpc || typeof Grpc.setHost !== 'function') {
  console.log('[Grpc] GrpcClient is not properly initialized. Use custom dev client via `expo run:android`.');
}

const client = new GrpcClient();
console.log('Grpc:', Grpc);
console.log('NativeModules:', NativeModules);
Grpc.setHost('host:6969');

export const authClient = new AuthServiceClientImpl({
  request: async (service, method, data) => {
    const path = `/${service}/${method}`;
    const { responseData } = await client.unaryCall(path, data)
    return responseData;
  },
});

This completelly works (NativeModules.Grpc is defined, always)

import { GrpcError, GrpcUnaryCall, type GrpcMetadata, type ServerOutputStream } from '@mitch528/react-native-grpc'; // only for types
import { fromByteArray, toByteArray } from 'base64-js';
import { NativeEventEmitter, NativeModules } from 'react-native';
import { AuthServiceClientImpl } from '../../proto/auth';

const GrpcClient = NativeModules.Grpc;
const GrpcEmitter = new NativeEventEmitter(GrpcClient);

GrpcEmitter.addListener('grpc-call', (event) => {
  const deferred = deferredMap.get(event.id);

  if (deferred) {
    switch (event.type) {
      case 'headers':
        deferred.headers?.resolve(event.payload);
        break;
      case 'response':
        const data = toByteArray(event.payload);

        deferred.data?.notifyData(data);
        deferred.response?.resolve(data);
        break;
      case 'trailers':
        deferred.trailers?.resolve(event.payload);
        deferred.data?.notifyComplete();

        deferredMap.delete(event.id);
        break;
      case 'error':
        const error = new GrpcError(event.error, event.code, event.trailers);

        deferred.response?.reject(error);
        deferred.data?.noitfyError(error);

        break;
    }
  }
});

if (!GrpcClient || typeof GrpcClient.setHost !== 'function') {
  throw new Error('[Grpc] GrpcClient is not properly initialized. Use custom dev client via `expo run:android`.');
}

GrpcClient.setHost('host:6969');

let idCtr = 1;

function getId(): number {
  return idCtr++;
}

type GrpcRequestObject = {
  data: string;
};

type Deferred<T> = {
  completed: boolean;
  promise: Promise<T>;
  resolve: (value: T) => void;
  reject: (reason: any) => void;
};

function createDeferred<T>(signal: AbortSignal) {
  const deferred: Deferred<T> = { completed: false } as Deferred<T>;
  
  deferred.promise = new Promise<T>((resolve, reject) => {
    deferred.resolve = (value) => {
      deferred.completed = true;
      
      resolve(value);
    };
    deferred.reject = (reason) => {
      deferred.completed = true;
      
      reject(reason);
    };
  });
  
  signal.addEventListener('abort', () => {
    if (!deferred.completed) {
      deferred.reject('aborted');
    }
  });
  
  return deferred;
}

type DeferredCalls = {
  headers?: Deferred<GrpcMetadata>;
  response?: Deferred<Uint8Array>;
  trailers?: Deferred<GrpcMetadata>;
  data?: ServerOutputStream;
};

type DeferredCallMap = Map<number, DeferredCalls>;

const deferredMap: DeferredCallMap = new Map<number, DeferredCalls>();

export const authClient = new AuthServiceClientImpl({
  request: async (service, method, data) => {
    const path = `/${service}/${method}`;
    
    // lib code
    const requestData = fromByteArray(data);
    const obj: GrpcRequestObject = {
      data: requestData,
    };
    const id = getId();
    const abort = new AbortController();
    
    abort.signal.addEventListener('abort', () => {
      GrpcClient.cancelGrpcCall(id);
    });
    const response = createDeferred<Uint8Array>(abort.signal);
    const headers = createDeferred<GrpcMetadata>(abort.signal);
    const trailers = createDeferred<GrpcMetadata>(abort.signal);
    
    deferredMap.set(id, {
      response,
      headers,
      trailers,
    });
    //
    
    // console.log('req data', data, typeof data);
    // console.log('req path', path);
    // console.log('req headers', headers);
    // console.log('GrpcClient.unaryCall', GrpcClient.unaryCall)
    GrpcClient.unaryCall(id, path, obj, {});
    const call = new GrpcUnaryCall(
      method,
      data,
      {},
      headers.promise,
      response.promise,
      trailers.promise,
      abort as any,
    );
    // console.log('result', call);
    // console.log('result end');

    const res = await call.response;
    console.log('res', res);
    const head = await call.headers;
    console.log('head', head);
    const trai = await call.trailers;
    console.log('trai', trai);
    
    return res!;
  },
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions