Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chrome debugger with BSON fixed #3601

Merged
merged 6 commits into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions lib/browser/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,40 @@

"use strict";

import { EJSON } from "bson";

import { keys, objectTypes } from "./constants";
import { getterForProperty, createMethods, createMethod } from "./util";
import { getterForProperty, createMethods } from "./util";
import { promisify } from "../utils";

export default class User {
logOut() {
return promisify(cb => this._logOut(cb));
}

async callFunction(name, args, service) {
const stringifiedArgs = EJSON.stringify(args, { relaxed: false });
const result = await promisify(cb => this._callFunction(name, stringifiedArgs, service, cb));
return EJSON.parse(result);
}

get functions() {
return new Proxy(this, {
get(target, name, receiver) {
if (typeof name === "string" && name !== "inspect") {
return function (...args) {
return target.callFunction(name, args);
};
} else {
return Reflect.get(target, name, receiver);
}
},
});
}

get customData() {
return EJSON.parse(this._customData);
}
}

createMethods(User.prototype, objectTypes.USER, [
Expand All @@ -50,9 +76,9 @@ Object.defineProperties(User.prototype, {
providerType: { get: getterForProperty("providerType") },
isLoggedIn: { get: getterForProperty("isLoggedIn") },
state: { get: getterForProperty("state") },
customData: { get: getterForProperty("customData") },
apiKeys: { get: getterForProperty("apiKeys") },
deviceId: { get: getterForProperty("deviceId") },
_customData: { get: getterForProperty("_customData") },
});

export function createUser(realmId, info) {
Expand Down
9 changes: 6 additions & 3 deletions lib/email-password-auth-methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
//
////////////////////////////////////////////////////////////////////////////

'use strict';
"use strict";

const { EJSON } = require("bson");

const {promisify} = require("./utils.js");

Expand All @@ -41,8 +43,9 @@ const instanceMethods = {
return promisify(cb => this._resetPassword(password, token, token_id, cb));
},

callResetPasswordFunction(email, password, ...bsonArgs) {
return promisify(cb => this._callResetPasswordFunction(email, password, bsonArgs, cb));
callResetPasswordFunction(email, password, ...args) {
const stringifiedArgs = EJSON.stringify(args, { relaxed: false });
return promisify(cb => this._callResetPasswordFunction(email, password, stringifiedArgs, cb));
},
};

Expand Down
34 changes: 4 additions & 30 deletions lib/extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,6 @@ function setConstructorOnPrototype(klass) {
}
}

function waitForCompletion(session, fn, timeout, timeoutErrorMessage) {
const waiter = new Promise((resolve, reject) => {
fn.call(session, (error) => {
if (error === undefined) {
setTimeout(() => resolve(), 1);
} else {
setTimeout(() => reject(error), 1);
}
});
});
if (timeout === undefined) {
return waiter;
}
return Promise.race([
waiter,
new Promise((resolve, reject) => {
setTimeout(() => {
reject(timeoutErrorMessage);
}, timeout);
})
]);
}

function openLocalRealm(realmConstructor, config) {
let promise = Promise.resolve(new realmConstructor(config));
promise.progress = (callback) => { return promise; };
Expand Down Expand Up @@ -350,6 +327,10 @@ module.exports = function(realmConstructor, environment) {
Object.defineProperties(realmConstructor.User, getOwnPropertyDescriptors(userMethods.static));
Object.defineProperties(realmConstructor.User.prototype, getOwnPropertyDescriptors(userMethods.instance));

let sessionMethods = require("./session");
Object.defineProperties(realmConstructor.App.Sync.Session, getOwnPropertyDescriptors(sessionMethods.static));
Object.defineProperties(realmConstructor.App.Sync.Session.prototype, getOwnPropertyDescriptors(sessionMethods.instance));

let credentialMethods = require("./credentials");
Object.defineProperties(realmConstructor.Credentials, getOwnPropertyDescriptors(credentialMethods.static))

Expand Down Expand Up @@ -391,13 +372,6 @@ module.exports = function(realmConstructor, environment) {
timeOut: 30 * 1000,
timeOutBehavior: 'throwException'
};
realmConstructor.App.Sync.Session.prototype.uploadAllLocalChanges = function(timeout) {
return waitForCompletion(this, this._waitForUploadCompletion, timeout, `Uploading changes did not complete in ${timeout} ms.`);
};

realmConstructor.App.Sync.Session.prototype.downloadAllServerChanges = function(timeout) {
return waitForCompletion(this, this._waitForDownloadCompletion, timeout, `Downloading changes did not complete in ${timeout} ms.`);
};

realmConstructor.App.Sync.ConnectionState = {
Disconnected: "disconnected",
Expand Down
14 changes: 9 additions & 5 deletions lib/mongo_client.js → lib/mongo-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@

"use strict";

const { EJSON } = require("bson");
const {DefaultNetworkTransport} = require('realm-network-transport');

const { cleanArguments } = require("./utils");

/**
* A remote collection of documents.
*/
Expand Down Expand Up @@ -214,14 +217,15 @@ class MongoDBCollection {
}

async* watch({ids = undefined, filter = undefined} = {}) {
let args = {
const args = cleanArguments({
database: this.databaseName,
collection: this.collectionName,
ids,
filter,
};

const request = this.user._makeStreamingRequest("watch", this.serviceName, this.user._cleanArgs([args]));
});

const stringifiedArgs = EJSON.stringify([args], { relaxed: false });
const request = this.user._makeStreamingRequest("watch", this.serviceName, stringifiedArgs);
const reply = await (new DefaultNetworkTransport().fetch(request));
if (!reply.ok) {
throw {code: reply.status, message: await reply.text()};
Expand All @@ -232,7 +236,7 @@ class MongoDBCollection {
watchStream.feedBuffer(chunk);
while (watchStream.state == 'HAVE_EVENT') {
let next = watchStream.nextEvent();
yield next;
yield EJSON.parse(next);
}
if (watchStream.state == 'HAVE_ERROR')
throw watchStream.error;
Expand Down
74 changes: 74 additions & 0 deletions lib/session.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2021 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.
//
////////////////////////////////////////////////////////////////////////////

"use strict";

const { EJSON } = require("bson");

function waitForCompletion(session, fn, timeout, timeoutErrorMessage) {
const waiter = new Promise((resolve, reject) => {
fn.call(session, (error) => {
if (error === undefined) {
setTimeout(() => resolve(), 1);
} else {
setTimeout(() => reject(error), 1);
}
});
});
if (timeout === undefined) {
return waiter;
}
return Promise.race([
waiter,
new Promise((resolve, reject) => {
setTimeout(() => {
reject(timeoutErrorMessage);
}, timeout);
})
]);
}

const instanceMethods = {
get config() {
// Parse the EJSON properties
const config = this._config;
if (config) {
return {
...config,
partitionValue: EJSON.parse(config.partitionValue),
};
}
},

uploadAllLocalChanges(timeout) {
return waitForCompletion(this, this._waitForUploadCompletion, timeout, `Uploading changes did not complete in ${timeout} ms.`);
},

downloadAllServerChanges(timeout) {
return waitForCompletion(this, this._waitForDownloadCompletion, timeout, `Downloading changes did not complete in ${timeout} ms.`);
},
}

const staticMethods = {
// none
};

module.exports = {
static: staticMethods,
instance: instanceMethods,
};
30 changes: 13 additions & 17 deletions lib/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@

"use strict";

const {MongoDBCollection} = require("./mongo_client.js");
const {promisify} = require("./utils.js");
const { EJSON } = require("bson");

const {MongoDBCollection} = require("./mongo-client");
const {cleanArguments, promisify} = require("./utils");

const instanceMethods = {
linkCredentials(credentials) {
Expand All @@ -30,8 +32,11 @@ const instanceMethods = {
return promisify(cb => this._logOut(cb));
},

callFunction(name, args, service = undefined) {
return promisify(cb => this._callFunction(name, this._cleanArgs(args), service, cb));
async callFunction(name, args, service = undefined) {
const cleanedArgs = cleanArguments(args);
const stringifiedArgs = EJSON.stringify(cleanedArgs, { relaxed: false });
const result = await promisify(cb => this._callFunction(name, stringifiedArgs, service, cb));
return EJSON.parse(result);
},

async refreshCustomData() {
Expand Down Expand Up @@ -92,6 +97,10 @@ const instanceMethods = {
});
},

get customData() {
return EJSON.parse(this._customData);
},

// Internal helpers.
_functionsOnService(service) {
const user = this;
Expand All @@ -107,19 +116,6 @@ const instanceMethods = {
},
});
},

_cleanArgs(args) {
for (const arg of args) {
if (typeof arg === "object") {
for (const [key, value] of Object.entries(arg)) {
if (value === undefined) {
delete arg[key];
}
}
}
}
return args;
},
}

const staticMethods = {
Expand Down
21 changes: 21 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,29 @@

"use strict";

/**
* @param {array|object} args Arguments to clean.
* @return {array|object} A recursive copy of the argument, with properties removed where value is undefined.
*/
function cleanArguments(args) {
if (Array.isArray(args)) {
return args.map(cleanArguments);
} else if (typeof args === "object") {
const result = {};
for (const [k, v] of Object.entries(args)) {
if (typeof v !== "undefined") {
result[k] = v;
}
}
return result;
} else {
return args;
}
}

module.exports = {
cleanArguments,

/**
* Helper to wrap callback-taking C++ function into a Promise-returning JS function
* @example
Expand Down
14 changes: 5 additions & 9 deletions src/js_email_password_auth.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class EmailPasswordAuthClass : public ClassDefinition<T, app::App::UsernamePassw
using FunctionType = typename T::Function;
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
using StringType = typename T::String;
using String = js::String<T>;
using Object = js::Object<T>;
using Value = js::Value<T>;
Expand Down Expand Up @@ -149,17 +150,12 @@ void EmailPasswordAuthClass<T>::call_reset_password_function(ContextType ctx, Ob

auto email = Value::validated_to_string(ctx, args[0], "email");
auto password = Value::validated_to_string(ctx, args[1], "password");
auto call_args_js = Value::validated_to_array(ctx, args[2], "args");
auto stringified_ejson_args = Value::validated_to_string(ctx, args[2], "args");
auto callback = Value::validated_to_function(ctx, args[3], "callback");

auto bson_args = String::to_bson(stringified_ejson_args);

bson::BsonArray call_args_bson;
uint32_t length = Object::validated_get_length(ctx, call_args_js);
for (uint32_t index = 0; index < length; ++index) {
auto obj = Object::get_property(ctx, call_args_js, index);
call_args_bson.push_back(Value::to_bson(ctx, obj));
}

client.call_reset_password_function(email, password, call_args_bson, Function::wrap_void_callback(ctx, this_object, callback));
client.call_reset_password_function(email, password, bson_args.operator const bson::BsonArray &(), Function::wrap_void_callback(ctx, this_object, callback));
}

}
Expand Down
11 changes: 0 additions & 11 deletions src/js_realm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,6 @@ class RealmClass : public ClassDefinition<T, SharedRealm, ObservableClass<T>> {
static void delete_file(ContextType, ObjectType, Arguments &, ReturnValue &);
static void realm_file_exists(ContextType, ObjectType, Arguments &, ReturnValue &);

static void bson_parse_json(ContextType, ObjectType, Arguments &, ReturnValue &);

// static properties
static void get_default_path(ContextType, ObjectType, ReturnValue &);
static void set_default_path(ContextType, ObjectType, ValueType value);
Expand All @@ -356,7 +354,6 @@ class RealmClass : public ClassDefinition<T, SharedRealm, ObservableClass<T>> {
{"copyBundledRealmFiles", wrap<copy_bundled_realm_files>},
{"deleteFile", wrap<delete_file>},
{"exists", wrap<realm_file_exists>},
{"_bsonParseJsonForTest", wrap<bson_parse_json>},
#if REALM_ENABLE_SYNC
{"_asyncOpen", wrap<async_open_realm>},
#endif
Expand Down Expand Up @@ -1338,14 +1335,6 @@ void RealmClass<T>::update_schema(ContextType ctx, ObjectType this_object, Argum
);
}

template<typename T>
void RealmClass<T>::bson_parse_json(ContextType ctx, ObjectType, Arguments& args, ReturnValue &return_value) {
args.validate_count(1);
auto json = std::string(Value::validated_to_string(ctx, args[0]));
auto parsed = bson::parse(json);
return_value.set(Value::from_bson(ctx, parsed));
}

#if REALM_ENABLE_SYNC
template<typename T>
class AsyncOpenTaskClass : public ClassDefinition<T, std::shared_ptr<AsyncOpenTask>> {
Expand Down
Loading