diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a7b9e68..1631daeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ X.Y.Z-rc Release notes ### Enhancements * Support migration from Realms sync 1.0 to sync 2.0 versions +* Handling of the situation when the client has to reset the Realm due to diverging histories (#795). ### Bug fixes * None diff --git a/docs/sync.js b/docs/sync.js index 265679c0..5ef888ea 100644 --- a/docs/sync.js +++ b/docs/sync.js @@ -62,7 +62,22 @@ class Sync { */ static setLogLevel(log_level) {} - + /** + * Initiate a client reset. The Realm must be closed prior to the reset. + * @param {string} [path] - The Realm to reset. + * Throws error if reset is not possible. + * @example + * { + * const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm' } }; + * config.sync.error = (sender, error) => { + * if (error.code === 7) { // 7 -> client reset + * Realm.Sync.initiateClientReset(original_path); + * // copy required objects from Realm at error.config.path + * } + * } + * } + */ + static initiateClientReset(path) {} } /** diff --git a/lib/extensions.js b/lib/extensions.js index e6033fc9..e89b9f53 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -79,7 +79,7 @@ module.exports = function(realmConstructor) { openAsync(config, callback, progressCallback) { const message = "Realm.openAsync is now deprecated in favor of Realm.open. This function will be removed in future versions."; (console.warn || console.log).call(console, message); - + realmConstructor._waitForDownload(config, (syncSession) => { if (progressCallback) { diff --git a/lib/index.d.ts b/lib/index.d.ts index 6af3823d..0d933de4 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -415,6 +415,7 @@ declare namespace Realm.Sync { function removeAllListeners(name?: string): void; function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): void; function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void; + function initiateClientReset(path: string): void; function setFeatureToken(token: string): void; /** diff --git a/src/js_sync.hpp b/src/js_sync.hpp index 8c7b4adf..6c08e9ee 100644 --- a/src/js_sync.hpp +++ b/src/js_sync.hpp @@ -229,10 +229,21 @@ public: HANDLESCOPE auto error_object = Object::create_empty(m_ctx); + + auto error_code = error.error_code.value(); + if (error.is_client_reset_requested()) { + error_code = 7; // FIXME: define a proper constant + + auto config_object = Object::create_empty(m_ctx); + Object::set_property(m_ctx, config_object, "path", Value::from_string(m_ctx, error.user_info[SyncError::c_recovery_file_path_key])); + Object::set_property(m_ctx, config_object, "readOnly", Value::from_boolean(m_ctx, true)); + Object::set_property(m_ctx, error_object, "config", config_object); + } + 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())); + Object::set_property(m_ctx, error_object, "code", Value::from_number(m_ctx, error_code)); auto user_info = Object::create_empty(m_ctx); for (auto& kvp : error.user_info) { @@ -478,9 +489,7 @@ void SessionClass::add_progress_notification(ContextType ctx, FunctionType, O progressFunc = std::move(progress_handler); - auto registrationToken = session->register_progress_notifier(std::move(progressFunc), notifierType, is_streaming); - auto syncSession = create_object>(ctx, new WeakSession(session)); PropertyAttributes attributes = ReadOnly | DontEnum | DontDelete; Object::set_property(ctx, callback_function, "_syncSession", syncSession, attributes); @@ -525,6 +534,7 @@ public: static FunctionType create_constructor(ContextType); static void set_sync_log_level(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void initiate_client_reset(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); // private static std::function session_bind_callback(ContextType ctx, ObjectType sync_constructor); @@ -535,6 +545,7 @@ public: MethodMap const static_methods = { {"setLogLevel", wrap}, + {"initiateClientReset", wrap}, }; }; @@ -549,6 +560,15 @@ inline typename T::Function SyncClass::create_constructor(ContextType ctx) { return sync_constructor; } +template +void SyncClass::initiate_client_reset(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue & return_value) { + validate_argument_count(argc, 1); + std::string path = Value::validated_to_string(ctx, arguments[0]); + if (!SyncManager::shared().immediately_run_file_actions(std::string(path))) { + throw std::runtime_error(util::format("Realm was not configured correctly. Client Reset could not be run for Realm at: %1", path)); + } +} + template 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); diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 1e13a6b4..7e4e0f8a 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -780,4 +780,38 @@ module.exports = { }); }); }, + + testClientReset() { + // FIXME: try to enable for React Native + if (!isNodeProccess) { + return Promise.resolve(); + } + + return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then(user => { + return new Promise((resolve, _reject) => { + var realm; + const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm' } }; + config.sync.error = (sender, error) => { + try { + TestCase.assertEqual(error.code, 7); // 7 -> client reset + TestCase.assertDefined(error.config); + TestCase.assertNotEqual(error.config.path, ''); + const original_path = realm.path; + realm.close(); + Realm.Sync.initiateClientReset(original_path); + // copy required objects from Realm at error.config.path + resolve(); + } + catch (e) { + _reject(e); + } + }; + realm = new Realm(config); + const session = realm.syncSession; + + TestCase.assertEqual(session.config.error, config.sync.error); + session._simulateError(211, 'ClientReset'); // 211 -> divering histories + }); + }); + } }