iOS:Support debugging

This commit is contained in:
pengfeizhou 2021-02-22 19:03:34 +08:00 committed by osborn
parent 5ca650c9cb
commit 9db60d94ba
26 changed files with 461 additions and 422 deletions

View File

@ -1,12 +1,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="pub.doric.devkit">
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<application>
<activity android:name=".ui.DoricDevActivity" />
<activity android:name=".qrcode.activity.CaptureActivity"/>
<activity
android:name=".ui.DoricDevActivity"
android:exported="true" />
<activity android:name=".qrcode.activity.CaptureActivity" />
</application>
</manifest>

View File

@ -6,7 +6,7 @@ import org.json.JSONObject;
public interface IDevKit {
enum Command {
HOT_RELOAD, EXCEPTION, LOG
HOT_RELOAD, EXCEPTION, LOG, DEBUG
}
void connectDevKit(String url);

View File

@ -49,7 +49,7 @@ public class WSClient extends WebSocketListener {
boolean intercept(String type, String command, JSONObject payload) throws JSONException;
}
private Set<Interceptor> interceptors = new HashSet<>();
private final Set<Interceptor> interceptors = new HashSet<>();
public WSClient(String url) {
OkHttpClient okHttpClient = new OkHttpClient

View File

@ -17,6 +17,8 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.github.pengfeizhou.jscore.JSONBuilder;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
@ -25,6 +27,7 @@ import pub.doric.DoricContext;
import pub.doric.DoricContextManager;
import pub.doric.devkit.DevKit;
import pub.doric.devkit.DoricDev;
import pub.doric.devkit.IDevKit;
import pub.doric.devkit.R;
import pub.doric.devkit.event.ConnectExceptionEvent;
import pub.doric.devkit.event.EOFExceptionEvent;
@ -150,7 +153,11 @@ public class DoricDevActivity extends AppCompatActivity {
cell.findViewById(R.id.debug_text_view).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DevKit.getInstance().sendDevCommand(
IDevKit.Command.DEBUG,
new JSONBuilder()
.put("source", doricContext.getSource())
.toJSONObject());
}
});

View File

