diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bb8ffca..61486ae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ Old files can still be opened and files open in read-only mode will not be modif * The SyncConfig now gets two more optional parameters, `validate_ssl` and `ssl_trust_certificate_path`. ### Enhancements -* None +* Add Realm open async API support. ### Bug fixes * None diff --git a/lib/extensions.js b/lib/extensions.js index 727b910a..999deeb9 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -40,6 +40,34 @@ module.exports = function(realmConstructor) { setConstructorOnPrototype(realmConstructor.Results); setConstructorOnPrototype(realmConstructor.Object); + //Add async open API + Object.defineProperties(realmConstructor, getOwnPropertyDescriptors({ + open(config) { + return new Promise((resolve, reject) => { + const realm = new Realm(config); + realm.wait((error) => { + if (error) { + reject(new Error("Cannot asynchronously open synced Realm, because the associated session previously experienced a fatal error")); + } + + resolve(realm); + }); + }); + }, + + openAsync(config, callback) { + const realm = new Realm(config); + realm.wait((error) => { + if (error) { + callback(new Error("Cannot asynchronously open synced Realm, because the associated session previously experienced a fatal error")); + return; + } + + callback(null, realm); + }); + } + })); + // Add sync methods if (realmConstructor.Sync) { let userMethods = require('./user-methods'); diff --git a/src/js_realm.hpp b/src/js_realm.hpp index b96e1eec..f76f5876 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -164,6 +164,8 @@ class RealmClass : public ClassDefinition> { public: using ObjectDefaultsMap = typename Schema::ObjectDefaultsMap; using ConstructorMap = typename Schema::ConstructorMap; + + using WaitHandler = void(); static FunctionType create_constructor(ContextType); @@ -175,6 +177,7 @@ public: 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 wait_for_download_completion(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 &); @@ -220,6 +223,7 @@ public: {"deleteAll", wrap}, {"write", wrap}, {"addListener", wrap}, + {"wait", wrap}, {"removeListener", wrap}, {"removeAllListeners", wrap}, {"close", wrap}, @@ -537,6 +541,40 @@ void RealmClass::get_sync_session(ContextType ctx, ObjectType object, ReturnV } } + +template +void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + auto callback = Value::validated_to_function(ctx, arguments[0]); + + SharedRealm realm = *get_internal>(this_object); + + Protected protected_callback(ctx, callback); + Protected protected_this(ctx, this_object); + Protected protected_ctx(Context::get_global_context(ctx)); + + EventLoopDispatcher wait_handler([=]() { + HANDLESCOPE + //Function::call(protected_ctx, protected_callback, protected_this, 0, nullptr); + Function::callback(protected_ctx, protected_callback, protected_this, 0, nullptr); + + //::node::MakeCallback(context->GetIsolate(), context->Global(), callback, 1, arguments); + //::v8::Isolate::GetCurrent()->RunMicrotasks(); + }); + std::function waitFunc = std::move(wait_handler); + + if (std::shared_ptr session = SyncManager::shared().get_existing_active_session(realm->config().path)) { + session->wait_for_download_completion([=](std::error_code error_code) { + waitFunc(); + }); + return; + } + + ValueType callback_arguments[1]; + callback_arguments[0] = Value::from_null(ctx); + Function::call(ctx, callback, this_object, 1, callback_arguments); +} + #endif template diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index c0f88575..46ede801 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -50,17 +50,25 @@ module.exports = { testProperties() { return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { - return new Promise((resolve, _reject) => { + return new Promise((resolve, reject) => { + const accessTokenRefreshed = this; + let successCounter = 0; + function checkSuccess() { + successCounter++; + if (successCounter == 2) { + resolve(); + } + } function postTokenRefreshChecks(sender, error) { try { TestCase.assertEqual(error, accessTokenRefreshed); TestCase.assertEqual(sender.url, `realm://localhost:9080/${user.identity}/myrealm`); - resolve(); + checkSuccess(); } catch (e) { - _reject(e) + reject(e) } }; @@ -70,14 +78,106 @@ module.exports = { const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm', error: postTokenRefreshChecks } }; const realm = new Realm(config); const session = realm.syncSession; - - TestCase.assertInstanceOf(session, Realm.Sync.Session); TestCase.assertEqual(session.user.identity, user.identity); TestCase.assertEqual(session.config.url, config.sync.url); TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); TestCase.assertUndefined(session.url); TestCase.assertEqual(session.state, 'active'); + checkSuccess(); + }); + }); + }, + + testPropertiesWithOpen() { + return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { + return new Promise((resolve, reject) => { + + const accessTokenRefreshed = this; + let successCounter = 0; + + function checkSuccess() { + successCounter++; + if (successCounter == 2) { + resolve(); + } + } + + function postTokenRefreshChecks(sender, error) { + try { + TestCase.assertEqual(error, accessTokenRefreshed); + TestCase.assertEqual(sender.url, `realm://localhost:9080/${user.identity}/myrealm`); + checkSuccess(); + } + catch (e) { + reject(e) + } + }; + + // Let the error handler trigger our checks when the access token was refreshed. + postTokenRefreshChecks._notifyOnAccessTokenRefreshed = accessTokenRefreshed; + + const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm', error: postTokenRefreshChecks } }; + Realm.open(config) + .then(realm => { + const session = realm.syncSession; + TestCase.assertInstanceOf(session, Realm.Sync.Session); + TestCase.assertEqual(session.user.identity, user.identity); + TestCase.assertEqual(session.config.url, config.sync.url); + TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); + TestCase.assertEqual(session.state, 'active'); + checkSuccess(); + }) + .catch(e => reject(e)); + }); + }); + }, + + + testPropertiesWithOpenAsync() { + return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { + return new Promise((resolve, reject) => { + + const accessTokenRefreshed = this; + let successCounter = 0; + + function checkSuccess() { + successCounter++; + if (successCounter == 2) { + resolve(); + } + } + + function postTokenRefreshChecks(sender, error) { + try { + TestCase.assertEqual(error, accessTokenRefreshed); + TestCase.assertEqual(sender.url, `realm://localhost:9080/${user.identity}/myrealm`); + checkSuccess(); + } + catch (e) { + reject(e) + } + }; + + // Let the error handler trigger our checks when the access token was refreshed. + postTokenRefreshChecks._notifyOnAccessTokenRefreshed = accessTokenRefreshed; + + const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm', error: postTokenRefreshChecks } }; + Realm.openAsync(config, (error, realm) => { + if (error) { + reject(error); + return; + } + + const session = realm.syncSession; + TestCase.assertInstanceOf(session, Realm.Sync.Session); + TestCase.assertEqual(session.user.identity, user.identity); + TestCase.assertEqual(session.config.url, config.sync.url); + TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); + TestCase.assertEqual(session.state, 'active'); + + checkSuccess(); + }); }); }); }, diff --git a/tests/spec/unit_tests.js b/tests/spec/unit_tests.js index d84d349c..2826381b 100644 --- a/tests/spec/unit_tests.js +++ b/tests/spec/unit_tests.js @@ -28,6 +28,10 @@ const Realm = require('realm'); const RealmTests = require('../js'); jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; +const isDebuggerAttached = typeof v8debug === 'object'; +if (isDebuggerAttached) { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000000; +} // Create this method with appropriate implementation for Node testing. Realm.copyBundledRealmFiles = function() {