package im.zego;

import android.app.Application;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
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.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import im.zego.zim.ZIM;
import im.zego.zim.callback.*;
import im.zego.zim.entity.*;
import im.zego.zim.enums.*;
import im.zego.zim.event.*;
import im.zego.zim.internal.util.ZIMLogUtil;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONObject;

public class RNZimReactnativeSdkModule extends ReactContextBaseJavaModule {

    private static final String Prefix = "im.zego.zim.reactnative.";

    private final ReactApplicationContext reactContext;

    private HashMap<String, ZIM> engineMap;
    private HashMap<ZIM, String> engineMapForCallback;

    public RNZimReactnativeSdkModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
        this.engineMap = new HashMap<>();
        this.engineMapForCallback = new HashMap<>();
    }

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

    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put("prefix", Prefix);
        return constants;
    }

    // Used for passive call
    private void sendEvent(String eventName, WritableMap dataMap, ZIM zim) {
        WritableMap paramsMap = Arguments.createMap();
        paramsMap.putString("handle", this.engineMapForCallback.get(zim));
        paramsMap.putMap("data", dataMap);

        this.reactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit(Prefix + eventName, paramsMap);
    }

    // Used for API active call
    private void sendAPIEvent(String eventName, WritableMap params) {
        this.reactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit(Prefix + eventName, params);
    }

    private ZIM getZIMEngine(String handle, final Promise promise) {
        ZIM zim = this.engineMap.get(handle);
        if (zim == null) {
            promise.reject("-1", "no instance");
            return null;
        }
        return zim;
    }

    private boolean isSucc(final ZIMError errorInfo, final Promise promise) {
        if (errorInfo.code != ZIMErrorCode.SUCCESS) {
            promise.reject(String.valueOf(errorInfo.code.value()), errorInfo.message);
            return false;
        }
        return true;
    }

    @ReactMethod
    public void addListener(String eventName) {}

    @ReactMethod
    public void removeListeners(Integer count) {}

    @ReactMethod
    public void getVersion(final Promise promise) {
        promise.resolve(ZIM.getVersion());
    }

    @ReactMethod
    public void writeCustomLog(String customLog, String moduleName) {
        try {
            // Class<?> clz = Class.forName("im.zego.zim.internal.util.ZIMLogUtil");
            Class<ZIMLogUtil> clz = ZIMLogUtil.class;
            Method method = clz.getDeclaredMethod("writeCustomLog", String.class, String.class);
            method.setAccessible(true);
            method.invoke(null, customLog, moduleName);
            method.setAccessible(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @ReactMethod
    public void setLogConfig(ReadableMap config) {
        ZIMLogConfig configObj = new ZIMLogConfig();
        if (config.hasKey("logPath")) {
            configObj.logPath = config.getString("logPath");
        }

        if (config.hasKey("logSize")) {
            configObj.logSize = Double.valueOf(config.getDouble("logSize")).longValue();
        }

        ZIM.setLogConfig(configObj);
    }

    @ReactMethod
    public void setCacheConfig(ReadableMap config) {
        ZIMCacheConfig configObj = new ZIMCacheConfig();
        if (config.hasKey("cachePath")) {
            configObj.cachePath = config.getString("cachePath");
        }

        ZIM.setCacheConfig(configObj);
    }

    @ReactMethod
    public void setAdvancedConfig(String key, String value) {
        ZIM.setAdvancedConfig(key, value);
    }

    @ReactMethod
    public void setGeofencingConfig(ReadableArray areaList, int type, final Promise promise) {
        promise.resolve(
            ZIM.setGeofencingConfig(RNZIMConverter.oIntArray(areaList), ZIMGeofencingType.getZIMGeofencingType(type))
        );
    }

    @ReactMethod
    public void createEngine(String handle, Double appID, String appSign) {
        ZIM oldZIM = ZIM.getInstance();
        if (oldZIM != null) {
            oldZIM.destroy();
        }

        ZIMAppConfig appConfig = new ZIMAppConfig();
        appConfig.appID = appID.longValue();
        appConfig.appSign = appSign;

        ZIM zim = ZIM.create(appConfig, (Application) this.reactContext.getApplicationContext());
        if (zim != null) {
            this.engineMap.put(handle, zim);
            this.engineMapForCallback.put(zim, handle);

            zim.setEventHandler(
                new ZIMEventHandler() {
                    @Override
                    public void onError(ZIM zim, ZIMError errorInfo) {
                        super.onError(zim, errorInfo);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("code", errorInfo.code.value());
                        dataMap.putString("message", errorInfo.message);

                        sendEvent("error", dataMap, zim);
                    }

                    @Override
                    public void onConnectionStateChanged(
                        ZIM zim,
                        ZIMConnectionState state,
                        ZIMConnectionEvent event,
                        JSONObject extendedData
                    ) {
                        super.onConnectionStateChanged(zim, state, event, extendedData);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("state", state.value());
                        dataMap.putInt("event", event.value());
                        dataMap.putString("extendedData", extendedData.toString());

                        sendEvent("connectionStateChanged", dataMap, zim);
                    }

                    @Override
                    public void onExperimentalEventReceived(ZIM zim, String content) {
                        super.onExperimentalEventReceived(zim, content);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("data", content);

                        sendEvent("experimentalEventReceived", dataMap, zim);
                    }

                    @Override
                    public void onTokenWillExpire(ZIM zim, int second) {
                        super.onTokenWillExpire(zim, second);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("second", second);

                        sendEvent("tokenWillExpire", dataMap, zim);
                    }

                    @Override
                    public void onUserInfoUpdated(ZIM zim, ZIMUserFullInfo info) {
                        super.onUserInfoUpdated(zim, info);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putMap("info", RNZIMConverter.mZIMUserFullInfo(info));

                        sendEvent("userInfoUpdated", dataMap, zim);
                    }

                    @Override
                    public void onUserStatusUpdated(ZIM zim, ArrayList<ZIMUserStatus> infos) {
                        super.onUserStatusUpdated(zim, infos);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("userStatusList", RNZIMConverter.mZIMUserStatusList(infos));

                        sendEvent("userStatusUpdated", dataMap, zim);
                    }

                    @Override
                    public void onUserRuleUpdated(ZIM zim, ZIMUserRule info) {
                        super.onUserRuleUpdated(zim, info);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putMap("userRule", RNZIMConverter.mZIMUserOfflinePushRule(info.offlinePushRule));

                        sendEvent("userRuleUpdated", dataMap, zim);
                    }

                    @Override
                    public void onConversationsAllDeleted(ZIM zim, ZIMConversationsAllDeletedInfo info) {
                        super.onConversationsAllDeleted(zim, info);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("count", info.count);

                        sendEvent("conversationsAllDeleted", dataMap, zim);
                    }

                    @Override
                    public void onConversationChanged(ZIM zim, ArrayList<ZIMConversationChangeInfo> infos) {
                        super.onConversationChanged(zim, infos);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("infoList", RNZIMConverter.mZIMConversationChangeInfoList(infos));

                        sendEvent("conversationChanged", dataMap, zim);
                    }

                    @Override
                    public void onConversationSyncStateChanged(ZIM zim, ZIMConversationSyncState state) {
                        super.onConversationSyncStateChanged(zim, state);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("state", state.value());

                        sendEvent("conversationSyncStateChanged", dataMap, zim);
                    }

                    @Override
                    public void onConversationTotalUnreadMessageCountUpdated(ZIM zim, int count) {
                        super.onConversationTotalUnreadMessageCountUpdated(zim, count);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("totalUnreadMessageCount", count);

                        sendEvent("conversationTotalUnreadMessageCountUpdated", dataMap, zim);
                    }

                    @Override
                    public void onPeerMessageReceived(
                        ZIM zim,
                        ArrayList<ZIMMessage> messageList,
                        ZIMMessageReceivedInfo info,
                        String fromConvID
                    ) {
                        super.onPeerMessageReceived(zim, messageList, info, fromConvID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("fromConversationID", fromConvID);
                        dataMap.putMap("info", RNZIMConverter.mZIMMessageReceivedInfo(info));
                        dataMap.putArray("messageList", RNZIMConverter.mZIMMessageList(messageList));

                        sendEvent("peerMessageReceived", dataMap, zim);
                    }

                    @Override
                    public void onRoomMessageReceived(
                        ZIM zim,
                        ArrayList<ZIMMessage> messageList,
                        ZIMMessageReceivedInfo info,
                        String fromConvID
                    ) {
                        super.onRoomMessageReceived(zim, messageList, info, fromConvID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("fromConversationID", fromConvID);
                        dataMap.putMap("info", RNZIMConverter.mZIMMessageReceivedInfo(info));
                        dataMap.putArray("messageList", RNZIMConverter.mZIMMessageList(messageList));

                        sendEvent("roomMessageReceived", dataMap, zim);
                    }

                    @Override
                    public void onGroupMessageReceived(
                        ZIM zim,
                        ArrayList<ZIMMessage> messageList,
                        ZIMMessageReceivedInfo info,
                        String fromConvID
                    ) {
                        super.onGroupMessageReceived(zim, messageList, info, fromConvID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("fromConversationID", fromConvID);
                        dataMap.putMap("info", RNZIMConverter.mZIMMessageReceivedInfo(info));
                        dataMap.putArray("messageList", RNZIMConverter.mZIMMessageList(messageList));

                        sendEvent("groupMessageReceived", dataMap, zim);
                    }

                    @Override
                    public void onMessageReceived(ZIM zim, ZIMMessageReceivedEventResult result) {
                        super.onMessageReceived(zim, result);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putMap(
                            "receivedInfo",
                            RNZIMConverter.mZIMMessageReceivedInfo(result.getReceivedInfo())
                        );
                        dataMap.putArray("messageList", RNZIMConverter.mZIMMessageList(result.getMessageList()));

                        sendEvent("messageReceived", dataMap, zim);
                    }

                    @Override
                    public void onBroadcastMessageReceived(ZIM zim, ZIMMessage message) {
                        super.onBroadcastMessageReceived(zim, message);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putMap("message", RNZIMConverter.mZIMMessage(message));

                        sendEvent("broadcastMessageReceived", dataMap, zim);
                    }

                    @Override
                    public void onConversationMessageReceiptChanged(ZIM zim, ArrayList<ZIMMessageReceiptInfo> infos) {
                        super.onConversationMessageReceiptChanged(zim, infos);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("infos", RNZIMConverter.mZIMMessageReceiptInfo(infos));

                        sendEvent("conversationMessageReceiptChanged", dataMap, zim);
                    }

                    @Override
                    public void onMessageReceiptChanged(ZIM zim, ArrayList<ZIMMessageReceiptInfo> infos) {
                        super.onMessageReceiptChanged(zim, infos);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("infos", RNZIMConverter.mZIMMessageReceiptInfo(infos));

                        sendEvent("messageReceiptChanged", dataMap, zim);
                    }

                    @Override
                    public void onMessageRevokeReceived(ZIM zim, ArrayList<ZIMRevokeMessage> messageList) {
                        super.onMessageRevokeReceived(zim, messageList);

                        WritableMap dataMap = Arguments.createMap();
                        WritableArray messageArray = Arguments.createArray();
                        for (ZIMMessage message : messageList) {
                            messageArray.pushMap(RNZIMConverter.mZIMMessage(message));
                        }
                        dataMap.putArray("messageList", messageArray);

                        sendEvent("messageRevokeReceived", dataMap, zim);
                    }

                    @Override
                    public void onMessageReactionsChanged(ZIM zim, ZIMMessageReactionsChangedEventResult result) {
                        super.onMessageReactionsChanged(zim, result);

                        WritableMap dataMap = Arguments.createMap();

                        dataMap.putArray("reactions", RNZIMConverter.mZIMMessageReactionList(result.getReactions()));

                        WritableArray infosArray = Arguments.createArray();
                        for (ZIMMessageReactionChangeInfo info : result.getChangeInfoList()) {
                            WritableMap infoMap = Arguments.createMap();
                            infoMap.putMap("message", RNZIMConverter.mZIMMessage(info.getMessage()));

                            WritableArray userArray = Arguments.createArray();
                            for (ZIMMessageReactionUserChangeInfo user : info.getUserChangeInfoList()) {
                                WritableMap userMap = Arguments.createMap();
                                userMap.putString("userID", user.getUserID());
                                userMap.putString("reactionType", user.getReactionType());
                                userMap.putInt("action", user.getAction().value());
                                userMap.putInt("sumCount", user.getSumCount());
                                userArray.pushMap(userMap);
                            }
                            infoMap.putArray("userChangeInfoList", userArray);

                            infosArray.pushMap(infoMap);
                        }
                        dataMap.putArray("changeInfoList", infosArray);

                        sendEvent("messageReactionsChanged", dataMap, zim);
                    }

                    @Override
                    public void onMessageRepliedCountChanged(ZIM zim, ArrayList<ZIMMessageRootRepliedCountInfo> infos) {
                        super.onMessageRepliedCountChanged(zim, infos);

                        WritableArray infosArray = Arguments.createArray();
                        for (ZIMMessageRootRepliedCountInfo info : infos) {
                            WritableMap infoMap = Arguments.createMap();
                            infoMap.putString("conversationID", info.conversationID);
                            infoMap.putInt("conversationType", info.conversationType.value());
                            infoMap.putInt("count", info.count);
                            infoMap.putString("messageID", String.valueOf(info.messageID));
                            infosArray.pushMap(infoMap);
                        }

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("infos", infosArray);

                        sendEvent("messageRepliedCountChanged", dataMap, zim);
                    }

                    @Override
                    public void onMessageRepliedInfoChanged(ZIM zim, ArrayList<ZIMMessage> messageList) {
                        super.onMessageRepliedInfoChanged(zim, messageList);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("messageList", RNZIMConverter.mZIMMessageList(messageList));

                        sendEvent("messageRepliedInfoChanged", dataMap, zim);
                    }

                    @Override
                    public void onMessagePinStatusChanged(ZIM zim, ArrayList<ZIMMessagePinStatusChangeInfo> infos) {
                        super.onMessagePinStatusChanged(zim, infos);

                        WritableArray infosArray = Arguments.createArray();
                        for (ZIMMessagePinStatusChangeInfo info : infos) {
                            WritableMap infoMap = Arguments.createMap();
                            infoMap.putInt("pinStatus", info.getPinStatus().value());
                            infoMap.putMap("message", RNZIMConverter.mZIMMessage(info.getMessage()));
                            infosArray.pushMap(infoMap);
                        }

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("infos", infosArray);

                        sendEvent("messagePinStatusChanged", dataMap, zim);
                    }

                    @Override
                    public void onMessageEdited(ZIM zim, ArrayList<ZIMMessage> messageList) {
                        super.onMessageEdited(zim, messageList);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("messageList", RNZIMConverter.mZIMMessageList(messageList));

                        sendEvent("messageEdited", dataMap, zim);
                    }

                    @Override
                    public void onMessageDeleted(ZIM zim, ZIMMessageDeletedInfo deletedInfo) {
                        super.onMessageDeleted(zim, deletedInfo);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("conversationID", deletedInfo.conversationID);
                        dataMap.putInt("conversationType", deletedInfo.conversationType.value());
                        dataMap.putInt("messageDeleteType", deletedInfo.messageDeleteType.value());
                        dataMap.putArray("messageList", RNZIMConverter.mZIMMessageList(deletedInfo.messageList));

                        sendEvent("messageDeleted", dataMap, zim);
                    }

                    @Override
                    public void onMessageSentStatusChanged(ZIM zim, ArrayList<ZIMMessageSentStatusChangeInfo> infos) {
                        super.onMessageSentStatusChanged(zim, infos);

                        WritableMap dataMap = Arguments.createMap();
                        WritableArray infosArray = Arguments.createArray();
                        for (ZIMMessageSentStatusChangeInfo info : infos) {
                            infosArray.pushMap(RNZIMConverter.mZIMMessageSentStatusChangeInfo(info));
                        }
                        dataMap.putArray("infos", infosArray);

                        sendEvent("messageSentStatusChanged", dataMap, zim);
                    }

                    @Override
                    public void onRoomMemberJoined(ZIM zim, ArrayList<ZIMUserInfo> memberList, String roomID) {
                        super.onRoomMemberJoined(zim, memberList, roomID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("roomID", roomID);
                        dataMap.putArray("memberList", RNZIMConverter.mZIMUserInfoList(memberList));

                        sendEvent("roomMemberJoined", dataMap, zim);
                    }

                    @Override
                    public void onRoomMemberLeft(ZIM zim, ArrayList<ZIMUserInfo> memberList, String roomID) {
                        super.onRoomMemberLeft(zim, memberList, roomID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("roomID", roomID);
                        dataMap.putArray("memberList", RNZIMConverter.mZIMUserInfoList(memberList));

                        sendEvent("roomMemberLeft", dataMap, zim);
                    }

                    @Override
                    public void onRoomStateChanged(
                        ZIM zim,
                        ZIMRoomState state,
                        ZIMRoomEvent event,
                        JSONObject extendedData,
                        String roomID
                    ) {
                        super.onRoomStateChanged(zim, state, event, extendedData, roomID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("state", state.value());
                        dataMap.putInt("event", event.value());
                        dataMap.putString("extendedData", extendedData.toString());
                        dataMap.putString("roomID", roomID);

                        sendEvent("roomStateChanged", dataMap, zim);
                    }

                    @Override
                    public void onRoomAttributesUpdated(ZIM zim, ZIMRoomAttributesUpdateInfo info, String roomID) {
                        super.onRoomAttributesUpdated(zim, info, roomID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("roomID", roomID);

                        WritableMap infoMap = Arguments.createMap();
                        infoMap.putInt("action", info.action.value());
                        infoMap.putMap("roomAttributes", RNZIMConverter.mMap(info.roomAttributes));

                        WritableArray infoArray = Arguments.createArray();
                        infoArray.pushMap(infoMap);
                        dataMap.putArray("infos", infoArray);

                        sendEvent("roomAttributesUpdated", dataMap, zim);
                    }

                    @Override
                    public void onRoomAttributesBatchUpdated(
                        ZIM zim,
                        ArrayList<ZIMRoomAttributesUpdateInfo> infos,
                        String roomID
                    ) {
                        super.onRoomAttributesBatchUpdated(zim, infos, roomID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("roomID", roomID);

                        WritableArray infoArray = Arguments.createArray();
                        for (ZIMRoomAttributesUpdateInfo info : infos) {
                            WritableMap infoMap = Arguments.createMap();
                            infoMap.putInt("action", info.action.value());
                            infoMap.putMap("roomAttributes", RNZIMConverter.mMap(info.roomAttributes));
                            infoArray.pushMap(infoMap);
                        }

                        dataMap.putArray("infos", infoArray);

                        sendEvent("roomAttributesBatchUpdated", dataMap, zim);
                    }

                    @Override
                    public void onRoomMemberAttributesUpdated(
                        ZIM zim,
                        ArrayList<ZIMRoomMemberAttributesUpdateInfo> infos,
                        ZIMRoomOperatedInfo operatedInfo,
                        String roomID
                    ) {
                        super.onRoomMemberAttributesUpdated(zim, infos, operatedInfo, roomID);

                        WritableMap dataMap = Arguments.createMap();
                        ReadableArray infoArray = RNZIMConverter.mZIMRoomMemberAttributesUpdateInfoList(infos);
                        dataMap.putArray("infos", infoArray);
                        dataMap.putMap("operatedInfo", RNZIMConverter.mZIMRoomOperatedInfo(operatedInfo));
                        dataMap.putString("roomID", roomID);

                        sendEvent("roomMemberAttributesUpdated", dataMap, zim);
                    }

                    @Override
                    public void onGroupStateChanged(
                        ZIM zim,
                        ZIMGroupState state,
                        ZIMGroupEvent event,
                        ZIMGroupOperatedInfo operatedInfo,
                        ZIMGroupFullInfo groupInfo
                    ) {
                        super.onGroupStateChanged(zim, state, event, operatedInfo, groupInfo);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("state", state.value());
                        dataMap.putInt("event", event.value());
                        dataMap.putMap("operatedInfo", RNZIMConverter.mZIMGroupOperatedInfo(operatedInfo));
                        dataMap.putMap("groupInfo", RNZIMConverter.mZIMGroupFullInfo(groupInfo));

                        sendEvent("groupStateChanged", dataMap, zim);
                    }

                    @Override
                    public void onGroupNameUpdated(
                        ZIM zim,
                        String groupName,
                        ZIMGroupOperatedInfo operatedInfo,
                        String groupID
                    ) {
                        super.onGroupNameUpdated(zim, groupName, operatedInfo, groupID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("groupID", groupID);
                        dataMap.putString("groupName", groupName);
                        dataMap.putMap("operatedInfo", RNZIMConverter.mZIMGroupOperatedInfo(operatedInfo));

                        sendEvent("groupNameUpdated", dataMap, zim);
                    }

                    @Override
                    public void onGroupAvatarUrlUpdated(
                        ZIM zim,
                        String groupAvatarUrl,
                        ZIMGroupOperatedInfo operatedInfo,
                        String groupID
                    ) {
                        super.onGroupAvatarUrlUpdated(zim, groupAvatarUrl, operatedInfo, groupID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("groupID", groupID);
                        dataMap.putString("groupAvatarUrl", groupAvatarUrl);
                        dataMap.putMap("operatedInfo", RNZIMConverter.mZIMGroupOperatedInfo(operatedInfo));

                        sendEvent("groupAvatarUrlUpdated", dataMap, zim);
                    }

                    @Override
                    public void onGroupAliasUpdated(ZIM zim, String groupAlias, String operatedUserID, String groupID) {
                        super.onGroupAliasUpdated(zim, groupAlias, operatedUserID, groupID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("groupID", groupID);
                        dataMap.putString("groupAlias", groupAlias);
                        dataMap.putString("operatedUserID", operatedUserID);

                        sendEvent("groupAliasUpdated", dataMap, zim);
                    }

                    @Override
                    public void onGroupNoticeUpdated(
                        ZIM zim,
                        String groupNotice,
                        ZIMGroupOperatedInfo operatedInfo,
                        String groupID
                    ) {
                        super.onGroupNoticeUpdated(zim, groupNotice, operatedInfo, groupID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("groupID", groupID);
                        dataMap.putString("groupNotice", groupNotice);
                        dataMap.putMap("operatedInfo", RNZIMConverter.mZIMGroupOperatedInfo(operatedInfo));

                        sendEvent("groupNoticeUpdated", dataMap, zim);
                    }

                    @Override
                    public void onGroupAttributesUpdated(
                        ZIM zim,
                        ArrayList<ZIMGroupAttributesUpdateInfo> infos,
                        ZIMGroupOperatedInfo operatedInfo,
                        String groupID
                    ) {
                        super.onGroupAttributesUpdated(zim, infos, operatedInfo, groupID);

                        WritableMap dataMap = Arguments.createMap();
                        WritableArray infoArray = Arguments.createArray();
                        for (ZIMGroupAttributesUpdateInfo info : infos) {
                            WritableMap infoMap = Arguments.createMap();
                            infoMap.putInt("action", info.action.value());
                            infoMap.putMap("groupAttributes", RNZIMConverter.mMap(info.groupAttributes));

                            infoArray.pushMap(infoMap);
                        }
                        dataMap.putArray("infoList", infoArray);
                        dataMap.putMap("operatedInfo", RNZIMConverter.mZIMGroupOperatedInfo(operatedInfo));
                        dataMap.putString("groupID", groupID);

                        sendEvent("groupAttributesUpdated", dataMap, zim);
                    }

                    @Override
                    public void onGroupMutedInfoUpdated(
                        ZIM zim,
                        ZIMGroupMuteInfo info,
                        ZIMGroupOperatedInfo operatedInfo,
                        String groupID
                    ) {
                        super.onGroupMutedInfoUpdated(zim, info, operatedInfo, groupID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putMap("mutedInfo", RNZIMConverter.mZIMGroupMuteInfo(info));
                        dataMap.putMap("operatedInfo", RNZIMConverter.mZIMGroupOperatedInfo(operatedInfo));
                        dataMap.putString("groupID", groupID);

                        sendEvent("groupMutedInfoUpdated", dataMap, zim);
                    }

                    @Override
                    public void onGroupVerifyInfoUpdated(
                        ZIM zim,
                        ZIMGroupVerifyInfo info,
                        ZIMGroupOperatedInfo operatedInfo,
                        String groupID
                    ) {
                        super.onGroupVerifyInfoUpdated(zim, info, operatedInfo, groupID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putMap("verifyInfo", RNZIMConverter.mZIMGroupVerifyInfo(info));
                        dataMap.putMap("operatedInfo", RNZIMConverter.mZIMGroupOperatedInfo(operatedInfo));
                        dataMap.putString("groupID", groupID);

                        sendEvent("groupVerifyInfoUpdated", dataMap, zim);
                    }

                    @Override
                    public void onGroupApplicationListChanged(
                        ZIM zim,
                        ArrayList<ZIMGroupApplicationInfo> infos,
                        ZIMGroupApplicationListChangeAction action
                    ) {
                        super.onGroupApplicationListChanged(zim, infos, action);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("action", action.value());
                        dataMap.putArray("applicationList", RNZIMConverter.mZIMGroupApplicationInfoList(infos));

                        sendEvent("groupApplicationListChanged", dataMap, zim);
                    }

                    @Override
                    public void onGroupApplicationUpdated(ZIM zim, ArrayList<ZIMGroupApplicationInfo> infos) {
                        super.onGroupApplicationUpdated(zim, infos);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("applicationList", RNZIMConverter.mZIMGroupApplicationInfoList(infos));

                        sendEvent("groupApplicationUpdated", dataMap, zim);
                    }

                    @Override
                    public void onGroupMemberStateChanged(
                        ZIM zim,
                        ZIMGroupMemberState state,
                        ZIMGroupMemberEvent event,
                        ArrayList<ZIMGroupMemberInfo> userList,
                        ZIMGroupOperatedInfo operatedInfo,
                        String groupID
                    ) {
                        super.onGroupMemberStateChanged(zim, state, event, userList, operatedInfo, groupID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("state", state.value());
                        dataMap.putInt("event", event.value());
                        dataMap.putArray("userList", RNZIMConverter.mZIMGroupMemberInfoList(userList));
                        dataMap.putMap("operatedInfo", RNZIMConverter.mZIMGroupOperatedInfo(operatedInfo));
                        dataMap.putString("groupID", groupID);

                        sendEvent("groupMemberStateChanged", dataMap, zim);
                    }

                    @Override
                    public void onGroupMemberInfoUpdated(
                        ZIM zim,
                        ArrayList<ZIMGroupMemberInfo> userList,
                        ZIMGroupOperatedInfo operatedInfo,
                        String groupID
                    ) {
                        super.onGroupMemberInfoUpdated(zim, userList, operatedInfo, groupID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("userList", RNZIMConverter.mZIMGroupMemberInfoList(userList));
                        dataMap.putMap("operatedInfo", RNZIMConverter.mZIMGroupOperatedInfo(operatedInfo));
                        dataMap.putString("groupID", groupID);

                        sendEvent("groupMemberInfoUpdated", dataMap, zim);
                    }

                    @Override
                    public void onCommunityListChanged(ZIM zim, ZIMCommunityListChangedEventResult result) {
                        super.onCommunityListChanged(zim, result);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray(
                            "changeInfoList",
                            RNZIMConverter.mZIMCommunityChangeInfoList(result.getChangeInfoList())
                        );

                        sendEvent("communityListChanged", dataMap, zim);
                    }

                    @Override
                    public void onCommunityInfoUpdated(ZIM zim, ZIMCommunityInfoUpdatedEventResult result) {
                        super.onCommunityInfoUpdated(zim, result);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray(
                            "updateInfoList",
                            RNZIMConverter.mZIMCommunityFullInfoUpdateInfoList(result.getUpdateInfoList())
                        );

                        sendEvent("communityInfoUpdated", dataMap, zim);
                    }

                    @Override
                    public void onCommunityMemberStateChanged(
                        ZIM zim,
                        ZIMCommunityMemberStateChangedEventResult result
                    ) {
                        super.onCommunityMemberStateChanged(zim, result);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("communityID", result.getCommunityID());
                        dataMap.putArray(
                            "changeInfoList",
                            RNZIMConverter.mZIMCommunityMemberStateChangeInfoList(result.getChangeInfoList())
                        );

                        sendEvent("communityMemberStateChanged", dataMap, zim);
                    }

                    @Override
                    public void onCommunityMemberInfoUpdated(ZIM zim, ZIMCommunityMemberInfoUpdatedEventResult result) {
                        super.onCommunityMemberInfoUpdated(zim, result);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("communityID", result.getCommunityID());
                        dataMap.putArray(
                            "updateInfoList",
                            RNZIMConverter.mZIMCommunityMemberInfoUpdateInfoList(result.getUpdateInfoList())
                        );

                        sendEvent("communityMemberInfoUpdated", dataMap, zim);
                    }

                    @Override
                    public void onCommunityChannelListChanged(
                        ZIM zim,
                        ZIMCommunityChannelListChangedEventResult result
                    ) {
                        super.onCommunityChannelListChanged(zim, result);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("communityID", result.getCommunityID());
                        dataMap.putArray(
                            "changeInfoList",
                            RNZIMConverter.mZIMCommunityChannelChangeInfoList(result.getChangeInfoList())
                        );

                        sendEvent("communityChannelListChanged", dataMap, zim);
                    }

                    @Override
                    public void onCommunityChannelInfoUpdated(
                        ZIM zim,
                        ZIMCommunityChannelInfoUpdatedEventResult result
                    ) {
                        super.onCommunityChannelInfoUpdated(zim, result);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("communityID", result.getCommunityID());
                        dataMap.putArray(
                            "updateInfoList",
                            RNZIMConverter.mZIMCommunityChannelFullInfoUpdateInfoList(result.getUpdateInfoList())
                        );

                        sendEvent("communityChannelInfoUpdated", dataMap, zim);
                    }

                    @Override
                    public void onCallInvitationCreated(ZIM zim, ZIMCallInvitationCreatedInfo info, String callID) {
                        super.onCallInvitationCreated(zim, info, callID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("callID", callID);
                        dataMap.putInt("timeout", info.timeout);
                        dataMap.putString("extendedData", info.extendedData);
                        dataMap.putInt("mode", info.mode.value());
                        dataMap.putString("caller", info.caller);
                        dataMap.putDouble("createTime", info.createTime);
                        dataMap.putArray("callUserList", RNZIMConverter.mZIMCallUserInfoList(info.callUserList));

                        sendEvent("callInvitationCreated", dataMap, zim);
                    }

                    @Override
                    public void onCallInvitationReceived(ZIM zim, ZIMCallInvitationReceivedInfo info, String callID) {
                        super.onCallInvitationReceived(zim, info, callID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("callID", callID);
                        dataMap.putString("inviter", info.inviter);
                        dataMap.putInt("timeout", info.timeout);
                        dataMap.putString("extendedData", info.extendedData);
                        dataMap.putInt("mode", info.mode.value());
                        dataMap.putString("caller", info.caller);
                        dataMap.putDouble("createTime", info.createTime);
                        dataMap.putArray("callUserList", RNZIMConverter.mZIMCallUserInfoList(info.callUserList));

                        sendEvent("callInvitationReceived", dataMap, zim);
                    }

                    @Override
                    public void onCallInvitationCancelled(ZIM zim, ZIMCallInvitationCancelledInfo info, String callID) {
                        super.onCallInvitationCancelled(zim, info, callID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("callID", callID);
                        dataMap.putString("inviter", info.inviter);
                        dataMap.putString("extendedData", info.extendedData);
                        dataMap.putInt("mode", info.mode.value());

                        sendEvent("callInvitationCancelled", dataMap, zim);
                    }

                    @Override
                    public void onCallInvitationTimeout(ZIM zim, ZIMCallInvitationTimeoutInfo info, String callID) {
                        super.onCallInvitationTimeout(zim, info, callID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("callID", callID);
                        dataMap.putInt("mode", info.mode.value());

                        sendEvent("callInvitationTimeout", dataMap, zim);
                    }

                    @Override
                    public void onCallInvitationEnded(ZIM zim, ZIMCallInvitationEndedInfo info, String callID) {
                        super.onCallInvitationEnded(zim, info, callID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("callID", callID);
                        dataMap.putInt("mode", info.mode.value());
                        dataMap.putString("caller", info.caller);
                        dataMap.putString("operatedUserID", info.operatedUserID);
                        dataMap.putString("extendedData", info.extendedData);
                        dataMap.putDouble("endTime", info.endTime);

                        sendEvent("callInvitationEnded", dataMap, zim);
                    }

                    @Override
                    public void onCallUserStateChanged(ZIM zim, ZIMCallUserStateChangeInfo info, String callID) {
                        super.onCallUserStateChanged(zim, info, callID);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putString("callID", callID);
                        dataMap.putArray("callUserList", RNZIMConverter.mZIMCallUserInfoList(info.callUserList));

                        sendEvent("callUserStateChanged", dataMap, zim);
                    }

                    @Override
                    public void onBlacklistChanged(
                        ZIM zim,
                        ArrayList<ZIMUserInfo> infos,
                        ZIMBlacklistChangeAction action
                    ) {
                        super.onBlacklistChanged(zim, infos, action);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("action", action.value());
                        dataMap.putArray("userList", RNZIMConverter.mZIMUserInfoList(infos));

                        sendEvent("blacklistChanged", dataMap, zim);
                    }

                    @Override
                    public void onFriendListChanged(
                        ZIM zim,
                        ArrayList<ZIMFriendInfo> infos,
                        ZIMFriendListChangeAction action
                    ) {
                        super.onFriendListChanged(zim, infos, action);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("action", action.value());
                        dataMap.putArray("friendList", RNZIMConverter.mZIMFriendInfoList(infos));

                        sendEvent("friendListChanged", dataMap, zim);
                    }

                    @Override
                    public void onFriendInfoUpdated(ZIM zim, ArrayList<ZIMFriendInfo> infos) {
                        super.onFriendInfoUpdated(zim, infos);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("friendList", RNZIMConverter.mZIMFriendInfoList(infos));

                        sendEvent("friendInfoUpdated", dataMap, zim);
                    }

                    @Override
                    public void onFriendApplicationListChanged(
                        ZIM zim,
                        ArrayList<ZIMFriendApplicationInfo> infos,
                        ZIMFriendApplicationListChangeAction action
                    ) {
                        super.onFriendApplicationListChanged(zim, infos, action);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putInt("action", action.value());
                        dataMap.putArray("applicationList", RNZIMConverter.mZIMFriendApplicationInfoList(infos));

                        sendEvent("friendApplicationListChanged", dataMap, zim);
                    }

                    @Override
                    public void onFriendApplicationUpdated(ZIM zim, ArrayList<ZIMFriendApplicationInfo> infos) {
                        super.onFriendApplicationUpdated(zim, infos);

                        WritableMap dataMap = Arguments.createMap();
                        dataMap.putArray("applicationList", RNZIMConverter.mZIMFriendApplicationInfoList(infos));

                        sendEvent("friendApplicationUpdated", dataMap, zim);
                    }
                }
            );
        }
    }

    @ReactMethod
    public void destroyEngine(String handle) {
        ZIM zim = this.engineMap.get(handle);
        if (zim != null) {
            zim.destroy();

            this.engineMap.remove(handle);
            this.engineMapForCallback.remove(zim);
        }
    }

    @ReactMethod
    public void uploadLog(String handle, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.uploadLog(
            new ZIMLogUploadedCallback() {
                @Override
                public void onLogUploaded(ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(null);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void callExperimentalAPI(String handle, String params, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.callExperimentalAPI(
            params,
            new ZIMExperimentalAPICalledCallback() {
                @Override
                public void onExperimentalAPICalled(String result, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(result);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void login(String handle, String userID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMLoginConfig configObj = new ZIMLoginConfig();
        configObj.userName = config.hasKey("userName") ? config.getString("userName") : "";
        configObj.customStatus = config.hasKey("customStatus") ? config.getString("customStatus") : "";
        configObj.customStatusDuration = config.hasKey("customStatusDuration")
            ? Double.valueOf(config.getDouble("customStatusDuration")).longValue()
            : 0;
        configObj.token = config.hasKey("token") ? config.getString("token") : "";
        configObj.isOfflineLogin = config.hasKey("isOfflineLogin") ? config.getBoolean("isOfflineLogin") : false;

        zim.login(
            userID,
            configObj,
            new ZIMLoggedInCallback() {
                @Override
                public void onLoggedIn(ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(null);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void logout(String handle, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.logout();

        promise.resolve(null);
    }

    @ReactMethod
    public void renewToken(String handle, String token, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.renewToken(
            token,
            new ZIMTokenRenewedCallback() {
                @Override
                public void onTokenRenewed(String token, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("token", token);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryUsersInfo(String handle, ReadableArray userIDs, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMUsersInfoQueryConfig configObj = new ZIMUsersInfoQueryConfig();
        if (config != null) {
            configObj.isQueryFromServer = config.hasKey("isQueryFromServer")
                ? config.getBoolean("isQueryFromServer")
                : false;
        }

        zim.queryUsersInfo(
            RNZIMConverter.oStringArray(userIDs),
            configObj,
            new ZIMUsersInfoQueriedCallback() {
                @Override
                public void onUsersInfoQueried(
                    ArrayList<ZIMUserFullInfo> userList,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        WritableArray userInfoArray = Arguments.createArray();
                        for (ZIMUserFullInfo userInfo : userList) {
                            userInfoArray.pushMap(RNZIMConverter.mZIMUserFullInfo(userInfo));
                        }

                        returnMap.putArray("userList", userInfoArray);
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateUserBadge(String handle, int badge, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateUserBadge(
            badge,
            new ZIMUserBadgeUpdatedCallback() {
                @Override
                public void onUserBadgeUpdated(ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(null);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateUserName(String handle, String userName, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateUserName(
            userName,
            new ZIMUserNameUpdatedCallback() {
                @Override
                public void onUserNameUpdated(String userName, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("userName", userName);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateUserAvatarUrl(String handle, String userAvatarUrl, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateUserAvatarUrl(
            userAvatarUrl,
            new ZIMUserAvatarUrlUpdatedCallback() {
                @Override
                public void onUserAvatarUrlUpdated(String userAvatarUrl, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("userAvatarUrl", userAvatarUrl);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateUserExtendedData(String handle, String userExtendedData, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateUserExtendedData(
            userExtendedData,
            new ZIMUserExtendedDataUpdatedCallback() {
                @Override
                public void onUserExtendedDataUpdated(String extendedData, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("extendedData", extendedData);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateUserCustomStatus(String handle, String customStatus, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMUserCustomStatusUpdateConfig configObj = new ZIMUserCustomStatusUpdateConfig();
        if (config != null) {
            configObj.setDuration(
                config.hasKey("duration") ? Double.valueOf(config.getDouble("duration")).longValue() : 0
            );
        }

        zim.updateUserCustomStatus(
            customStatus,
            configObj,
            new ZIMUserCustomStatusUpdatedCallback() {
                @Override
                public void onUserCustomStatusUpdated(String customStatus, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("customStatus", customStatus);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateUserOfflinePushRule(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMUserOfflinePushRule configObj = new ZIMUserOfflinePushRule();
        if (config != null) {
            if (config.hasKey("onlinePlatforms")) {
                configObj.onlinePlatforms = RNZIMConverter.oIntArray(config.getArray("onlinePlatforms"));
            }
            if (config.hasKey("notToReceiveOfflinePushPlatforms")) {
                configObj.notToReceiveOfflinePushPlatforms = RNZIMConverter.oIntArray(
                    config.getArray("notToReceiveOfflinePushPlatforms")
                );
            }
        }

        zim.updateUserOfflinePushRule(
            configObj,
            new ZIMUserOfflinePushRuleUpdatedCallback() {
                @Override
                public void onUserOfflinePushRuleUpdated(ZIMUserOfflinePushRule offlinePushRule, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(RNZIMConverter.mZIMUserOfflinePushRule(offlinePushRule));
                    }
                }
            }
        );
    }

    @ReactMethod
    public void querySelfUserInfo(String handle, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.querySelfUserInfo(
            new ZIMSelfUserInfoQueriedCallback() {
                @Override
                public void onSelfUserInfoQueried(ZIMSelfUserInfo info, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        WritableMap infoMap = Arguments.createMap();
                        infoMap.putMap(
                            "userRule",
                            RNZIMConverter.mZIMUserOfflinePushRule(info.userRule.offlinePushRule)
                        );
                        infoMap.putMap("userStatus", RNZIMConverter.mZIMUserStatus(info.userStatus));
                        infoMap.putMap("userFullInfo", RNZIMConverter.mZIMUserFullInfo(info.userFullInfo));
                        returnMap.putMap("selfUserInfo", infoMap);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryLocalFileCache(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMFileCacheQueryConfig configObj = new ZIMFileCacheQueryConfig();
        configObj.endTime = config.hasKey("endTime") ? Double.valueOf(config.getDouble("endTime")).longValue() : 0;

        zim.queryLocalFileCache(
            configObj,
            new ZIMFileCacheQueriedCallback() {
                @Override
                public void onFileCacheQueried(ZIMFileCacheInfo info, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putDouble("totalFileSize", info.totalFileSize);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void clearLocalFileCache(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMFileCacheClearConfig configObj = new ZIMFileCacheClearConfig();
        configObj.endTime = config.hasKey("endTime") ? Double.valueOf(config.getDouble("endTime")).longValue() : 0;

        zim.clearLocalFileCache(
            configObj,
            new ZIMFileCacheClearedCallback() {
                @Override
                public void onFileCacheCleared(ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(null);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void subscribeUsersStatus(
        String handle,
        boolean isSub,
        ReadableArray userIDs,
        int duration,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ArrayList<String> userList = RNZIMConverter.oStringArray(userIDs);

        if (isSub) {
            ZIMUserStatusSubscribeConfig configObj = new ZIMUserStatusSubscribeConfig();
            configObj.subscriptionDuration = duration;

            zim.subscribeUsersStatus(
                userList,
                configObj,
                new ZIMUsersStatusSubscribedCallback() {
                    @Override
                    public void onUsersStatusSubscribed(ArrayList<ZIMErrorUserInfo> errorUserList, ZIMError errorInfo) {
                        if (isSucc(errorInfo, promise)) {
                            WritableMap returnMap = Arguments.createMap();
                            returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                            promise.resolve(returnMap);
                        }
                    }
                }
            );
        } else {
            zim.unsubscribeUsersStatus(
                userList,
                new ZIMUsersStatusUnsubscribedCallback() {
                    @Override
                    public void onUsersStatusUnsubscribed(
                        ArrayList<ZIMErrorUserInfo> errorUserList,
                        ZIMError errorInfo
                    ) {
                        if (isSucc(errorInfo, promise)) {
                            WritableMap returnMap = Arguments.createMap();
                            returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                            promise.resolve(returnMap);
                        }
                    }
                }
            );
        }
    }

    @ReactMethod
    public void queryUsersStatus(String handle, ReadableArray userIDs, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryUsersStatus(
            RNZIMConverter.oStringArray(userIDs),
            new ZIMUsersStatusQueriedCallback() {
                @Override
                public void onUsersStatusQueried(
                    ArrayList<ZIMUserStatus> userStatusList,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putArray("userStatusList", RNZIMConverter.mZIMUserStatusList(userStatusList));
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void querySubscribedUsersStatus(String handle, ReadableArray userIDs, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMSubscribedUserStatusQueryConfig configObj = new ZIMSubscribedUserStatusQueryConfig();
        configObj.userIDs = RNZIMConverter.oStringArray(userIDs);

        zim.querySubscribedUserStatusList(
            configObj,
            new ZIMSubscribedUserStatusListQueriedCallback() {
                @Override
                public void onSubscribedUserStatusListQueried(
                    ArrayList<ZIMUserStatusSubscription> userStatusSubscriptionList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        WritableArray userStatusArray = Arguments.createArray();
                        for (ZIMUserStatusSubscription info : userStatusSubscriptionList) {
                            WritableMap infoMap = Arguments.createMap();
                            infoMap.putMap("userStatus", RNZIMConverter.mZIMUserStatus(info.userStatus));
                            infoMap.putDouble("subscribeExpiredTime", info.subscribeExpiredTime);

                            userStatusArray.pushMap(infoMap);
                        }

                        returnMap.putArray("userStatusSubscriptionList", userStatusArray);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryConversation(String handle, String conversationID, int conversationType, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryConversation(
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            new ZIMConversationQueriedCallback() {
                @Override
                public void onConversationQueried(ZIMConversation conversation, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("conversation", RNZIMConverter.mZIMConversation(conversation));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryConversationList(String handle, ReadableMap config, ReadableMap option, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMConversationQueryConfig configObj = new ZIMConversationQueryConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.nextConversation = config.hasKey("nextConversation")
                ? RNZIMConverter.oZIMConversation(config.getMap("nextConversation"))
                : null;
        }

        ZIMConversationFilterOption optionObj = new ZIMConversationFilterOption();
        if (option != null) {
            optionObj.isOnlyUnreadConversation = option.hasKey("isOnlyUnreadConversation")
                ? option.getBoolean("isOnlyUnreadConversation")
                : false;
            if (option.hasKey("pinnedFilter")) {
                optionObj.pinnedFilter = ZIMConversationPinnedFilter.getZIMConversationPinnedFilter(
                    option.getInt("pinnedFilter")
                );
            }

            if (option.hasKey("marks")) {
                optionObj.marks = RNZIMConverter.oIntArray(option.getArray("marks"));
            }

            if (option.hasKey("conversationTypes")) {
                optionObj.conversationTypes = RNZIMConverter.oZIMConversationTypeArray(
                    option.getArray("conversationTypes")
                );
            }
        }

        zim.queryConversationList(
            configObj,
            optionObj,
            new ZIMConversationListQueriedCallback() {
                @Override
                public void onConversationListQueried(ArrayList<ZIMConversation> conversationList, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putArray("conversationList", RNZIMConverter.mZIMConversationList(conversationList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryConversationPinnedList(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMConversationQueryConfig configObj = new ZIMConversationQueryConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.nextConversation = config.hasKey("nextConversation")
                ? RNZIMConverter.oZIMConversation(config.getMap("nextConversation"))
                : null;
        }

        zim.queryConversationPinnedList(
            configObj,
            new ZIMConversationPinnedListQueriedCallback() {
                @Override
                public void onConversationPinnedListQueried(
                    ArrayList<ZIMConversation> conversationList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putArray("conversationList", RNZIMConverter.mZIMConversationList(conversationList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryConversationTotalUnreadMessageCount(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMConversationTotalUnreadMessageCountQueryConfig configObj =
            new ZIMConversationTotalUnreadMessageCountQueryConfig();
        if (config != null) {
            if (config.hasKey("marks")) {
                configObj.marks = RNZIMConverter.oIntArray(config.getArray("marks"));
            }

            if (config.hasKey("conversationTypes")) {
                configObj.conversationTypes = RNZIMConverter.oZIMConversationTypeArray(
                    config.getArray("conversationTypes")
                );
            }
        }

        zim.queryConversationTotalUnreadMessageCount(
            configObj,
            new ZIMConversationTotalUnreadMessageCountQueriedCallback() {
                @Override
                public void onConversationTotalUnreadMessageCountQueried(int unreadMessageCount, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putInt("unreadMessageCount", unreadMessageCount);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void deleteConversation(
        String handle,
        String conversationID,
        int conversationType,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMConversationDeleteConfig configObj = new ZIMConversationDeleteConfig();
        if (config != null) {
            configObj.isAlsoDeleteServerConversation = config.hasKey("isAlsoDeleteServerConversation")
                ? config.getBoolean("isAlsoDeleteServerConversation")
                : true;
        }

        zim.deleteConversation(
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            configObj,
            new ZIMConversationDeletedCallback() {
                @Override
                public void onConversationDeleted(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void deleteAllConversations(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMConversationDeleteConfig configObj = new ZIMConversationDeleteConfig();
        if (config != null) {
            configObj.isAlsoDeleteServerConversation = config.hasKey("isAlsoDeleteServerConversation")
                ? config.getBoolean("isAlsoDeleteServerConversation")
                : true;
        }

        zim.deleteAllConversations(
            configObj,
            new ZIMConversationsAllDeletedCallback() {
                @Override
                public void onConversationsAllDeleted(ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(null);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setConversationNotificationStatus(
        String handle,
        int status,
        String conversationID,
        int conversationType,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.setConversationNotificationStatus(
            ZIMConversationNotificationStatus.getZIMConversationNotificationStatus(status),
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            new ZIMConversationNotificationStatusSetCallback() {
                @Override
                public void onConversationNotificationStatusSet(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setConversationDraft(
        String handle,
        String draft,
        String conversationID,
        int conversationType,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.setConversationDraft(
            draft,
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            new ZIMConversationDraftSetCallback() {
                @Override
                public void onConversationDraftSet(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateConversationPinnedState(
        String handle,
        boolean isPinned,
        String conversationID,
        int conversationType,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateConversationPinnedState(
            isPinned,
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            new ZIMConversationPinnedStateUpdatedCallback() {
                @Override
                public void onConversationPinnedStateUpdated(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setConversationMark(
        String handle,
        int markType,
        boolean enable,
        ReadableArray conversationInfos,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ArrayList<ZIMConversationBaseInfo> infos = new ArrayList<>();
        for (Object info : conversationInfos.toArrayList()) {
            ZIMConversationType type = ZIMConversationType.getZIMConversationType(
                Double.valueOf((double) ((HashMap<String, Object>) info).get("conversationType")).intValue()
            );
            ZIMConversationBaseInfo infoObj = new ZIMConversationBaseInfo();
            infoObj.conversationID = (String) ((HashMap<String, Object>) info).get("conversationID");
            infoObj.conversationType = type;

            infos.add(infoObj);
        }

        zim.setConversationMark(
            markType,
            enable,
            infos,
            new ZIMConversationMarkSetCallback() {
                @Override
                public void onConversationMarkSet(
                    ArrayList<ZIMConversationBaseInfo> failedConversationInfos,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        WritableArray infoArray = Arguments.createArray();
                        for (ZIMConversationBaseInfo info : failedConversationInfos) {
                            WritableMap infoMap = Arguments.createMap();
                            infoMap.putString("conversationID", info.conversationID);
                            infoMap.putInt("conversationType", info.conversationType.value());
                            infoArray.pushMap(infoMap);
                        }
                        returnMap.putArray("failedConversationInfos", infoArray);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setConversationDestructDuration(
        String handle,
        int duration,
        String conversationID,
        int conversationType,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.setConversationMessageDestructDuration(
            duration,
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            new ZIMConversationMessageDestructDurationSetCallback() {
                @Override
                public void onConversationMessageDestructDurationSet(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void clearConversationUnreadMessageCount(
        String handle,
        String conversationID,
        int conversationType,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.clearConversationUnreadMessageCount(
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            new ZIMConversationUnreadMessageCountClearedCallback() {
                @Override
                public void onConversationUnreadMessageCountCleared(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void clearConversationTotalUnreadMessageCount(String handle, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.clearConversationTotalUnreadMessageCount(
            new ZIMConversationTotalUnreadMessageCountClearedCallback() {
                @Override
                public void onConversationTotalUnreadMessageCountCleared(ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(null);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void deleteAllConversationMessages(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessageDeleteConfig configObj = new ZIMMessageDeleteConfig();
        if (config != null) {
            configObj.isAlsoDeleteServerMessage = config.hasKey("isAlsoDeleteServerMessage")
                ? config.getBoolean("isAlsoDeleteServerMessage")
                : true;
        }

        zim.deleteAllConversationMessages(
            configObj,
            new ZIMConversationMessagesAllDeletedCallback() {
                @Override
                public void onConversationMessagesAllDeleted(ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(null);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void sendMessage(
        String handle,
        ReadableMap message,
        String conversationID,
        int conversationType,
        ReadableMap config,
        final String _handle,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMConversationType type = ZIMConversationType.getZIMConversationType(conversationType);
        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        ZIMMessageSendConfig configObj = new ZIMMessageSendConfig();
        if (config != null) {
            configObj.priority = config.hasKey("priority")
                ? ZIMMessagePriority.getZIMMessagePriority(config.getInt("priority"))
                : ZIMMessagePriority.LOW;
            configObj.hasReceipt = config.hasKey("hasReceipt") ? config.getBoolean("hasReceipt") : false;
            configObj.isRetrySend = config.hasKey("isRetrySend") ? config.getBoolean("isRetrySend") : false;
            configObj.isNotifyMentionedUsers = config.hasKey("isNotifyMentionedUsers")
                ? config.getBoolean("isNotifyMentionedUsers")
                : true;
            configObj.disableUnreadMessageCount = config.hasKey("disableUnreadMessageCount")
                ? config.getBoolean("disableUnreadMessageCount")
                : false;
            configObj.isAllowMultipleMessagePartialFailed = config.hasKey("isAllowMultipleMessagePartialFailed")
                ? config.getBoolean("isAllowMultipleMessagePartialFailed")
                : false;
            configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);
        }

        zim.sendMessage(
            messageObj,
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            configObj,
            new ZIMMessageSentFullCallback() {
                @Override
                public void onMessageSent(ZIMMessage message, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        WritableMap messageMap = RNZIMConverter.mZIMMessage(message);
                        returnMap.putMap("message", messageMap);
                        promise.resolve(returnMap);
                    }
                }

                @Override
                public void onMessageAttached(ZIMMessage message) {
                    WritableMap paramsMap = Arguments.createMap();
                    paramsMap.putString("handle", _handle);

                    WritableArray dataArray = Arguments.createArray();
                    dataArray.pushMap(RNZIMConverter.mZIMMessage(message));
                    paramsMap.putArray("data", dataArray);

                    sendAPIEvent("messageAttachedHook", paramsMap);
                }

                @Override
                public void onMediaUploadingProgress(
                    ZIMMediaMessage message,
                    long currentFileSize,
                    long totalFileSize
                ) {
                    WritableMap paramsMap = Arguments.createMap();
                    paramsMap.putString("handle", _handle);

                    WritableArray dataArray = Arguments.createArray();
                    dataArray.pushMap(RNZIMConverter.mZIMMessage(message));
                    dataArray.pushDouble(currentFileSize);
                    dataArray.pushDouble(totalFileSize);
                    paramsMap.putArray("data", dataArray);

                    sendAPIEvent("messageLoadingProgress", paramsMap);
                }

                @Override
                public void onMultipleMediaUploadingProgress(
                    ZIMMultipleMessage message,
                    long currentFileSize,
                    long totalFileSize,
                    int messageInfoIndex,
                    long currentIndexFileSize,
                    long totalIndexFileSize
                ) {
                    WritableMap paramsMap = Arguments.createMap();
                    paramsMap.putString("handle", _handle);

                    WritableArray dataArray = Arguments.createArray();
                    dataArray.pushMap(RNZIMConverter.mZIMMessage(message));
                    dataArray.pushDouble(currentFileSize);
                    dataArray.pushDouble(totalFileSize);
                    dataArray.pushDouble(messageInfoIndex);
                    dataArray.pushDouble(currentIndexFileSize);
                    dataArray.pushDouble(totalIndexFileSize);
                    paramsMap.putArray("data", dataArray);

                    sendAPIEvent("messageLoadingProgress", paramsMap);
                }
            }
        );
    }

    @ReactMethod
    public void editMessage(String handle, ReadableMap message, final String _handle, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        ZIMMessageEditConfig configObj = new ZIMMessageEditConfig();

        zim.editMessage(
            messageObj,
            configObj,
            new ZIMMessageSentFullCallback() {
                @Override
                public void onMessageSent(ZIMMessage message, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        WritableMap messageMap = RNZIMConverter.mZIMMessage(message);
                        returnMap.putMap("message", messageMap);
                        promise.resolve(returnMap);
                    }
                }

                @Override
                public void onMessageAttached(ZIMMessage message) {
                    WritableMap paramsMap = Arguments.createMap();
                    paramsMap.putString("handle", _handle);

                    WritableArray dataArray = Arguments.createArray();
                    dataArray.pushMap(RNZIMConverter.mZIMMessage(message));
                    paramsMap.putArray("data", dataArray);

                    sendAPIEvent("messageAttachedHook", paramsMap);
                }

                @Override
                public void onMediaUploadingProgress(
                    ZIMMediaMessage message,
                    long currentFileSize,
                    long totalFileSize
                ) {
                    WritableMap paramsMap = Arguments.createMap();
                    paramsMap.putString("handle", _handle);

                    WritableArray dataArray = Arguments.createArray();
                    dataArray.pushMap(RNZIMConverter.mZIMMessage(message));
                    dataArray.pushDouble(currentFileSize);
                    dataArray.pushDouble(totalFileSize);
                    paramsMap.putArray("data", dataArray);

                    sendAPIEvent("messageLoadingProgress", paramsMap);
                }

                @Override
                public void onMultipleMediaUploadingProgress(
                    ZIMMultipleMessage message,
                    long currentFileSize,
                    long totalFileSize,
                    int messageInfoIndex,
                    long currentIndexFileSize,
                    long totalIndexFileSize
                ) {
                    WritableMap paramsMap = Arguments.createMap();
                    paramsMap.putString("handle", _handle);

                    WritableArray dataArray = Arguments.createArray();
                    dataArray.pushMap(RNZIMConverter.mZIMMessage(message));
                    dataArray.pushDouble(currentFileSize);
                    dataArray.pushDouble(totalFileSize);
                    dataArray.pushDouble(messageInfoIndex);
                    dataArray.pushDouble(currentIndexFileSize);
                    dataArray.pushDouble(totalIndexFileSize);
                    paramsMap.putArray("data", dataArray);

                    sendAPIEvent("messageLoadingProgress", paramsMap);
                }
            }
        );
    }

    @ReactMethod
    public void deleteMessages(
        String handle,
        ReadableArray messageList,
        String conversationID,
        int conversationType,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessageDeleteConfig configObj = new ZIMMessageDeleteConfig();
        if (config != null) {
            configObj.isAlsoDeleteServerMessage = config.hasKey("isAlsoDeleteServerMessage")
                ? config.getBoolean("isAlsoDeleteServerMessage")
                : true;
        }

        zim.deleteMessages(
            RNZIMConverter.oZIMMessageList(messageList),
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            configObj,
            new ZIMMessageDeletedCallback() {
                @Override
                public void onMessageDeleted(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void deleteAllMessage(
        String handle,
        String conversationID,
        int conversationType,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessageDeleteConfig configObj = new ZIMMessageDeleteConfig();
        if (config != null) {
            configObj.isAlsoDeleteServerMessage = config.hasKey("isAlsoDeleteServerMessage")
                ? config.getBoolean("isAlsoDeleteServerMessage")
                : true;
        }

        zim.deleteAllMessage(
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            configObj,
            new ZIMMessageDeletedCallback() {
                @Override
                public void onMessageDeleted(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryHistoryMessage(
        String handle,
        String conversationID,
        int conversationType,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessageQueryConfig configObj = new ZIMMessageQueryConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.reverse = config.hasKey("reverse") ? config.getBoolean("reverse") : false;
            configObj.mode = ZIMMessageQueryMode.getEnum(config.hasKey("mode") ? config.getInt("mode") : 0);
            configObj.nextMessage = config.hasKey("nextMessage")
                ? RNZIMConverter.oZIMMessage(config.getMap("nextMessage"))
                : null;
        }

        zim.queryHistoryMessage(
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            configObj,
            new ZIMMessageQueriedCallback() {
                @Override
                public void onMessageQueried(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ArrayList<ZIMMessage> messageList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());
                        returnMap.putArray("messageList", RNZIMConverter.mZIMMessageList(messageList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void downloadMediaFile(
        String handle,
        ReadableMap message,
        int fileType,
        ReadableMap config,
        final String _handle,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        ZIMMediaDownloadConfig configObj = new ZIMMediaDownloadConfig();
        configObj.messageInfoIndex = config.hasKey("messageInfoIndex") ? config.getInt("messageInfoIndex") : 0;

        zim.downloadMediaFile(
            messageObj,
            ZIMMediaFileType.getZIMMediaFileType(fileType),
            configObj,
            new ZIMMediaDownloadedCallback() {
                @Override
                public void onMediaDownloaded(ZIMMessage message, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        WritableMap messageMap = RNZIMConverter.mZIMMessage(message);
                        returnMap.putMap("message", messageMap);
                        promise.resolve(returnMap);
                    }
                }

                @Override
                public void onMediaDownloadingProgress(ZIMMessage message, long currentFileSize, long totalFileSize) {
                    WritableMap paramsMap = Arguments.createMap();
                    paramsMap.putString("handle", _handle);

                    WritableArray dataArray = Arguments.createArray();
                    dataArray.pushMap(RNZIMConverter.mZIMMessage(message));
                    dataArray.pushDouble(currentFileSize);
                    dataArray.pushDouble(totalFileSize);
                    paramsMap.putArray("data", dataArray);

                    sendAPIEvent("messageLoadingProgress", paramsMap);
                }
            }
        );
    }

    @ReactMethod
    public void insertMessageToLocalDB(
        String handle,
        ReadableMap message,
        String conversationID,
        int conversationType,
        String senderUserID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        zim.insertMessageToLocalDB(
            messageObj,
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            senderUserID,
            new ZIMMessageInsertedCallback() {
                @Override
                public void onMessageInserted(ZIMMessage message, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        WritableMap messageMap = RNZIMConverter.mZIMMessage(message);
                        returnMap.putMap("message", messageMap);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateMessageLocalExtendedData(
        String handle,
        String localExtendedData,
        ReadableMap message,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        zim.updateMessageLocalExtendedData(
            localExtendedData,
            messageObj,
            new ZIMMessageLocalExtendedDataUpdatedCallback() {
                @Override
                public void onMessageLocalExtendedDataUpdated(ZIMMessage message, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        WritableMap messageMap = RNZIMConverter.mZIMMessage(message);
                        returnMap.putMap("message", messageMap);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void cancelSendingMessage(String handle, ReadableMap message, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        zim.cancelSendingMessage(
            messageObj,
            new ZIMSendingMessageCancelConfig(),
            new ZIMSendingMessageCancelledCallback() {
                @Override
                public void onSendingMessageCancelled(ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(null);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void sendConversationMessageReceiptRead(
        String handle,
        String conversationID,
        int conversationType,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.sendConversationMessageReceiptRead(
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            new ZIMConversationMessageReceiptReadSentCallback() {
                @Override
                public void onConversationMessageReceiptReadSent(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void sendMessageReceiptsRead(
        String handle,
        ReadableArray messageList,
        String conversationID,
        int conversationType,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.sendMessageReceiptsRead(
            RNZIMConverter.oZIMMessageList(messageList),
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            new ZIMMessageReceiptsReadSentCallback() {
                @Override
                public void onMessageReceiptsReadSent(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ArrayList<Long> errorMessageIDs,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());

                        WritableArray errorMessageIDsArray = Arguments.createArray();
                        for (long key : errorMessageIDs) {
                            errorMessageIDsArray.pushString(String.valueOf(key));
                        }
                        returnMap.putArray("errorMessageIDs", errorMessageIDsArray);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryMessageReceiptsInfo(
        String handle,
        ReadableArray messageList,
        String conversationID,
        int conversationType,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryMessageReceiptsInfo(
            RNZIMConverter.oZIMMessageList(messageList),
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            new ZIMMessageReceiptsInfoQueriedCallback() {
                @Override
                public void onMessageReceiptsInfoQueried(
                    ArrayList<ZIMMessageReceiptInfo> infos,
                    ArrayList<Long> errorMessageIDs,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        returnMap.putArray("infos", RNZIMConverter.mZIMMessageReceiptInfo(infos));

                        WritableArray errorMessageIDsArray = Arguments.createArray();
                        for (long key : errorMessageIDs) {
                            errorMessageIDsArray.pushString(String.valueOf(key));
                        }
                        returnMap.putArray("errorMessageIDs", errorMessageIDsArray);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryGroupMessageReceiptMemberList(
        String handle,
        ReadableMap message,
        String groupID,
        int count,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        ZIMGroupMessageReceiptMemberListQueryConfig configObj = new ZIMGroupMessageReceiptMemberListQueryConfig();
        if (config != null) {
            configObj.filterType = ZIMMessageReceiptFilterType.getEnum(config.hasKey("filterType") ? config.getInt("filterType") : 0);
            configObj.nextFlag = config.hasKey("nextFlag") ? config.getInt("nextFlag") : 0;
        }            

        zim.queryGroupMessageReceiptMemberList(messageObj, groupID, count, configObj, new ZIMGroupMessageReceiptMemberListQueriedCallback() {
            @Override
            public void onGroupMessageReceiptMemberListQueried(
                String groupID,
                ArrayList<ZIMGroupMemberInfo> userList,
                int nextFlag,
                ZIMError errorInfo
            ) {
                if (isSucc(errorInfo, promise)) {
                    WritableMap returnMap = Arguments.createMap();
                    returnMap.putString("groupID", groupID);
                    returnMap.putInt("nextFlag", nextFlag);
                    returnMap.putArray("userList", RNZIMConverter.mZIMGroupMemberInfoList(userList));

                    promise.resolve(returnMap);
                }
            }
        });
    }

    @ReactMethod
    public void revokeMessage(String handle, ReadableMap message, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        ZIMMessageRevokeConfig configObj = new ZIMMessageRevokeConfig();
        if (config != null) {
            configObj.revokeExtendedData = config.hasKey("revokeExtendedData")
                ? config.getString("revokeExtendedData")
                : "";
            configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);
        }

        zim.revokeMessage(
            messageObj,
            configObj,
            new ZIMMessageRevokedCallback() {
                @Override
                public void onMessageRevoked(ZIMMessage message, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("message", RNZIMConverter.mZIMMessage(message));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryCombineMessageDetail(String handle, ReadableMap message, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCombineMessage messageObj = (ZIMCombineMessage) RNZIMConverter.oZIMMessage(message);

        zim.queryCombineMessageDetail(
            messageObj,
            new ZIMCombineMessageDetailQueriedCallback() {
                @Override
                public void onCombineMessageDetailQueried(ZIMCombineMessage message, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("message", RNZIMConverter.mZIMMessage(message));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void pinMessage(
        String handle,
        ReadableMap message,
        boolean isPinned,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        ZIMMessagePinConfig configObj = new ZIMMessagePinConfig();
        if (config != null) {
            configObj.disableAutoReplace = config.hasKey("disableAutoReplace")
                ? config.getBoolean("disableAutoReplace")
                : false;
        }

        zim.pinMessage(
            messageObj,
            isPinned,
            configObj,
            new ZIMMessagePinnedCallback() {
                @Override
                public void onMessagePinned(ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(null);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryPinnedMessageList(
        String handle,
        String conversationID,
        int conversationType,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryPinnedMessageList(
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            new ZIMPinnedMessageListQueriedCallback() {
                @Override
                public void onPinnedMessageListQueried(ArrayList<ZIMMessage> messageList, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putArray("messageList", RNZIMConverter.mZIMMessageList(messageList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void addMessageReaction(
        String handle,
        String reactionType,
        ReadableMap message,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        ZIMMessageReactionAddConfig configObj = new ZIMMessageReactionAddConfig();
        if (config != null) {
            configObj.setIncreaseCount(config.hasKey("increaseCount") ? config.getInt("increaseCount") : 0);
        }

        zim.addMessageReaction(
            reactionType,
            messageObj,
            configObj,
            new ZIMMessageReactionAddedCallback() {
                @Override
                public void onMessageReactionAdded(ZIMMessageReaction reaction, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("reaction", RNZIMConverter.mZIMMessageReaction(reaction));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void deleteMessageReaction(String handle, String reactionType, ReadableMap message, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);

        zim.deleteMessageReaction(
            reactionType,
            messageObj,
            new ZIMMessageReactionDeletedCallback() {
                @Override
                public void onMessageReactionDeleted(ZIMMessageReaction reaction, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("reaction", RNZIMConverter.mZIMMessageReaction(reaction));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void deleteMessageAllReactions(String handle, ReadableMap message, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);

        zim.deleteMessageAllReactions(
            messageObj,
            new ZIMMessageReactionsAllDeletedCallback() {
                @Override
                public void onMessageReactionsAllDeleted(ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        promise.resolve(null);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryMessageReactionUserList(
        String handle,
        ReadableMap message,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        ZIMMessageReactionUserQueryConfig configObj = new ZIMMessageReactionUserQueryConfig();
        if (config != null) {
            configObj.reactionType = config.hasKey("reactionType") ? config.getString("reactionType") : "";
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.nextFlag = config.hasKey("nextFlag")
                ? Double.valueOf(config.getDouble("nextFlag")).longValue()
                : 0;
        }

        zim.queryMessageReactionUserList(
            messageObj,
            configObj,
            new ZIMMessageReactionUserListQueriedCallback() {
                @Override
                public void onMessageReactionUserListQueried(
                    ZIMMessage message,
                    ArrayList<ZIMMessageReactionUserFullInfo> userInfoList,
                    String reactionType,
                    long nextFlag,
                    int totalCount,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("reactionType", reactionType);
                        returnMap.putDouble("nextFlag", nextFlag);
                        returnMap.putInt("totalCount", totalCount);

                        WritableArray userArray = Arguments.createArray();
                        for (ZIMMessageReactionUserFullInfo user : userInfoList) {
                            WritableMap userMap = Arguments.createMap();
                            userMap.putString("userID", user.getUserID());

                            WritableArray reactionsArray = Arguments.createArray();
                            for (ZIMMessageReactionSimpleInfo info : user.getReactions()) {
                                WritableMap map = Arguments.createMap();
                                map.putString("reactionType", info.getReactionType());
                                map.putInt("sumCount", info.getSumCount());
                                reactionsArray.pushMap(map);
                            }
                            userMap.putArray("reactions", reactionsArray);

                            userArray.pushMap(userMap);
                        }
                        returnMap.putArray("userInfoList", userArray);

                        returnMap.putMap("message", RNZIMConverter.mZIMMessage(message));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void replyMessage(
        String handle,
        ReadableMap message,
        ReadableMap originMessage,
        ReadableMap config,
        final String _handle,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        ZIMMessage originMessageObj = RNZIMConverter.oZIMMessage(originMessage);

        ZIMMessageSendConfig configObj = new ZIMMessageSendConfig();
        if (config != null) {
            configObj.priority = config.hasKey("priority")
                ? ZIMMessagePriority.getZIMMessagePriority(config.getInt("priority"))
                : ZIMMessagePriority.LOW;
            configObj.hasReceipt = config.hasKey("hasReceipt") ? config.getBoolean("hasReceipt") : false;
            configObj.isRetrySend = config.hasKey("isRetrySend") ? config.getBoolean("isRetrySend") : false;
            configObj.isNotifyMentionedUsers = config.hasKey("isNotifyMentionedUsers")
                ? config.getBoolean("isNotifyMentionedUsers")
                : true;
            configObj.disableUnreadMessageCount = config.hasKey("disableUnreadMessageCount")
                ? config.getBoolean("disableUnreadMessageCount")
                : false;
            configObj.isAllowMultipleMessagePartialFailed = config.hasKey("isAllowMultipleMessagePartialFailed")
                ? config.getBoolean("isAllowMultipleMessagePartialFailed")
                : false;
            configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);
        }

        zim.replyMessage(
            messageObj,
            originMessageObj,
            configObj,
            new ZIMMessageSentFullCallback() {
                @Override
                public void onMessageSent(ZIMMessage message, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("message", RNZIMConverter.mZIMMessage(message));
                        promise.resolve(returnMap);
                    }
                }

                @Override
                public void onMessageAttached(ZIMMessage message) {
                    WritableMap paramsMap = Arguments.createMap();
                    paramsMap.putString("handle", _handle);

                    WritableArray dataArray = Arguments.createArray();
                    dataArray.pushMap(RNZIMConverter.mZIMMessage(message));
                    paramsMap.putArray("data", dataArray);

                    sendAPIEvent("messageAttachedHook", paramsMap);
                }

                @Override
                public void onMediaUploadingProgress(
                    ZIMMediaMessage message,
                    long currentFileSize,
                    long totalFileSize
                ) {
                    WritableMap paramsMap = Arguments.createMap();
                    paramsMap.putString("handle", _handle);

                    WritableArray dataArray = Arguments.createArray();
                    dataArray.pushMap(RNZIMConverter.mZIMMessage(message));
                    dataArray.pushDouble(currentFileSize);
                    dataArray.pushDouble(totalFileSize);
                    paramsMap.putArray("data", dataArray);

                    sendAPIEvent("messageLoadingProgress", paramsMap);
                }

                @Override
                public void onMultipleMediaUploadingProgress(
                    ZIMMultipleMessage message,
                    long currentFileSize,
                    long totalFileSize,
                    int messageInfoIndex,
                    long currentIndexFileSize,
                    long totalIndexFileSize
                ) {
                    WritableMap paramsMap = Arguments.createMap();
                    paramsMap.putString("handle", _handle);

                    WritableArray dataArray = Arguments.createArray();
                    dataArray.pushMap(RNZIMConverter.mZIMMessage(message));
                    dataArray.pushDouble(currentFileSize);
                    dataArray.pushDouble(totalFileSize);
                    dataArray.pushDouble(messageInfoIndex);
                    dataArray.pushDouble(currentIndexFileSize);
                    dataArray.pushDouble(totalIndexFileSize);
                    paramsMap.putArray("data", dataArray);

                    sendAPIEvent("messageLoadingProgress", paramsMap);
                }
            }
        );
    }

    @ReactMethod
    public void queryMessageRepliedList(String handle, ReadableMap message, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessage messageObj = RNZIMConverter.oZIMMessage(message);
        ZIMMessageRepliedListQueryConfig configObj = new ZIMMessageRepliedListQueryConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.nextFlag = config.hasKey("nextFlag")
                ? Double.valueOf(config.getDouble("nextFlag")).longValue()
                : 0;
        }

        zim.queryMessageRepliedList(
            messageObj,
            configObj,
            new ZIMMessageRepliedListQueriedCallback() {
                @Override
                public void onMessageRepliedListQueried(
                    ArrayList<ZIMMessage> messageList,
                    long nextFlag,
                    ZIMMessageRootRepliedInfo rootRepliedInfo,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap infoMap = Arguments.createMap();
                        infoMap.putInt("state", rootRepliedInfo.state.value());
                        infoMap.putDouble("repliedCount", rootRepliedInfo.repliedCount);
                        infoMap.putString("senderUserID", rootRepliedInfo.senderUserID);
                        infoMap.putDouble("sentTime", rootRepliedInfo.sentTime);
                        if (rootRepliedInfo.message != null) {
                            infoMap.putMap("message", RNZIMConverter.mZIMMessage(rootRepliedInfo.message));
                        }

                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putDouble("nextFlag", nextFlag);
                        returnMap.putMap("rootRepliedInfo", infoMap);
                        returnMap.putArray("messageList", RNZIMConverter.mZIMMessageList(messageList));

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryMessages(
        String handle,
        ReadableArray messageSeqs,
        String conversationID,
        int conversationType,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ArrayList<Long> messageSeqList = new ArrayList<>();
        for (Object key : messageSeqs.toArrayList()) {
            messageSeqList.add(Double.valueOf((double) key).longValue());
        }

        zim.queryMessages(
            messageSeqList,
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            new ZIMMessageQueriedCallback() {
                @Override
                public void onMessageQueried(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ArrayList<ZIMMessage> messageList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());
                        returnMap.putArray("messageList", RNZIMConverter.mZIMMessageList(messageList));

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void createRoom(String handle, ReadableMap roomInfo, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMRoomCreatedCallback callback = new ZIMRoomCreatedCallback() {
            @Override
            public void onRoomCreated(ZIMRoomFullInfo roomInfo, ZIMError errorInfo) {
                if (isSucc(errorInfo, promise)) {
                    WritableMap returnMap = Arguments.createMap();
                    returnMap.putMap("roomInfo", RNZIMConverter.mZIMRoomFullInfo(roomInfo));
                    promise.resolve(returnMap);
                }
            }
        };

        ZIMRoomInfo roomInfoObj = new ZIMRoomInfo();
        roomInfoObj.roomID = roomInfo.getString("roomID");
        roomInfoObj.roomName = roomInfo.hasKey("roomName") ? roomInfo.getString("roomName") : "";

        if (config != null) {
            ZIMRoomAdvancedConfig configObj = new ZIMRoomAdvancedConfig();
            configObj.roomDestroyDelayTime = config.hasKey("roomDestroyDelayTime")
                ? config.getInt("roomDestroyDelayTime")
                : 0;

            if (config.hasKey("roomAttributes")) {
                configObj.roomAttributes = RNZIMConverter.oMap(config.getMap("roomAttributes"));
            }

            zim.createRoom(roomInfoObj, configObj, callback);
        } else {
            zim.createRoom(roomInfoObj, callback);
        }
    }

    @ReactMethod
    public void enterRoom(String handle, ReadableMap roomInfo, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMRoomInfo roomInfoObj = new ZIMRoomInfo();
        roomInfoObj.roomID = roomInfo.getString("roomID");
        roomInfoObj.roomName = roomInfo.hasKey("roomName") ? roomInfo.getString("roomName") : "";

        ZIMRoomAdvancedConfig configObj = null;
        if (config != null) {
            configObj = new ZIMRoomAdvancedConfig();
            configObj.roomDestroyDelayTime = config.hasKey("roomDestroyDelayTime")
                ? config.getInt("roomDestroyDelayTime")
                : 0;

            if (config.hasKey("roomAttributes")) {
                configObj.roomAttributes = RNZIMConverter.oMap(config.getMap("roomAttributes"));
            }
        }

        zim.enterRoom(
            roomInfoObj,
            configObj,
            new ZIMRoomEnteredCallback() {
                @Override
                public void onRoomEntered(ZIMRoomFullInfo roomInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("roomInfo", RNZIMConverter.mZIMRoomFullInfo(roomInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void joinRoom(String handle, String roomID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.joinRoom(
            roomID,
            new ZIMRoomJoinedCallback() {
                @Override
                public void onRoomJoined(ZIMRoomFullInfo roomInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("roomInfo", RNZIMConverter.mZIMRoomFullInfo(roomInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void switchRoom(
        String handle,
        String fromRoomID,
        ReadableMap roomInfo,
        boolean isCreate,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMRoomSwitchedCallback callback = new ZIMRoomSwitchedCallback() {
            @Override
            public void onRoomSwitched(ZIMRoomFullInfo roomInfo, ZIMError errorInfo) {
                if (isSucc(errorInfo, promise)) {
                    WritableMap returnMap = Arguments.createMap();
                    returnMap.putMap("roomInfo", RNZIMConverter.mZIMRoomFullInfo(roomInfo));
                    promise.resolve(returnMap);
                }
            }
        };

        ZIMRoomInfo roomInfoObj = new ZIMRoomInfo();
        roomInfoObj.roomID = roomInfo.getString("roomID");
        roomInfoObj.roomName = roomInfo.hasKey("roomName") ? roomInfo.getString("roomName") : "";

        ZIMRoomAdvancedConfig configObj = new ZIMRoomAdvancedConfig();
        if (config != null) {
            configObj.roomDestroyDelayTime = config.hasKey("roomDestroyDelayTime")
                ? config.getInt("roomDestroyDelayTime")
                : 0;

            if (config.hasKey("roomAttributes")) {
                configObj.roomAttributes = RNZIMConverter.oMap(config.getMap("roomAttributes"));
            }
        }

        zim.switchRoom(fromRoomID, roomInfoObj, isCreate, configObj, callback);
    }

    @ReactMethod
    public void leaveRoom(String handle, String roomID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.leaveRoom(
            roomID,
            new ZIMRoomLeftCallback() {
                @Override
                public void onRoomLeft(String roomID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("roomID", roomID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void leaveAllRoom(String handle, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.leaveAllRoom(
            new ZIMRoomAllLeftCallback() {
                @Override
                public void onRoomAllLeft(ArrayList<String> roomIDs, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putArray("roomIDs", RNZIMConverter.mStringArray(roomIDs));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryRoomMemberList(String handle, String roomID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMRoomMemberQueryConfig configObj = new ZIMRoomMemberQueryConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.nextFlag = config.hasKey("nextFlag") ? config.getString("nextFlag") : "";
        }

        zim.queryRoomMemberList(
            roomID,
            configObj,
            new ZIMRoomMemberQueriedCallback() {
                @Override
                public void onRoomMemberQueried(
                    String roomID,
                    ArrayList<ZIMUserInfo> memberList,
                    String nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        returnMap.putString("roomID", roomID);
                        returnMap.putString("nextFlag", nextFlag);
                        returnMap.putArray("memberList", RNZIMConverter.mZIMUserInfoList(memberList));

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryRoomMembers(String handle, ReadableArray userIDs, String roomID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryRoomMembers(
            RNZIMConverter.oStringArray(userIDs),
            roomID,
            new ZIMRoomMembersQueriedCallback() {
                @Override
                public void onRoomMembersQueried(
                    String roomID,
                    ArrayList<ZIMRoomMemberInfo> memberList,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        WritableArray memberArray = Arguments.createArray();
                        for (ZIMRoomMemberInfo userInfo : memberList) {
                            memberArray.pushMap(RNZIMConverter.mZIMRoomMemberInfo(userInfo));
                        }

                        returnMap.putString("roomID", roomID);
                        returnMap.putArray("memberList", memberArray);
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryRoomOnlineMemberCount(String handle, String roomID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryRoomOnlineMemberCount(
            roomID,
            new ZIMRoomOnlineMemberCountQueriedCallback() {
                @Override
                public void onRoomOnlineMemberCountQueried(String roomID, int count, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("roomID", roomID);
                        returnMap.putInt("count", count);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryRoomAllAttributes(String handle, String roomID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryRoomAllAttributes(
            roomID,
            new ZIMRoomAttributesQueriedCallback() {
                @Override
                public void onRoomAttributesQueried(
                    String roomID,
                    HashMap<String, String> roomAttributes,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("roomID", roomID);
                        returnMap.putMap("roomAttributes", RNZIMConverter.mMap(roomAttributes));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setRoomAttributes(
        String handle,
        ReadableMap roomAttributes,
        String roomID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMRoomAttributesSetConfig configObj = new ZIMRoomAttributesSetConfig();
        if (config != null) {
            configObj.isUpdateOwner = config.hasKey("isUpdateOwner") ? config.getBoolean("isUpdateOwner") : false;
            configObj.isDeleteAfterOwnerLeft = config.hasKey("isDeleteAfterOwnerLeft")
                ? config.getBoolean("isDeleteAfterOwnerLeft")
                : false;
            configObj.isForce = config.hasKey("isForce") ? config.getBoolean("isForce") : false;
        }

        zim.setRoomAttributes(
            RNZIMConverter.oMap(roomAttributes),
            roomID,
            configObj,
            new ZIMRoomAttributesOperatedCallback() {
                @Override
                public void onRoomAttributesOperated(String roomID, ArrayList<String> errorKeys, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("roomID", roomID);
                        returnMap.putArray("errorKeys", RNZIMConverter.mStringArray(errorKeys));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void deleteRoomAttributes(
        String handle,
        ReadableArray keys,
        String roomID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMRoomAttributesDeleteConfig configObj = new ZIMRoomAttributesDeleteConfig();
        if (config != null) {
            configObj.isForce = config.hasKey("isForce") ? config.getBoolean("isForce") : false;
        }

        zim.deleteRoomAttributes(
            RNZIMConverter.oStringArray(keys),
            roomID,
            configObj,
            new ZIMRoomAttributesOperatedCallback() {
                @Override
                public void onRoomAttributesOperated(String roomID, ArrayList<String> errorKeys, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("roomID", roomID);
                        returnMap.putArray("errorKeys", RNZIMConverter.mStringArray(errorKeys));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void beginRoomAttributesBatchOperation(String handle, String roomID, ReadableMap config) {
        ZIM zim = this.engineMap.get(handle);
        if (zim == null) {
            return;
        }

        ZIMRoomAttributesBatchOperationConfig configObj = new ZIMRoomAttributesBatchOperationConfig();
        if (config != null) {
            configObj.isUpdateOwner = config.hasKey("isUpdateOwner") ? config.getBoolean("isUpdateOwner") : false;
            configObj.isDeleteAfterOwnerLeft = config.hasKey("isDeleteAfterOwnerLeft")
                ? config.getBoolean("isDeleteAfterOwnerLeft")
                : false;
            configObj.isForce = config.hasKey("isForce") ? config.getBoolean("isForce") : false;
        }

        zim.beginRoomAttributesBatchOperation(roomID, configObj);
    }

    @ReactMethod
    public void endRoomAttributesBatchOperation(String handle, String roomID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.endRoomAttributesBatchOperation(
            roomID,
            new ZIMRoomAttributesBatchOperatedCallback() {
                @Override
                public void onRoomAttributesBatchOperated(String roomID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("roomID", roomID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setRoomMembersAttributes(
        String handle,
        ReadableMap attributes,
        ReadableArray userIDs,
        String roomID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMRoomMemberAttributesSetConfig configObj = new ZIMRoomMemberAttributesSetConfig();
        configObj.isDeleteAfterOwnerLeft = config.hasKey("isDeleteAfterOwnerLeft")
            ? config.getBoolean("isDeleteAfterOwnerLeft")
            : true;

        zim.setRoomMembersAttributes(
            RNZIMConverter.oMap(attributes),
            RNZIMConverter.oStringArray(userIDs),
            roomID,
            configObj,
            new ZIMRoomMembersAttributesOperatedCallback() {
                @Override
                public void onRoomMembersAttributesOperated(
                    String roomID,
                    ArrayList<ZIMRoomMemberAttributesOperatedInfo> infos,
                    ArrayList<String> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("roomID", roomID);
                        returnMap.putArray("infos", RNZIMConverter.mZIMRoomMemberAttributesOperatedInfoList(infos));
                        returnMap.putArray("errorUserList", RNZIMConverter.mStringArray(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryRoomMembersAttributes(String handle, ReadableArray userIDs, String roomID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryRoomMembersAttributes(
            RNZIMConverter.oStringArray(userIDs),
            roomID,
            new ZIMRoomMembersAttributesQueriedCallback() {
                @Override
                public void onRoomMembersAttributesQueried(
                    String roomID,
                    ArrayList<ZIMRoomMemberAttributesInfo> infos,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("roomID", roomID);

                        ReadableArray infoArray = RNZIMConverter.mZIMRoomMemberAttributesInfoList(infos);
                        returnMap.putArray("infos", infoArray);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryRoomMemberAttributesList(String handle, String roomID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMRoomMemberAttributesQueryConfig configObj = new ZIMRoomMemberAttributesQueryConfig();
        configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
        configObj.nextFlag = config.hasKey("nextFlag") ? config.getString("nextFlag") : "";

        zim.queryRoomMemberAttributesList(
            roomID,
            configObj,
            new ZIMRoomMemberAttributesListQueriedCallback() {
                @Override
                public void onRoomMemberAttributesListQueried(
                    String roomID,
                    ArrayList<ZIMRoomMemberAttributesInfo> infos,
                    String nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("roomID", roomID);

                        ReadableArray infoArray = RNZIMConverter.mZIMRoomMemberAttributesInfoList(infos);
                        returnMap.putArray("infos", infoArray);

                        returnMap.putString("nextFlag", nextFlag);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void createGroup(
        String handle,
        ReadableMap groupInfo,
        ReadableArray userIDs,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupCreatedCallback callback = new ZIMGroupCreatedCallback() {
            @Override
            public void onGroupCreated(
                ZIMGroupFullInfo groupInfo,
                ArrayList<ZIMGroupMemberInfo> userList,
                ArrayList<ZIMErrorUserInfo> errorUserList,
                ZIMError errorInfo
            ) {
                if (isSucc(errorInfo, promise)) {
                    WritableMap returnMap = Arguments.createMap();
                    returnMap.putMap("groupInfo", RNZIMConverter.mZIMGroupFullInfo(groupInfo));
                    returnMap.putArray("userList", RNZIMConverter.mZIMGroupMemberInfoList(userList));
                    returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                    promise.resolve(returnMap);
                }
            }
        };

        ZIMGroupInfo groupInfoObj = new ZIMGroupInfo();
        groupInfoObj.groupID = groupInfo.getString("groupID");
        groupInfoObj.groupName = groupInfo.hasKey("groupName") ? groupInfo.getString("groupName") : "";
        groupInfoObj.groupAvatarUrl = groupInfo.hasKey("groupAvatarUrl") ? groupInfo.getString("groupAvatarUrl") : "";

        ArrayList<String> userIDArray = RNZIMConverter.oStringArray(userIDs);

        if (config != null) {
            ZIMGroupAdvancedConfig configObj = new ZIMGroupAdvancedConfig();
            configObj.groupNotice = config.hasKey("groupNotice") ? config.getString("groupNotice") : "";
            configObj.maxMemberCount = config.hasKey("maxMemberCount") ? config.getInt("maxMemberCount") : 0;
            configObj.joinMode = ZIMGroupJoinMode.getZIMGroupJoinMode(
                config.hasKey("joinMode") ? config.getInt("joinMode") : 0
            );
            configObj.inviteMode = ZIMGroupInviteMode.getZIMGroupInviteMode(
                config.hasKey("inviteMode") ? config.getInt("inviteMode") : 0
            );
            configObj.beInviteMode = ZIMGroupBeInviteMode.getZIMGroupBeInviteMode(
                config.hasKey("beInviteMode") ? config.getInt("beInviteMode") : 0
            );

            if (config.hasKey("groupAttributes")) {
                configObj.groupAttributes = RNZIMConverter.oMap(config.getMap("groupAttributes"));
            }

            zim.createGroup(groupInfoObj, userIDArray, configObj, callback);
        } else {
            zim.createGroup(groupInfoObj, userIDArray, callback);
        }
    }

    @ReactMethod
    public void joinGroup(String handle, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.joinGroup(
            groupID,
            new ZIMGroupJoinedCallback() {
                @Override
                public void onGroupJoined(ZIMGroupFullInfo groupInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("groupInfo", RNZIMConverter.mZIMGroupFullInfo(groupInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void leaveGroup(String handle, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.leaveGroup(
            groupID,
            new ZIMGroupLeftCallback() {
                @Override
                public void onGroupLeft(String groupID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void dismissGroup(String handle, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.dismissGroup(
            groupID,
            new ZIMGroupDismissedCallback() {
                @Override
                public void onGroupDismissed(String groupID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryGroupList(String handle, int count, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupListQueriedCallback callback = new ZIMGroupListQueriedCallback() {
            @Override
            public void onGroupListQueried(ArrayList<ZIMGroup> groupList, long nextFlag, ZIMError errorInfo) {
                if (isSucc(errorInfo, promise)) {
                    WritableMap returnMap = Arguments.createMap();
                    returnMap.putDouble("nextFlag", nextFlag);
                    returnMap.putArray("groupList", RNZIMConverter.mZIMGroupList(groupList));
                    promise.resolve(returnMap);
                }
            }
        };

        if (config != null) {
            ZIMGroupListQueryConfig configObj = new ZIMGroupListQueryConfig();
            configObj.nextFlag = config.hasKey("nextFlag")
                ? Double.valueOf(config.getDouble("nextFlag")).longValue()
                : 0;
            if (config.hasKey("userIDs")) {
                configObj.userIDs = RNZIMConverter.oStringArray(config.getArray("userIDs"));
            }

            zim.queryGroupList(count, configObj, callback);
        } else {
            zim.queryGroupList(callback);
        }
    }

    @ReactMethod
    public void updateGroupNotice(String handle, String groupNotice, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateGroupNotice(
            groupNotice,
            groupID,
            new ZIMGroupNoticeUpdatedCallback() {
                @Override
                public void onGroupNoticeUpdated(String groupID, String groupNotice, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putString("groupNotice", groupNotice);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateGroupName(String handle, String groupName, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateGroupName(
            groupName,
            groupID,
            new ZIMGroupNameUpdatedCallback() {
                @Override
                public void onGroupNameUpdated(String groupID, String groupName, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putString("groupName", groupName);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateGroupAvatarUrl(String handle, String groupAvatarUrl, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateGroupAvatarUrl(
            groupAvatarUrl,
            groupID,
            new ZIMGroupAvatarUrlUpdatedCallback() {
                @Override
                public void onGroupAvatarUrlUpdated(String groupID, String groupAvatarUrl, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putString("groupAvatarUrl", groupAvatarUrl);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void muteGroup(String handle, boolean isMute, String groupID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupMuteConfig configObj = new ZIMGroupMuteConfig();
        configObj.mode = ZIMGroupMuteMode.getZIMGroupMuteMode(config.hasKey("mode") ? config.getInt("mode") : 0);
        configObj.duration = config.hasKey("duration") ? config.getInt("duration") : 0;
        if (config.hasKey("roles")) {
            configObj.roles = RNZIMConverter.oIntArray(config.getArray("roles"));
        }

        zim.muteGroup(
            isMute,
            groupID,
            configObj,
            new ZIMGroupMutedCallback() {
                @Override
                public void onGroupMuted(String groupID, boolean isMute, ZIMGroupMuteInfo info, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putBoolean("isMute", isMute);
                        returnMap.putMap("mutedInfo", RNZIMConverter.mZIMGroupMuteInfo(info));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void muteGroupMembers(
        String handle,
        boolean isMute,
        ReadableArray userIDs,
        String groupID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupMemberMuteConfig configObj = new ZIMGroupMemberMuteConfig();
        configObj.duration = config.hasKey("duration") ? config.getInt("duration") : 0;

        zim.muteGroupMembers(
            isMute,
            RNZIMConverter.oStringArray(userIDs),
            groupID,
            configObj,
            new ZIMGroupMembersMutedCallback() {
                @Override
                public void onGroupMembersMuted(
                    String groupID,
                    boolean isMute,
                    int duration,
                    ArrayList<String> mutedMemberIDs,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putBoolean("isMute", isMute);
                        returnMap.putInt("duration", duration);
                        returnMap.putArray("mutedUserIDs", RNZIMConverter.mStringArray(mutedMemberIDs));
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryGroupInfo(String handle, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryGroupInfo(
            groupID,
            new ZIMGroupInfoQueriedCallback() {
                @Override
                public void onGroupInfoQueried(ZIMGroupFullInfo groupInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("groupInfo", RNZIMConverter.mZIMGroupFullInfo(groupInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryGroupMemberCount(String handle, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryGroupMemberCount(
            groupID,
            new ZIMGroupMemberCountQueriedCallback() {
                @Override
                public void onGroupMemberCountQueried(String groupID, int count, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putInt("count", count);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setGroupAttributes(String handle, ReadableMap groupAttributes, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.setGroupAttributes(
            RNZIMConverter.oMap(groupAttributes),
            groupID,
            new ZIMGroupAttributesOperatedCallback() {
                @Override
                public void onGroupAttributesOperated(String groupID, ArrayList<String> errorKeys, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putArray("errorKeys", RNZIMConverter.mStringArray(errorKeys));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void deleteGroupAttributes(String handle, ReadableArray keys, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.deleteGroupAttributes(
            RNZIMConverter.oStringArray(keys),
            groupID,
            new ZIMGroupAttributesOperatedCallback() {
                @Override
                public void onGroupAttributesOperated(String groupID, ArrayList<String> errorKeys, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putArray("errorKeys", RNZIMConverter.mStringArray(errorKeys));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryGroupAttributes(String handle, ReadableArray keys, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryGroupAttributes(
            RNZIMConverter.oStringArray(keys),
            groupID,
            new ZIMGroupAttributesQueriedCallback() {
                @Override
                public void onGroupAttributesQueried(
                    String groupID,
                    HashMap<String, String> groupAttributes,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putMap("groupAttributes", RNZIMConverter.mMap(groupAttributes));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryGroupAllAttributes(String handle, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryGroupAllAttributes(
            groupID,
            new ZIMGroupAttributesQueriedCallback() {
                @Override
                public void onGroupAttributesQueried(
                    String groupID,
                    HashMap<String, String> groupAttributes,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putMap("groupAttributes", RNZIMConverter.mMap(groupAttributes));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateGroupAlias(String handle, String groupAlias, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateGroupAlias(
            groupAlias,
            groupID,
            new ZIMGroupAliasUpdatedCallback() {
                @Override
                public void onGroupAliasUpdated(String groupID, String groupAlias, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putString("groupAlias", groupAlias);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setGroupMemberNickname(
        String handle,
        String nickname,
        String forUserID,
        String groupID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.setGroupMemberNickname(
            nickname,
            forUserID,
            groupID,
            new ZIMGroupMemberNicknameUpdatedCallback() {
                @Override
                public void onGroupMemberNicknameUpdated(
                    String groupID,
                    String forUserID,
                    String nickname,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putString("forUserID", forUserID);
                        returnMap.putString("nickname", nickname);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setGroupMemberRole(String handle, int role, String forUserID, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.setGroupMemberRole(
            role,
            forUserID,
            groupID,
            new ZIMGroupMemberRoleUpdatedCallback() {
                @Override
                public void onGroupMemberRoleUpdated(String groupID, String forUserID, int role, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putString("forUserID", forUserID);
                        returnMap.putInt("role", role);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void transferGroupOwner(String handle, String toUserID, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.transferGroupOwner(
            toUserID,
            groupID,
            new ZIMGroupOwnerTransferredCallback() {
                @Override
                public void onGroupOwnerTransferred(String groupID, String toUserID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putString("toUserID", toUserID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryGroupMemberInfo(String handle, String userID, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryGroupMemberInfo(
            userID,
            groupID,
            new ZIMGroupMemberInfoQueriedCallback() {
                @Override
                public void onGroupMemberInfoQueried(String groupID, ZIMGroupMemberInfo userInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putMap("userInfo", RNZIMConverter.mZIMGroupMemberInfo(userInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void inviteUsersIntoGroup(String handle, ReadableArray userIDs, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.inviteUsersIntoGroup(
            RNZIMConverter.oStringArray(userIDs),
            groupID,
            new ZIMGroupUsersInvitedCallback() {
                @Override
                public void onGroupUsersInvited(
                    String groupID,
                    ArrayList<ZIMGroupMemberInfo> userList,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putArray("userList", RNZIMConverter.mZIMGroupMemberInfoList(userList));
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void kickGroupMembers(String handle, ReadableArray userIDs, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.kickGroupMembers(
            RNZIMConverter.oStringArray(userIDs),
            groupID,
            new ZIMGroupMemberKickedCallback() {
                @Override
                public void onGroupMemberKicked(
                    String groupID,
                    ArrayList<String> kickedUserIDList,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putArray("kickedUserIDs", RNZIMConverter.mStringArray(kickedUserIDList));
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryGroupMemberList(String handle, String groupID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupMemberQueryConfig configObj = new ZIMGroupMemberQueryConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.nextFlag = config.hasKey("nextFlag") ? config.getInt("nextFlag") : 0;
        }

        zim.queryGroupMemberList(
            groupID,
            configObj,
            new ZIMGroupMemberListQueriedCallback() {
                @Override
                public void onGroupMemberListQueried(
                    String groupID,
                    ArrayList<ZIMGroupMemberInfo> userList,
                    int nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putArray("userList", RNZIMConverter.mZIMGroupMemberInfoList(userList));
                        returnMap.putInt("nextFlag", nextFlag);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryGroupMemberMutedList(String handle, String groupID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupMemberMutedListQueryConfig configObj = new ZIMGroupMemberMutedListQueryConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.nextFlag = config.hasKey("nextFlag")
                ? Double.valueOf(config.getDouble("nextFlag")).longValue()
                : 0;
        }

        zim.queryGroupMemberMutedList(
            groupID,
            configObj,
            new ZIMGroupMemberMutedListQueriedCallback() {
                @Override
                public void onGroupMemberMutedListQueried(
                    String groupID,
                    long nextFlag,
                    ArrayList<ZIMGroupMemberInfo> userList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putDouble("nextFlag", nextFlag);
                        returnMap.putArray("userList", RNZIMConverter.mZIMGroupMemberInfoList(userList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateGroupJoinMode(String handle, int mode, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateGroupJoinMode(
            ZIMGroupJoinMode.getZIMGroupJoinMode(mode),
            groupID,
            new ZIMGroupJoinModeUpdatedCallback() {
                @Override
                public void onGroupJoinModeUpdated(String groupID, ZIMGroupJoinMode mode, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putInt("mode", mode.value());
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateGroupInviteMode(String handle, int mode, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateGroupInviteMode(
            ZIMGroupInviteMode.getZIMGroupInviteMode(mode),
            groupID,
            new ZIMGroupInviteModeUpdatedCallback() {
                @Override
                public void onGroupInviteModeUpdated(String groupID, ZIMGroupInviteMode mode, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putInt("mode", mode.value());
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateGroupBeInviteMode(String handle, int mode, String groupID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateGroupBeInviteMode(
            ZIMGroupBeInviteMode.getZIMGroupBeInviteMode(mode),
            groupID,
            new ZIMGroupBeInviteModeUpdatedCallback() {
                @Override
                public void onGroupBeInviteModeUpdated(String groupID, ZIMGroupBeInviteMode mode, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putInt("mode", mode.value());
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void sendGroupJoinApplication(String handle, String groupID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupJoinApplicationSendConfig configObj = new ZIMGroupJoinApplicationSendConfig();
        configObj.wording = config.hasKey("wording") ? config.getString("wording") : "";
        configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);

        zim.sendGroupJoinApplication(
            groupID,
            configObj,
            new ZIMGroupJoinApplicationSentCallback() {
                @Override
                public void onGroupJoinApplicationSent(String groupID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void acceptGroupJoinApplication(
        String handle,
        String userID,
        String groupID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupJoinApplicationAcceptConfig configObj = new ZIMGroupJoinApplicationAcceptConfig();
        configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);

        zim.acceptGroupJoinApplication(
            userID,
            groupID,
            configObj,
            new ZIMGroupJoinApplicationAcceptedCallback() {
                @Override
                public void onGroupJoinApplicationAccepted(String groupID, String userID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putString("userID", userID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void rejectGroupJoinApplication(
        String handle,
        String userID,
        String groupID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupJoinApplicationRejectConfig configObj = new ZIMGroupJoinApplicationRejectConfig();
        configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);

        zim.rejectGroupJoinApplication(
            userID,
            groupID,
            configObj,
            new ZIMGroupJoinApplicationRejectedCallback() {
                @Override
                public void onGroupJoinApplicationRejected(String groupID, String userID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putString("userID", userID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void sendGroupInviteApplications(
        String handle,
        ReadableArray invitees,
        String groupID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupInviteApplicationSendConfig configObj = new ZIMGroupInviteApplicationSendConfig();
        configObj.wording = config.hasKey("wording") ? config.getString("wording") : "";
        configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);

        zim.sendGroupInviteApplications(
            RNZIMConverter.oStringArray(invitees),
            groupID,
            configObj,
            new ZIMGroupInviteApplicationsSentCallback() {
                @Override
                public void onGroupInviteApplicationsSent(
                    String groupID,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void acceptGroupInviteApplication(
        String handle,
        String userID,
        String groupID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupInviteApplicationAcceptConfig configObj = new ZIMGroupInviteApplicationAcceptConfig();
        configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);

        zim.acceptGroupInviteApplication(
            userID,
            groupID,
            configObj,
            new ZIMGroupInviteApplicationAcceptedCallback() {
                @Override
                public void onGroupInviteApplicationAccepted(
                    ZIMGroupFullInfo groupInfo,
                    String userID,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("groupInfo", RNZIMConverter.mZIMGroupFullInfo(groupInfo));
                        returnMap.putString("inviterUserID", userID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void rejectGroupInviteApplication(
        String handle,
        String userID,
        String groupID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupInviteApplicationRejectConfig configObj = new ZIMGroupInviteApplicationRejectConfig();
        configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);

        zim.rejectGroupInviteApplication(
            userID,
            groupID,
            configObj,
            new ZIMGroupInviteApplicationRejectedCallback() {
                @Override
                public void onGroupInviteApplicationRejected(String groupID, String userID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putString("inviterUserID", userID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryGroupApplicationList(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupApplicationListQueryConfig configObj = new ZIMGroupApplicationListQueryConfig();
        configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
        configObj.nextFlag = config.hasKey("nextFlag") ? config.getInt("nextFlag") : 0;

        zim.queryGroupApplicationList(
            configObj,
            new ZIMGroupApplicationListQueriedCallback() {
                @Override
                public void onGroupApplicationListQueried(
                    ArrayList<ZIMGroupApplicationInfo> applicationList,
                    long nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putArray(
                            "applicationList",
                            RNZIMConverter.mZIMGroupApplicationInfoList(applicationList)
                        );
                        returnMap.putDouble("nextFlag", nextFlag);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void createCommunity(String handle, ReadableMap communityInfo, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCommunityInfo communityInfoObj = new ZIMCommunityInfo();
        communityInfoObj.setCommunityID(
            communityInfo.hasKey("communityID") ? communityInfo.getString("communityID") : ""
        );
        communityInfoObj.setCommunityName(
            communityInfo.hasKey("communityName") ? communityInfo.getString("communityName") : ""
        );
        communityInfoObj.setCommunityAvatarUrl(
            communityInfo.hasKey("communityAvatarUrl") ? communityInfo.getString("communityAvatarUrl") : ""
        );

        ZIMCommunityCreateConfig configObj = new ZIMCommunityCreateConfig();
        if (config != null) {
            configObj.setCommunityNotice(config.hasKey("communityNotice") ? config.getString("communityNotice") : "");

            if (config.hasKey("communityAttributes")) {
                configObj.setCommunityAttributes(RNZIMConverter.oMap(config.getMap("communityAttributes")));
            }
        }

        zim.createCommunity(
            communityInfoObj,
            configObj,
            new ZIMCommunityCreatedCallback() {
                @Override
                public void onCommunityCreated(ZIMCommunityFullInfo communityInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("communityInfo", RNZIMConverter.mZIMCommunityFullInfo(communityInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void dismissCommunity(String handle, String communityID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.dismissCommunity(
            communityID,
            new ZIMCommunityDismissedCallback() {
                @Override
                public void onCommunityDismissed(String communityID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void joinCommunity(String handle, String communityID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.joinCommunity(
            communityID,
            new ZIMCommunityJoinedCallback() {
                @Override
                public void onCommunityJoined(ZIMCommunityFullInfo communityInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("communityInfo", RNZIMConverter.mZIMCommunityFullInfo(communityInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void leaveCommunity(String handle, String communityID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.leaveCommunity(
            communityID,
            new ZIMCommunityLeftCallback() {
                @Override
                public void onCommunityLeft(String communityID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void inviteUsersIntoCommunity(
        String handle,
        ReadableArray userIDs,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.inviteUsersIntoCommunity(
            RNZIMConverter.oStringArray(userIDs),
            communityID,
            new ZIMCommunityUsersInvitedCallback() {
                @Override
                public void onCommunityUsersInvited(
                    String communityID,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void kickCommunityMembers(String handle, ReadableArray userIDs, String communityID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.kickCommunityMembers(
            RNZIMConverter.oStringArray(userIDs),
            communityID,
            new ZIMCommunityMembersKickedCallback() {
                @Override
                public void onCommunityMembersKicked(
                    String communityID,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryCommunityList(String handle, int count, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCommunityListQueryConfig configObj = new ZIMCommunityListQueryConfig();
        if (config != null) {
            configObj.setNextFlag(
                config.hasKey("nextFlag") ? Double.valueOf(config.getDouble("nextFlag")).longValue() : 0
            );
        }

        zim.queryCommunityList(
            count,
            configObj,
            new ZIMCommunityListQueriedCallback() {
                @Override
                public void onCommunityListQueried(
                    ArrayList<ZIMCommunity> communityList,
                    long nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putDouble("nextFlag", nextFlag);

                        WritableArray array = Arguments.createArray();
                        for (ZIMCommunity info : communityList) {
                            array.pushMap(RNZIMConverter.mZIMCommunity(info));
                        }
                        returnMap.putArray("communityList", array);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateCommunityName(String handle, String communityName, String communityID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateCommunityName(
            communityName,
            communityID,
            new ZIMCommunityNameUpdatedCallback() {
                @Override
                public void onCommunityNameUpdated(String communityID, String communityName, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("communityName", communityName);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateCommunityAvatarUrl(
        String handle,
        String communityAvatarUrl,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateCommunityAvatarUrl(
            communityAvatarUrl,
            communityID,
            new ZIMCommunityAvatarUrlUpdatedCallback() {
                @Override
                public void onCommunityAvatarUrlUpdated(
                    String communityID,
                    String communityAvatarUrl,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("communityAvatarUrl", communityAvatarUrl);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateCommunityNotice(
        String handle,
        String communityNotice,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateCommunityNotice(
            communityNotice,
            communityID,
            new ZIMCommunityNoticeUpdatedCallback() {
                @Override
                public void onCommunityNoticeUpdated(String communityID, String communityNotice, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("communityNotice", communityNotice);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setCommunityAttributes(
        String handle,
        ReadableMap communityAttributes,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.setCommunityAttributes(
            RNZIMConverter.oMap(communityAttributes),
            communityID,
            new ZIMCommunityAttributesOperatedCallback() {
                @Override
                public void onCommunityAttributesOperated(
                    String communityID,
                    ArrayList<String> errorKeys,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putArray("errorKeys", RNZIMConverter.mStringArray(errorKeys));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void deleteCommunityAttributes(
        String handle,
        ReadableArray keys,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.deleteCommunityAttributes(
            RNZIMConverter.oStringArray(keys),
            communityID,
            new ZIMCommunityAttributesOperatedCallback() {
                @Override
                public void onCommunityAttributesOperated(
                    String communityID,
                    ArrayList<String> errorKeys,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putArray("errorKeys", RNZIMConverter.mStringArray(errorKeys));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setCommunityNotificationStatus(String handle, int status, String communityID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.setCommunityNotificationStatus(
            ZIMCommunityMessageNotificationStatus.getEnum(status),
            communityID,
            new ZIMCommunityNotificationStatusSetCallback() {
                @Override
                public void onCommunityNotificationStatusSet(String communityID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryCommunityInfo(String handle, String communityID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryCommunityInfo(
            communityID,
            new ZIMCommunityInfoQueriedCallback() {
                @Override
                public void onCommunityInfoQueried(ZIMCommunityFullInfo communityInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("communityInfo", RNZIMConverter.mZIMCommunityFullInfo(communityInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateCommunityMemberRole(
        String handle,
        int memberRole,
        String forUserID,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateCommunityMemberRole(
            memberRole,
            forUserID,
            communityID,
            new ZIMCommunityMemberRoleUpdatedCallback() {
                @Override
                public void onCommunityMemberRoleUpdated(
                    String communityID,
                    String forUserID,
                    int memberRole,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("forUserID", forUserID);
                        returnMap.putInt("memberRole", memberRole);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void transferCommunityOwner(String handle, String toUserID, String communityID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.transferCommunityOwner(
            toUserID,
            communityID,
            new ZIMCommunityOwnerTransferredCallback() {
                @Override
                public void onCommunityOwnerTransferred(String communityID, String toUserID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("toUserID", toUserID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryCommunityMemberList(
        String handle,
        String communityID,
        int count,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCommunityMemberListQueryConfig configObj = new ZIMCommunityMemberListQueryConfig();
        if (config != null) {
            configObj.setNextFlag(
                config.hasKey("nextFlag") ? Double.valueOf(config.getDouble("nextFlag")).longValue() : 0
            );
            configObj.setChannelID(config.hasKey("channelID") ? config.getString("channelID") : "");
            configObj.setIsMutedMember(config.hasKey("isMutedMember") ? config.getBoolean("isMutedMember") : false);
        }

        zim.queryCommunityMemberList(
            communityID,
            count,
            configObj,
            new ZIMCommunityMemberListQueriedCallback() {
                @Override
                public void onCommunityMemberListQueried(
                    String communityID,
                    ArrayList<ZIMCommunityMemberInfo> memberList,
                    long nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putDouble("nextFlag", nextFlag);
                        returnMap.putArray("memberList", RNZIMConverter.mZIMCommunityMemberInfoList(memberList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryCommunityMembers(String handle, ReadableArray userIDs, String communityID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryCommunityMembers(
            RNZIMConverter.oStringArray(userIDs),
            communityID,
            new ZIMCommunityMembersQueriedCallback() {
                @Override
                public void onCommunityMembersQueried(
                    String communityID,
                    ArrayList<ZIMCommunityMemberInfo> memberList,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putArray("memberList", RNZIMConverter.mZIMCommunityMemberInfoList(memberList));
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void createCommunityChannel(
        String handle,
        ReadableMap channelInfo,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCommunityChannelInfo channelInfoObj = new ZIMCommunityChannelInfo();
        channelInfoObj.setCommunityID(channelInfo.hasKey("communityID") ? channelInfo.getString("communityID") : "");
        channelInfoObj.setChannelType(
            ZIMCommunityChannelType.getEnum(channelInfo.hasKey("channelType") ? channelInfo.getInt("channelType") : 0)
        );
        channelInfoObj.setChannelID(channelInfo.hasKey("channelID") ? channelInfo.getString("channelID") : "");
        channelInfoObj.setChannelName(channelInfo.hasKey("channelName") ? channelInfo.getString("channelName") : "");
        channelInfoObj.setChannelAvatarUrl(
            channelInfo.hasKey("channelAvatarUrl") ? channelInfo.getString("channelAvatarUrl") : ""
        );

        ZIMCommunityChannelCreateConfig configObj = new ZIMCommunityChannelCreateConfig();
        if (config != null) {
            configObj.setChannelNotice(config.hasKey("channelNotice") ? config.getString("channelNotice") : "");

            if (config.hasKey("channelAttributes")) {
                configObj.setChannelAttributes(RNZIMConverter.oMap(config.getMap("channelAttributes")));
            }
        }

        zim.createCommunityChannel(
            channelInfoObj,
            configObj,
            new ZIMCommunityChannelCreatedCallback() {
                @Override
                public void onCommunityChannelCreated(ZIMCommunityChannelFullInfo channelInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("channelInfo", RNZIMConverter.mZIMCommunityChannelFullInfo(channelInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void dismissCommunityChannel(String handle, String channelID, String communityID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.dismissCommunityChannel(
            channelID,
            communityID,
            new ZIMCommunityChannelDismissedCallback() {
                @Override
                public void onCommunityChannelDismissed(String communityID, String channelID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("channelID", channelID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateCommunityChannelName(
        String handle,
        String channelName,
        String channelID,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateCommunityChannelName(
            channelName,
            channelID,
            communityID,
            new ZIMCommunityChannelNameUpdatedCallback() {
                @Override
                public void onCommunityChannelNameUpdated(
                    String communityID,
                    String channelID,
                    String channelName,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("channelID", channelID);
                        returnMap.putString("channelName", channelName);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateCommunityChannelAvatarUrl(
        String handle,
        String channelAvatarUrl,
        String channelID,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateCommunityChannelAvatarUrl(
            channelAvatarUrl,
            channelID,
            communityID,
            new ZIMCommunityChannelAvatarUrlUpdatedCallback() {
                @Override
                public void onCommunityChannelAvatarUrlUpdated(
                    String communityID,
                    String channelID,
                    String channelAvatarUrl,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("channelID", channelID);
                        returnMap.putString("channelAvatarUrl", channelAvatarUrl);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateCommunityChannelNotice(
        String handle,
        String channelNotice,
        String channelID,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateCommunityChannelNotice(
            channelNotice,
            channelID,
            communityID,
            new ZIMCommunityChannelNoticeUpdatedCallback() {
                @Override
                public void onCommunityChannelNoticeUpdated(
                    String communityID,
                    String channelID,
                    String channelNotice,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("channelID", channelID);
                        returnMap.putString("channelNotice", channelNotice);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void setCommunityChannelAttributes(
        String handle,
        ReadableMap channelAttributes,
        String channelID,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.setCommunityChannelAttributes(
            RNZIMConverter.oMap(channelAttributes),
            channelID,
            communityID,
            new ZIMCommunityChannelAttributesOperatedCallback() {
                @Override
                public void onCommunityChannelAttributesOperated(
                    String communityID,
                    String channelID,
                    ArrayList<String> errorKeys,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("channelID", channelID);
                        returnMap.putArray("errorKeys", RNZIMConverter.mStringArray(errorKeys));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void deleteCommunityChannelAttributes(
        String handle,
        ReadableArray keys,
        String channelID,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.deleteCommunityChannelAttributes(
            RNZIMConverter.oStringArray(keys),
            channelID,
            communityID,
            new ZIMCommunityChannelAttributesOperatedCallback() {
                @Override
                public void onCommunityChannelAttributesOperated(
                    String communityID,
                    String channelID,
                    ArrayList<String> errorKeys,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("channelID", channelID);
                        returnMap.putArray("errorKeys", RNZIMConverter.mStringArray(errorKeys));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryCommunityChannelsInfo(
        String handle,
        ReadableArray channelIDs,
        String communityID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryCommunityChannelsInfo(
            RNZIMConverter.oStringArray(channelIDs),
            communityID,
            new ZIMCommunityChannelsInfoQueriedCallback() {
                @Override
                public void onCommunityChannelsInfoQueried(
                    String communityID,
                    ArrayList<ZIMCommunityChannelFullInfo> channelInfos,
                    ArrayList<String> errorChannelIDs,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putArray(
                            "channelInfos",
                            RNZIMConverter.mZIMCommunityChannelFullInfoList(channelInfos)
                        );
                        returnMap.putArray("errorChannelIDs", RNZIMConverter.mStringArray(errorChannelIDs));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryCommunityChannelList(
        String handle,
        String communityID,
        int count,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCommunityChannelListQueryConfig configObj = new ZIMCommunityChannelListQueryConfig();
        if (config != null) {
            configObj.setNextFlag(
                config.hasKey("nextFlag") ? Double.valueOf(config.getDouble("nextFlag")).longValue() : 0
            );
        }

        zim.queryCommunityChannelList(
            communityID,
            count,
            configObj,
            new ZIMCommunityChannelListQueriedCallback() {
                @Override
                public void onCommunityChannelListQueried(
                    String communityID,
                    ArrayList<ZIMCommunityChannel> channelList,
                    long nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putDouble("nextFlag", nextFlag);
                        returnMap.putArray("channelList", RNZIMConverter.mZIMCommunityChannelList(channelList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void muteCommunityChannels(
        String handle,
        boolean isMute,
        ReadableArray channelIDs,
        String communityID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCommunityChannelMuteConfig configObj = new ZIMCommunityChannelMuteConfig();
        if (config != null) {
            configObj.setMode(ZIMCommunityChannelMuteMode.getEnum(config.hasKey("mode") ? config.getInt("mode") : 0));
            configObj.setDuration(config.hasKey("duration") ? config.getInt("duration") : 0);
            if (config.hasKey("roles")) {
                configObj.setRoles(RNZIMConverter.oIntArray(config.getArray("roles")));
            }
        }

        zim.muteCommunityChannels(
            isMute,
            RNZIMConverter.oStringArray(channelIDs),
            communityID,
            configObj,
            new ZIMCommunityChannelsMutedCallback() {
                @Override
                public void onCommunityChannelsMuted(
                    String communityID,
                    boolean isMute,
                    ArrayList<String> errorChannelIDs,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putBoolean("isMute", isMute);
                        returnMap.putArray("errorChannelIDs", RNZIMConverter.mStringArray(errorChannelIDs));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void muteCommunityMembers(
        String handle,
        boolean isMute,
        ReadableArray userIDs,
        String communityID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCommunityMemberMuteConfig configObj = new ZIMCommunityMemberMuteConfig();
        if (config != null) {
            configObj.setDuration(config.hasKey("duration") ? config.getInt("duration") : 0);
            configObj.setChannelID(config.hasKey("channelID") ? config.getString("channelID") : "");
        }

        zim.muteCommunityMembers(
            isMute,
            RNZIMConverter.oStringArray(userIDs),
            communityID,
            configObj,
            new ZIMCommunityMembersMutedCallback() {
                @Override
                public void onCommunityMembersMuted(
                    String communityID,
                    String channelID,
                    boolean isMute,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("communityID", communityID);
                        returnMap.putString("channelID", channelID);
                        returnMap.putBoolean("isMute", isMute);
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void callInvite(String handle, ReadableArray invitees, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCallInviteConfig configObj = new ZIMCallInviteConfig();
        if (config != null) {
            configObj.mode = ZIMCallInvitationMode.getZIMCallInvitationMode(
                config.hasKey("mode") ? config.getInt("mode") : 0
            );
            configObj.timeout = config.hasKey("timeout") ? config.getInt("timeout") : 90;
            configObj.extendedData = config.hasKey("extendedData") ? config.getString("extendedData") : "";
            configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);
            configObj.enableNotReceivedCheck = config.hasKey("enableNotReceivedCheck")
                ? config.getBoolean("enableNotReceivedCheck")
                : false;
        }

        zim.callInvite(
            RNZIMConverter.oStringArray(invitees),
            configObj,
            new ZIMCallInvitationSentCallback() {
                @Override
                public void onCallInvitationSent(String callID, ZIMCallInvitationSentInfo info, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("callID", callID);
                        returnMap.putInt("timeout", info.timeout);
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(info.errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void callCancel(
        String handle,
        ReadableArray invitees,
        String callID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCallCancelConfig configObj = new ZIMCallCancelConfig();
        if (config != null) {
            configObj.extendedData = config.hasKey("extendedData") ? config.getString("extendedData") : "";
            configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);
        }

        zim.callCancel(
            RNZIMConverter.oStringArray(invitees),
            callID,
            configObj,
            new ZIMCallCancelSentCallback() {
                @Override
                public void onCallCancelSent(String callID, ArrayList<String> errorInvitees, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("callID", callID);
                        returnMap.putArray("errorInvitees", RNZIMConverter.mStringArray(errorInvitees));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void callAccept(String handle, String callID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCallAcceptConfig configObj = new ZIMCallAcceptConfig();
        if (config != null) {
            configObj.extendedData = config.hasKey("extendedData") ? config.getString("extendedData") : "";
        }

        zim.callAccept(
            callID,
            configObj,
            new ZIMCallAcceptanceSentCallback() {
                @Override
                public void onCallAcceptanceSent(String callID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("callID", callID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void callReject(String handle, String callID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCallRejectConfig configObj = new ZIMCallRejectConfig();
        if (config != null) {
            configObj.extendedData = config.hasKey("extendedData") ? config.getString("extendedData") : "";
        }

        zim.callReject(
            callID,
            configObj,
            new ZIMCallRejectionSentCallback() {
                @Override
                public void onCallRejectionSent(String callID, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("callID", callID);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void callQuit(String handle, String callID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCallQuitConfig configObj = new ZIMCallQuitConfig();
        if (config != null) {
            configObj.extendedData = config.hasKey("extendedData") ? config.getString("extendedData") : "";
            configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);
        }

        zim.callQuit(
            callID,
            configObj,
            new ZIMCallQuitSentCallback() {
                @Override
                public void onCallQuitSent(String callID, ZIMCallQuitSentInfo info, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("callID", callID);
                        returnMap.putDouble("createTime", info.createTime);
                        returnMap.putDouble("acceptTime", info.acceptTime);
                        returnMap.putDouble("quitTime", info.quitTime);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void callEnd(String handle, String callID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCallEndConfig configObj = new ZIMCallEndConfig();
        if (config != null) {
            configObj.extendedData = config.hasKey("extendedData") ? config.getString("extendedData") : "";
            configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);
        }

        zim.callEnd(
            callID,
            configObj,
            new ZIMCallEndSentCallback() {
                @Override
                public void onCallEndSent(String callID, ZIMCallEndedSentInfo info, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("callID", callID);
                        returnMap.putDouble("createTime", info.createTime);
                        returnMap.putDouble("acceptTime", info.acceptTime);
                        returnMap.putDouble("endTime", info.endTime);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void callJoin(String handle, String callID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCallJoinConfig configObj = new ZIMCallJoinConfig();
        if (config != null) {
            configObj.extendedData = config.hasKey("extendedData") ? config.getString("extendedData") : "";
        }

        zim.callJoin(
            callID,
            configObj,
            new ZIMCallJoinSentCallback() {
                @Override
                public void onCallJoinSent(String callID, ZIMCallJoinSentInfo info, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("callID", callID);
                        returnMap.putDouble("createTime", info.createTime);
                        returnMap.putDouble("joinTime", info.joinTime);
                        returnMap.putString("extendedData", info.extendedData);
                        returnMap.putArray("callUserList", RNZIMConverter.mZIMCallUserInfoList(info.callUserList));

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void callingInvite(
        String handle,
        ReadableArray invitees,
        String callID,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCallingInviteConfig configObj = new ZIMCallingInviteConfig();
        if (config != null) {
            configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);
        }

        zim.callingInvite(
            RNZIMConverter.oStringArray(invitees),
            callID,
            configObj,
            new ZIMCallingInvitationSentCallback() {
                @Override
                public void onCallingInvitationSent(
                    String callID,
                    ZIMCallingInvitationSentInfo info,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("callID", callID);
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(info.errorUserList));

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryCallInvitationList(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMCallInvitationQueryConfig configObj = new ZIMCallInvitationQueryConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.nextFlag = config.hasKey("nextFlag")
                ? Double.valueOf(config.getDouble("nextFlag")).longValue()
                : 0;
        }

        zim.queryCallInvitationList(
            configObj,
            new ZIMCallInvitationListQueriedCallback() {
                @Override
                public void onCallInvitationListQueried(
                    ArrayList<ZIMCallInfo> callList,
                    long nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putDouble("nextFlag", nextFlag);
                        WritableArray callArray = Arguments.createArray();
                        for (ZIMCallInfo info : callList) {
                            WritableMap infoMap = Arguments.createMap();
                            infoMap.putInt("mode", info.mode.value());
                            infoMap.putInt("state", info.state.value());
                            infoMap.putString("callID", info.callID);
                            infoMap.putString("caller", info.caller);
                            infoMap.putString("extendedData", info.extendedData);
                            infoMap.putDouble("createTime", info.createTime);
                            infoMap.putDouble("endTime", info.endTime);
                            infoMap.putArray("callUserList", RNZIMConverter.mZIMCallUserInfoList(info.callUserList));

                            callArray.pushMap(infoMap);
                        }
                        returnMap.putArray("callList", callArray);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void searchConversations(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMConversationSearchConfig configObj = new ZIMConversationSearchConfig();
        if (config != null) {
            if (config.hasKey("mode")) {
                configObj.mode = ZIMConversationSearchMode.getZIMConversationSearchMode(config.getInt("mode"));
            }
            configObj.nextFlag = config.hasKey("nextFlag") ? config.getInt("nextFlag") : 0;
            configObj.totalConversationCount = config.hasKey("totalConversationCount")
                ? config.getInt("totalConversationCount")
                : 0;
            configObj.conversationMessageCount = config.hasKey("conversationMessageCount")
                ? config.getInt("conversationMessageCount")
                : 0;
            configObj.startTime = config.hasKey("startTime")
                ? Double.valueOf(config.getDouble("startTime")).longValue()
                : 0;
            configObj.endTime = config.hasKey("endTime") ? Double.valueOf(config.getDouble("endTime")).longValue() : 0;
            if (config.hasKey("keywords")) {
                configObj.keywords = RNZIMConverter.oStringArray(config.getArray("keywords"));
            }
            if (config.hasKey("senderUserIDs")) {
                configObj.senderUserIDs = RNZIMConverter.oStringArray(config.getArray("senderUserIDs"));
            }
            if (config.hasKey("messageTypes")) {
                configObj.messageTypes = RNZIMConverter.oZIMMessageTypeArray(config.getArray("messageTypes"));
            }
            if (config.hasKey("subMessageTypes")) {
                configObj.subMessageTypes = RNZIMConverter.oIntArray(config.getArray("subMessageTypes"));
            }
        }

        zim.searchLocalConversations(
            configObj,
            new ZIMConversationsSearchedCallback() {
                @Override
                public void onConversationsSearched(
                    ArrayList<ZIMConversationSearchInfo> conversationSearchInfoList,
                    int nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putInt("nextFlag", nextFlag);

                        WritableArray infoArray = Arguments.createArray();
                        for (ZIMConversationSearchInfo info : conversationSearchInfoList) {
                            WritableMap infoMap = Arguments.createMap();
                            infoMap.putString("conversationID", info.conversationID);
                            infoMap.putInt("conversationType", info.conversationType.value());
                            infoMap.putMap("conversation", RNZIMConverter.mZIMConversation(info.conversation));
                            infoMap.putInt("totalMessageCount", info.totalMessageCount);
                            infoMap.putArray("messageList", RNZIMConverter.mZIMMessageList(info.messageList));

                            infoArray.pushMap(infoMap);
                        }
                        returnMap.putArray("conversationSearchInfoList", infoArray);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void searchGlobalMessages(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessageSearchConfig configObj = new ZIMMessageSearchConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.order = ZIMMessageOrder.getZIMMessageOrder(config.hasKey("order") ? config.getInt("order") : 0);
            configObj.startTime = config.hasKey("startTime")
                ? Double.valueOf(config.getDouble("startTime")).longValue()
                : 0;
            configObj.endTime = config.hasKey("endTime") ? Double.valueOf(config.getDouble("endTime")).longValue() : 0;
            if (config.hasKey("nextMessage")) {
                configObj.nextMessage = RNZIMConverter.oZIMMessage(config.getMap("nextMessage"));
            }
            if (config.hasKey("keywords")) {
                configObj.keywords = RNZIMConverter.oStringArray(config.getArray("keywords"));
            }
            if (config.hasKey("senderUserIDs")) {
                configObj.senderUserIDs = RNZIMConverter.oStringArray(config.getArray("senderUserIDs"));
            }
            if (config.hasKey("messageTypes")) {
                configObj.messageTypes = RNZIMConverter.oZIMMessageTypeArray(config.getArray("messageTypes"));
            }
            if (config.hasKey("subMessageTypes")) {
                configObj.subMessageTypes = RNZIMConverter.oIntArray(config.getArray("subMessageTypes"));
            }
        }

        zim.searchGlobalLocalMessages(
            configObj,
            new ZIMMessagesGlobalSearchedCallback() {
                @Override
                public void onMessagesGlobalSearched(
                    ArrayList<ZIMMessage> messageList,
                    ZIMMessage nextMessage,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        if (nextMessage != null) {
                            returnMap.putMap("nextMessage", RNZIMConverter.mZIMMessage(nextMessage));
                        } else {
                            returnMap.putNull("nextMessage");
                        }

                        returnMap.putArray("messageList", RNZIMConverter.mZIMMessageList(messageList));

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void searchMessages(
        String handle,
        String conversationID,
        int conversationType,
        ReadableMap config,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMMessageSearchConfig configObj = new ZIMMessageSearchConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.order = ZIMMessageOrder.getZIMMessageOrder(config.hasKey("order") ? config.getInt("order") : 0);
            configObj.startTime = config.hasKey("startTime")
                ? Double.valueOf(config.getDouble("startTime")).longValue()
                : 0;
            configObj.endTime = config.hasKey("endTime") ? Double.valueOf(config.getDouble("endTime")).longValue() : 0;
            if (config.hasKey("nextMessage")) {
                configObj.nextMessage = RNZIMConverter.oZIMMessage(config.getMap("nextMessage"));
            }
            if (config.hasKey("keywords")) {
                configObj.keywords = RNZIMConverter.oStringArray(config.getArray("keywords"));
            }
            if (config.hasKey("senderUserIDs")) {
                configObj.senderUserIDs = RNZIMConverter.oStringArray(config.getArray("senderUserIDs"));
            }
            if (config.hasKey("messageTypes")) {
                configObj.messageTypes = RNZIMConverter.oZIMMessageTypeArray(config.getArray("messageTypes"));
            }
            if (config.hasKey("subMessageTypes")) {
                configObj.subMessageTypes = RNZIMConverter.oIntArray(config.getArray("subMessageTypes"));
            }
        }

        zim.searchLocalMessages(
            conversationID,
            ZIMConversationType.getZIMConversationType(conversationType),
            configObj,
            new ZIMMessagesSearchedCallback() {
                @Override
                public void onMessagesSearched(
                    String conversationID,
                    ZIMConversationType conversationType,
                    ArrayList<ZIMMessage> messageList,
                    ZIMMessage nextMessage,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("conversationID", conversationID);
                        returnMap.putInt("conversationType", conversationType.value());

                        if (nextMessage != null) {
                            returnMap.putMap("nextMessage", RNZIMConverter.mZIMMessage(nextMessage));
                        } else {
                            returnMap.putNull("nextMessage");
                        }

                        returnMap.putArray("messageList", RNZIMConverter.mZIMMessageList(messageList));

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void searchGroups(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupSearchConfig configObj = new ZIMGroupSearchConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.nextFlag = config.hasKey("nextFlag") ? config.getInt("nextFlag") : 0;
            configObj.isAlsoMatchGroupMemberUserName = config.hasKey("isAlsoMatchGroupMemberUserName")
                ? config.getBoolean("isAlsoMatchGroupMemberUserName")
                : false;
            configObj.isAlsoMatchGroupMemberNickname = config.hasKey("isAlsoMatchGroupMemberNickname")
                ? config.getBoolean("isAlsoMatchGroupMemberNickname")
                : false;
            if (config.hasKey("keywords")) {
                configObj.keywords = RNZIMConverter.oStringArray(config.getArray("keywords"));
            }
        }

        zim.searchLocalGroups(
            configObj,
            new ZIMGroupsSearchedCallback() {
                @Override
                public void onGroupsSearched(
                    ArrayList<ZIMGroupSearchInfo> groupSearchInfoList,
                    int nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putInt("nextFlag", nextFlag);

                        WritableArray infoArray = Arguments.createArray();
                        for (ZIMGroupSearchInfo info : groupSearchInfoList) {
                            WritableMap infoMap = Arguments.createMap();
                            infoMap.putMap("groupInfo", RNZIMConverter.mZIMGroupInfo(info.groupInfo));
                            infoMap.putArray("userList", RNZIMConverter.mZIMGroupMemberInfoList(info.userList));
                            infoArray.pushMap(infoMap);
                        }
                        returnMap.putArray("groupSearchInfoList", infoArray);

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void searchGroupMembers(String handle, String groupID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMGroupMemberSearchConfig configObj = new ZIMGroupMemberSearchConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.nextFlag = config.hasKey("nextFlag") ? config.getInt("nextFlag") : 0;
            configObj.isAlsoMatchGroupMemberNickname = config.hasKey("isAlsoMatchGroupMemberNickname")
                ? config.getBoolean("isAlsoMatchGroupMemberNickname")
                : false;
            if (config.hasKey("keywords")) {
                configObj.keywords = RNZIMConverter.oStringArray(config.getArray("keywords"));
            }
        }

        zim.searchLocalGroupMembers(
            groupID,
            configObj,
            new ZIMGroupMembersSearchedCallback() {
                @Override
                public void onGroupMembersSearched(
                    String groupID,
                    ArrayList<ZIMGroupMemberInfo> userList,
                    int nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putString("groupID", groupID);
                        returnMap.putInt("nextFlag", nextFlag);
                        returnMap.putArray("userList", RNZIMConverter.mZIMGroupMemberInfoList(userList));

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void searchFriends(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMFriendSearchConfig configObj = new ZIMFriendSearchConfig();
        if (config != null) {
            configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
            configObj.nextFlag = config.hasKey("nextFlag") ? config.getInt("nextFlag") : 0;
            configObj.isAlsoMatchFriendAlias = config.hasKey("isAlsoMatchFriendAlias")
                ? config.getBoolean("isAlsoMatchFriendAlias")
                : false;
            if (config.hasKey("keywords")) {
                configObj.keywords = RNZIMConverter.oStringArray(config.getArray("keywords"));
            }
        }

        zim.searchLocalFriends(
            configObj,
            new ZIMFriendsSearchedCallback() {
                @Override
                public void onFriendsSearched(ArrayList<ZIMFriendInfo> friendInfos, int nextFlag, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putInt("nextFlag", nextFlag);
                        returnMap.putArray("friendInfos", RNZIMConverter.mZIMFriendInfoList(friendInfos));

                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void addFriend(String handle, String userID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMFriendAddConfig configObj = new ZIMFriendAddConfig();
        configObj.wording = config.hasKey("wording") ? config.getString("wording") : "";
        configObj.friendAlias = config.hasKey("friendAlias") ? config.getString("friendAlias") : "";
        if (config.hasKey("friendAttributes")) {
            configObj.friendAttributes = RNZIMConverter.oMap(config.getMap("friendAttributes"));
        }

        zim.addFriend(
            userID,
            configObj,
            new ZIMFriendAddedCallback() {
                @Override
                public void onFriendAdded(ZIMFriendInfo friendInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("friendInfo", RNZIMConverter.mZIMFriendInfo(friendInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void sendFriendApplication(String handle, String userID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMFriendApplicationSendConfig configObj = new ZIMFriendApplicationSendConfig();
        configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);
        configObj.wording = config.hasKey("wording") ? config.getString("wording") : "";
        configObj.friendAlias = config.hasKey("friendAlias") ? config.getString("friendAlias") : "";
        if (config.hasKey("friendAttributes")) {
            configObj.friendAttributes = RNZIMConverter.oMap(config.getMap("friendAttributes"));
        }

        zim.sendFriendApplication(
            userID,
            configObj,
            new ZIMFriendApplicationSentCallback() {
                @Override
                public void onFriendApplicationSent(ZIMFriendApplicationInfo applicationInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("applicationInfo", RNZIMConverter.mZIMFriendApplicationInfo(applicationInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void deleteFriends(String handle, ReadableArray userIDs, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMFriendDeleteConfig configObj = new ZIMFriendDeleteConfig();
        configObj.type = ZIMFriendDeleteType.getZIMFriendDeleteType(config.hasKey("type") ? config.getInt("type") : 0);

        zim.deleteFriends(
            RNZIMConverter.oStringArray(userIDs),
            configObj,
            new ZIMFriendsDeletedCallback() {
                @Override
                public void onFriendsDeleted(ArrayList<ZIMErrorUserInfo> errorUserList, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void checkFriendsRelation(String handle, ReadableArray userIDs, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMFriendRelationCheckConfig configObj = new ZIMFriendRelationCheckConfig();
        configObj.type = ZIMFriendRelationCheckType.getZIMFriendRelationCheckType(
            config.hasKey("type") ? config.getInt("type") : 0
        );

        zim.checkFriendsRelation(
            RNZIMConverter.oStringArray(userIDs),
            configObj,
            new ZIMFriendsRelationCheckedCallback() {
                @Override
                public void onFriendsRelationChecked(
                    ArrayList<ZIMFriendRelationInfo> relationInfos,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();

                        WritableArray relationInfoArray = Arguments.createArray();
                        for (ZIMFriendRelationInfo info : relationInfos) {
                            WritableMap map = Arguments.createMap();
                            map.putString("userID", info.userID);
                            map.putInt("type", info.type.value());
                            relationInfoArray.pushMap(map);
                        }
                        returnMap.putArray("relationInfos", relationInfoArray);

                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateFriendAlias(String handle, String friendAlias, String userID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateFriendAlias(
            friendAlias,
            userID,
            new ZIMFriendAliasUpdatedCallback() {
                @Override
                public void onFriendAliasUpdated(ZIMFriendInfo friendInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("friendInfo", RNZIMConverter.mZIMFriendInfo(friendInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void updateFriendAttributes(
        String handle,
        ReadableMap friendAttributes,
        String userID,
        final Promise promise
    ) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.updateFriendAttributes(
            RNZIMConverter.oMap(friendAttributes),
            userID,
            new ZIMFriendAttributesUpdatedCallback() {
                @Override
                public void onFriendAttributesUpdated(ZIMFriendInfo friendInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("friendInfo", RNZIMConverter.mZIMFriendInfo(friendInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void acceptFriendApplication(String handle, String userID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMFriendApplicationAcceptConfig configObj = new ZIMFriendApplicationAcceptConfig();
        configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);
        configObj.friendAlias = config.hasKey("friendAlias") ? config.getString("friendAlias") : "";
        if (config.hasKey("friendAttributes")) {
            configObj.friendAttributes = RNZIMConverter.oMap(config.getMap("friendAttributes"));
        }

        zim.acceptFriendApplication(
            userID,
            configObj,
            new ZIMFriendApplicationAcceptedCallback() {
                @Override
                public void onFriendApplicationAccepted(ZIMFriendInfo friendInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("friendInfo", RNZIMConverter.mZIMFriendInfo(friendInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void rejectFriendApplication(String handle, String userID, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMFriendApplicationRejectConfig configObj = new ZIMFriendApplicationRejectConfig();
        configObj.pushConfig = RNZIMConverter.oZIMPushConfig(config);

        zim.rejectFriendApplication(
            userID,
            configObj,
            new ZIMFriendApplicationRejectedCallback() {
                @Override
                public void onFriendApplicationRejected(ZIMUserInfo userInfo, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putMap("userInfo", RNZIMConverter.mZIMUserInfo(userInfo));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryFriendsInfo(String handle, ReadableArray userIDs, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.queryFriendsInfo(
            RNZIMConverter.oStringArray(userIDs),
            new ZIMFriendsInfoQueriedCallback() {
                @Override
                public void onFriendsInfoQueried(
                    ArrayList<ZIMFriendInfo> friendInfos,
                    ArrayList<ZIMErrorUserInfo> errorUserList,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putArray("friendInfos", RNZIMConverter.mZIMFriendInfoList(friendInfos));
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryFriendList(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMFriendListQueryConfig configObj = new ZIMFriendListQueryConfig();
        configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
        configObj.nextFlag = config.hasKey("nextFlag") ? config.getInt("nextFlag") : 0;

        zim.queryFriendList(
            configObj,
            new ZIMFriendListQueriedCallback() {
                @Override
                public void onFriendListQueried(ArrayList<ZIMFriendInfo> friendList, int nextFlag, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putInt("nextFlag", nextFlag);
                        returnMap.putArray("friendList", RNZIMConverter.mZIMFriendInfoList(friendList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryFriendApplicationList(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMFriendApplicationListQueryConfig configObj = new ZIMFriendApplicationListQueryConfig();
        configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
        configObj.nextFlag = config.hasKey("nextFlag") ? config.getInt("nextFlag") : 0;

        zim.queryFriendApplicationList(
            configObj,
            new ZIMFriendApplicationListQueriedCallback() {
                @Override
                public void onFriendApplicationListQueried(
                    ArrayList<ZIMFriendApplicationInfo> applicationList,
                    int nextFlag,
                    ZIMError errorInfo
                ) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putInt("nextFlag", nextFlag);
                        returnMap.putArray(
                            "applicationList",
                            RNZIMConverter.mZIMFriendApplicationInfoList(applicationList)
                        );
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void addUsersToBlacklist(String handle, ReadableArray userIDs, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.addUsersToBlacklist(
            RNZIMConverter.oStringArray(userIDs),
            new ZIMBlacklistUsersAddedCallback() {
                @Override
                public void onBlacklistUsersAdded(ArrayList<ZIMErrorUserInfo> errorUserList, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void removeUsersFromBlacklist(String handle, ReadableArray userIDs, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.removeUsersFromBlacklist(
            RNZIMConverter.oStringArray(userIDs),
            new ZIMBlacklistUsersRemovedCallback() {
                @Override
                public void onBlacklistUsersRemoved(ArrayList<ZIMErrorUserInfo> errorUserList, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putArray("errorUserList", RNZIMConverter.mZIMErrorUserInfo(errorUserList));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void checkUserIsInBlacklist(String handle, String userID, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        zim.checkUserIsInBlacklist(
            userID,
            new ZIMBlacklistCheckedCallback() {
                @Override
                public void onBlacklistChecked(boolean isUserInBlacklist, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putBoolean("isUserInBlacklist", isUserInBlacklist);
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }

    @ReactMethod
    public void queryBlacklist(String handle, ReadableMap config, final Promise promise) {
        ZIM zim = getZIMEngine(handle, promise);
        if (zim == null) {
            return;
        }

        ZIMBlacklistQueryConfig configObj = new ZIMBlacklistQueryConfig();
        configObj.count = config.hasKey("count") ? config.getInt("count") : 0;
        configObj.nextFlag = config.hasKey("nextFlag") ? config.getInt("nextFlag") : 0;

        zim.queryBlacklist(
            configObj,
            new ZIMBlacklistQueriedCallback() {
                @Override
                public void onBlacklistQueried(ArrayList<ZIMUserInfo> blacklist, int nextFlag, ZIMError errorInfo) {
                    if (isSucc(errorInfo, promise)) {
                        WritableMap returnMap = Arguments.createMap();
                        returnMap.putInt("nextFlag", nextFlag);
                        returnMap.putArray("blacklist", RNZIMConverter.mZIMUserInfoList(blacklist));
                        promise.resolve(returnMap);
                    }
                }
            }
        );
    }
}
