From 7c1dab70795877b350564aaf2c4cadd78be0aa32 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Tue, 7 Feb 2017 11:01:26 +0100 Subject: [PATCH] Propagate token refresh errors to the session error handler (#843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Get a session’s error handler * Change the signature of all methods to add the callee * Deserialize rpc callbacks * Expose the session error handle on its config property * When a token refresh error occurs and there is a session error handler propagate the error to the handler --- lib/browser/rpc.js | 8 ++- lib/user-methods.js | 33 ++++++------ src/event_loop_dispatcher.hpp | 4 +- src/js_class.hpp | 2 +- src/js_list.hpp | 48 ++++++++--------- src/js_realm.hpp | 52 +++++++++--------- src/js_realm_object.hpp | 4 +- src/js_results.hpp | 28 +++++----- src/js_sync.hpp | 99 +++++++++++++++++++++-------------- src/jsc/jsc_class.hpp | 2 +- src/node/node_class.hpp | 2 +- src/rpc.cpp | 25 +++++---- src/rpc.hpp | 6 ++- tests/js/session-tests.js | 1 + 14 files changed, 176 insertions(+), 138 deletions(-) diff --git a/lib/browser/rpc.js b/lib/browser/rpc.js index faadb04b..e77a2f56 100644 --- a/lib/browser/rpc.js +++ b/lib/browser/rpc.js @@ -44,6 +44,7 @@ if (XMLHttpRequest.__proto__ != global.XMLHttpRequestEventTarget) { registerTypeConverter(objectTypes.DATA, (_, {value}) => base64.decode(value)); registerTypeConverter(objectTypes.DATE, (_, {value}) => new Date(value)); registerTypeConverter(objectTypes.DICT, deserializeDict); +registerTypeConverter(objectTypes.FUNCTION, deserializeFunction); export function registerTypeConverter(type, handler) { typeConverters[type] = handler; @@ -167,6 +168,10 @@ function deserializeDict(realmId, info) { return object; } +function deserializeFunction(realmId, info) { + return registeredCallbacks[info.value]; +} + function makeRequest(url, data) { let statusCode; let responseText; @@ -223,8 +228,9 @@ function sendRequest(command, data, host = sessionHost) { let error; try { let realmId = data.realmId; + let thisObject = deserialize(realmId, response.this); let args = deserialize(realmId, response.arguments); - result = registeredCallbacks[callback].apply(null, args); + result = registeredCallbacks[callback].apply(thisObject, args); result = serialize(realmId, result); } catch (e) { error = e.message || ('' + e); diff --git a/lib/user-methods.js b/lib/user-methods.js index c0469b1a..c5275610 100644 --- a/lib/user-methods.js +++ b/lib/user-methods.js @@ -66,23 +66,22 @@ function refreshAccessToken(user, localRealmPath, realmUrl) { .then((responseAndJson) => { const response = responseAndJson.response; const json = responseAndJson.json; - if (response.status != 200) { - //FIXME: propagate error to session error handler - /* - let session = user._sessionForOnDiskPath(localRealmPath); - let errorHandler = session._errorHandler; - if (errorHandler) { - errorHandler(session, new AuthError(json)); - } - */ - } else { - // Look up a fresh instance of the user. - // We do this because in React Native Remote Debugging - // `Realm.clearTestState()` will have invalidated the user object - let newUser = user.constructor.all[user.identity]; - if (newUser) { - let session = newUser._sessionForOnDiskPath(localRealmPath); - if (session && session.state !== 'invalid') { + // Look up a fresh instance of the user. + // We do this because in React Native Remote Debugging + // `Realm.clearTestState()` will have invalidated the user object + let newUser = user.constructor.all[user.identity]; + if (newUser) { + let session = newUser._sessionForOnDiskPath(localRealmPath); + if (session) { + if (response.status != 200) { + let errorHandler = session.config.error; + let error = new AuthError(json); + if (errorHandler) { + errorHandler(session, error); + } else { + (console.error || console.log).call(console, `Unhandled session token refresh error: ${error}`); + } + } else if (session.state !== 'invalid') { parsedRealmUrl.set('pathname', json.access_token.token_data.path); session._refreshAccessToken(json.access_token.token, parsedRealmUrl.href); diff --git a/src/event_loop_dispatcher.hpp b/src/event_loop_dispatcher.hpp index 42910ceb..1d08e036 100644 --- a/src/event_loop_dispatcher.hpp +++ b/src/event_loop_dispatcher.hpp @@ -80,7 +80,9 @@ public: { } - + + const std::function& func() const { return m_state->m_func; } + void operator()(Args... args) { if (m_thread == std::this_thread::get_id()) { diff --git a/src/js_class.hpp b/src/js_class.hpp index 39603897..70b28e2e 100644 --- a/src/js_class.hpp +++ b/src/js_class.hpp @@ -31,7 +31,7 @@ template using ConstructorType = void(typename T::Context, typename T::Object, size_t, const typename T::Value[]); template -using MethodType = void(typename T::Context, typename T::Object, size_t, const typename T::Value[], ReturnValue &); +using MethodType = void(typename T::Context, typename T::Function, typename T::Object, size_t, const typename T::Value[], ReturnValue &); template struct PropertyType { diff --git a/src/js_list.hpp b/src/js_list.hpp index 86a248e9..a572d635 100644 --- a/src/js_list.hpp +++ b/src/js_list.hpp @@ -59,20 +59,20 @@ struct ListClass : ClassDefinition, CollectionClass> { static bool set_index(ContextType, ObjectType, uint32_t, ValueType); // methods - static void push(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void pop(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void unshift(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void shift(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void splice(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void snapshot(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void filtered(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &); + static void push(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void pop(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void unshift(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void shift(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void splice(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void snapshot(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void filtered(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void sorted(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); // observable - static void add_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void remove_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void remove_all_listeners(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); std::string const name = "List"; @@ -125,7 +125,7 @@ bool ListClass::set_index(ContextType ctx, ObjectType object, uint32_t index, } template -void ListClass::push(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::push(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count_at_least(argc, 1); auto list = get_internal>(this_object); @@ -137,7 +137,7 @@ void ListClass::push(ContextType ctx, ObjectType this_object, size_t argc, co } template -void ListClass::pop(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::pop(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); auto list = get_internal>(this_object); @@ -156,7 +156,7 @@ void ListClass::pop(ContextType ctx, ObjectType this_object, size_t argc, con } template -void ListClass::unshift(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::unshift(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count_at_least(argc, 1); auto list = get_internal>(this_object); @@ -168,7 +168,7 @@ void ListClass::unshift(ContextType ctx, ObjectType this_object, size_t argc, } template -void ListClass::shift(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::shift(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); auto list = get_internal>(this_object); @@ -185,7 +185,7 @@ void ListClass::shift(ContextType ctx, ObjectType this_object, size_t argc, c } template -void ListClass::splice(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::splice(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count_at_least(argc, 1); auto list = get_internal>(this_object); @@ -221,7 +221,7 @@ void ListClass::splice(ContextType ctx, ObjectType this_object, size_t argc, } template -void ListClass::snapshot(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::snapshot(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); auto list = get_internal>(this_object); @@ -229,7 +229,7 @@ void ListClass::snapshot(ContextType ctx, ObjectType this_object, size_t argc } template -void ListClass::filtered(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::filtered(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count_at_least(argc, 1); auto list = get_internal>(this_object); @@ -237,7 +237,7 @@ void ListClass::filtered(ContextType ctx, ObjectType this_object, size_t argc } template -void ListClass::sorted(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1, 2); auto list = get_internal>(this_object); @@ -245,12 +245,12 @@ void ListClass::sorted(ContextType ctx, ObjectType this_object, size_t argc, } template -void ListClass::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { return_value.set(get_internal>(this_object)->is_valid()); } template -void ListClass::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); auto list = get_internal>(this_object); @@ -271,7 +271,7 @@ void ListClass::add_listener(ContextType ctx, ObjectType this_object, size_t } template -void ListClass::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); auto list = get_internal>(this_object); @@ -291,7 +291,7 @@ void ListClass::remove_listener(ContextType ctx, ObjectType this_object, size } template -void ListClass::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ListClass::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); auto list = get_internal>(this_object); diff --git a/src/js_realm.hpp b/src/js_realm.hpp index c34b7bd6..a1a50e55 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -151,16 +151,16 @@ public: static FunctionType create_constructor(ContextType); // methods - static void objects(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void object_for_primary_key(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void create(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void delete_one(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void delete_all(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void write(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void add_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void remove_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void remove_all_listeners(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void close(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void objects(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void object_for_primary_key(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void create(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void delete_one(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void delete_all(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void write(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void close(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); // properties static void get_path(ContextType, ObjectType, ReturnValue &); @@ -173,9 +173,9 @@ public: // static methods static void constructor(ContextType, ObjectType, size_t, const ValueType[]); - static void schema_version(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void clear_test_state(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void copy_bundled_realm_files(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void schema_version(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void clear_test_state(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void copy_bundled_realm_files(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); // static properties static void get_default_path(ContextType, ObjectType, ReturnValue &); @@ -437,7 +437,7 @@ void RealmClass::constructor(ContextType ctx, ObjectType this_object, size_t } template -void RealmClass::schema_version(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::schema_version(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1, 2); realm::Realm::Config config; @@ -459,14 +459,14 @@ void RealmClass::schema_version(ContextType ctx, ObjectType this_object, size template -void RealmClass::clear_test_state(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::clear_test_state(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); js::clear_test_state(); } template -void RealmClass::copy_bundled_realm_files(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::copy_bundled_realm_files(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); realm::copy_bundled_realm_files(); } @@ -518,7 +518,7 @@ void RealmClass::get_sync_session(ContextType ctx, ObjectType object, ReturnV #endif template -void RealmClass::objects(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::objects(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); SharedRealm realm = *get_internal>(this_object); @@ -528,7 +528,7 @@ void RealmClass::objects(ContextType ctx, ObjectType this_object, size_t argc } template -void RealmClass::object_for_primary_key(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::object_for_primary_key(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 2); SharedRealm realm = *get_internal>(this_object); @@ -544,7 +544,7 @@ void RealmClass::object_for_primary_key(ContextType ctx, ObjectType this_obje } template -void RealmClass::create(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::create(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 2, 3); SharedRealm realm = *get_internal>(this_object); @@ -565,7 +565,7 @@ void RealmClass::create(ContextType ctx, ObjectType this_object, size_t argc, } template -void RealmClass::delete_one(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::delete_one(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); SharedRealm realm = *get_internal>(this_object); @@ -612,7 +612,7 @@ void RealmClass::delete_one(ContextType ctx, ObjectType this_object, size_t a } template -void RealmClass::delete_all(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::delete_all(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); SharedRealm realm = *get_internal>(this_object); @@ -627,7 +627,7 @@ void RealmClass::delete_all(ContextType ctx, ObjectType this_object, size_t a } template -void RealmClass::write(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::write(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); SharedRealm realm = *get_internal>(this_object); @@ -647,7 +647,7 @@ void RealmClass::write(ContextType ctx, ObjectType this_object, size_t argc, } template -void RealmClass::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 2); validated_notification_name(ctx, arguments[0]); @@ -661,7 +661,7 @@ void RealmClass::add_listener(ContextType ctx, ObjectType this_object, size_t } template -void RealmClass::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 2); validated_notification_name(ctx, arguments[0]); @@ -675,7 +675,7 @@ void RealmClass::remove_listener(ContextType ctx, ObjectType this_object, siz } template -void RealmClass::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0, 1); if (argc) { validated_notification_name(ctx, arguments[0]); @@ -689,7 +689,7 @@ void RealmClass::remove_all_listeners(ContextType ctx, ObjectType this_object } template -void RealmClass::close(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmClass::close(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); SharedRealm realm = *get_internal>(this_object); diff --git a/src/js_realm_object.hpp b/src/js_realm_object.hpp index 6bbef168..aa4b34ca 100644 --- a/src/js_realm_object.hpp +++ b/src/js_realm_object.hpp @@ -46,7 +46,7 @@ struct RealmObjectClass : ClassDefinition { static bool set_property(ContextType, ObjectType, const String &, ValueType); static std::vector get_property_names(ContextType, ObjectType); - static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &); + static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType [], ReturnValue &); const std::string name = "RealmObject"; @@ -62,7 +62,7 @@ struct RealmObjectClass : ClassDefinition { }; template -void RealmObjectClass::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void RealmObjectClass::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { return_value.set(get_internal>(this_object)->is_valid()); } diff --git a/src/js_results.hpp b/src/js_results.hpp index cc5caa72..12ab72a5 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -66,15 +66,15 @@ struct ResultsClass : ClassDefinition, CollectionClass< static void get_length(ContextType, ObjectType, ReturnValue &); static void get_index(ContextType, ObjectType, uint32_t, ReturnValue &); - static void snapshot(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void filtered(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &); + static void snapshot(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void filtered(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void sorted(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); // observable - static void add_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void remove_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void remove_all_listeners(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); std::string const name = "Results"; @@ -205,7 +205,7 @@ void ResultsClass::get_index(ContextType ctx, ObjectType object, uint32_t ind } template -void ResultsClass::snapshot(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ResultsClass::snapshot(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); auto results = get_internal>(this_object); @@ -213,7 +213,7 @@ void ResultsClass::snapshot(ContextType ctx, ObjectType this_object, size_t a } template -void ResultsClass::filtered(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ResultsClass::filtered(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count_at_least(argc, 1); auto results = get_internal>(this_object); @@ -221,7 +221,7 @@ void ResultsClass::filtered(ContextType ctx, ObjectType this_object, size_t a } template -void ResultsClass::sorted(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ResultsClass::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1, 2); auto results = get_internal>(this_object); @@ -229,12 +229,12 @@ void ResultsClass::sorted(ContextType ctx, ObjectType this_object, size_t arg } template -void ResultsClass::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ResultsClass::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { return_value.set(get_internal>(this_object)->is_valid()); } template -void ResultsClass::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ResultsClass::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); auto results = get_internal>(this_object); @@ -255,7 +255,7 @@ void ResultsClass::add_listener(ContextType ctx, ObjectType this_object, size } template -void ResultsClass::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ResultsClass::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); auto results = get_internal>(this_object); @@ -275,7 +275,7 @@ void ResultsClass::remove_listener(ContextType ctx, ObjectType this_object, s } template -void ResultsClass::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void ResultsClass::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 0); auto results = get_internal>(this_object); diff --git a/src/js_sync.hpp b/src/js_sync.hpp index bb30192f..d1c6c852 100644 --- a/src/js_sync.hpp +++ b/src/js_sync.hpp @@ -70,7 +70,7 @@ public: {"isAdmin", {wrap, nullptr}}, }; - static void create_user(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void create_user(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); MethodMap const static_methods = { {"createUser", wrap} @@ -84,8 +84,8 @@ public: {"all", {wrap, nullptr}}, }; - static void logout(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void session_for_on_disk_path(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void logout(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void session_for_on_disk_path(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); MethodMap const methods = { {"logout", wrap}, @@ -117,7 +117,7 @@ void UserClass::is_admin(ContextType ctx, ObjectType object, ReturnValue &ret } template -void UserClass::create_user(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void UserClass::create_user(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 3, 4); SharedUser *user = new SharedUser(SyncManager::shared().get_user( Value::validated_to_string(ctx, arguments[1]), @@ -161,8 +161,8 @@ void UserClass::current_user(ContextType ctx, ObjectType object, ReturnValue */ template -void UserClass::logout(ContextType ctx, ObjectType object, size_t, const ValueType[], ReturnValue &) { - get_internal>(object)->get()->log_out(); +void UserClass::logout(ContextType ctx, FunctionType, ObjectType this_object, size_t, const ValueType[], ReturnValue &) { + get_internal>(this_object)->get()->log_out(); } template @@ -186,8 +186,8 @@ public: static void get_url(ContextType, ObjectType, ReturnValue &); static void get_state(ContextType, ObjectType, ReturnValue &); - static void simulate_error(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void refresh_access_token(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void simulate_error(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void refresh_access_token(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); PropertyMap const properties = { {"config", {wrap, nullptr}}, @@ -203,8 +203,44 @@ public: }; template -void UserClass::session_for_on_disk_path(ContextType ctx, ObjectType object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - auto user = *get_internal>(object); +class SyncSessionErrorHandlerFunctor { +public: + SyncSessionErrorHandlerFunctor(typename T::Context ctx, typename T::Function error_func) + : m_ctx(Context::get_global_context(ctx)) + , m_func(ctx, error_func) + { } + + typename T::Function func() const { return m_func; } + + void operator()(std::shared_ptr session, SyncError error) { + HANDLESCOPE + + auto error_object = Object::create_empty(m_ctx); + Object::set_property(m_ctx, error_object, "message", Value::from_string(m_ctx, error.message)); + Object::set_property(m_ctx, error_object, "isFatal", Value::from_boolean(m_ctx, error.is_fatal)); + Object::set_property(m_ctx, error_object, "category", Value::from_string(m_ctx, error.error_code.category().name())); + Object::set_property(m_ctx, error_object, "code", Value::from_number(m_ctx, error.error_code.value())); + + auto user_info = Object::create_empty(m_ctx); + for (auto& kvp : error.user_info) { + Object::set_property(m_ctx, user_info, kvp.first, Value::from_string(m_ctx, kvp.second)); + } + Object::set_property(m_ctx, error_object, "userInfo", user_info); + + typename T::Value arguments[2]; + arguments[0] = create_object>(m_ctx, new WeakSession(session)); + arguments[1] = error_object; + + Function::call(m_ctx, m_func, 2, arguments); + } +private: + const Protected m_ctx; + const Protected m_func; +}; + +template +void UserClass::session_for_on_disk_path(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + auto user = *get_internal>(this_object); if (auto session = user->session_for_on_disk_path(Value::validated_to_string(ctx, arguments[0]))) { return_value.set(create_object>(ctx, new WeakSession(session))); } else { @@ -218,6 +254,10 @@ void SessionClass::get_config(ContextType ctx, ObjectType object, ReturnValue ObjectType config = Object::create_empty(ctx); Object::set_property(ctx, config, "user", create_object>(ctx, new SharedUser(session->config().user))); Object::set_property(ctx, config, "url", Value::from_string(ctx, session->config().realm_url)); + if (auto* dispatcher = session->config().error_handler.template target>()) { + auto& handler = *dispatcher->func().template target>(); + Object::set_property(ctx, config, "error", handler.func()); + } return_value.set(config); } else { return_value.set_undefined(); @@ -263,10 +303,10 @@ void SessionClass::get_state(ContextType ctx, ObjectType object, ReturnValue } template -void SessionClass::simulate_error(ContextType ctx, ObjectType object, size_t argc, const ValueType arguments[], ReturnValue &) { +void SessionClass::simulate_error(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &) { validate_argument_count(argc, 2); - if (auto session = get_internal>(object)->lock()) { + if (auto session = get_internal>(this_object)->lock()) { SyncError error; error.error_code = std::error_code(Value::validated_to_number(ctx, arguments[0]), realm::sync::protocol_error_category()); error.message = Value::validated_to_string(ctx, arguments[1]); @@ -275,10 +315,10 @@ void SessionClass::simulate_error(ContextType ctx, ObjectType object, size_t } template -void SessionClass::refresh_access_token(ContextType ctx, ObjectType object, size_t argc, const ValueType arguments[], ReturnValue &) { +void SessionClass::refresh_access_token(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &) { validate_argument_count(argc, 2); - if (auto session = get_internal>(object)->lock()) { + if (auto session = get_internal>(this_object)->lock()) { std::string access_token = Value::validated_to_string(ctx, arguments[0], "accessToken"); std::string realm_url = Value::validated_to_string(ctx, arguments[1], "realmUrl"); session->refresh_access_token(std::move(access_token), std::move(realm_url)); @@ -304,8 +344,8 @@ public: static FunctionType create_constructor(ContextType); - static void set_sync_log_level(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void set_verify_servers_ssl_certificate(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void set_sync_log_level(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void set_verify_servers_ssl_certificate(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); // private static void populate_sync_config(ContextType, ObjectType realm_constructor, ObjectType config_object, Realm::Config&); @@ -335,7 +375,7 @@ inline typename T::Function SyncClass::create_constructor(ContextType ctx) { } template -void SyncClass::set_sync_log_level(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void SyncClass::set_sync_log_level(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); std::string log_level = Value::validated_to_string(ctx, arguments[0]); std::istringstream in(log_level); // Throws @@ -349,7 +389,7 @@ void SyncClass::set_sync_log_level(ContextType ctx, ObjectType this_object, s } template -void SyncClass::set_verify_servers_ssl_certificate(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { +void SyncClass::set_verify_servers_ssl_certificate(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); bool verify_servers_ssl_certificate = Value::validated_to_boolean(ctx, arguments[0]); realm::SyncManager::shared().set_client_should_validate_ssl(verify_servers_ssl_certificate); @@ -393,28 +433,7 @@ void SyncClass::populate_sync_config(ContextType ctx, ObjectType realm_constr std::function error_handler; ValueType error_func = Object::get_property(ctx, sync_config_object, "error"); if (!Value::is_undefined(ctx, error_func)) { - Protected protected_error_func(ctx, Value::validated_to_function(ctx, error_func)); - error_handler = EventLoopDispatcher([=](auto session, auto error) { - HANDLESCOPE - - ObjectType error_object = Object::create_empty(protected_ctx); - Object::set_property(protected_ctx, error_object, "message", Value::from_string(protected_ctx, error.message)); - Object::set_property(protected_ctx, error_object, "isFatal", Value::from_boolean(protected_ctx, error.is_fatal)); - Object::set_property(protected_ctx, error_object, "category", Value::from_string(protected_ctx, error.error_code.category().name())); - Object::set_property(protected_ctx, error_object, "code", Value::from_number(protected_ctx, error.error_code.value())); - - ObjectType user_info = Object::create_empty(protected_ctx); - for (auto& kvp : error.user_info) { - Object::set_property(protected_ctx, user_info, kvp.first, Value::from_string(protected_ctx, kvp.second)); - } - Object::set_property(protected_ctx, error_object, "userInfo", user_info); - - ValueType arguments[2]; - arguments[0] = create_object>(protected_ctx, new WeakSession(session)); - arguments[1] = error_object; - - Function::call(protected_ctx, protected_error_func, 2, arguments); - }); + error_handler = EventLoopDispatcher(SyncSessionErrorHandlerFunctor(ctx, Value::validated_to_function(ctx, error_func))); } ObjectType user = Object::validated_get_object(ctx, sync_config_object, "user"); diff --git a/src/jsc/jsc_class.hpp b/src/jsc/jsc_class.hpp index 31e7e28b..6e6f7618 100644 --- a/src/jsc/jsc_class.hpp +++ b/src/jsc/jsc_class.hpp @@ -360,7 +360,7 @@ template JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) { jsc::ReturnValue return_value(ctx); try { - F(ctx, this_object, argc, arguments, return_value); + F(ctx, function, this_object, argc, arguments, return_value); return return_value; } catch (std::exception &e) { diff --git a/src/node/node_class.hpp b/src/node/node_class.hpp index fa10fc7c..5fa0be8a 100644 --- a/src/node/node_class.hpp +++ b/src/node/node_class.hpp @@ -301,7 +301,7 @@ void wrap(const v8::FunctionCallbackInfo& info) { auto arguments = node::get_arguments(info); try { - F(isolate, info.This(), arguments.size(), arguments.data(), return_value); + F(isolate, info.Callee(), info.This(), arguments.size(), arguments.data(), return_value); } catch (std::exception &e) { Nan::ThrowError(node::Exception::value(isolate, e)); diff --git a/src/rpc.cpp b/src/rpc.cpp index 45570ace..bec700d5 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -231,6 +231,7 @@ RPCServer::RPCServer() { } m_callbacks.clear(); + m_callback_ids.clear(); JSGarbageCollect(m_context); js::clear_test_state(); @@ -249,22 +250,24 @@ RPCServer::~RPCServer() { JSGlobalContextRelease(m_context); } -void RPCServer::run_callback(JSContextRef ctx, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], jsc::ReturnValue &return_value) { +void RPCServer::run_callback(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], jsc::ReturnValue &return_value) { RPCServer* server = get_rpc_server(JSContextGetGlobalContext(ctx)); if (!server) { return; } // The first argument was curried to be the callback id. - RPCObjectID callback_id = jsc::Value::to_number(ctx, arguments[0]); - JSObjectRef arguments_array = jsc::Object::create_array(ctx, uint32_t(argc - 1), argc == 1 ? nullptr : arguments + 1); + RPCObjectID callback_id = server->m_callback_ids[function]; + JSObjectRef arguments_array = jsc::Object::create_array(ctx, uint32_t(argc), arguments); json arguments_json = server->serialize_json_value(arguments_array); + json this_json = server->serialize_json_value(this_object); // The next task on the stack will instruct the JS to run this callback. // This captures references since it will be executed before exiting this function. server->m_worker.add_task([&]() -> json { return { {"callback", callback_id}, + {"this", this_json}, {"arguments", arguments_json}, }; }); @@ -422,6 +425,15 @@ json RPCServer::serialize_json_value(JSValueRef js_value) { {"value", jsc::Value::to_number(m_context, js_object)}, }; } + else if (jsc::Value::is_function(m_context, js_object)) { + auto it = m_callback_ids.find(js_object); + if (it != m_callback_ids.end()) { + return { + {"type", RealmObjectTypesFunction}, + {"value", it->second} + }; + } + } else { // Serialize this JS object as a plain object since it doesn't match any known types above. std::vector js_keys = jsc::Object::get_property_names(m_context, js_object); @@ -474,13 +486,8 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) { if (!m_callbacks.count(callback_id)) { JSObjectRef callback = JSObjectMakeFunctionWithCallback(m_context, nullptr, js::wrap); - - // Curry the first argument to be the callback id. - JSValueRef bind_args[2] = {jsc::Value::from_null(m_context), jsc::Value::from_number(m_context, callback_id)}; - JSValueRef bound_callback = jsc::Object::call_method(m_context, callback, "bind", 2, bind_args); - - callback = jsc::Value::to_function(m_context, bound_callback); m_callbacks.emplace(callback_id, js::Protected(m_context, callback)); + m_callback_ids.emplace(callback, callback_id); } return m_callbacks.at(callback_id); diff --git a/src/rpc.hpp b/src/rpc.hpp index 4fe333b7..ca253e0a 100644 --- a/src/rpc.hpp +++ b/src/rpc.hpp @@ -67,11 +67,15 @@ class RPCServer { std::map m_requests; std::map> m_objects; std::map> m_callbacks; + // The key here is the same as the value in m_callbacks. We use the raw pointer as a key here, + // because protecting the value in m_callbacks pins the function object and prevents it from being moved + // by the garbage collector upon compaction. + std::map m_callback_ids; ConcurrentDeque m_callback_results; RPCObjectID m_session_id; RPCWorker m_worker; - static void run_callback(JSContextRef, JSObjectRef, size_t, const JSValueRef[], jsc::ReturnValue &); + static void run_callback(JSContextRef, JSObjectRef, JSObjectRef, size_t, const JSValueRef[], jsc::ReturnValue &); RPCObjectID store_object(JSObjectRef object); diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index c95b6c35..05036e28 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -86,6 +86,7 @@ module.exports = { let realm = new Realm(config); let session = realm.syncSession; + TestCase.assertEqual(session.config.error, config.sync.error); session._simulateError(123, 'simulated error'); return wait(100).then(() => {