BREAKING: Change the js1 - packager - RN App protocol to version 2
Reviewed By: cwdick Differential Revision: D4551991 fbshipit-source-id: 395c38ee5c71ddc24d8743e7ec90cc89de087503
This commit is contained in:
Родитель
33817b83d6
Коммит
a2addbd932
|
@ -269,7 +269,7 @@ RCT_EXPORT_MODULE()
|
||||||
if (!port) {
|
if (!port) {
|
||||||
port = @8081; // Packager default port
|
port = @8081; // Packager default port
|
||||||
}
|
}
|
||||||
return [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@:%@/message?role=shell", scheme, host, port]];
|
return [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@:%@/message?role=ios-rn-rctdevmenu", scheme, host, port]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move non-UI logic into separate RCTDevSettings module
|
// TODO: Move non-UI logic into separate RCTDevSettings module
|
||||||
|
@ -310,28 +310,26 @@ RCT_EXPORT_MODULE()
|
||||||
|
|
||||||
- (BOOL)isSupportedVersion:(NSNumber *)version
|
- (BOOL)isSupportedVersion:(NSNumber *)version
|
||||||
{
|
{
|
||||||
NSArray<NSNumber *> *const kSupportedVersions = @[ @1 ];
|
NSArray<NSNumber *> *const kSupportedVersions = @[ @2 ];
|
||||||
return [kSupportedVersions containsObject:version];
|
return [kSupportedVersions containsObject:version];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didReceiveWebSocketMessage:(NSDictionary<NSString *, id> *)message
|
- (void)didReceiveWebSocketMessage:(NSDictionary<NSString *, id> *)message
|
||||||
{
|
{
|
||||||
if ([self isSupportedVersion:message[@"version"]]) {
|
if ([self isSupportedVersion:message[@"version"]]) {
|
||||||
[self processTarget:message[@"target"] action:message[@"action"] options:message[@"options"]];
|
[self processMethod:message[@"method"] params:message[@"params"]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)processTarget:(NSString *)target action:(NSString *)action options:(NSDictionary<NSString *, id> *)options
|
- (void)processMethod:(NSString *)method params:(NSDictionary<NSString *, id> *)params
|
||||||
{
|
{
|
||||||
if ([target isEqualToString:@"bridge"]) {
|
if ([method isEqualToString:@"reload"]) {
|
||||||
if ([action isEqualToString:@"reload"]) {
|
if (![params isEqual:[NSNull null]] && [params[@"debug"] boolValue]) {
|
||||||
if ([options[@"debug"] boolValue]) {
|
|
||||||
_bridge.executorClass = objc_lookUpClass("RCTWebSocketExecutor");
|
_bridge.executorClass = objc_lookUpClass("RCTWebSocketExecutor");
|
||||||
}
|
}
|
||||||
[_bridge reload];
|
[_bridge reload];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
- (dispatch_queue_t)methodQueue
|
- (dispatch_queue_t)methodQueue
|
||||||
{
|
{
|
||||||
|
|
|
@ -65,7 +65,7 @@ public class DevServerHelper {
|
||||||
private static final String ONCHANGE_ENDPOINT_URL_FORMAT =
|
private static final String ONCHANGE_ENDPOINT_URL_FORMAT =
|
||||||
"http://%s/onchange";
|
"http://%s/onchange";
|
||||||
private static final String WEBSOCKET_PROXY_URL_FORMAT = "ws://%s/debugger-proxy?role=client";
|
private static final String WEBSOCKET_PROXY_URL_FORMAT = "ws://%s/debugger-proxy?role=client";
|
||||||
private static final String PACKAGER_CONNECTION_URL_FORMAT = "ws://%s/message?role=shell";
|
private static final String PACKAGER_CONNECTION_URL_FORMAT = "ws://%s/message?role=android-rn-devserverhelper";
|
||||||
private static final String PACKAGER_STATUS_URL_FORMAT = "http://%s/status";
|
private static final String PACKAGER_STATUS_URL_FORMAT = "http://%s/status";
|
||||||
private static final String HEAP_CAPTURE_UPLOAD_URL_FORMAT = "http://%s/jscheapcaptureupload";
|
private static final String HEAP_CAPTURE_UPLOAD_URL_FORMAT = "http://%s/jscheapcaptureupload";
|
||||||
private static final String INSPECTOR_DEVICE_URL_FORMAT = "http://%s/inspector/device?name=%s";
|
private static final String INSPECTOR_DEVICE_URL_FORMAT = "http://%s/inspector/device?name=%s";
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.json.JSONObject;
|
||||||
*/
|
*/
|
||||||
final public class JSPackagerClient implements ReconnectingWebSocket.MessageCallback {
|
final public class JSPackagerClient implements ReconnectingWebSocket.MessageCallback {
|
||||||
private static final String TAG = JSPackagerClient.class.getSimpleName();
|
private static final String TAG = JSPackagerClient.class.getSimpleName();
|
||||||
private static final int PROTOCOL_VERSION = 1;
|
private static final int PROTOCOL_VERSION = 2;
|
||||||
|
|
||||||
public class Responder {
|
public class Responder {
|
||||||
private Object mId;
|
private Object mId;
|
||||||
|
@ -38,8 +38,8 @@ final public class JSPackagerClient implements ReconnectingWebSocket.MessageCall
|
||||||
try {
|
try {
|
||||||
JSONObject message = new JSONObject();
|
JSONObject message = new JSONObject();
|
||||||
message.put("version", PROTOCOL_VERSION);
|
message.put("version", PROTOCOL_VERSION);
|
||||||
message.put("target", "profiler");
|
message.put("id", mId);
|
||||||
message.put("action", result);
|
message.put("result", result);
|
||||||
mWebSocket.sendMessage(RequestBody.create(WebSocket.TEXT, message.toString()));
|
mWebSocket.sendMessage(RequestBody.create(WebSocket.TEXT, message.toString()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FLog.e(TAG, "Responding failed", e);
|
FLog.e(TAG, "Responding failed", e);
|
||||||
|
@ -109,8 +109,9 @@ final public class JSPackagerClient implements ReconnectingWebSocket.MessageCall
|
||||||
JSONObject message = new JSONObject(response.string());
|
JSONObject message = new JSONObject(response.string());
|
||||||
|
|
||||||
int version = message.optInt("version");
|
int version = message.optInt("version");
|
||||||
String target = message.optString("target");
|
String method = message.optString("method");
|
||||||
String action = message.optString("action");
|
Object id = message.opt("id");
|
||||||
|
Object params = message.opt("params");
|
||||||
|
|
||||||
if (version != PROTOCOL_VERSION) {
|
if (version != PROTOCOL_VERSION) {
|
||||||
FLog.e(
|
FLog.e(
|
||||||
|
@ -119,20 +120,21 @@ final public class JSPackagerClient implements ReconnectingWebSocket.MessageCall
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!"bridge".equals(target)) {
|
if (method == null) {
|
||||||
|
abortOnMessage(id, "No method provided");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestHandler handler = mRequestHandlers.get(action);
|
RequestHandler handler = mRequestHandlers.get(method);
|
||||||
if (handler == null) {
|
if (handler == null) {
|
||||||
FLog.e(TAG, "No request handler for action: " + action);
|
abortOnMessage(id, "No request handler for method: " + method);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!"pokeSamplingProfiler".equals(action)) {
|
if (id == null) {
|
||||||
handler.onNotification(null);
|
handler.onNotification(params);
|
||||||
} else {
|
} else {
|
||||||
handler.onRequest(null, new Responder("profiler"));
|
handler.onRequest(params, new Responder(id));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FLog.e(TAG, "Handling the message failed", e);
|
FLog.e(TAG, "Handling the message failed", e);
|
||||||
|
@ -140,4 +142,12 @@ final public class JSPackagerClient implements ReconnectingWebSocket.MessageCall
|
||||||
response.close();
|
response.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void abortOnMessage(Object id, String reason) {
|
||||||
|
if (id != null) {
|
||||||
|
(new Responder(id)).error(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
FLog.e(TAG, "Handling the message failed with reason: " + reason);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,93 +33,114 @@ public class JSPackagerClientTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_onMessage_ShouldTriggerCallback() throws IOException {
|
public void test_onMessage_ShouldTriggerNotification() throws IOException {
|
||||||
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
||||||
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("actionValue", handler));
|
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("methodValue", handler));
|
||||||
WebSocket webSocket = mock(WebSocket.class);
|
WebSocket webSocket = mock(WebSocket.class);
|
||||||
|
|
||||||
client.onMessage(
|
client.onMessage(
|
||||||
ResponseBody.create(
|
ResponseBody.create(
|
||||||
WebSocket.TEXT,
|
WebSocket.TEXT,
|
||||||
"{\"version\": 1, \"target\": \"bridge\", \"action\": \"actionValue\"}"));
|
"{\"version\": 2, \"method\": \"methodValue\", \"params\": \"paramsValue\"}"));
|
||||||
verify(handler).onNotification(any());
|
verify(handler).onNotification(eq("paramsValue"));
|
||||||
|
verify(handler, never()).onRequest(any(), any(JSPackagerClient.Responder.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_onMessage_ShouldTriggerRequest() throws IOException {
|
||||||
|
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
||||||
|
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("methodValue", handler));
|
||||||
|
WebSocket webSocket = mock(WebSocket.class);
|
||||||
|
|
||||||
|
client.onMessage(
|
||||||
|
ResponseBody.create(
|
||||||
|
WebSocket.TEXT,
|
||||||
|
"{\"version\": 2, \"id\": \"idValue\", \"method\": \"methodValue\", \"params\": \"paramsValue\"}"));
|
||||||
|
verify(handler, never()).onNotification(any());
|
||||||
|
verify(handler).onRequest(eq("paramsValue"), any(JSPackagerClient.Responder.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_onMessage_WithoutParams_ShouldTriggerNotification() throws IOException {
|
||||||
|
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
||||||
|
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("methodValue", handler));
|
||||||
|
WebSocket webSocket = mock(WebSocket.class);
|
||||||
|
|
||||||
|
client.onMessage(
|
||||||
|
ResponseBody.create(
|
||||||
|
WebSocket.TEXT,
|
||||||
|
"{\"version\": 2, \"method\": \"methodValue\"}"));
|
||||||
|
verify(handler).onNotification(eq(null));
|
||||||
|
verify(handler, never()).onRequest(any(), any(JSPackagerClient.Responder.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_onMessage_WithInvalidContentType_ShouldNotTriggerCallback() throws IOException {
|
public void test_onMessage_WithInvalidContentType_ShouldNotTriggerCallback() throws IOException {
|
||||||
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
||||||
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("actionValue", handler));
|
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("methodValue", handler));
|
||||||
WebSocket webSocket = mock(WebSocket.class);
|
WebSocket webSocket = mock(WebSocket.class);
|
||||||
|
|
||||||
client.onMessage(
|
client.onMessage(
|
||||||
ResponseBody.create(
|
ResponseBody.create(
|
||||||
WebSocket.BINARY,
|
WebSocket.BINARY,
|
||||||
"{\"version\": 1, \"target\": \"bridge\", \"action\": \"actionValue\"}"));
|
"{\"version\": 2, \"method\": \"methodValue\"}"));
|
||||||
verify(handler, never()).onNotification(any());
|
verify(handler, never()).onNotification(any());
|
||||||
|
verify(handler, never()).onRequest(any(), any(JSPackagerClient.Responder.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_onMessage_WithoutTarget_ShouldNotTriggerCallback() throws IOException {
|
public void test_onMessage_WithoutMethod_ShouldNotTriggerCallback() throws IOException {
|
||||||
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
||||||
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("actionValue", handler));
|
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("methodValue", handler));
|
||||||
WebSocket webSocket = mock(WebSocket.class);
|
WebSocket webSocket = mock(WebSocket.class);
|
||||||
|
|
||||||
client.onMessage(
|
client.onMessage(
|
||||||
ResponseBody.create(
|
ResponseBody.create(
|
||||||
WebSocket.TEXT,
|
WebSocket.TEXT,
|
||||||
"{\"version\": 1, \"action\": \"actionValue\"}"));
|
"{\"version\": 2}"));
|
||||||
verify(handler, never()).onNotification(any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_onMessage_With_Null_Target_ShouldNotTriggerCallback() throws IOException {
|
|
||||||
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
|
||||||
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("actionValue", handler));
|
|
||||||
WebSocket webSocket = mock(WebSocket.class);
|
|
||||||
|
|
||||||
client.onMessage(
|
|
||||||
ResponseBody.create(
|
|
||||||
WebSocket.TEXT,
|
|
||||||
"{\"version\": 1, \"target\": null, \"action\": \"actionValue\"}"));
|
|
||||||
verify(handler, never()).onNotification(any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_onMessage_WithoutAction_ShouldNotTriggerCallback() throws IOException {
|
|
||||||
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
|
||||||
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("actionValue", handler));
|
|
||||||
WebSocket webSocket = mock(WebSocket.class);
|
|
||||||
|
|
||||||
client.onMessage(
|
|
||||||
ResponseBody.create(
|
|
||||||
WebSocket.TEXT,
|
|
||||||
"{\"version\": 1, \"target\": \"bridge\"}"));
|
|
||||||
verify(handler, never()).onNotification(any());
|
verify(handler, never()).onNotification(any());
|
||||||
|
verify(handler, never()).onRequest(any(), any(JSPackagerClient.Responder.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_onMessage_With_Null_Action_ShouldNotTriggerCallback() throws IOException {
|
public void test_onMessage_With_Null_Action_ShouldNotTriggerCallback() throws IOException {
|
||||||
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
||||||
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("actionValue", handler));
|
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("methodValue", handler));
|
||||||
WebSocket webSocket = mock(WebSocket.class);
|
WebSocket webSocket = mock(WebSocket.class);
|
||||||
|
|
||||||
client.onMessage(
|
client.onMessage(
|
||||||
ResponseBody.create(
|
ResponseBody.create(
|
||||||
WebSocket.TEXT,
|
WebSocket.TEXT,
|
||||||
"{\"version\": 1, \"target\": \"bridge\", \"action\": null}"));
|
"{\"version\": 2, \"method\": null}"));
|
||||||
verify(handler, never()).onNotification(any());
|
verify(handler, never()).onNotification(any());
|
||||||
|
verify(handler, never()).onRequest(any(), any(JSPackagerClient.Responder.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_onMessage_WithInvalidMethod_ShouldNotTriggerCallback() throws IOException {
|
||||||
|
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
||||||
|
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("methodValue", handler));
|
||||||
|
WebSocket webSocket = mock(WebSocket.class);
|
||||||
|
|
||||||
|
client.onMessage(
|
||||||
|
ResponseBody.create(
|
||||||
|
WebSocket.BINARY,
|
||||||
|
"{\"version\": 2, \"method\": \"methodValue2\"}"));
|
||||||
|
verify(handler, never()).onNotification(any());
|
||||||
|
verify(handler, never()).onRequest(any(), any(JSPackagerClient.Responder.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_onMessage_WrongVersion_ShouldNotTriggerCallback() throws IOException {
|
public void test_onMessage_WrongVersion_ShouldNotTriggerCallback() throws IOException {
|
||||||
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
JSPackagerClient.RequestHandler handler = mock(JSPackagerClient.RequestHandler.class);
|
||||||
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("actionValue", handler));
|
final JSPackagerClient client = new JSPackagerClient("ws://not_needed", createRH("methodValue", handler));
|
||||||
WebSocket webSocket = mock(WebSocket.class);
|
WebSocket webSocket = mock(WebSocket.class);
|
||||||
|
|
||||||
client.onMessage(
|
client.onMessage(
|
||||||
ResponseBody.create(
|
ResponseBody.create(
|
||||||
WebSocket.TEXT,
|
WebSocket.TEXT,
|
||||||
"{\"version\": 2, \"target\": \"bridge\", \"action\": \"actionValue\"}"));
|
"{\"version\": 1, \"method\": \"methodValue\"}"));
|
||||||
verify(handler, never()).onNotification(any());
|
verify(handler, never()).onNotification(any());
|
||||||
|
verify(handler, never()).onRequest(any(), any(JSPackagerClient.Responder.class));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2013-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const WebSocket = require('ws');
|
||||||
|
|
||||||
|
const parseMessage = require('./messageSocket').parseMessage;
|
||||||
|
|
||||||
|
const PROTOCOL_VERSION = 2;
|
||||||
|
const TARGET_SERVER = 'server';
|
||||||
|
|
||||||
|
function getMessageId() {
|
||||||
|
return `${Date.now()}:${Math.random()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsPackagerClient {
|
||||||
|
constructor(url) {
|
||||||
|
this.ws = new WebSocket(url);
|
||||||
|
this.msgCallbacks = new Map();
|
||||||
|
|
||||||
|
this.openPromise = new Promise((resolve, reject) => {
|
||||||
|
this.ws.on('error', error => reject(error));
|
||||||
|
this.ws.on('open', resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('message', (data, flags) => {
|
||||||
|
const message = parseMessage(data, flags.binary);
|
||||||
|
const msgCallback = this.msgCallbacks.get(message.id);
|
||||||
|
if (message === undefined || message.id === undefined) {
|
||||||
|
// gracefully ignore wrong messages or broadcasts
|
||||||
|
} else if (msgCallback === undefined) {
|
||||||
|
console.warn(`Response with non-existing message id: '${message.id}'`);
|
||||||
|
} else {
|
||||||
|
if (message.error === undefined) {
|
||||||
|
msgCallback.resolve(message.result);
|
||||||
|
} else {
|
||||||
|
msgCallback.reject(message.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRequest(method, target, params) {
|
||||||
|
return this.openPromise.then(() => new Promise((resolve, reject) => {
|
||||||
|
const messageId = getMessageId();
|
||||||
|
this.msgCallbacks.set(messageId, {resolve: resolve, reject: reject});
|
||||||
|
this.ws.send(
|
||||||
|
JSON.stringify({
|
||||||
|
version: PROTOCOL_VERSION,
|
||||||
|
target: target,
|
||||||
|
method: method,
|
||||||
|
id: messageId,
|
||||||
|
params: params,
|
||||||
|
}),
|
||||||
|
error => {
|
||||||
|
if (error !== undefined) {
|
||||||
|
this.msgCallbacks.delete(messageId);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNotification(method, target, params) {
|
||||||
|
return this.openPromise.then(() => new Promise((resolve, reject) => {
|
||||||
|
this.ws.send(
|
||||||
|
JSON.stringify({
|
||||||
|
version: PROTOCOL_VERSION,
|
||||||
|
target: target,
|
||||||
|
method: method,
|
||||||
|
params: params,
|
||||||
|
}),
|
||||||
|
error => {
|
||||||
|
if (error !== undefined) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
sendBroadcast(method, params) {
|
||||||
|
return this.sendNotification(method, undefined, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPeers() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.sendRequest('getpeers', TARGET_SERVER, undefined).then(
|
||||||
|
response => {
|
||||||
|
if (!response instanceof Map) {
|
||||||
|
reject('Results received from server are of wrong format:\n' +
|
||||||
|
JSON.stringify(response));
|
||||||
|
} else {
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getId() {
|
||||||
|
return this.sendRequest('getid', TARGET_SERVER, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = JsPackagerClient;
|
|
@ -8,8 +8,9 @@
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const url = require('url');
|
||||||
const WebSocketServer = require('ws').Server;
|
const WebSocketServer = require('ws').Server;
|
||||||
const PROTOCOL_VERSION = 1;
|
const PROTOCOL_VERSION = 2;
|
||||||
|
|
||||||
function parseMessage(data, binary) {
|
function parseMessage(data, binary) {
|
||||||
if (binary) {
|
if (binary) {
|
||||||
|
@ -29,6 +30,30 @@ function parseMessage(data, binary) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBroadcast(message) {
|
||||||
|
return (
|
||||||
|
typeof message.method === 'string' &&
|
||||||
|
message.id === undefined &&
|
||||||
|
message.target === undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRequest(message) {
|
||||||
|
return (
|
||||||
|
typeof message.method === 'string' &&
|
||||||
|
typeof message.target === 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isResponse(message) {
|
||||||
|
return (
|
||||||
|
typeof message.id === 'object' &&
|
||||||
|
typeof message.id.requestId !== undefined &&
|
||||||
|
typeof message.id.clientId === 'string' && (
|
||||||
|
message.result !== undefined ||
|
||||||
|
message.error !== undefined
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
function attachToServer(server, path) {
|
function attachToServer(server, path) {
|
||||||
const wss = new WebSocketServer({
|
const wss = new WebSocketServer({
|
||||||
server: server,
|
server: server,
|
||||||
|
@ -37,11 +62,19 @@ function attachToServer(server, path) {
|
||||||
const clients = new Map();
|
const clients = new Map();
|
||||||
let nextClientId = 0;
|
let nextClientId = 0;
|
||||||
|
|
||||||
|
function getClientWs(clientId) {
|
||||||
|
const clientWs = clients.get(clientId);
|
||||||
|
if (clientWs === undefined) {
|
||||||
|
throw `could not find id "${clientId}" while forwarding request`;
|
||||||
|
}
|
||||||
|
return clientWs;
|
||||||
|
}
|
||||||
|
|
||||||
function handleSendBroadcast(broadcasterId, message) {
|
function handleSendBroadcast(broadcasterId, message) {
|
||||||
const forwarded = {
|
const forwarded = {
|
||||||
version: PROTOCOL_VERSION,
|
version: PROTOCOL_VERSION,
|
||||||
target: message.target,
|
method: message.method,
|
||||||
action: message.action,
|
params: message.params,
|
||||||
};
|
};
|
||||||
for (const [otherId, otherWs] of clients) {
|
for (const [otherId, otherWs] of clients) {
|
||||||
if (otherId !== broadcasterId) {
|
if (otherId !== broadcasterId) {
|
||||||
|
@ -58,14 +91,78 @@ function attachToServer(server, path) {
|
||||||
wss.on('connection', function(clientWs) {
|
wss.on('connection', function(clientWs) {
|
||||||
const clientId = `client#${nextClientId++}`;
|
const clientId = `client#${nextClientId++}`;
|
||||||
|
|
||||||
function handleCatchedError(message, error) {
|
function handleCaughtError(message, error) {
|
||||||
const errorMessage = {
|
const errorMessage = {
|
||||||
|
id: message.id,
|
||||||
|
method: message.method,
|
||||||
target: message.target,
|
target: message.target,
|
||||||
action: message.action === undefined ? 'undefined' : 'defined',
|
error: message.error === undefined ? 'undefined' : 'defined',
|
||||||
|
params: message.params === undefined ? 'undefined' : 'defined',
|
||||||
|
result: message.result === undefined ? 'undefined' : 'defined',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (message.id === undefined) {
|
||||||
console.error(
|
console.error(
|
||||||
`Handling message from ${clientId} failed with:\n${error}\n` +
|
`Handling message from ${clientId} failed with:\n${error}\n` +
|
||||||
`message:\n${JSON.stringify(errorMessage)}`);
|
`message:\n${JSON.stringify(errorMessage)}`);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
clientWs.send(JSON.stringify({
|
||||||
|
version: PROTOCOL_VERSION,
|
||||||
|
error: error,
|
||||||
|
id: message.id,
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to reply to ${clientId} with error:\n${error}` +
|
||||||
|
`\nmessage:\n${JSON.stringify(errorMessage)}` +
|
||||||
|
`\ndue to error: ${e.toString()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleServerRequest(message) {
|
||||||
|
let result = null;
|
||||||
|
switch (message.method) {
|
||||||
|
case 'getid':
|
||||||
|
result = clientId;
|
||||||
|
break;
|
||||||
|
case 'getpeers':
|
||||||
|
result = {};
|
||||||
|
clients.forEach((otherWs, otherId) => {
|
||||||
|
if (clientId !== otherId) {
|
||||||
|
result[otherId] = url.parse(otherWs.upgradeReq.url).query;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw `unkown method: ${message.method}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientWs.send(JSON.stringify({
|
||||||
|
version: PROTOCOL_VERSION,
|
||||||
|
result: result,
|
||||||
|
id: message.id
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function forwardRequest(message) {
|
||||||
|
getClientWs(message.target).send(JSON.stringify({
|
||||||
|
version: PROTOCOL_VERSION,
|
||||||
|
method: message.method,
|
||||||
|
params: message.params,
|
||||||
|
id: (message.id === undefined
|
||||||
|
? undefined
|
||||||
|
: {requestId: message.id, clientId: clientId}),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function forwardResponse(message) {
|
||||||
|
getClientWs(message.id.clientId).send(JSON.stringify({
|
||||||
|
version: PROTOCOL_VERSION,
|
||||||
|
result: message.result,
|
||||||
|
error: message.error,
|
||||||
|
id: message.id.requestId,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
clients.set(clientId, clientWs);
|
clients.set(clientId, clientWs);
|
||||||
|
@ -82,16 +179,28 @@ function attachToServer(server, path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (isBroadcast(message)) {
|
||||||
handleSendBroadcast(clientId, message);
|
handleSendBroadcast(clientId, message);
|
||||||
|
} else if (isRequest(message)) {
|
||||||
|
if (message.target === 'server') {
|
||||||
|
handleServerRequest(message);
|
||||||
|
} else {
|
||||||
|
forwardRequest(message);
|
||||||
|
}
|
||||||
|
} else if (isResponse(message)) {
|
||||||
|
forwardResponse(message);
|
||||||
|
} else {
|
||||||
|
throw 'Invalid message, did not match the protocol';
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleCatchedError(message, e.toString());
|
handleCaughtError(message, e.toString());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
broadcast: (target, action) => {
|
broadcast: (method, params) => {
|
||||||
handleSendBroadcast(null, {target: target, action: action});
|
handleSendBroadcast(null, {method: method, params: params});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче