npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

ko-call

v1.1.36

Published

A library that includes all the voice channel functions and UI

Downloads

122

Readme

KO-CALL

npm package

ko-call

Installation

IOS

  1. Need to install these dependencies:
yarn add ko-call [email protected] && [email protected] && npx react-native link react-native-keep-awake && cd ios && pod install
  1. In AppDelegate.m add following lines:
...
  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"edtalk"
                                            initialProperties:nil];
  
  //EKLENECEK OLAN BÖLÜM//
  //PushRegistry register işlemi yapılıyor
  self.voipQueue = dispatch_queue_create("com.edtalk.voipqueue", NULL);
  //self.pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
  self.pushRegistry = [[PKPushRegistry alloc] initWithQueue: self.voipQueue];
  self.pushRegistry.delegate = self;
  self.pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
  //PushRegistry register işlemi yapılıyor
  
  
  [RNCallKeep setup:@{
    @"appName": @"ed-talk",
    @"maximumCallGroups": @3,
    @"maximumCallsPerCallGroup": @1,
    @"supportsVideo": @NO,
  }];
  //EKLENECEK OLAN BÖLÜM//
...
/* arama ve notifications metotları */

/* Cihaz tokeni burada alınıyor ve React tarafında da ulaşabilmek için
UserDefaults a kaydediliyor */
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
  // Register VoIP push token (a property of PKPushCredentials) with server
  NSLog(@"my console log");
  
  if([credentials.token length] == 0) {
  NSLog(@"voip token NULL");
  return;
  }
  
  const unsigned *tokenBytes = (const unsigned *)[credentials.token bytes];

  NSString *tkn = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
  ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
  ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
  ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
  
  self.prefs = [NSUserDefaults standardUserDefaults];
  [self.prefs setObject:tkn forKey:@"deviceToken"];
  NSLog(@"TOKEN: %@", tkn);
  
}

//Bu metot olmak zorunda, o sebeple eklendi
- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type
{

}
/* Cihaz tokeni burada alınıyor ve React tarafında da ulaşabilmek için
UserDefaults a kaydediliyor */

/* Gelen notification burada yakalanıyor ve işlemler burada yapılıyor */
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {

  //Eğer gelen bildirim voip bildirimi ise işlemleri yap
  if (type == PKPushTypeVoIP) {
    
    //UserDefault ile ReactNative tarafındaki Settings iletişimi sağlanıyor
    self.prefs = [NSUserDefaults standardUserDefaults];

    

    //Gelen verileri alıyoruz
    NSDictionary *payloadDict = payload.dictionaryPayload[@"data"];
    self.channel = payloadDict[@"channel"];
    self.token = payloadDict[@"token"];
    self.phoneNum = payloadDict[@"number"];
    self.callerName = payloadDict[@"teacher"];
    self.appId = payloadDict[@"appId"];
    
    //
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    self.remoteTimeStamp = [formatter numberFromString:payloadDict[@"callTime"]];
    NSDate *now = [NSDate date];
    self.localTimeStamp = @(round([now timeIntervalSince1970] * 1000));
    double timeDiffInMillis = [self.localTimeStamp doubleValue] - [self.remoteTimeStamp doubleValue];
    self.timeDiff = @(timeDiffInMillis / 1000.0);
    NSNumber *callTime = @28;
    NSLog(@"Time difference: %@", self.timeDiff);
    //UUID oluşturuluyor
    NSUUID * _Nonnull uuidTemp = [NSUUID UUID];
    self.uuid = uuidTemp;
    //react-native-callkeep paketi için uuid gönderilecek. String'e dönüştürülüyor
    NSString * _Nonnull uuidString = [[uuidTemp UUIDString] lowercaseString];
    [self.prefs setObject:uuidString forKey:@"uuid"];
    
    
    //Agora engine initialize işlemi yapılıyor
    [self initializeAgoraEngine];
    
    
    //Arama için gerekli configuration ayarları oluşturuluyor
    CXProviderConfiguration *providerConfiguration;
    providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:@"ed-talk"];
    
    providerConfiguration.supportsVideo = false;
    providerConfiguration.supportedHandleTypes = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:(int)CXHandleTypePhoneNumber], nil];
    providerConfiguration.maximumCallsPerCallGroup = 1;
    
    
    self.provider = [[CXProvider alloc] initWithConfiguration:providerConfiguration];
    [self.provider setDelegate:self queue:dispatch_get_main_queue()];
    //Arama için gerekli configuration ayarları oluşturuluyor
    
    //Aramayı raporlarmak için ön hazırlıklar yapılıyor
    CXCallUpdate * _Nonnull callUpdate = [[CXCallUpdate alloc] init];;
    CXHandle *phoneNumber = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value: self.phoneNum];
    callUpdate.remoteHandle = phoneNumber;
    callUpdate.localizedCallerName = self.callerName;
    callUpdate.supportsDTMF = NO;
    callUpdate.supportsGrouping = NO;
    callUpdate.supportsHolding = NO;
    //Aramayı raporlarmak için ön hazırlıklar yapılıyor
    NSNumber *callDuration = @([callTime doubleValue] - [self.timeDiff doubleValue]);
    NSLog(@"Call duration: %@", callDuration);
    NSLog(@"Time difference is not greater than callTime");
    if ([self.timeDiff doubleValue] < [callTime doubleValue]) {
        // Do something if callTime is greater than time difference
        NSLog(@"Time difference is greater than callTime");
        //Arama yeni arama olarak CallKit'e raporlanıyor
        [self.provider reportNewIncomingCallWithUUID:self.uuid update:callUpdate completion:^(NSError * _Nullable error) {
          if (error != nil) {
            NSLog(@"reportNewIncomingCall error: %@",error.localizedDescription);
          }
          NSLog(@"timer comes here");          //25 Saniye süre için timer çağırılıyor
          [self startTimer:callDuration];
          completion();
        }];
        //Arama yeni arama olarak CallKit'e raporlanıyor
    } else {
        // Do something else if callTime is not greater than time difference
        NSLog(@"Time difference is not greater than callTime");
        [self.provider reportNewIncomingCallWithUUID:self.uuid update:callUpdate completion:^(NSError * _Nullable error) {
          if (error != nil) {
            NSLog(@"reportNewIncomingCall error: %@",error.localizedDescription);
          }
          NSLog(@"timer comes here");
          [self resetCallVariable];
          completion();
        }];
        [self endCall];
        //Missed call logic
    }
    completion();
  } else {
    NSLog(@"Gelen bildirim voip değillll");
  }
}

/* Gelen notification burada yakalanıyor ve işlemler burada yapılıyor */

//Timer durduruluyor ve saniye sıfırlanıyor
- (void) resetCallVariable {
  //Timerı durdur
  [self.timer invalidate];
  self.timer = nil;
  //Saniyeyi sıfırla
  self.second = 0;
}
- (void) startTimer:(NSNumber*)duration {
  self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
      NSInteger durationSeconds = [duration integerValue];
      self.second = self.second + 1;
      NSLog(@"%ld",(long)self.second);
      if(self.second >= durationSeconds) {
          [self resetCallVariable];
          [self endCall];
          [timer invalidate];
          self.timer = nil;
      }
  }];
}

//Agora initialize fonksiyonu
- (void)initializeAgoraEngine {
    self.agoraKit = [AgoraRtcEngineKit sharedEngineWithAppId: self.appId delegate:self];
    //Mikrofon ve aktif arama değişkenlerini false olarak işaretliyoruz
    self.isMuted = false;
    self.isActiveCall = false;
}

//Agora kanala giriş yapılıyor
- (void)joinChannel {
  //Ses ayarlarını yapıyoruz
  
    //Agora kanalına girmeden önce broadcaster olduğumuzu belirtiyoruz.
    AgoraRtcChannelMediaOptions *option = [[AgoraRtcChannelMediaOptions alloc] init];
    option.channelProfile =  AgoraChannelProfileLiveBroadcasting;
    option.clientRoleType = AgoraClientRoleBroadcaster;
  
    //Kanala yukarıdaki ayarlar ile giriyoruzz
    [self.agoraKit joinChannelByToken:self.token channelId:self.channel uid:0 mediaOptions:option joinSuccess:^(NSString * _Nonnull channel, NSUInteger uid, NSInteger elapsed) {
    }];
    
    NSInteger speakerResult = [self.agoraKit setDefaultAudioRouteToSpeakerphone:false];
    
    [self.agoraKit enableAudio];
  
    [self configureAudioSession];
    
    //Kanala giriş yapıldığı için aktif arama olduğunu belirtiyoruz
    //Eğer aktif arama varsa endCall metotunda agoradan da çıkılacak
    self.isActiveCall = true;

}

//Arama biterse yapılacak işlemler
-(void) endCall {
  [self.provider reportCallWithUUID: self.uuid endedAtDate:NSDate.date reason: CXCallEndedReasonRemoteEnded];
    //Eğer aktif arama varsa agora kanalından ayrıl ve engine destroy et
    if (self.isActiveCall) {
      [self.agoraKit leaveChannel:nil];
      self.isActiveCall = false;
    }
    [AgoraRtcEngineKit destroy];
}

//Aramaya yanıt verildiğinde yapılacak işlemler
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
    //Bütün değerler sıfırlanıyor
    [self resetCallVariable];
    //Gelen token ve kanal bilgileri ile kanala giriliyor
    [self joinChannel];
  
    [action fulfill];
}

//Arama reddedildiğinde yapılan işlemler
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
    //Arama kapandığında uuid değişeknini sıfırla
    self.prefs = [NSUserDefaults standardUserDefaults];
    [self.prefs setObject:@"" forKey:@"uuid"];
  
    //Bütün değerler sıfırlanıyor
    [self resetCallVariable];
    //Arama kapatılıyor
    [self endCall];
    [action fulfill];
}

//Mikrofon kapatıldığında yapılan işlemler
- (void)provider:(CXProvider *)provider performSetMutedCallAction:(nonnull CXSetMutedCallAction *)action {
    [self.agoraKit muteLocalAudioStream:!self.isMuted];
    self.isMuted = !self.isMuted;
    [action fulfill];
}

//Cihaz kilitliyken arama gelirse mikrofonu etkinleştiriyoruz
- (void) configureAudioSession {
  [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord mode:AVAudioSessionModeDefault options: AVAudioSessionCategoryOptionDuckOthers | AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionAllowBluetoothA2DP error:nil];
  
  [[AVAudioSession sharedInstance] setMode:AVAudioSessionModeVoiceChat error:nil];
  
}

//Eğer karşı taraf aramayı kapatırsa
- (void) rtcEngine:(AgoraRtcEngineKit *)engine didOfflineOfUid:(NSUInteger)uid reason:(AgoraUserOfflineReason)reason {
  [self endCall];
}
  1. in AppDelegate.h file:
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
#import <AgoraRtcKit/AgoraRtcEngineKit.h>
#import <CallKit/CallKit.h>
#import <PushKit/PushKit.h>
#import <AVFAudio/AVAudioSession.h>
#import <RNCallkeep/RNCallKeep.h>

//PKPushRegistryDelegate, CXProviderDelegate, AgoraRtcEngineDelegate isimli delegeler ekleniyor
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, PKPushRegistryDelegate, CXProviderDelegate, AgoraRtcEngineDelegate>

@property (nonatomic, strong) UIWindow *window;

//UserDefaults için genel değişken tanımlanıyor
@property NSUserDefaults *prefs;

//Agora için gerekli olan veriler buraya tanımlanıyor
@property (strong, nonatomic) AgoraRtcEngineKit *agoraKit;
@property NSString *channel;
@property NSString *token;
@property NSString *phoneNum;
@property NSString *callerName;
@property NSString *appId;
@property NSNumber *remoteTimeStamp;
@property NSNumber *localTimeStamp;
@property NSNumber *timeDiff;
@property Boolean isActiveCall;
@property Boolean isMuted;

//Arama için gerekli değişkenler
@property CXProvider *provider;
@property PKPushRegistry *pushRegistry;
@property NSUUID *uuid;
@property NSObject<OS_dispatch_queue> *voipQueue;

//Aramanın 25 saniye sürüp kapanması için gerekli değişkenler
@property NSInteger second;
@property NSTimer *timer;