@ -1,5 +1,8 @@
import WebSocket from "ws"
import "colors";
import path from "path";
import { glob } from "./util";
import { Shell } from "./shell";
export type MSG = {
type: "D2C" | "C2D" | "C2S" | "D2S" | "S2C" | "S2D",
@ -45,6 +48,26 @@ export async function createServer() {
debug?.send(result);
} else if (resultObject.type === "C2S") {
switch (resultObject.cmd) {
case 'DEBUG':
let source = resultObject.payload.source as string;
if (source.endsWith(".js")) {
source = source.replace(".js", ".ts");
} else if (!source.endsWith(".ts")) {
source = source + ".ts"
}
const tsFiles = await glob(`**/${source}`, {
cwd: path.resolve(process.cwd(), "src")
})
if (!!!tsFiles || tsFiles.length === 0) {
console.error(`Cannot find ${source} in ${path.resolve(process.cwd(), "src")}`);
}
const sourceFile = tsFiles[0];
Shell.exec("node", [
"--inspect-brk",
path.resolve(process.cwd(), "src", sourceFile)
])
console.log(`Debugger on ${sourceFile}`);
break;
case 'EXCEPTION':
console.log(resultObject.payload.source.red);
console.log(resultObject.payload.exception.red);

View File

@ -22,10 +22,11 @@
#import <Foundation/Foundation.h>
#import <DoricCore/Doric.h>
#import "DoricWSClient.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricDebugDriver : NSObject <DoricDriverProtocol>
- (instancetype)initWithWSClient:(DoricWSClient *)wsClient;
@end
NS_ASSUME_NONNULL_END

View File

@ -23,19 +23,21 @@
#import "DoricDebugDriver.h"
#import "DoricDebugJSEngine.h"
#import "DoricConstant.h"
#import "DoricContextManager.h"
#import "DoricDev.h"
@interface DoricDebugDriver ()
@property(nonatomic, strong) DoricJSEngine *jsExecutor;
@property(nonatomic, copy) NSString *theContextId;
@end
@implementation DoricDebugDriver
@dynamic registry;
- (instancetype)init {
- (instancetype)initWithWSClient:(DoricWSClient *)wsClient {
if (self = [super init]) {
_jsExecutor = [[DoricDebugJSEngine alloc] init];
_jsExecutor = [[DoricDebugJSEngine alloc] initWithWSClient:wsClient];
_theContextId = nil;
}
return self;
}
@ -150,7 +152,7 @@ - (DoricAsyncResult *)createContext:(NSString *)contextId script:(NSString *)scr
__strong typeof(_self) self = _self;
if (!self) return;
@try {
[self.jsExecutor prepareContext:contextId script:script source:source];
self.theContextId = contextId;
[ret setupResult:@YES];
} @catch (NSException *exception) {
[ret setupError:exception];
@ -166,7 +168,9 @@ - (DoricAsyncResult *)destroyContext:(NSString *)contextId {
__strong typeof(_self) self = _self;
if (!self) return;
@try {
[self.jsExecutor destroyContext:contextId];
if ([contextId isEqualToString:self.theContextId]) {
[DoricDev.instance stopDebugging:NO];
}
[ret setupResult:@YES];
} @catch (NSException *exception) {
[ret setupError:exception];

View File

@ -24,11 +24,11 @@
#import <JavaScriptCore/JavaScriptCore.h>
#import <DoricCore/DoricJSEngine.h>
#import "DoricWSClient.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricDebugJSEngine : DoricJSEngine
- (instancetype)initWithWSClient:(DoricWSClient *)wsClient;
@end
NS_ASSUME_NONNULL_END

View File

@ -22,68 +22,22 @@
#import "DoricContext.h"
#import "DoricDebugJSEngine.h"
#import "DoricJSRemoteExecutor.h"
#import "DoricUtil.h"
#import "NSString+JsonString.h"
#import "DoricDev.h"
@interface DoricDebugMonitor : NSObject <DoricMonitorProtocol>
@end
@implementation DoricDebugMonitor
- (void)onException:(NSException *)exception inContext:(DoricContext *)context {
DoricLog(@"DefaultMonitor - source: %@- onException - %@", context.source, exception.reason);
NSDictionary *jsonDic = @{
@"cmd": @"EXCEPTION",
@"data": @{
@"source": [context.source stringByReplacingOccurrencesOfString:@".js" withString:@".ts"],
@"exception": exception.reason
}
};
NSString *jsonStr = [NSString dc_convertToJsonWithDic:jsonDic];
[[DoricDev instance] sendDevCommand:jsonStr];
}
- (void)onLog:(DoricLogType)type message:(NSString *)message {
DoricLog(message);
NSString *typeString = @"DEFAULT";
if (type == DoricLogTypeDebug) {
typeString = @"DEFAULT";
} else if (type == DoricLogTypeWarning) {
typeString = @"WARN";
} else if (type == DoricLogTypeError) {
typeString = @"ERROR";
}
NSDictionary *jsonDic = @{
@"cmd": @"LOG",
@"data": @{
@"type": typeString,
@"message": message
}
};
NSString *jsonStr = [NSString dc_convertToJsonWithDic:jsonDic];
[[DoricDev instance] sendDevCommand:jsonStr];
}
@end
#import "DoricRemoteJSExecutor.h"
@interface DoricDebugJSEngine ()
@property(nonatomic, weak) DoricWSClient *wsClient;
@end
@implementation DoricDebugJSEngine
- (instancetype)init {
- (instancetype)initWithWSClient:(DoricWSClient *)wsClient {
if (self = [super init]) {
_wsClient = wsClient;
}
return self;
}
- (void)initJSEngine {
[self.registry registerMonitor:[DoricDebugMonitor new]];
self.jsExecutor = [[DoricJSRemoteExecutor alloc] init];
self.jsExecutor = [[DoricRemoteJSExecutor alloc] initWithWSClient:self.wsClient];
}
@end

View File

@ -19,10 +19,12 @@
//
// Created by jingpeng.wang on 2020/2/25.
//
#import "DoricWSClient.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricDev : NSObject
@property(nonatomic, strong) DoricWSClient *wsClient;
+ (instancetype)instance;
- (void)openDevMode;
@ -33,8 +35,11 @@ NS_ASSUME_NONNULL_BEGIN
- (void)connectDevKit:(NSString *)url;
- (void)sendDevCommand:(NSString *)command;
- (void)startDebugging:(NSString *)source;
- (void)stopDebugging:(BOOL)resume;
- (void)reload:(NSString *)source script:(NSString *)script;
@end
NS_ASSUME_NONNULL_END

View File

@ -20,20 +20,50 @@
// Created by jingpeng.wang on 2020/2/25.
//
#import <DoricCore/Doric.h>
#import <DoricCore/DoricContextManager.h>
#import <DoricCore/DoricNativeDriver.h>
#import <DoricCore/DoricConstant.h>
#import <DoricCore/DoricContextManager.h>
#import "DoricDev.h"
#import "DoricWSClient.h"
#import "DoricDebugDriver.h"
#import "DoricDevViewController.h"
#import "DoricDevMonitor.h"
@interface DoricContextDebuggable : NSObject
@property(nonatomic, weak) DoricContext *doricContext;
@property(nonatomic, weak) id <DoricDriverProtocol> nativeDriver;
@property(nonatomic, weak) DoricWSClient *wsClient;
@end
@implementation DoricContextDebuggable
- (instancetype)initWithWSClient:(DoricWSClient *)client context:(DoricContext *)context {
if (self = [super init]) {
_wsClient = client;
_doricContext = context;
_nativeDriver = context.driver;
}
return self;
}
- (void)startDebug {
[self.doricContext setDriver:[[DoricDebugDriver alloc] initWithWSClient:self.wsClient]];
[self.doricContext reload:self.doricContext.script];
}
- (void)stopDebug:(BOOL)resume {
id <DoricDriverProtocol> driver = self.doricContext.driver;
if ([driver isKindOfClass:DoricDebugDriver.class]) {
}
if (resume) {
self.doricContext.driver = self.nativeDriver;
[self.doricContext reload:self.doricContext.script];
}
}
@end
@interface DoricDev ()
@property(nonatomic, strong) DoricWSClient *wsclient;
@property(nonatomic, strong) DoricContext *context;
@property(nonatomic, strong) DoricDebugDriver *driver;
@property(nonatomic, strong) DoricContextDebuggable *debuggable;
@end
@implementation DoricDev
@ -43,10 +73,6 @@ - (instancetype)init {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOpenEvent) name:@"OpenEvent" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onEOFEvent) name:@"EOFEvent" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onConnectExceptionEvent) name:@"ConnectExceptionEvent" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onStartDebugEvent:) name:@"StartDebugEvent" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onEnterDebugEvent) name:@"EnterDebugEvent" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onStopDebugEvent) name:@"StopDebugEvent" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDebuggerReadyEvent) name:@"DebuggerReadyEvent" object:nil];
[DoricNativeDriver.instance.registry registerMonitor:[DoricDevMonitor new]];
}
return self;
@ -75,25 +101,21 @@ - (void)openDevMode {
}
- (void)closeDevMode {
if (self.wsclient) {
[self.wsclient close];
self.wsclient = nil;
if (self.wsClient) {
[self.wsClient close];
self.wsClient = nil;
}
}
- (BOOL)isInDevMode {
return self.wsclient != nil;
return self.wsClient != nil;
}
- (void)connectDevKit:(NSString *)url {
if (self.wsclient) {
[self.wsclient close];
if (self.wsClient) {
[self.wsClient close];
}
self.wsclient = [[DoricWSClient alloc] initWithUrl:url];
}
- (void)sendDevCommand:(NSString *)command {
[self.wsclient send:command];
self.wsClient = [[DoricWSClient alloc] initWithUrl:url];
}
- (void)onOpenEvent {
@ -108,34 +130,51 @@ - (void)onConnectExceptionEvent {
ShowToast(@"dev kit connection exception", DoricGravityBottom);
}
- (void)onStartDebugEvent:(NSNotification *)notification {
NSString *contextId = notification.object;
ShowToast(contextId, DoricGravityBottom);
for (DoricContext *context in [[DoricContextManager instance] aliveContexts]) {
BOOL result = [context.contextId compare:contextId] == NSOrderedSame;
if (result) {
_context = context;
- (DoricContext *)matchContext:(NSString *)source {
for (DoricContext *context in [DoricContextManager.instance aliveContexts]) {
if ([source containsString:context.source] || [context.source isEqualToString:@"__dev__"]) {
return context;
}
}
return nil;
}
- (void)reload:(NSString *)source script:(NSString *)script {
DoricContext *context = [self matchContext:source];
if (context) {
if ([context.driver isKindOfClass:DoricDebugDriver.class]) {
DoricLog(@"Context source %@ in debugging,skip reload", source);
} else {
DoricLog(@"Context reload :id %@,source %@", context.contextId, source);
[context reload:script];
}
} else {
DoricLog(@"Cannot find context source %@ for reload", source);
}
}
- (void)onEnterDebugEvent {
_driver = [DoricDebugDriver new];
}
- (void)startDebugging:(NSString *)source {
[self.debuggable stopDebug:YES];
DoricContext *context = [self matchContext:source];
if (context) {
[self.wsClient sendToDebugger:@"DEBUG_RES" payload:@{
@"contextId": context.contextId
}];
self.debuggable = [[DoricContextDebuggable alloc] initWithWSClient:self.wsClient context:context];
[self.debuggable startDebug];
} else {
DoricLog(@"Cannot find context source %@ for debugging", source);
[self.wsClient sendToDebugger:@"DEBUG_STOP" payload:@{
@"msg": @"Cannot find suitable alive context for debugging"
}];
}
};
- (void)onStopDebugEvent {
_context.driver = [DoricNativeDriver instance];
_context.rootNode.viewId = nil;
[_context callEntity:DORIC_ENTITY_INIT, _context.extra, nil];
[_context callEntity:DORIC_ENTITY_CREATE, nil];
[_context callEntity:DORIC_ENTITY_BUILD withArgumentsArray:@[_context.initialParams]];
}
- (void)onDebuggerReadyEvent {
_context.driver = _driver;
_context.rootNode.viewId = nil;
[_context callEntity:DORIC_ENTITY_INIT, _context.extra, nil];
[_context callEntity:DORIC_ENTITY_CREATE, nil];
[_context callEntity:DORIC_ENTITY_BUILD withArgumentsArray:@[_context.initialParams]];
- (void)stopDebugging:(BOOL)resume {
[self.wsClient sendToDebugger:@"DEBUG_STOP" payload:@{
@"msg": @"Stop debugging"
}];
[self.debuggable stopDebug:resume];
self.debuggable = nil;
}
@end

View File

@ -1,3 +1,18 @@
/*
* Copyright [2019] [Doric.Pub]
*
* 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.
*/
//
// DoricDevMonitor.h
// DoricDevkit

View File

@ -1,3 +1,18 @@
/*
* Copyright [2019] [Doric.Pub]
*
* 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.
*/
//
// DoricDevMonitor.m
// DoricDevkit
@ -8,7 +23,6 @@
#import "DoricDevMonitor.h"
#import "DoricDev.h"
#import "NSString+JsonString.h"
#import "Doric.h"
@implementation DoricDevMonitor
@ -16,16 +30,11 @@ - (void)onException:(NSException *)exception inContext:(DoricContext *)context {
if (!DoricDev.instance.isInDevMode) {
return;
}
NSDictionary *jsonDic = @{
@"cmd": @"EXCEPTION",
@"data": @{
@"source": [context.source stringByReplacingOccurrencesOfString:@".js" withString:@".ts"],
@"exception": exception.reason
}
};
NSString *jsonStr = [NSString dc_convertToJsonWithDic:jsonDic];
[[DoricDev instance] sendDevCommand:jsonStr];
[DoricDev.instance.wsClient sendToServer:@"EXCEPTION"
payload:@{
@"source": [context.source stringByReplacingOccurrencesOfString:@".js" withString:@".ts"],
@"exception": exception.reason
}];
}
- (void)onLog:(DoricLogType)type message:(NSString *)message {
@ -33,23 +42,16 @@ - (void)onLog:(DoricLogType)type message:(NSString *)message {
return;
}
NSString *typeString = @"DEFAULT";
if (type == DoricLogTypeDebug) {
typeString = @"DEFAULT";
} else if (type == DoricLogTypeWarning) {
if (type == DoricLogTypeWarning) {
typeString = @"WARN";
} else if (type == DoricLogTypeError) {
typeString = @"ERROR";
}
NSDictionary *jsonDic = @{
@"cmd": @"LOG",
@"data": @{
@"type": typeString,
@"message": message
}
};
NSString *jsonStr = [NSString dc_convertToJsonWithDic:jsonDic];
[[DoricDev instance] sendDevCommand:jsonStr];
[DoricDev.instance.wsClient sendToServer:@"EXCEPTION"
payload:@{
@"type": typeString,
@"message": message
}];
}
@end

View File

@ -25,7 +25,6 @@
#import "DoricDev.h"
#import "DoricDevViewController.h"
#import "DoricJSRemoteExecutor.h"
#import "QRScanViewController.h"
@interface DoricDevViewController () <UITableViewDelegate, UITableViewDataSource>
@ -51,7 +50,6 @@ - (void)viewDidLoad {
}
if (self.isSimulator) {
NSString *result = @"127.0.0.1";
[DoricJSRemoteExecutor configIp:result];
[[DoricDev instance] connectDevKit:[NSString stringWithFormat:@"ws://%@:7777", result]];
ShowToast([NSString stringWithFormat:@"Connected to %@", result], DoricGravityBottom);
} else {
@ -94,9 +92,6 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
@"source": [context.source stringByReplacingOccurrencesOfString:@".js" withString:@".ts"]
}
};
NSString *jsonStr = [NSString dc_convertToJsonWithDic:jsonDic];
[[DoricDev instance] sendDevCommand:jsonStr];
}
@end

View File

@ -1,213 +0,0 @@
/*
* Copyright [2019] [Doric.Pub]
*
* 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.
*/
//
// DoricJSRemoteExecutor.m
// Doric
//
// Created by on 2019/10/31.
//
#import "DoricJSRemoteExecutor.h"
#import "SRWebSocket.h"
#import "DoricUtil.h"
#import "DoricJSRemoteArgType.h"
#import "NSString+JsonString.h"
static NSString *debuggerAddress = @"";
typedef id (^Block0)(void);
typedef id (^Block1)(id arg0);
typedef id (^Block2)(id arg0, id arg1);
typedef id (^Block3)(id arg0, id arg1, id arg2);
typedef id (^Block4)(id arg0, id arg1, id arg2, id arg3);
typedef id (^Block5)(id arg0, id arg1, id arg2, id arg3, id arg4);
@interface DoricJSRemoteExecutor () <SRWebSocketDelegate>
@property(nonatomic, strong) SRWebSocket *srWebSocket;
@property(nonatomic, strong) NSMutableDictionary <NSString *, id> *blockMDic;
@property(nonatomic, strong) JSValue *temp;
@end
@implementation DoricJSRemoteExecutor
- (instancetype)init {
if (self = [super init]) {
[self srWebSocket];
_semaphore = dispatch_semaphore_create(0);
DC_LOCK(self.semaphore);
}
return self;
}
#pragma mark - SRWebSocketDelegate
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
DoricLog(@"debugger webSocketDidOpen");
DC_UNLOCK(self.semaphore);
[[NSNotificationCenter defaultCenter] postNotificationName:@"DebuggerReadyEvent" object:nil];
}
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload {
DoricLog(@"debugger webSocketdidReceivePong");
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
NSData *jsonData = [message dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
if (err) {
DoricLog(@"debugger webSocketdidReceiveMessage parse error%@", err);
return;
}
NSString *cmd = [[dic valueForKey:@"cmd"] copy];
if ([cmd isEqualToString:@"injectGlobalJSFunction"]) {
NSString *name = [dic valueForKey:@"name"];
NSArray *argsArr = [dic valueForKey:@"arguments"];
NSMutableArray *argsMarr = [NSMutableArray new];
for (NSUInteger i = 0; i < argsArr.count; i++) {
[argsMarr addObject:argsArr[i]];
}
id result;
id tmpBlk = self.blockMDic[name];
if (argsArr.count == 0) {
result = ((Block0) tmpBlk)();
} else if (argsArr.count == 1) {
result = ((Block1) tmpBlk)(argsArr[0]);
} else if (argsArr.count == 2) {
result = ((Block2)tmpBlk)(argsArr[0], argsArr[1]);
} else if (argsArr.count == 3) {
result = ((Block3)tmpBlk)(argsArr[0], argsArr[1], argsArr[2]);
} else if (argsArr.count == 4) {
result = ((Block4)tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3]);
} else if (argsArr.count == 5) {
result = ((Block5)tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3], argsArr[4]);
}else {
DoricLog(@"error:args to more than 5. args:%@",argsArr);
}
} else if ([cmd isEqualToString:@"invokeMethod"]) {
@try {
self.temp = [JSValue valueWithObject:[dic valueForKey:@"result"] inContext:nil];
} @catch (NSException *exception) {
DoricLog(@"debugger ", NSStringFromSelector(_cmd), exception.reason);
} @finally {
DC_UNLOCK(self.semaphore);
}
}
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
DoricLog(@"debugger webSocketdidFailWithError");
DC_UNLOCK(self.semaphore);
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
DoricLog(@"debugger webSocketdidCloseWithCode");
[[NSNotificationCenter defaultCenter] postNotificationName:@"StopDebugEvent" object:nil];
}
#pragma mark - DoricJSExecutorProtocol
- (NSString *)loadJSScript:(NSString *)script source:(NSString *)source {
return nil;
}
- (void)injectGlobalJSObject:(NSString *)name obj:(id)obj {
if ([obj isKindOfClass:NSClassFromString(@"NSBlock")]) {
self.blockMDic[name] = obj;
}
NSDictionary *jsonDic = @{
@"cmd": @"injectGlobalJSFunction",
@"name": name
};
NSString *jsonStr = [NSString dc_convertToJsonWithDic:jsonDic];
if (!jsonStr) {
return;
}
[self.srWebSocket send:jsonStr];
}
- (JSValue *)invokeObject:(NSString *)objName method:(NSString *)funcName args:(NSArray *)args {
NSMutableArray *argsMArr = [NSMutableArray new];
for (id arg in args) {
NSDictionary *dic = [self dicForArg:arg];
[argsMArr addObject:dic];
}
NSArray *argsArr = [argsMArr copy];
NSDictionary *jsonDic = @{
@"cmd": @"invokeMethod",
@"objectName": objName,
@"functionName": funcName,
@"values": argsArr
};
NSString *jsonStr = [NSString dc_convertToJsonWithDic:jsonDic];
if (!jsonStr) {
return nil;
}
[self.srWebSocket send:jsonStr];
DC_LOCK(self.semaphore);
return self.temp;
}
- (NSDictionary *)dicForArg:(id)arg {
DoricJSRemoteArgType type = DoricargTypeWithArg(arg);
if (type == DoricJSRemoteArgTypeObject || type == DoricJSRemoteArgTypeArray) {
NSString *jsonStr = [NSString dc_convertToJsonWithDic:(NSDictionary *)arg];
arg = jsonStr;
}
NSDictionary *dic = @{
@"type": @(type),
@"value": arg
};
return dic;
}
+ (void)configIp:(NSString *)ip {
debuggerAddress = [[@"ws://" stringByAppendingString:ip] stringByAppendingString:@":2080"];
}
- (void)close {
[self.srWebSocket close];
}
#pragma mark - Properties
- (SRWebSocket *)srWebSocket {
if (!_srWebSocket) {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:debuggerAddress] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
_srWebSocket = [[SRWebSocket alloc] initWithURLRequest:request];
_srWebSocket.delegate = self;
[_srWebSocket open];
}
return _srWebSocket;
}
- (NSMutableDictionary *)blockMDic {
if (!_blockMDic) {
_blockMDic = [NSMutableDictionary new];
}
return _blockMDic;
}
@end

View File

@ -14,24 +14,20 @@
* limitations under the License.
*/
//
// DoricJSRemoteExecutor.h
// Pods
// DoricRemoteJSExecutor.h
// DoricDevkit
//
// Created by 王劲鹏 on 2019/10/31.
// Created by pengfei.zhou on 2021/2/22.
//
#import <Foundation/Foundation.h>
#import <DoricCore/DoricJSExecutorProtocol.h>
#import "DoricWSClient.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricJSRemoteExecutor : NSObject <DoricJSExecutorProtocol>
@property(nonatomic, strong) dispatch_semaphore_t semaphore;
+ (void)configIp:(NSString *)ip;
- (void)close;
@interface DoricRemoteJSExecutor : NSObject <DoricJSExecutorProtocol>
- (instancetype)initWithWSClient:(DoricWSClient *)wsClient;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,194 @@
/*
* Copyright [2019] [Doric.Pub]
*
* 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.
*/
//
// DoricRemoteJSExecutor.m
// DoricDevkit
//
// Created by pengfei.zhou on 2021/2/22.
//
#import "DoricRemoteJSExecutor.h"
#import "NSString+JsonString.h"
#import <DoricCore/Doric.h>
typedef NS_ENUM(NSUInteger, DoricJSRemoteArgType) {
DoricJSRemoteArgTypeNil = 0,
DoricJSRemoteArgTypeNumber,
DoricJSRemoteArgTypeBool,
DoricJSRemoteArgTypeString,
DoricJSRemoteArgTypeObject,
DoricJSRemoteArgTypeArray,
};
typedef id (^Block0)(void);
typedef id (^Block1)(id arg0);
typedef id (^Block2)(id arg0, id arg1);
typedef id (^Block3)(id arg0, id arg1, id arg2);
typedef id (^Block4)(id arg0, id arg1, id arg2, id arg3);
typedef id (^Block5)(id arg0, id arg1, id arg2, id arg3, id arg4);
@interface DoricRemoteJSExecutor () <DoricWSClientInterceptor>
@property(nonatomic, weak) DoricWSClient *wsClient;
@property(nonatomic, strong) NSMutableDictionary <NSString *, id> *blockMDic;
@property(nonatomic, strong) JSValue *temp;
@property(nonatomic, strong) dispatch_semaphore_t semaphore;
@end
@implementation DoricRemoteJSExecutor
- (instancetype)initWithWSClient:(DoricWSClient *)wsClient {
if (self = [super init]) {
_wsClient = wsClient;
[_wsClient addInterceptor:self];
_blockMDic = [NSMutableDictionary new];
_semaphore = dispatch_semaphore_create(0);
}
return self;
}
- (NSString *)loadJSScript:(NSString *)script source:(NSString *)source {
return nil;
}
- (void)injectGlobalJSObject:(NSString *)name obj:(id)obj {
if ([obj isKindOfClass:NSClassFromString(@"NSBlock")]) {
self.blockMDic[name] = obj;
[self.wsClient sendToDebugger:@"injectGlobalJSFunction" payload:@{
@"name": name,
}];
} else if ([obj isKindOfClass:NSNumber.class]) {
[self.wsClient sendToDebugger:@"injectGlobalJSObject" payload:@{
@"name": name,
@"type": @(DoricJSRemoteArgTypeNumber),
@"value": obj,
}];
} else if ([obj isKindOfClass:NSString.class]) {
[self.wsClient sendToDebugger:@"injectGlobalJSObject" payload:@{
@"name": name,
@"type": @(DoricJSRemoteArgTypeString),
@"value": obj,
}];
} else if ([obj isKindOfClass:NSObject.class]) {
[self.wsClient sendToDebugger:@"injectGlobalJSObject" payload:@{
@"name": name,
@"type": @(DoricJSRemoteArgTypeObject),
@"value": obj,
}];
} else if ([obj isKindOfClass:NSArray.class]) {
[self.wsClient sendToDebugger:@"injectGlobalJSObject" payload:@{
@"name": name,
@"type": @(DoricJSRemoteArgTypeArray),
@"value": obj,
}];
} else if (obj == nil) {
[self.wsClient sendToDebugger:@"injectGlobalJSObject" payload:@{
@"name": name,
@"type": @(DoricJSRemoteArgTypeNil),
}];
}
}
- (JSValue *)invokeObject:(NSString *)objName method:(NSString *)funcName args:(NSArray *)args {
NSMutableArray *argsMArr = [NSMutableArray new];
for (id arg in args) {
NSDictionary *dic = [self dicForArg:arg];
[argsMArr addObject:dic];
}
[self.wsClient sendToDebugger:@"invokeMethod" payload:@{
@"cmd": @"invokeMethod",
@"objectName": objName,
@"functionName": funcName,
@"values": [argsMArr copy]
}];
DC_LOCK(self.semaphore);
return self.temp;
}
- (NSDictionary *)dicForArg:(id)arg {
DoricJSRemoteArgType type = [self argType:arg];
if (type == DoricJSRemoteArgTypeObject || type == DoricJSRemoteArgTypeArray) {
NSString *jsonStr = [NSString dc_convertToJsonWithDic:arg];
arg = jsonStr;
}
NSDictionary *dic = @{
@"type": @(type),
@"value": arg
};
return dic;
}
- (DoricJSRemoteArgType)argType:(id)arg {
DoricJSRemoteArgType type = DoricJSRemoteArgTypeNil;
if ([arg isKindOfClass:[NSNumber class]]) {
type = DoricJSRemoteArgTypeNumber;
} else if ([arg isKindOfClass:[NSString class]]) {
type = DoricJSRemoteArgTypeString;
} else if ([arg isKindOfClass:[NSDictionary class]]) {
type = DoricJSRemoteArgTypeObject;
} else if ([arg isKindOfClass:[NSMutableArray class]]) {
type = DoricJSRemoteArgTypeArray;
}
return type;
}
- (BOOL)interceptType:(NSString *)type command:(NSString *)cmd payload:(NSDictionary *)payload {
if ([type isEqualToString:@"D2C"]) {
if ([cmd isEqualToString:@"injectGlobalJSFunction"]) {
NSString *name = payload[@"name"];
NSArray *argsArr = payload[@"arguments"];
id tmpBlk = self.blockMDic[name];
id result;
if (argsArr.count == 0) {
result = ((Block0) tmpBlk)();
} else if (argsArr.count == 1) {
result = ((Block1) tmpBlk)(argsArr[0]);
} else if (argsArr.count == 2) {
result = ((Block2) tmpBlk)(argsArr[0], argsArr[1]);
} else if (argsArr.count == 3) {
result = ((Block3) tmpBlk)(argsArr[0], argsArr[1], argsArr[2]);
} else if (argsArr.count == 4) {
result = ((Block4) tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3]);
} else if (argsArr.count == 5) {
result = ((Block5) tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3], argsArr[4]);
} else {
DoricLog(@"error:args to more than 5. args:%@", argsArr);
result = nil;
}
} else if ([cmd isEqualToString:@"invokeMethod"]) {
@try {
self.temp = [JSValue valueWithObject:payload[@"result"] inContext:nil];
} @catch (NSException *exception) {
DoricLog(@"debugger ", NSStringFromSelector(_cmd), exception.reason);
} @finally {
DC_UNLOCK(self.semaphore);
}
}
}
return NO;
}
@end

View File

@ -24,12 +24,22 @@
NS_ASSUME_NONNULL_BEGIN
@protocol DoricWSClientInterceptor <NSObject>
- (BOOL)interceptType:(NSString *)type command:(NSString *)cmd payload:(NSDictionary *)payload;
@end
@interface DoricWSClient : NSObject
- (instancetype)initWithUrl:(NSString *)url;
- (void)send:(NSString *)command;
- (void)close;
- (void)addInterceptor:(id <DoricWSClientInterceptor>)interceptor;
- (void)removeInterceptor:(id <DoricWSClientInterceptor>)interceptor;
- (void)sendToDebugger:(NSString *)cmd payload:(NSDictionary *)payload;
- (void)sendToServer:(NSString *)cmd payload:(NSDictionary *)payload;
@end
NS_ASSUME_NONNULL_END

View File

@ -22,16 +22,20 @@
#import "DoricWSClient.h"
#import "SRWebSocket.h"
#import "DoricUtil.h"
#import "DoricContextManager.h"
#import <DoricCore/Doric.h>
#import <DoricCore/DoricContextManager.h>
#import <DoricCore/NSString+JsonString.h>
#import "DoricDev.h"
@interface DoricWSClient () <SRWebSocketDelegate>
@property(nonatomic, strong) SRWebSocket *websocket;
@property(nonatomic, strong) NSHashTable <id <DoricWSClientInterceptor>> *interceptors;
@end
@implementation DoricWSClient
- (instancetype)initWithUrl:(NSString *)url {
if (self = [super init]) {
_interceptors = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
_websocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:url]];
_websocket.delegate = self;
[_websocket open];
@ -58,7 +62,27 @@ - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
DoricLog(@"webSocketdidReceiveMessage parse error%@", err);
return;
}
NSString *cmd = [[dic valueForKey:@"cmd"] mutableCopy];
NSString *type = dic[@"type"];
NSString *cmd = dic[@"cmd"];
NSDictionary *payload = dic[@"payload"];
for (id <DoricWSClientInterceptor> interceptor in self.interceptors) {
if ([interceptor interceptType:type command:cmd payload:payload]) {
return;
}
}
if ([cmd isEqualToString:@"DEBUG_REQ"]) {
NSString *source = payload[@"source"];
[DoricDev.instance startDebugging:source];
} else if ([cmd isEqualToString:@"DEBUG_STOP"]) {
[DoricDev.instance stopDebugging:YES];
} else if ([cmd isEqualToString:@"RELOAD"]) {
NSString *source = payload[@"source"];
NSString *script = payload[@"script"];
[DoricDev.instance reload:source script:script];
}
if ([cmd compare:@"SWITCH_TO_DEBUG"] == NSOrderedSame) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"EnterDebugEvent" object:nil];
} else if ([cmd compare:@"RELOAD"] == NSOrderedSame) {
@ -82,8 +106,33 @@ - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reas
[[NSNotificationCenter defaultCenter] postNotificationName:@"EOFEvent" object:nil];
}
- (void)send:(NSString *)command {
[_websocket send:command];
- (void)send:(NSDictionary *)command {
NSString *jsonStr = [NSString dc_convertToJsonWithDic:command];
[_websocket send:jsonStr];
}
- (void)addInterceptor:(id <DoricWSClientInterceptor>)interceptor {
[self.interceptors addObject:interceptor];
}
- (void)removeInterceptor:(id <DoricWSClientInterceptor>)interceptor {
[self.interceptors removeObject:interceptor];
}
- (void)sendToDebugger:(NSString *)cmd payload:(NSDictionary *)payload {
[self send:@{
@"type": @"C2D",
@"cmd": cmd,
@"payload": payload,
}];
}
- (void)sendToServer:(NSString *)cmd payload:(NSDictionary *)payload {
[self send:@{
@"type": @"C2S",
@"cmd": cmd,
@"payload": payload,
}];
}
- (void)close {

View File

@ -24,7 +24,6 @@
#import <AVFoundation/AVFoundation.h>
#import <DoricCore/Doric.h>
#import <DoricDevkit/DoricDev.h>
#import "DoricJSRemoteExecutor.h"
@interface QRScanViewController () <AVCaptureMetadataOutputObjectsDelegate>
@property(strong, nonatomic) AVCaptureDevice *device;
@ -105,7 +104,6 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:
NSString *result = qrObject.stringValue;
NSLog(@"Scan result is %@", result);
[[DoricDev instance] connectDevKit:[NSString stringWithFormat:@"ws://%@:7777", result]];
[DoricJSRemoteExecutor configIp:result];
ShowToast([NSString stringWithFormat:@"Connected to %@", result], DoricGravityBottom);
[self.navigationController popViewControllerAnimated:NO];
}

View File

@ -46,6 +46,8 @@ NS_ASSUME_NONNULL_BEGIN
- (JSValue *)invokeDoricMethod:(NSString *)method argumentsArray:(NSArray *)args;
- (void)ensureRunOnJSThread:(dispatch_block_t)block;
- (void)initJSEngine;
@end
NS_ASSUME_NONNULL_END

View File

@ -96,6 +96,7 @@ - (instancetype)init {
[self initJSExecutor];
[self initDoricEnvironment];
}];
[self.registry registerMonitor:[DoricDefaultMonitor new]];
}
return self;
}
@ -126,7 +127,6 @@ - (void)threadRun {
}
- (void)initJSEngine {
[self.registry registerMonitor:[DoricDefaultMonitor new]];
self.jsExecutor = [DoricJSCoreExecutor new];
}

