269 lines
10 KiB
Objective-C
269 lines
10 KiB
Objective-C
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
|
||
|
||
//
|
||
// LookinServer.m
|
||
// LookinServer
|
||
//
|
||
// Created by Li Kai on 2018/8/5.
|
||
// https://lookin.work
|
||
//
|
||
|
||
#import "LKS_ConnectionManager.h"
|
||
#import "Lookin_PTChannel.h"
|
||
#import "LKS_RequestHandler.h"
|
||
#import "LookinConnectionResponseAttachment.h"
|
||
#import "LKS_ExportManager.h"
|
||
#import "LookinServerDefines.h"
|
||
#import "LKS_TraceManager.h"
|
||
#import "LKS_MultiplatformAdapter.h"
|
||
|
||
NSString *const LKS_ConnectionDidEndNotificationName = @"LKS_ConnectionDidEndNotificationName";
|
||
|
||
@interface LKS_ConnectionManager () <Lookin_PTChannelDelegate>
|
||
|
||
@property(nonatomic, weak) Lookin_PTChannel *peerChannel_;
|
||
|
||
@property(nonatomic, strong) LKS_RequestHandler *requestHandler;
|
||
|
||
@end
|
||
|
||
@implementation LKS_ConnectionManager
|
||
|
||
+ (instancetype)sharedInstance {
|
||
static LKS_ConnectionManager *sharedInstance;
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
sharedInstance = [[LKS_ConnectionManager alloc] init];
|
||
});
|
||
return sharedInstance;
|
||
}
|
||
|
||
+ (void)load {
|
||
// 触发 init 方法
|
||
[LKS_ConnectionManager sharedInstance];
|
||
}
|
||
|
||
- (instancetype)init {
|
||
if (self = [super init]) {
|
||
NSLog(@"LookinServer - Will launch. Framework version: %@", LOOKIN_SERVER_READABLE_VERSION);
|
||
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleApplicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil];
|
||
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_2D" object:nil];
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_3D" object:nil];
|
||
[[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_Export" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
|
||
[[LKS_ExportManager sharedInstance] exportAndShare];
|
||
}];
|
||
[[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_RelationSearch" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
|
||
[[LKS_TraceManager sharedInstance] addSearchTarger:note.object];
|
||
}];
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleGetLookinInfo:) name:@"GetLookinInfo" object:nil];
|
||
|
||
self.requestHandler = [LKS_RequestHandler new];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (void)_handleWillResignActiveNotification {
|
||
self.applicationIsActive = NO;
|
||
|
||
if (self.peerChannel_ && ![self.peerChannel_ isConnected]) {
|
||
[self.peerChannel_ close];
|
||
self.peerChannel_ = nil;
|
||
}
|
||
}
|
||
|
||
- (void)_handleApplicationDidBecomeActive {
|
||
self.applicationIsActive = YES;
|
||
[self searchPortToListenIfNoConnection];
|
||
}
|
||
|
||
- (void)searchPortToListenIfNoConnection {
|
||
if ([self.peerChannel_ isConnected]) {
|
||
NSLog(@"LookinServer - Abort to search ports. Already has connected channel.");
|
||
return;
|
||
}
|
||
NSLog(@"LookinServer - Searching port to listen...");
|
||
[self.peerChannel_ close];
|
||
self.peerChannel_ = nil;
|
||
|
||
if ([self isiOSAppOnMac]) {
|
||
[self _tryToListenOnPortFrom:LookinSimulatorIPv4PortNumberStart to:LookinSimulatorIPv4PortNumberEnd current:LookinSimulatorIPv4PortNumberStart];
|
||
} else {
|
||
[self _tryToListenOnPortFrom:LookinUSBDeviceIPv4PortNumberStart to:LookinUSBDeviceIPv4PortNumberEnd current:LookinUSBDeviceIPv4PortNumberStart];
|
||
}
|
||
}
|
||
|
||
- (BOOL)isiOSAppOnMac {
|
||
#if TARGET_OS_SIMULATOR
|
||
return YES;
|
||
#else
|
||
if (@available(iOS 14.0, *)) {
|
||
// isiOSAppOnMac 这个 API 看似在 iOS 14.0 上可用,但其实在 iOS 14 beta 上是不存在的、有 unrecognized selector 问题,因此这里要用 respondsToSelector 做一下保护
|
||
NSProcessInfo *info = [NSProcessInfo processInfo];
|
||
if ([info respondsToSelector:@selector(isiOSAppOnMac)]) {
|
||
return [info isiOSAppOnMac];
|
||
} else if ([info respondsToSelector:@selector(isMacCatalystApp)]) {
|
||
return [info isMacCatalystApp];
|
||
} else {
|
||
return NO;
|
||
}
|
||
}
|
||
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
||
return [NSProcessInfo processInfo].isMacCatalystApp;
|
||
}
|
||
return NO;
|
||
#endif
|
||
}
|
||
|
||
- (void)_tryToListenOnPortFrom:(int)fromPort to:(int)toPort current:(int)currentPort {
|
||
Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self];
|
||
channel.targetPort = currentPort;
|
||
[channel listenOnPort:currentPort IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) {
|
||
if (error) {
|
||
if (error.code == 48) {
|
||
// 该地址已被占用
|
||
} else {
|
||
// 未知失败
|
||
}
|
||
|
||
if (currentPort < toPort) {
|
||
// 尝试下一个端口
|
||
NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@). Will try anothor address ...", currentPort, error);
|
||
[self _tryToListenOnPortFrom:fromPort to:toPort current:(currentPort + 1)];
|
||
} else {
|
||
// 所有端口都尝试完毕,全部失败
|
||
NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@).", currentPort, error);
|
||
NSLog(@"LookinServer - Connect failed in the end.");
|
||
}
|
||
|
||
} else {
|
||
// 成功
|
||
NSLog(@"LookinServer - Connected successfully on 127.0.0.1:%d", currentPort);
|
||
// 此时 peerChannel_ 状态为 listening
|
||
self.peerChannel_ = channel;
|
||
}
|
||
}];
|
||
}
|
||
|
||
- (void)dealloc {
|
||
if (self.peerChannel_) {
|
||
[self.peerChannel_ close];
|
||
}
|
||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||
}
|
||
|
||
- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag {
|
||
[self _sendData:data frameOfType:requestType tag:tag];
|
||
}
|
||
|
||
- (void)pushData:(NSObject *)data type:(uint32_t)type {
|
||
[self _sendData:data frameOfType:type tag:0];
|
||
}
|
||
|
||
- (void)_sendData:(NSObject *)data frameOfType:(uint32_t)frameOfType tag:(uint32_t)tag {
|
||
if (self.peerChannel_) {
|
||
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data];
|
||
dispatch_data_t payload = [archivedData createReferencingDispatchData];
|
||
|
||
[self.peerChannel_ sendFrameOfType:frameOfType tag:tag withPayload:payload callback:^(NSError *error) {
|
||
if (error) {
|
||
}
|
||
}];
|
||
}
|
||
}
|
||
|
||
#pragma mark - Lookin_PTChannelDelegate
|
||
|
||
- (BOOL)ioFrameChannel:(Lookin_PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize {
|
||
if (channel != self.peerChannel_) {
|
||
return NO;
|
||
} else if ([self.requestHandler canHandleRequestType:type]) {
|
||
return YES;
|
||
} else {
|
||
[channel close];
|
||
return NO;
|
||
}
|
||
}
|
||
|
||
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(Lookin_PTData*)payload {
|
||
id object = nil;
|
||
if (payload) {
|
||
id unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithContentsOfDispatchData:payload.dispatchData]];
|
||
if ([unarchivedObject isKindOfClass:[LookinConnectionAttachment class]]) {
|
||
LookinConnectionAttachment *attachment = (LookinConnectionAttachment *)unarchivedObject;
|
||
object = attachment.data;
|
||
} else {
|
||
object = unarchivedObject;
|
||
}
|
||
}
|
||
[self.requestHandler handleRequestType:type tag:tag object:object];
|
||
}
|
||
|
||
/// 当 Client 端链接成功时,该方法会被调用,然后 channel 的状态会变成 connected
|
||
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didAcceptConnection:(Lookin_PTChannel*)otherChannel fromAddress:(Lookin_PTAddress*)address {
|
||
NSLog(@"LookinServer - channel:%@, acceptConnection:%@", channel.debugTag, otherChannel.debugTag);
|
||
|
||
Lookin_PTChannel *previousChannel = self.peerChannel_;
|
||
|
||
otherChannel.targetPort = address.port;
|
||
self.peerChannel_ = otherChannel;
|
||
|
||
[previousChannel cancel];
|
||
}
|
||
|
||
/// 当连接过 Lookin 客户端,然后 Lookin 客户端又被关闭时,会走到这里
|
||
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didEndWithError:(NSError*)error {
|
||
if (self.peerChannel_ != channel) {
|
||
// Client 端第一次连接上时,之前 listen 的 port 会被 Peertalk 内部 cancel(并在 didAcceptConnection 方法里给业务抛一个新建的 connected 状态的 channel),那个被 cancel 的 channel 会走到这里
|
||
NSLog(@"LookinServer - Ignore channel%@ end.", channel.debugTag);
|
||
return;
|
||
}
|
||
// Client 端关闭时,会走到这里
|
||
NSLog(@"LookinServer - channel%@ DidEndWithError:%@", channel.debugTag, error);
|
||
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:LKS_ConnectionDidEndNotificationName object:self];
|
||
[self searchPortToListenIfNoConnection];
|
||
}
|
||
|
||
#pragma mark - Handler
|
||
|
||
- (void)_handleLocalInspect:(NSNotification *)note {
|
||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Lookin" message:@"Failed to run local inspection. The feature has been removed. Please use the computer version of Lookin or consider SDKs like FLEX for similar functionality." preferredStyle:UIAlertControllerStyleAlert];
|
||
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
|
||
[alertController addAction:okAction];
|
||
UIWindow *keyWindow = [LKS_MultiplatformAdapter keyWindow];
|
||
UIViewController *rootViewController = [keyWindow rootViewController];
|
||
[rootViewController presentViewController:alertController animated:YES completion:nil];
|
||
|
||
NSLog(@"LookinServer - Failed to run local inspection. The feature has been removed. Please use the computer version of Lookin or consider SDKs like FLEX for similar functionality.");
|
||
}
|
||
|
||
- (void)handleGetLookinInfo:(NSNotification *)note {
|
||
NSDictionary* userInfo = note.userInfo;
|
||
if (!userInfo) {
|
||
return;
|
||
}
|
||
NSMutableDictionary* infoWrapper = userInfo[@"infos"];
|
||
if (![infoWrapper isKindOfClass:[NSMutableDictionary class]]) {
|
||
NSLog(@"LookinServer - GetLookinInfo failed. Params invalid.");
|
||
return;
|
||
}
|
||
infoWrapper[@"lookinServerVersion"] = LOOKIN_SERVER_READABLE_VERSION;
|
||
}
|
||
|
||
@end
|
||
|
||
/// 这个类使得用户可以通过 NSClassFromString(@"Lookin") 来判断 LookinServer 是否被编译进了项目里
|
||
|
||
@interface Lookin : NSObject
|
||
|
||
@end
|
||
|
||
@implementation Lookin
|
||
|
||
@end
|
||
|
||
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */
|