/*
* Copyright (c) 2023-2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "BridgeBinaryCodec.h"
#import "BridgeJsonCodec.h"
#import "BridgePlugin+jsMessage.h"
#import "BridgePluginManager+internal.h"
#import "ResultValue.h"
#include "base/log/log.h"
@implementation BridgePlugin (jsMessage)
- (void)jsCallMethod:(MethodData*)callMethod {
ErrorCode errorCode = BRIDGE_ERROR_NO;
NSString* errorMessage = nil;
id result = nil;
if (!callMethod.methodName.length) {
errorCode = BRIDGE_METHOD_NAME_ERROR;
errorMessage = BRIDGE_METHOD_NAME_ERROR_MESSAGE;
} else {
NSArray* parameterArray = (NSArray*)callMethod.parameter;
if (callMethod.methodName.length != 0) {
@try {
NSString* tmep = callMethod.methodName;
if ([tmep containsString:@"$"]) {
NSArray* strinMethodNameArr = [tmep componentsSeparatedByString:@"$"];
tmep = strinMethodNameArr[0];
}
result = [self performeNewSelector:tmep
withParams:parameterArray
target:self];
if (result && [result isKindOfClass:NSDictionary.class]) {
NSDictionary* dic = (NSDictionary*)result;
errorCode = (ErrorCode)[dic[@"errorCode"] intValue];
errorMessage = dic[@"errorMessage"];
}
} @catch (NSException* exception) {
errorCode = BRIDGE_METHOD_UNIMPL;
errorMessage = BRIDGE_METHOD_UNIMPL_MESSAGE;
LOGE("catch exception name : %{public}s, reason : %{public}s",
[exception.name ?: @"" UTF8String],
[exception.reason ?: @"" UTF8String]);
}
} else {
errorCode = BRIDGE_METHOD_UNIMPL;
errorMessage = BRIDGE_METHOD_UNIMPL_MESSAGE;
LOGE("method error, message : %{public}s", (errorMessage ?: @"").UTF8String);
}
}
if (self.type == JSON_TYPE) {
[self.bridgeManager platformSendMethodResult:self.bridgeName
methodName:callMethod.methodName
errorCode:errorCode
errorMessage:errorMessage.length ? errorMessage : @""
result:result];
} else {
// BINARY_TYPE
if ([result isKindOfClass:[NSArray class]]) {
ResultValue* resultValue = [[ResultValue alloc] init];
resultValue.errorCode = BRIDGE_DATA_ERROR;
resultValue.errorMessage = BRIDGE_DATA_ERROR_MESSAGE;
resultValue.methodName = [NSString stringWithFormat:@"%@: please use BridgeArray", callMethod.methodName];
[self callPlatformError:resultValue];
return;
}
[self.bridgeManager platformSendMethodResultBinary:self.bridgeName
methodName:callMethod.methodName
errorCode:errorCode
errorMessage:errorMessage.length ? errorMessage : @""
result:result];
}
}
- (NSString*)jsCallMethodSync:(MethodData*)callMethod
{
ErrorCode errorCode = BRIDGE_ERROR_NO;
NSString* errorMessage = BRIDGE_ERROR_NO_MESSAGE;
id result = nil;
if (!callMethod) {
errorCode = BRIDGE_INVALID;
errorMessage = BRIDGE_INVALID_MESSAGE;
return [self createResultJson:errorCode errorMessage:errorMessage result:result];
}
NSArray* parameterArray = (NSArray*)callMethod.parameter;
if (callMethod.methodName.length == 0) {
errorCode = BRIDGE_METHOD_UNIMPL;
errorMessage = BRIDGE_METHOD_UNIMPL_MESSAGE;
LOGE("method error, message : %{public}s", (errorMessage ?: @"").UTF8String);
return [self createResultJson:errorCode errorMessage:errorMessage result:result];
}
@try {
NSString* tmep = callMethod.methodName;
if ([tmep containsString:@"$"]) {
NSArray* strinMethodNameArr = [tmep componentsSeparatedByString:@"$"];
tmep = strinMethodNameArr[0];
}
result = [self performeNewSelector:tmep withParams:parameterArray target:self];
if (result && [result isKindOfClass:NSDictionary.class]) {
NSDictionary* dic = (NSDictionary*)result;
errorCode = (ErrorCode)[dic[@"errorCode"] intValue];
errorMessage = dic[@"errorMessage"];
}
} @catch (NSException* exception) {
errorCode = BRIDGE_METHOD_UNIMPL;
errorMessage = BRIDGE_METHOD_UNIMPL_MESSAGE;
LOGE("catch exception name : %{public}s, reason : %{public}s",
[exception.name ?: @"" UTF8String],
[exception.reason ?: @"" UTF8String]);
}
return [self createResultJson:errorCode errorMessage:errorMessage result:result];
}
- (NSString*)createResultJson:(int)errorCode errorMessage:(NSString*)errorMessage result:(id)result
{
NSNumber* numberErrorCode = [NSNumber numberWithInt:errorCode];
NSString* strErrorMessage = errorMessage ?: @"";
if (result == nil) {
result = @"";
}
NSDictionary* dict = @{ @"errorCode" : numberErrorCode, @"errorMessage" : strErrorMessage, @"result" : result };
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
NSString* resultJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return resultJson;
}
- (int)SafeParseErrorCode:(id)errorCodeObj
{
int errorCode = BRIDGE_ERROR_NO;
if ([errorCodeObj isKindOfClass:[NSNumber class]] || [errorCodeObj isKindOfClass:[NSString class]]) {
errorCode = [errorCodeObj intValue];
}
if (errorCode < BRIDGE_ERROR_NO || errorCode > BRIDGE_END) {
return BRIDGE_INVALID;
}
return errorCode;
}
- (ResultValue*)jsCallMethodBinarySync:(MethodData*)callMethod
{
ResultValue* resultValue = [[ResultValue alloc] init];
if (!callMethod) {
resultValue.errorCode = BRIDGE_INVALID;
resultValue.errorMessage = BRIDGE_INVALID_MESSAGE;
return resultValue;
}
if (callMethod.methodName.length == 0) {
resultValue.errorCode = BRIDGE_METHOD_NAME_ERROR;
resultValue.errorMessage = BRIDGE_METHOD_NAME_ERROR_MESSAGE;
return resultValue;
}
NSArray* parameterArray = (NSArray*)callMethod.parameter;
NSString* tmep = callMethod.methodName;
if ([tmep containsString:@"$"]) {
NSArray* strinMethodNameArr = [tmep componentsSeparatedByString:@"$"];
tmep = strinMethodNameArr[0];
}
id result = [self performeNewSelector:tmep withParams:parameterArray target:self];
if (result == nil) {
resultValue.errorCode = BRIDGE_DATA_ERROR;
resultValue.errorMessage = BRIDGE_DATA_ERROR_MESSAGE;
return resultValue;
}
if ([result isKindOfClass:[NSDictionary class]] && [((NSDictionary*)result) objectForKey:@"errorCode"]) {
NSDictionary* dic = (NSDictionary*)result;
resultValue.errorCode = (ErrorCode)[self SafeParseErrorCode:dic[@"errorCode"]];
resultValue.errorMessage = dic[@"errorMessage"] ?: @"";
} else {
resultValue.errorCode = BRIDGE_ERROR_NO;
resultValue.errorMessage = BRIDGE_ERROR_NO_MESSAGE;
resultValue.result = [[BridgeBinaryCodec sharedInstance] encode:result];
}
return resultValue;
}
- (void)jsSendMethodResult:(ResultValue*)object {
if (self.methodResult) {
if (object.errorCode > 0) {
if ([self.methodResult respondsToSelector:@selector(onError:errorCode:errorMessage:)]) {
[self.methodResult onError:object.methodName
errorCode:object.errorCode
errorMessage:object.errorMessage];
}
} else {
if ([self.methodResult respondsToSelector:@selector(onSuccess:resultValue:)]) {
[self.methodResult onSuccess:object.methodName
resultValue:object.result];
}
}
}
}
- (void)jsSendMessage:(id)data {
if (self.messageListener && [self.messageListener respondsToSelector:@selector(onMessage:)]) {
id object = [self.messageListener onMessage:data];
[self.bridgeManager platformSendMessageResponse:self.bridgeName
data:object];
}
}
- (void)jsSendMessageResponse:(id)data {
if (data && self.messageListener && [self.messageListener respondsToSelector:@selector(onMessageResponse:)]) {
[self.messageListener onMessageResponse:data];
}
}
- (void)jsCancelMethod:(NSString*)bridgeName methodName:(NSString*)methodName {
if (self.methodResult && [self.methodResult respondsToSelector:@selector(onMethodCancel:)]) {
[self.methodResult onMethodCancel:methodName];
}
}
- (id)performeNewSelector:(NSString*)methodName withParams:(NSArray*)params target:(id)target {
NSUInteger paramCount = params.count;
int signatureDefaultArgsNum = 2;
NSMethodSignature* signature;
SEL selector = nullptr;
if (params.count == 0) {
selector = NSSelectorFromString(methodName);
signature = [target methodSignatureForSelector:selector];
} else {
Class currentClass = [target class];
while (currentClass && currentClass != [BridgePlugin class] && currentClass != [NSObject class]) {
unsigned int methodCount;
Method* methodList = class_copyMethodList(currentClass, &methodCount);
for (int i = 0; i < methodCount; i++) {
Method method = methodList[i];
SEL c_sel = method_getName(method);
const char* name = sel_getName(c_sel);
if (![methodName hasSuffix:@":"]) {
methodName = [methodName stringByAppendingString:@":"];
}
const char* c_methodname = [methodName UTF8String];
signature = [target methodSignatureForSelector:c_sel];
if (signature && !strncmp(name, c_methodname, strlen(c_methodname))) {
selector = c_sel;
break;
}
}
free(methodList);
currentClass = selector ? [NSObject class] : class_getSuperclass(currentClass);
}
}
return [self handleMethodParam:signature target:target selector:selector params:params];
}
BOOL isNumberTypeMatch(id argument, const char *argumentType) {
if (![argument isKindOfClass:[NSNumber class]]) {
return NO;
}
const char* numberType = [argument objCType];
if (strcmp(numberType, "c") == 0 && strcmp(argumentType, "B") == 0) {
return YES;
}
return strcmp(argumentType, numberType) == 0;
}
- (id)handleMethodParam:(NSMethodSignature*)signature
target:(id)target
selector:(SEL)selector
params:(NSArray*)params {
int signatureDefaultArgsNum = 2;
if (!signature || selector == nullptr) {
LOGE("signature nil");
return @{@"errorCode": @(BRIDGE_METHOD_UNIMPL), @"errorMessage": BRIDGE_METHOD_UNIMPL_MESSAGE};
}
NSInteger paramsCount = signature.numberOfArguments - signatureDefaultArgsNum;
if (paramsCount != params.count) {
LOGE("params count error");
return @{@"errorCode": @(BRIDGE_METHOD_PARAM_ERROR), @"errorMessage": BRIDGE_METHOD_PARAM_ERROR_MESSAGE};
}
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = target;
invocation.selector = selector;
if (params.count > 0) {
if ([params containsObject:[NSNull null]]) {
return @{@"errorCode": @(BRIDGE_METHOD_PARAM_ERROR),
@"errorMessage": BRIDGE_METHOD_PARAM_ERROR_MESSAGE};
}
id err = [self bridgeFillInvocationParams:params
signature:signature
invocation:invocation
startIndex:signatureDefaultArgsNum];
if (err) {
return err;
}
}
[invocation retainArguments];
[invocation invoke];
if (signature.methodReturnLength) {
return [self handleReturnValue:signature invocation:invocation];
}
LOGE("no returnValue");
return nil;
}
- (id)bridgeFillInvocationParams:(NSArray*)params
signature:(NSMethodSignature*)signature
invocation:(NSInvocation*)invocation
startIndex:(int)signatureDefaultArgsNum {
NSInteger paramsCount = signature.numberOfArguments - signatureDefaultArgsNum;
for (int i = 0; i < paramsCount; i++) {
id argument = params[i];
if (argument == [NSNull null]) {
return
@{ @"errorCode" : @(BRIDGE_METHOD_PARAM_ERROR), @"errorMessage" : BRIDGE_METHOD_PARAM_ERROR_MESSAGE };
}
const char* argumentType = [signature getArgumentTypeAtIndex:i + signatureDefaultArgsNum];
if (!strcmp(argumentType, @encode(id))) {
[invocation setArgument:&argument atIndex:i + signatureDefaultArgsNum];
} else if ([argument isKindOfClass:[NSNumber class]]) {
if (!isNumberTypeMatch(argument, argumentType)) {
return @{
@"errorCode" : @(BRIDGE_METHOD_PARAM_ERROR),
@"errorMessage" : BRIDGE_METHOD_PARAM_ERROR_MESSAGE
};
}
if (!strcmp(argumentType, @encode(BOOL))) {
BOOL arg = [argument boolValue];
[invocation setArgument:&arg atIndex:i + signatureDefaultArgsNum];
} else if (!strcmp(argumentType, @encode(int))) {
int arg = [argument intValue];
[invocation setArgument:&arg atIndex:i + signatureDefaultArgsNum];
} else if (!strcmp(argumentType, @encode(float))) {
float arg = [argument floatValue];
[invocation setArgument:&arg atIndex:i + signatureDefaultArgsNum];
} else if (!strcmp(argumentType, @encode(long))) {
long arg = [argument longValue];
[invocation setArgument:&arg atIndex:i + signatureDefaultArgsNum];
} else if (!strcmp(argumentType, @encode(double))) {
double arg = [argument doubleValue];
[invocation setArgument:&arg atIndex:i + signatureDefaultArgsNum];
} else {
return @{
@"errorCode" : @(BRIDGE_METHOD_PARAM_ERROR),
@"errorMessage" : BRIDGE_METHOD_PARAM_ERROR_MESSAGE
};
}
} else {
return
@{ @"errorCode" : @(BRIDGE_METHOD_PARAM_ERROR), @"errorMessage" : BRIDGE_METHOD_PARAM_ERROR_MESSAGE };
}
}
return nil;
}
- (id)handleReturnValue:(NSMethodSignature*)signature
invocation:(NSInvocation*)invocation {
const char* returnType = signature.methodReturnType;
if (!strcmp(returnType, @encode(void))) {
LOGE("no returnValue");
return nil;
} else if (!strcmp(returnType, @encode(id))) {
void* returnValue;
[invocation getReturnValue:&returnValue];
id obj = (__bridge id)returnValue;
return obj;
} else {
void* returnValue = (void*)malloc(signature.methodReturnLength);
if (!returnValue) {
return nil;
}
[invocation getReturnValue:returnValue];
id result = nil;
if (!strcmp(returnType, @encode(BOOL))) {
result = [NSNumber numberWithBool:*((BOOL*)returnValue)];
} else if (!strcmp(returnType, @encode(int))) {
result = [NSNumber numberWithInt:*((int*)returnValue)];
} else if (!strcmp(returnType, @encode(float))) {
result = [NSNumber numberWithFloat:*((float*)returnValue)];
} else if (!strcmp(returnType, @encode(long))) {
result = [NSNumber numberWithLong:*((long*)returnValue)];
} else if (!strcmp(returnType, @encode(double))) {
result = [NSNumber numberWithDouble:*((double*)returnValue)];
}
free(returnValue);
return result;
}
}
- (void)callPlatformError:(ResultValue*)object {
if (self.methodResult &&
[self.methodResult respondsToSelector:@selector(onError:errorCode:errorMessage:)]) {
if (object.errorCode > 0) {
[self.methodResult onError:object.methodName
errorCode:object.errorCode
errorMessage:object.errorMessage];
}
}
}
@end