View File

@ -12,7 +12,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface NSString (JsonString)
+ (NSString *)dc_convertToJsonWithDic:(NSDictionary *)dic;
+ (NSString *)dc_convertToJsonWithDic:(id)obj;
@end
NS_ASSUME_NONNULL_END

View File

@ -9,11 +9,11 @@
#import "DoricUtil.h"
@implementation NSString (JsonString)
+ (NSString *)dc_convertToJsonWithDic:(NSDictionary *)dic {
+ (NSString *)dc_convertToJsonWithDic:(id)obj {
NSError *err;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&err];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:obj options:NSJSONWritingPrettyPrinted error:&err];
NSString *jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
if (err) {
DoricLog(NSStringFromSelector(_cmd), @"Convert dictionary to json string failed.");
return nil;

View File

@ -1,24 +0,0 @@
//
// DoricJSRemoteArgType.h
// Doric
//
// Created by Insomnia on 2019/11/7.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, DoricJSRemoteArgType) {
DoricJSRemoteArgTypeNil = 0,
DoricJSRemoteArgTypeNumber,
DoricJSRemoteArgTypeBool,
DoricJSRemoteArgTypeString,
DoricJSRemoteArgTypeObject,
DoricJSRemoteArgTypeArray,
};
DoricJSRemoteArgType DoricargTypeWithArg(id arg);
NS_ASSUME_NONNULL_END

View File

@ -1,21 +0,0 @@
//
// DoricJSRemoteArgType.m
// Doric
//
// Created by Insomnia on 2019/11/7.
//
#import "DoricJSRemoteArgType.h"
DoricJSRemoteArgType DoricargTypeWithArg(id arg) {
DoricJSRemoteArgType type = DoricJSRemoteArgTypeNil;
if ([arg isKindOfClass:[NSNumber class]]) {
type = DoricJSRemoteArgTypeNumber;
}else if ([arg isKindOfClass:[NSString class]]) {
type = DoricJSRemoteArgTypeString;
}else if ([arg isKindOfClass:[NSDictionary class]]) {
type = DoricJSRemoteArgTypeObject;
}else if ([arg isKindOfClass:[NSMutableArray class]]) {
type = DoricJSRemoteArgTypeArray;
}
return type;
}