2016-02-18 19:59:34 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// Copyright 2016 Realm Inc.
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
2015-10-28 17:37:17 +00:00
|
|
|
|
2015-10-02 05:56:47 +00:00
|
|
|
'use strict';
|
|
|
|
|
2016-02-28 20:36:30 +00:00
|
|
|
import * as base64 from './base64';
|
|
|
|
import { keys, objectTypes } from './constants';
|
2015-10-08 08:52:37 +00:00
|
|
|
|
2016-11-14 18:48:50 +00:00
|
|
|
const {id: idKey, realm: _realmKey} = keys;
|
2017-01-31 13:07:29 +00:00
|
|
|
let registeredCallbacks = [];
|
2015-10-21 20:25:12 +00:00
|
|
|
const typeConverters = {};
|
2015-10-02 05:56:47 +00:00
|
|
|
|
2017-01-31 13:07:29 +00:00
|
|
|
// Callbacks that are registered initially (currently only authenticateRealm) will
|
|
|
|
// carry this symbol so they are not wiped in clearTestState.
|
|
|
|
const persistentCallback = Symbol("persistentCallback");
|
|
|
|
|
2015-10-21 06:59:02 +00:00
|
|
|
let XMLHttpRequest = global.originalXMLHttpRequest || global.XMLHttpRequest;
|
2016-02-29 11:24:51 +00:00
|
|
|
let sessionHost;
|
2015-10-20 22:10:22 +00:00
|
|
|
let sessionId;
|
2015-10-06 20:36:42 +00:00
|
|
|
|
|
|
|
// Check if XMLHttpRequest has been overridden, and get the native one if that's the case.
|
2015-10-21 06:59:02 +00:00
|
|
|
if (XMLHttpRequest.__proto__ != global.XMLHttpRequestEventTarget) {
|
|
|
|
let fakeXMLHttpRequest = XMLHttpRequest;
|
|
|
|
delete global.XMLHttpRequest;
|
|
|
|
XMLHttpRequest = global.XMLHttpRequest;
|
|
|
|
global.XMLHttpRequest = fakeXMLHttpRequest;
|
2015-10-06 20:36:42 +00:00
|
|
|
}
|
2015-10-02 05:56:47 +00:00
|
|
|
|
2015-11-03 06:16:50 +00:00
|
|
|
registerTypeConverter(objectTypes.DATA, (_, {value}) => base64.decode(value));
|
|
|
|
registerTypeConverter(objectTypes.DATE, (_, {value}) => new Date(value));
|
2015-11-03 00:36:24 +00:00
|
|
|
registerTypeConverter(objectTypes.DICT, deserializeDict);
|
2015-11-16 11:33:11 +00:00
|
|
|
|
2016-02-28 20:36:30 +00:00
|
|
|
export function registerTypeConverter(type, handler) {
|
2015-10-02 05:56:47 +00:00
|
|
|
typeConverters[type] = handler;
|
|
|
|
}
|
|
|
|
|
2017-01-31 13:07:29 +00:00
|
|
|
export function createSession(authenticateRealm, host) {
|
|
|
|
authenticateRealm[persistentCallback] = true;
|
|
|
|
sessionId = sendRequest('create_session', { authenticateRealm: serialize(undefined, authenticateRealm) }, host);
|
2016-03-16 16:28:18 +00:00
|
|
|
sessionHost = host;
|
2016-02-29 11:24:51 +00:00
|
|
|
|
2015-10-20 22:10:22 +00:00
|
|
|
return sessionId;
|
2015-10-19 23:59:04 +00:00
|
|
|
}
|
|
|
|
|
2016-02-28 20:36:30 +00:00
|
|
|
export function createRealm(args) {
|
2015-10-15 10:00:13 +00:00
|
|
|
if (args) {
|
|
|
|
args = args.map((arg) => serialize(null, arg));
|
|
|
|
}
|
|
|
|
|
|
|
|
return sendRequest('create_realm', {arguments: args});
|
2015-10-02 05:56:47 +00:00
|
|
|
}
|
|
|
|
|
2017-01-31 13:07:29 +00:00
|
|
|
export function createUser(args) {
|
|
|
|
args = args.map((arg) => serialize(null, arg));
|
|
|
|
const result = sendRequest('create_user', {arguments: args});
|
|
|
|
return deserialize(undefined, result);
|
|
|
|
}
|
|
|
|
|
2016-02-28 20:36:30 +00:00
|
|
|
export function callMethod(realmId, id, name, args) {
|
2015-10-08 16:43:38 +00:00
|
|
|
if (args) {
|
|
|
|
args = args.map((arg) => serialize(realmId, arg));
|
|
|
|
}
|
2015-10-08 08:53:22 +00:00
|
|
|
|
2015-10-19 22:52:32 +00:00
|
|
|
let result = sendRequest('call_method', {realmId, id, name, arguments: args});
|
2015-10-08 16:43:38 +00:00
|
|
|
return deserialize(realmId, result);
|
2015-10-08 08:53:22 +00:00
|
|
|
}
|
|
|
|
|
2016-02-28 20:36:30 +00:00
|
|
|
export function getProperty(realmId, id, name) {
|
2015-10-19 19:06:47 +00:00
|
|
|
let result = sendRequest('get_property', {realmId, id, name});
|
2015-10-08 08:52:37 +00:00
|
|
|
return deserialize(realmId, result);
|
2015-10-02 05:56:47 +00:00
|
|
|
}
|
|
|
|
|
2016-02-28 20:36:30 +00:00
|
|
|
export function setProperty(realmId, id, name, value) {
|
2015-10-08 09:00:10 +00:00
|
|
|
value = serialize(realmId, value);
|
2015-10-19 19:06:47 +00:00
|
|
|
sendRequest('set_property', {realmId, id, name, value});
|
2015-10-02 05:56:47 +00:00
|
|
|
}
|
|
|
|
|
2017-01-31 13:07:29 +00:00
|
|
|
export function getAllUsers() {
|
|
|
|
let result = sendRequest('get_all_users');
|
|
|
|
return deserialize(undefined, result);
|
|
|
|
}
|
|
|
|
|
2016-02-28 20:36:30 +00:00
|
|
|
export function clearTestState() {
|
2015-10-16 01:48:13 +00:00
|
|
|
sendRequest('clear_test_state');
|
2016-04-29 20:01:01 +00:00
|
|
|
|
2017-01-31 13:07:29 +00:00
|
|
|
// Clear all registered callbacks that are specific to this session.
|
|
|
|
registeredCallbacks = registeredCallbacks.filter(cb => Reflect.has(cb, persistentCallback));
|
2016-04-29 20:01:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function registerCallback(callback) {
|
|
|
|
let key = registeredCallbacks.indexOf(callback);
|
|
|
|
return key >= 0 ? key : (registeredCallbacks.push(callback) - 1);
|
2015-10-15 10:12:28 +00:00
|
|
|
}
|
|
|
|
|
2015-10-08 08:52:37 +00:00
|
|
|
function serialize(realmId, value) {
|
2015-11-03 06:16:50 +00:00
|
|
|
if (typeof value == 'undefined') {
|
|
|
|
return {type: objectTypes.UNDEFINED};
|
|
|
|
}
|
2015-10-15 01:00:21 +00:00
|
|
|
if (typeof value == 'function') {
|
2016-04-29 20:01:01 +00:00
|
|
|
return {type: objectTypes.FUNCTION, value: registerCallback(value)};
|
2015-10-15 01:00:21 +00:00
|
|
|
}
|
2015-10-08 08:52:37 +00:00
|
|
|
if (!value || typeof value != 'object') {
|
|
|
|
return {value: value};
|
|
|
|
}
|
|
|
|
|
2015-11-03 00:36:24 +00:00
|
|
|
let id = value[idKey];
|
2015-10-08 08:52:37 +00:00
|
|
|
if (id) {
|
2015-10-14 23:05:49 +00:00
|
|
|
return {id};
|
2015-10-08 08:52:37 +00:00
|
|
|
}
|
|
|
|
|
2015-10-19 23:46:56 +00:00
|
|
|
if (value instanceof Date) {
|
2015-11-03 06:16:50 +00:00
|
|
|
return {type: objectTypes.DATE, value: value.getTime()};
|
2015-10-19 23:46:56 +00:00
|
|
|
}
|
|
|
|
|
2015-10-08 08:52:37 +00:00
|
|
|
if (Array.isArray(value)) {
|
|
|
|
let array = value.map((item) => serialize(realmId, item));
|
|
|
|
return {value: array};
|
|
|
|
}
|
|
|
|
|
2015-11-16 11:33:11 +00:00
|
|
|
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
2015-11-03 06:16:50 +00:00
|
|
|
return {type: objectTypes.DATA, value: base64.encode(value)};
|
2015-11-16 11:33:11 +00:00
|
|
|
}
|
|
|
|
|
2015-11-03 00:36:24 +00:00
|
|
|
let keys = Object.keys(value);
|
|
|
|
let values = keys.map((key) => serialize(realmId, value[key]));
|
|
|
|
return {type: objectTypes.DICT, keys, values};
|
2015-10-08 08:52:37 +00:00
|
|
|
}
|
|
|
|
|
2017-02-01 22:44:56 +00:00
|
|
|
export function deserialize(realmId, info) {
|
2015-10-08 08:52:37 +00:00
|
|
|
let type = info.type;
|
|
|
|
let handler = type && typeConverters[type];
|
|
|
|
if (handler) {
|
|
|
|
return handler(realmId, info);
|
|
|
|
}
|
|
|
|
|
|
|
|
let value = info.value;
|
|
|
|
if (value && Array.isArray(value)) {
|
|
|
|
return value.map((item) => deserialize(realmId, item));
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
2015-10-02 05:56:47 +00:00
|
|
|
}
|
|
|
|
|
2015-11-03 00:36:24 +00:00
|
|
|
function deserializeDict(realmId, info) {
|
|
|
|
let {keys, values} = info;
|
|
|
|
let object = {};
|
|
|
|
|
|
|
|
for (let i = 0, len = keys.length; i < len; i++) {
|
2016-04-28 23:22:16 +00:00
|
|
|
object[keys[i]] = deserialize(realmId, values[i]);
|
2015-11-03 00:36:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return object;
|
|
|
|
}
|
|
|
|
|
2016-04-28 23:08:38 +00:00
|
|
|
function makeRequest(url, data) {
|
|
|
|
let statusCode;
|
|
|
|
let responseText;
|
|
|
|
|
|
|
|
// The global __debug__ object is provided by Visual Studio Code.
|
|
|
|
if (global.__debug__) {
|
|
|
|
let request = global.__debug__.require('sync-request');
|
|
|
|
let response = request('POST', url, {json: data});
|
|
|
|
|
|
|
|
statusCode = response.statusCode;
|
|
|
|
responseText = response.body.toString('utf-8');
|
|
|
|
} else {
|
|
|
|
let body = JSON.stringify(data);
|
|
|
|
let request = new XMLHttpRequest();
|
|
|
|
|
|
|
|
request.open('POST', url, false);
|
|
|
|
request.send(body);
|
|
|
|
|
|
|
|
statusCode = request.status;
|
|
|
|
responseText = request.responseText;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (statusCode != 200) {
|
|
|
|
throw new Error(responseText);
|
|
|
|
}
|
|
|
|
|
|
|
|
return JSON.parse(responseText);
|
|
|
|
}
|
|
|
|
|
2016-03-16 16:28:18 +00:00
|
|
|
function sendRequest(command, data, host = sessionHost) {
|
|
|
|
if (!host) {
|
2016-02-29 11:24:51 +00:00
|
|
|
throw new Error('Must first create RPC session with a valid host');
|
|
|
|
}
|
|
|
|
|
2015-10-20 22:10:22 +00:00
|
|
|
data = Object.assign({}, data, sessionId ? {sessionId} : null);
|
|
|
|
|
2016-03-16 16:28:18 +00:00
|
|
|
let url = 'http://' + host + '/' + command;
|
2016-04-28 23:08:38 +00:00
|
|
|
let response = makeRequest(url, data);
|
2015-10-08 08:52:37 +00:00
|
|
|
|
2015-10-02 05:56:47 +00:00
|
|
|
if (!response || response.error) {
|
2015-12-01 22:05:33 +00:00
|
|
|
let error = response && response.error;
|
|
|
|
|
|
|
|
// Remove the type prefix from the error message (e.g. "Error: ").
|
|
|
|
if (error) {
|
|
|
|
error = error.replace(/^[a-z]+: /i, '');
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error(error || `Invalid response for "${command}"`);
|
2015-10-02 05:56:47 +00:00
|
|
|
}
|
|
|
|
|
2016-04-29 20:01:01 +00:00
|
|
|
let callback = response.callback;
|
|
|
|
if (callback != null) {
|
|
|
|
let result;
|
|
|
|
let error;
|
|
|
|
try {
|
|
|
|
let realmId = data.realmId;
|
|
|
|
let args = deserialize(realmId, response.arguments);
|
|
|
|
result = registeredCallbacks[callback].apply(null, args);
|
|
|
|
result = serialize(realmId, result);
|
|
|
|
} catch (e) {
|
|
|
|
error = e.message || ('' + e);
|
|
|
|
}
|
|
|
|
return sendRequest('callback_result', {callback, result, error});
|
|
|
|
}
|
|
|
|
|
2015-10-02 05:56:47 +00:00
|
|
|
return response.result;
|
|
|
|
}
|