@end
  1. in info.plist:
  	<key>NSCameraUsageDescription</key>
	<string>The app needs camera permission to attend the classes.</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>The app needs microphone permission to attend the classes.</string>
	<key>UIBackgroundModes</key>
	<array>
		<string>remote-notification</string>
		<string>voip</string>
	</array>
  1. in the podfile add this lines:
  pod 'RNCallKeep', :path => '../node_modules/react-native-callkeep'
  1. Enable Push Notifications
  1. Enable Background Modes

ANDROID

  1. in AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.edtalk">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />    
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:usesCleartextTraffic="true"
      android:theme="@style/AppTheme">

      <activity
        android:name=".MainActivity"
        android:resizeableActivity="true"
        android:supportsPictureInPicture="true"
        android:label="@string/app_name"
        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|smallestScreenSize|orientation"
        android:windowSoftInputMode="adjustResize"
        android:exported="true"
        android:launchMode="singleTask"
      >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
            <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
        </intent-filter>
        <intent-filter>
          <action android:name="android.intent.action.VIEW" />
          <category android:name="android.intent.category.DEFAULT" />
          <category android:name="android.intent.category.BROWSABLE" />
          <data android:scheme="edtalk" />
        </intent-filter>
      </activity>
      <receiver android:name=".ActionReceiver" />
    </application>
</manifest>
  1. in android build.gradle:
buildscript {
    ext {
        buildToolsVersion = "30.0.2"
        minSdkVersion = 26
        compileSdkVersion = 31
        targetSdkVersion = 31
        ndkVersion = "21.4.7075529"
    }
    ...
}
  1. in android/app/src/main/java/com/edtalk we need 3 files, first one is ActionReceiver.java:
package com.edtalk;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Vibrator;

public class ActionReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String[] actionArray=intent.getStringExtra("action").split(":");
        String action = actionArray[0];
        String notificationId = actionArray[1];

        if (action.equals("Reject")) {
            Vibrator rr = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
            KoCall.ringToneStop();
            rr.cancel();
            KoCall.removeNotification(notificationId);
            KoCall.resetCallVariables();
        }
    }
}

second one is KoCall.java:

package com.edtalk;

import static android.content.Context.NOTIFICATION_SERVICE;

import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Build;
import android.os.SystemClock;
import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.util.Rational;

import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import java.util.Calendar;
import java.util.concurrent.TimeUnit;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.modules.storage.ReactDatabaseSupplier;
import com.reactnativecommunity.asyncstorage.AsyncLocalStorageUtil;
import com.zxcpoiu.incallmanager.InCallManagerModule;


public class KoCall extends ReactContextBaseJavaModule {
    private static ReactApplicationContext reactContext;
    public static final String LOG_TAG = "KO:KoCall";

    //React native tarafında iletişide kullanılacak değişkenler
    public static int notificationId;
    private String teacher;
    //25 saniyelik süreyi korumak için kullanılacak değişkenler
    private long firstTime;
    private long currentTime;

    //Uygulamanın deeplink ismi bu alanda tutuluyor.
    //Her uygulama için bu alan değişecek.
    private final String deepLink = "edtalk";
    //InCallManager react-native tarafında başladığında native tarafta durmuyor
    //O sebeple başlatma ve durdurma işlemlerini native tarafta ara metot ile yapıyoruz.
    private static InCallManagerModule inCallManager;
    //AsyncStorage da bazı değerleri native tarafta güncellememiz gerekiyor.
    //O sebeple bu değişkenleri tutuyoruz
    private static ReactDatabaseSupplier reactDatabaseSupplier;
    static final String TABLE_CATALYST = "catalystLocalStorage";
    static final String KEY_COLUMN = "key";
    static final String VALUE_COLUMN = "value";

    //Picture-inPicture için değişkenleri tanımlıyoruz
    private static final int ASPECT_WIDTH = 16;
    private static final int ASPECT_HEIGHT = 9;
    private static boolean isPipSupported = false;
    private static boolean isCustomAspectRatioSupported = false;
    private static Rational aspectRatio;
    //Picture-inPicture için değişkenleri tanımlıyoruz


    public KoCall(ReactApplicationContext context) {
        super(context);
        reactContext = context;

        inCallManager = new InCallManagerModule(reactContext);

        //Sürüme göre Pictur-inPicture desteklenme işlerini değişkenlere atıyoruz
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            isPipSupported = true;
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            isCustomAspectRatioSupported = true;
            aspectRatio = new Rational(ASPECT_WIDTH, ASPECT_HEIGHT);
        }
        //Sürüme göre Pictur-inPicture desteklenme işlerini değişkenlere atıyoruz
    }

    @Override
    public String getName() {
        return "KoCall";
    }

    //Uygulama arka plandayken arama gelirse notification çıkması için bu metot kullanılıyor
    @SuppressLint("NotificationTrampoline")
    @ReactMethod
    private void showCallNotification(String teacher) {
        this.notificationId = (int) SystemClock.uptimeMillis();
        String channelId = "fcm_call_channel";
        String channelName = "Incoming Call";

        if (teacher != null && !teacher.isEmpty() && !teacher.equals("")) {
            this.teacher = teacher;
        } else {
            this.teacher = "Teacher";
        }

        //Aktif arama başlangıçta true ya çekiliyor.
        //Bu sayede uygulama açılınca kanala otomarik giriş sağlanacak
        setAsyncStorageItem("activeCall", "true");

        String notification_title = this.teacher + " is calling";

        String packageName = reactContext.getPackageName();
        Intent launchIntent = reactContext.getPackageManager().getLaunchIntentForPackage(packageName);
        String className = launchIntent.getComponent().getClassName();
        Class<?> activityClass;

        try {
            activityClass = Class.forName(className);
        } catch (Exception e) {
            return;
        }

        //NOTIFICATION BUTONLARI VE BUTONLARA BASILDIĞINDA YAPILACAK İŞLEMLER
        //Open Call butonuna bastığımızda uygulamada arama ekranının açılması gerekiyor.
        //Bu sebeple deeplink kullanıyoruz ve call ekranına notification Id gönderiyoruz
        String openCallUri = (new StringBuilder()).append(deepLink).append("://home/").toString();
        Intent openCallIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(openCallUri), reactContext, activityClass);
        openCallIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        //Reject e basıldığında BroadcastRecevier ile bu durumu yakalayarak uygulamayı açmadan sesi kapatıyoruz
        //AsyncStorage ile bazı verileri güncelliyoruz. Bize ActionReceiverClass bu konuda yardımcı oluyor
        Intent rejectIntent = new Intent(reactContext, ActionReceiver.class);
        rejectIntent.putExtra("action", "Reject:" + notificationId);
        //NOTIFICATION BUTONLARI VE BUTONLARA BASILDIĞINDA YAPILACAK İŞLEMLER

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(reactContext, channelId)
                .setContentTitle(notification_title)
                .setContentText(channelName)
                .setPriority(NotificationCompat.PRIORITY_MAX)
                .setCategory(NotificationCompat.CATEGORY_CALL)
                .setAutoCancel(true)
                .addAction(0, "Accept", PendingIntent.getActivity(reactContext, 0, openCallIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
                .addAction(0, "Reject", PendingIntent.getBroadcast(reactContext, 1, rejectIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
                .setDefaults(Notification.DEFAULT_VIBRATE)
                .setSmallIcon(R.mipmap.ic_launcher);


        NotificationManager notificationManager = (NotificationManager) reactContext.getSystemService(NOTIFICATION_SERVICE);
        int importance = NotificationManager.IMPORTANCE_HIGH;

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {

            NotificationChannel mChannel = new NotificationChannel(
                    channelId, channelName, importance);
            mChannel.setDescription(channelName);
            mChannel.enableLights(true);
            mChannel.enableVibration(true);
            notificationManager.createNotificationChannel(mChannel);
        }

        notificationBuilder.build().flags |= Notification.FLAG_AUTO_CANCEL;
        notificationManager.notify(notificationId, notificationBuilder.build());
        firstTime = Calendar.getInstance().getTimeInMillis();

    }

    //Eğer aramaya yanıt verilmezse missed call notification düşüyor
    @ReactMethod
    private void showMissedCallNotification(String teacher) {
        int _notificationId = (int) SystemClock.uptimeMillis();
        String channelId = "fcm_missed_call_channel";
        String channelName = "Missed Call";

        // 25 saniye sonra sesi durdur
        // Programda kapanıyor ama herhangi bir aksilik olursa sesi ve titreşimi kapatmamız iyi olur
        ringToneStop();
        Vibrator rr = (Vibrator) reactContext.getSystemService(Context.VIBRATOR_SERVICE);
        rr.cancel();
        //Aktif arama başlangıçta true ya çekiliyor.
        //Eğer arama karşılanmazsa bu alan boş olarak güncellenecek
        setAsyncStorageItem("activeCall", "");
        String _teacher = "";
        if (teacher != null && !teacher.isEmpty() && !teacher.equals("")) {
            _teacher = teacher;
        } else {
            _teacher = "Teacher";
        }

        String notification_title = "Missed call - " + _teacher;
        //Missedcall notificationa basıldığında uygulamamızın açılması için bu bölümü oluşturuyoruz.
        //setContentIntent ile Notification a burayı ekleyeceğiz
        Intent intent = new Intent(reactContext, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(reactContext, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT);

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(reactContext, channelId)
                .setContentTitle(notification_title)
                .setContentText(channelName)
                .setPriority(NotificationCompat.PRIORITY_MAX)
                .setCategory(NotificationCompat.CATEGORY_CALL)
                .setAutoCancel(true)
                .setContentIntent(pendingIntent)
                .setDefaults(Notification.DEFAULT_VIBRATE)
                .setSmallIcon(R.mipmap.ic_launcher);

        NotificationManager notificationManager = (NotificationManager) reactContext.getSystemService(NOTIFICATION_SERVICE);
        int importance = NotificationManager.IMPORTANCE_HIGH;

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {

            NotificationChannel mChannel = new NotificationChannel(
                    channelId, channelName, importance);
            mChannel.setDescription(channelName);
            mChannel.enableLights(true);
            mChannel.enableVibration(true);
            notificationManager.createNotificationChannel(mChannel);
        }

        notificationBuilder.build().flags |= Notification.FLAG_AUTO_CANCEL;
        notificationManager.notify(_notificationId, notificationBuilder.build());

    }

    //Eğer uygulama arka planda iken arama gelirse counter bu metot aracılığı ile
    //uygulamaya aktarılıyor. Buradaki amaç 25 saniye çalma süresini korumak
    @ReactMethod
    public void getCounter(Callback callback) {
        currentTime = Calendar.getInstance().getTimeInMillis();
        long counter = TimeUnit.MILLISECONDS.toSeconds(currentTime - firstTime);

        callback.invoke(String.valueOf(counter));
    }

    //Aramaya ait notificationId bu bölümden gidiyor
    @ReactMethod
    public void getNotificationId(Callback callback) {
        callback.invoke(notificationId);
    }

    //Arama notification silinmesi için bu metot kullanılyor.
    @ReactMethod
    public void removeCallNotification(String notificationId) {
        this.notificationId = 0;
        removeNotification(notificationId);
    }

    //Notification alma bölümünün aktif olup olmadığı kontrol ediliyor
    //Eğer bu metot false dönerse react-native tarafında Linking.openSettings()
    //ile uygulama ayarlarını açtıracağız
    @ReactMethod
    public void allowNotification(final Promise promise) {
        try {
            Boolean areEnabled = NotificationManagerCompat.from(reactContext).areNotificationsEnabled();
            promise.resolve(areEnabled);
        } catch (Exception e) {
            promise.reject("Error", e.getMessage());
        }
    }

    //IncallManager react-native tarafında başladığında native tarafta durmuyor
    //Bu sebeple başlatma işlemini ara bir metot ile yapıyoruz
    @ReactMethod
    public void startRingTone(int seconds) {
        inCallManager.startRingtone("_DEFAULT_", seconds);
    }

    //IncallManager react-native tarafında başladığında native tarafta durmuyor
    //Bu sebeple durdurma işlemini ara bir metot ile yapıyoruz
    @ReactMethod
    public void stopRingTone() {
        ringToneStop();
    }

    //Notification Id 0 lanıyor
    @ReactMethod
    public void resetNotificationId() {
        notificationId = 0;
    }

    //Picture-in-Picture için kullanılan metotlar burada yer alıyor

    @ReactMethod
    public void enterPictureInPictureMode() {
        enterPipMode();
    }
    //Picture-in-Picture için kullanılan metotlar burada yer alıyor

    //Eğer battery optimizasyonu kısıtlanmış olarak işaretli ise arka plan bildirimleri gelmiyor
    //Burada onun kontrolünü yapacağız
    @ReactMethod
    public void isBatteryOptEnabled(Promise promise) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            String packageName = reactContext.getPackageName();
            ActivityManager activityManager = (ActivityManager) reactContext.getSystemService(Context.ACTIVITY_SERVICE);
            Boolean isRestricted = activityManager.isBackgroundRestricted();

            if (!isRestricted) {
                promise.resolve(true);
                return;
            }
        }
        promise.resolve(false);
    }

    //AsyncStorage DB sinden veri okuyabilmemiz için bu metotu kullanacağız
    public static String getAsyncStorageItem(String key) {
        String result = "";
        SQLiteDatabase db = reactDatabaseSupplier.getInstance(reactContext).getReadableDatabase();

        if (db != null) {
            result = AsyncLocalStorageUtil.getItemImpl(db, key);
        }

        return result;
    }

    //AsyncStorage DB sinde güncelleme yapabilmemiz için bu metotu kullanacağız
    public static boolean setAsyncStorageItem(String key, String value) {
        SQLiteDatabase db = reactDatabaseSupplier.getInstance(reactContext).getReadableDatabase();
        ContentValues contentValues = new ContentValues();
        contentValues.put(KEY_COLUMN, key);
        contentValues.put(VALUE_COLUMN, value);

        long inserted = db.insertWithOnConflict(
                TABLE_CATALYST,
                null,
                contentValues,
                SQLiteDatabase.CONFLICT_REPLACE);
        db.close();
        return (-1 != inserted);
    }

    //Pip mode diğer class içinde de kullanabilmek için static bir metota taşıyoruz
    @RequiresApi(api = Build.VERSION_CODES.O)
    public static void enterPipMode() {
        if (isPipSupported) {
            if (isCustomAspectRatioSupported) {
                PictureInPictureParams params = new PictureInPictureParams.Builder().setAspectRatio(aspectRatio).build();
                reactContext.getCurrentActivity().enterPictureInPictureMode(params);
            } else
                reactContext.getCurrentActivity().enterPictureInPictureMode();
        }
    }

    //InCallManager durdurma işlemi
    public static void ringToneStop() {
        inCallManager.stopRingtone();
    }
    //InCallManager durdurma işlemi

    //Notification silme işlemi. Birden fazla yerde kullanıldığı için tekrara düşmemek adına buraya alındı
    public static void removeNotification(String notificationId) {
        int _notificationId = Integer.parseInt(notificationId);
        NotificationManager manager = (NotificationManager) reactContext.getSystemService(NOTIFICATION_SERVICE);
        manager.cancel(_notificationId);
    }

    //Arama reddedildiğinde AsyncStorage da bazı alanları güncellememiz gerekiyor
    public static void resetCallVariables() {

        //Aramaya ait verileri temizliyoruz
        setAsyncStorageItem("incomingCall", "");
        setAsyncStorageItem("activeCall", "");
        setAsyncStorageItem("appId", "");
        setAsyncStorageItem("token", "");
        setAsyncStorageItem("channelName", "");
        setAsyncStorageItem("teacherName", "");
        //setAsyncStorageItem("teacherImg", "");
        setAsyncStorageItem("providerType", "");
        //Arama reddedildiği için notification Id yi sıfırlıyor ve incomingCall u false a çekiyoruz
        notificationId = 0;
    }
}

and the finally last one is KoCallPackage.java:

package com.edtalk;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.JavaScriptModule;

public class KoCallPackage  implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Arrays.<NativeModule>asList(new KoCall(reactContext));
    }

    // Deprecated from RN 0.47
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

TEST CASES

library check list