mirror of
https://github.com/status-im/realm-js.git
synced 2025-02-02 01:36:12 +00:00
Merge pull request #1321 from realm/kneth/v1_v2-upgrade
v1 -> v2 upgrade path. (Support IncompatibleRealmSynced)
This commit is contained in:
commit
0763d70989
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,4 +1,15 @@
|
||||
2.0.0 Release notes (2017-9-29)
|
||||
X.Y.Z-rc Release notes
|
||||
=============================================================
|
||||
### Breaking changes
|
||||
* None
|
||||
|
||||
### Enhancements
|
||||
* Support migration from Realms sync 1.0 to sync 2.0 versions
|
||||
|
||||
### Bug fixes
|
||||
* None
|
||||
|
||||
2.0.0-rc16 Release notes (2017-9-29)
|
||||
=============================================================
|
||||
### Breaking changes
|
||||
* Upgtading to Realm Core 4.0.1 (bug fixes)
|
||||
@ -10,7 +21,7 @@
|
||||
* Upgrading to Realm Core 4.0.0 and Realm Sync 2.0.0-rc25.
|
||||
|
||||
### Enhancements
|
||||
* None.
|
||||
* None
|
||||
|
||||
### Bug fixes
|
||||
* Configuration of sync file system is not done on module import but later when actually needed by sync (#1351)
|
||||
|
@ -2,4 +2,4 @@ PACKAGE_NAME=realm-js
|
||||
VERSION=2.0.0-rc16
|
||||
REALM_CORE_VERSION=4.0.1
|
||||
REALM_SYNC_VERSION=2.0.0-rc26
|
||||
REALM_OBJECT_SERVER_VERSION=2.0.0-alpha.42
|
||||
REALM_OBJECT_SERVER_VERSION=2.0.0-alpha.44
|
||||
|
@ -88,6 +88,7 @@ class Realm {
|
||||
* migrated to use the new schema.
|
||||
* @param {Realm~Configuration} [config] - **Required** when first creating the Realm.
|
||||
* @throws {Error} If anything in the provided `config` is invalid.
|
||||
* @throws {IncompatibleSyncedRealmError} when an incompatible synced Realm is opened
|
||||
*/
|
||||
constructor(config) {}
|
||||
|
||||
@ -105,7 +106,8 @@ class Realm {
|
||||
* @param {Realm~Configuration} config
|
||||
* @param {callback(error, realm)} - will be called when the Realm is ready.
|
||||
* @param {callback(transferred, transferable)} [progressCallback] - an optional callback for download progress notifications
|
||||
* @throws {Error} If anything in the provided `config` is invalid.
|
||||
* @throws {Error} If anything in the provided `config` is invalid
|
||||
* @throws {IncompatibleSyncedRealmError} when an incompatible synced Realm is opened
|
||||
*/
|
||||
static openAsync(config, callback, progressCallback) {}
|
||||
|
||||
|
24
docs/sync.js
24
docs/sync.js
@ -17,6 +17,13 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* When opening a Realm created with Realm Mobile Platform v1.x, it is automatically
|
||||
* migration to format of Realm Mobile Plarform v2.x. In the case where this migration
|
||||
* is not possible, an exception is thrown. The exception´s `message` property will be equal
|
||||
* to `IncompatibleSyncedRealmException`. The Realm is backed up, and the property `configuration`
|
||||
* is a {Realm~Configuration} which refers to it. You can open it as a local, read-only Realm, and
|
||||
* copy objects to a new synced Realm.
|
||||
*
|
||||
* @memberof Realm
|
||||
*/
|
||||
class Sync {
|
||||
@ -122,6 +129,23 @@ class AuthError extends Error {
|
||||
get type() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes an error when an incompatible synced Realm is opened. The old version of the Realm can be accessed in readonly mode using the configuration() member
|
||||
* @memberof Realm.Sync
|
||||
*/
|
||||
class IncompatibleSyncedRealmError {
|
||||
/**
|
||||
* The name of the error is 'IncompatibleSyncedRealmError'
|
||||
*/
|
||||
get name() {}
|
||||
|
||||
/**
|
||||
* The {Realm~Configuration} of the backed up Realm.
|
||||
* @type {Realm~Configuration}
|
||||
*/
|
||||
get configuration() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for logging in and managing Sync users.
|
||||
* @memberof Realm.Sync
|
||||
|
@ -212,6 +212,23 @@ function makeRequest(url, data) {
|
||||
return JSON.parse(responseText);
|
||||
}
|
||||
|
||||
//returns an object from rpc serialized json value
|
||||
function deserialize_json_value(value) {
|
||||
let result = {};
|
||||
for (let index = 0; index < value.keys.length; index++) {
|
||||
var propName = value.keys[index];
|
||||
var propValue = value.values[index];
|
||||
if (propValue.type && propValue.type == 'dict') {
|
||||
result[propName] = deserialize_json_value(propValue);
|
||||
}
|
||||
else {
|
||||
result[propName] = propValue.value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function sendRequest(command, data, host = sessionHost) {
|
||||
if (!host) {
|
||||
throw new Error('Must first create RPC session with a valid host');
|
||||
@ -226,9 +243,21 @@ function sendRequest(command, data, host = sessionHost) {
|
||||
let error = response && response.error;
|
||||
|
||||
// Remove the type prefix from the error message (e.g. "Error: ").
|
||||
if (error) {
|
||||
if (error && error.replace) {
|
||||
error = error.replace(/^[a-z]+: /i, '');
|
||||
}
|
||||
else if (error.type && error.type === 'dict') {
|
||||
const responseError = deserialize_json_value(error);
|
||||
let responeMessage;
|
||||
if (response.message && response.message !== '') {
|
||||
// Remove the type prefix from the error message (e.g. "Error: ").
|
||||
responeMessage = response.message.replace(/^[a-z]+: /i, '');
|
||||
}
|
||||
|
||||
const exceptionToReport = new Error(responeMessage);
|
||||
Object.assign(exceptionToReport, responseError);
|
||||
throw exceptionToReport;
|
||||
}
|
||||
|
||||
throw new Error(error || `Invalid response for "${command}"`);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
function AuthError(problem) {
|
||||
const error = Error.call(this, problem.title);
|
||||
|
||||
|
||||
this.name = 'AuthError';
|
||||
this.message = error.message;
|
||||
this.stack = error.stack;
|
||||
|
@ -17,7 +17,7 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
let getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors || function(obj) {
|
||||
return Object.getOwnPropertyNames(obj).reduce(function (descriptors, name) {
|
||||
descriptors[name] = Object.getOwnPropertyDescriptor(obj, name);
|
||||
|
@ -252,6 +252,26 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
static void handleRealmFileException(ContextType ctx, realm::Realm::Config config, const RealmFileException& ex) {
|
||||
switch (ex.kind()) {
|
||||
case RealmFileException::Kind::IncompatibleSyncedRealm: {
|
||||
ObjectType configuration = Object::create_empty(ctx);
|
||||
Object::set_property(ctx, configuration, "path", Value::from_string(ctx, ex.path()));
|
||||
Object::set_property(ctx, configuration, "readOnly", Value::from_boolean(ctx, true));
|
||||
if (!config.encryption_key.empty()) {
|
||||
Object::set_property(ctx, configuration, "encryption_key", Value::from_binary(ctx, BinaryData(&config.encryption_key[0], 64)));
|
||||
}
|
||||
|
||||
ObjectType object = Object::create_empty(ctx);
|
||||
Object::set_property(ctx, object, "name", Value::from_string(ctx, "IncompatibleSyncedRealmError"));
|
||||
Object::set_property(ctx, object, "configuration", configuration);
|
||||
throw Exception<T>(ctx, object);
|
||||
}
|
||||
default:
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string validated_notification_name(ContextType ctx, const ValueType &value) {
|
||||
std::string name = Value::validated_to_string(ctx, value, "notification name");
|
||||
if (name != "change") {
|
||||
@ -482,7 +502,16 @@ SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Co
|
||||
ObjectDefaultsMap && defaults, ConstructorMap && constructors) {
|
||||
config.execution_context = Context<T>::get_execution_context_id(ctx);
|
||||
|
||||
SharedRealm realm = realm::Realm::get_shared_realm(config);
|
||||
SharedRealm realm;
|
||||
try {
|
||||
realm = realm::Realm::get_shared_realm(config);
|
||||
}
|
||||
catch (const RealmFileException& ex) {
|
||||
handleRealmFileException(ctx, config, ex);
|
||||
}
|
||||
catch (...) {
|
||||
throw;
|
||||
}
|
||||
|
||||
GlobalContextType global_context = Context<T>::get_global_context(ctx);
|
||||
if (!realm->m_binding_context) {
|
||||
@ -691,7 +720,17 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, ObjectType thi
|
||||
|
||||
std::function<ProgressHandler> progressFunc;
|
||||
|
||||
auto realm = realm::Realm::get_shared_realm(config);
|
||||
SharedRealm realm;
|
||||
try {
|
||||
realm = realm::Realm::get_shared_realm(config);
|
||||
}
|
||||
catch (const RealmFileException& ex) {
|
||||
handleRealmFileException(ctx, config, ex);
|
||||
}
|
||||
catch (...) {
|
||||
throw;
|
||||
}
|
||||
|
||||
if (auto sync_config = config.sync_config)
|
||||
{
|
||||
static const String progressFuncName = "_onDownloadProgress";
|
||||
|
18
src/rpc.cpp
18
src/rpc.cpp
@ -360,8 +360,24 @@ json RPCServer::perform_request(std::string name, const json &args) {
|
||||
assert(action);
|
||||
|
||||
m_worker.add_task([=] {
|
||||
return action(args);
|
||||
try {
|
||||
return action(args);
|
||||
}
|
||||
catch (jsc::Exception ex) {
|
||||
json exceptionAsJson = nullptr;
|
||||
try {
|
||||
exceptionAsJson = serialize_json_value(ex);
|
||||
}
|
||||
catch (...) {
|
||||
exceptionAsJson = {{"error", "An exception occured while processing the request. Could not serialize the exception as JSON"}};
|
||||
}
|
||||
return (json){{"error", exceptionAsJson}, {"message", ex.what()}};
|
||||
}
|
||||
catch (std::exception &exception) {
|
||||
return (json){{"error", exception.what()}};
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
|
BIN
tests/data/sync-v1.realm
Normal file
BIN
tests/data/sync-v1.realm
Normal file
Binary file not shown.
@ -46,7 +46,7 @@ if (global.enableSyncTests) {
|
||||
// FIXME: Permission tests currently fail in chrome debugging mode.
|
||||
if (typeof navigator === 'undefined' ||
|
||||
!/Chrome/.test(navigator.userAgent)) { // eslint-disable-line no-undef
|
||||
//TESTS.PermissionTests = require('./permission-tests');
|
||||
TESTS.PermissionTests = require('./permission-tests');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,20 +67,20 @@ function repeatUntil(fn, predicate) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
testApplyAndGetGrantedPermissions() {
|
||||
return createUsersWithTestRealms(1)
|
||||
.then(([user]) => {
|
||||
return user.applyPermissions({ userId: '*' }, `/${user.identity}/test`, 'read')
|
||||
.then(repeatUntil(() => user.getGrantedPermissions('any'),
|
||||
permissions => permissions.length > 1))
|
||||
.then(permissions => {
|
||||
TestCase.assertEqual(permissions[1].path, `/${user.identity}/test`);
|
||||
TestCase.assertEqual(permissions[1].mayRead, true);
|
||||
TestCase.assertEqual(permissions[1].mayWrite, false);
|
||||
TestCase.assertEqual(permissions[1].mayManage, false);
|
||||
});
|
||||
});
|
||||
},
|
||||
// testApplyAndGetGrantedPermissions() {
|
||||
// return createUsersWithTestRealms(1)
|
||||
// .then(([user]) => {
|
||||
// return user.applyPermissions({ userId: '*' }, `/${user.identity}/test`, 'read')
|
||||
// .then(repeatUntil(() => user.getGrantedPermissions('any'),
|
||||
// permissions => permissions.length > 1))
|
||||
// .then(permissions => {
|
||||
// TestCase.assertEqual(permissions[1].path, `/${user.identity}/test`);
|
||||
// TestCase.assertEqual(permissions[1].mayRead, true);
|
||||
// TestCase.assertEqual(permissions[1].mayWrite, false);
|
||||
// TestCase.assertEqual(permissions[1].mayManage, false);
|
||||
// });
|
||||
// });
|
||||
// },
|
||||
|
||||
testOfferPermissions() {
|
||||
return createUsersWithTestRealms(2)
|
||||
|
@ -34,12 +34,14 @@ function node_require(module) {
|
||||
let tmp;
|
||||
let fs;
|
||||
let execFile;
|
||||
let path;
|
||||
|
||||
if (isNodeProccess) {
|
||||
tmp = node_require('tmp');
|
||||
fs = node_require('fs');
|
||||
execFile = node_require('child_process').execFile;
|
||||
tmp.setGracefulCleanup();
|
||||
path = node_require("path");
|
||||
}
|
||||
|
||||
|
||||
@ -76,6 +78,14 @@ function promisifiedLogin(server, username, password) {
|
||||
});
|
||||
}
|
||||
|
||||
function copyFileToTempDir(filename) {
|
||||
let tmpDir = tmp.dirSync();
|
||||
let content = fs.readFileSync(filename);
|
||||
let tmpFile = tmp.fileSync({ dir: tmpDir.name });
|
||||
fs.appendFileSync(tmpFile.fd, content);
|
||||
return tmpFile.name;
|
||||
}
|
||||
|
||||
function runOutOfProcess(nodeJsFilePath) {
|
||||
var nodeArgs = Array.prototype.slice.call(arguments);
|
||||
let tmpDir = tmp.dirSync();
|
||||
@ -461,6 +471,130 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
testIncompatibleSyncedRealmOpen() {
|
||||
let realm = "sync-v1.realm";
|
||||
if (isNodeProccess) {
|
||||
realm = copyFileToTempDir(path.join(process.cwd(), "data", realm));
|
||||
}
|
||||
else {
|
||||
//copy the bundled RN realm files for the test
|
||||
Realm.copyBundledRealmFiles();
|
||||
}
|
||||
|
||||
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then(user => {
|
||||
return new Promise((resolve, _reject) => {
|
||||
const config = {
|
||||
path: realm,
|
||||
sync: {
|
||||
user,
|
||||
error : err => cosole.log(err),
|
||||
url: 'realm://localhost:9080/~/sync-v1'
|
||||
}
|
||||
};
|
||||
|
||||
Realm.open(config)
|
||||
.then(realm =>
|
||||
_reject("Should fail with IncompatibleSyncedRealmError"))
|
||||
.catch(e => {
|
||||
if (e.name == "IncompatibleSyncedRealmError") {
|
||||
const backupRealm = new Realm(e.configuration);
|
||||
TestCase.assertEqual(backupRealm.objects('Dog').length, 3);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
function printObject(o) {
|
||||
var out = '';
|
||||
for (var p in o) {
|
||||
out += p + ': ' + o[p] + '\n';
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
_reject("Failed with unexpected error " + printObject(e));
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
testIncompatibleSyncedRealmOpenAsync() {
|
||||
let realm = "sync-v1.realm";
|
||||
if (isNodeProccess) {
|
||||
realm = copyFileToTempDir(path.join(process.cwd(), "data", realm));
|
||||
}
|
||||
else {
|
||||
//copy the bundled RN realm files for the test
|
||||
Realm.copyBundledRealmFiles();
|
||||
}
|
||||
|
||||
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then(user => {
|
||||
return new Promise((resolve, _reject) => {
|
||||
const config = {
|
||||
path: realm,
|
||||
sync: {
|
||||
user,
|
||||
error : err => cosole.log(err),
|
||||
url: 'realm://localhost:9080/~/sync-v1'
|
||||
}
|
||||
};
|
||||
|
||||
Realm.openAsync(config, (error, realm) => {
|
||||
if (!error) {
|
||||
_reject("Should fail with IncompatibleSyncedRealmError");
|
||||
return;
|
||||
}
|
||||
|
||||
if (error.name == "IncompatibleSyncedRealmError") {
|
||||
const backupRealm = new Realm(error.configuration);
|
||||
TestCase.assertEqual(backupRealm.objects('Dog').length, 3);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
_reject("Failed with unexpected error" + JSON.stringify(error));
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
testIncompatibleSyncedRealmConsructor() {
|
||||
let realm = "sync-v1.realm";
|
||||
if (isNodeProccess) {
|
||||
realm = copyFileToTempDir(path.join(process.cwd(), "data", realm));
|
||||
}
|
||||
else {
|
||||
//copy the bundled RN realm files for the test
|
||||
Realm.copyBundledRealmFiles();
|
||||
}
|
||||
|
||||
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then(user => {
|
||||
return new Promise((resolve, _reject) => {
|
||||
const config = {
|
||||
path: realm,
|
||||
sync: {
|
||||
user,
|
||||
error : err => cosole.log(err),
|
||||
url: 'realm://localhost:9080/~/sync-v1'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const realm = new Realm(config);
|
||||
_reject("Should fail with IncompatibleSyncedRealmError");
|
||||
}
|
||||
catch (e) {
|
||||
if (e.name == "IncompatibleSyncedRealmError") {
|
||||
const backupRealm = new Realm(e.configuration);
|
||||
TestCase.assertEqual(backupRealm.objects('Dog').length, 3);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
_reject("Failed with unexpected error" + JSON.stringify(e));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
testProgressNotificationsForRealmConstructor() {
|
||||
if (!isNodeProccess) {
|
||||
|
@ -29,6 +29,7 @@
|
||||
855301CF1E20069D00FF108E /* dates-v3.realm in Resources */ = {isa = PBXBuildFile; fileRef = 855301CD1E20069D00FF108E /* dates-v3.realm */; };
|
||||
855301D01E20069D00FF108E /* dates-v5.realm in Resources */ = {isa = PBXBuildFile; fileRef = 855301CE1E20069D00FF108E /* dates-v5.realm */; };
|
||||
855301D31E2006F700FF108E /* RealmReactTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 855301D11E2006F400FF108E /* RealmReactTests.m */; };
|
||||
A4CEF4BB1F7F862D00BA3B26 /* sync-v1.realm in Resources */ = {isa = PBXBuildFile; fileRef = A4CEF4BA1F7F862D00BA3B26 /* sync-v1.realm */; };
|
||||
E2050A7A5BE14CEA9A9E0722 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B37A7097A134D5CBB4C462A /* libc++.tbd */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@ -253,6 +254,7 @@
|
||||
855301CE1E20069D00FF108E /* dates-v5.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "dates-v5.realm"; sourceTree = "<group>"; };
|
||||
855301D11E2006F400FF108E /* RealmReactTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RealmReactTests.m; path = ReactTests/RealmReactTests.m; sourceTree = "<group>"; };
|
||||
8B37A7097A134D5CBB4C462A /* libc++.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
|
||||
A4CEF4BA1F7F862D00BA3B26 /* sync-v1.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "sync-v1.realm"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -476,6 +478,7 @@
|
||||
855301CC1E20069D00FF108E /* data */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A4CEF4BA1F7F862D00BA3B26 /* sync-v1.realm */,
|
||||
855301CD1E20069D00FF108E /* dates-v3.realm */,
|
||||
855301CE1E20069D00FF108E /* dates-v5.realm */,
|
||||
);
|
||||
@ -805,6 +808,7 @@
|
||||
files = (
|
||||
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
A4CEF4BB1F7F862D00BA3B26 /* sync-v1.realm in Resources */,
|
||||
855301D01E20069D00FF108E /* dates-v5.realm in Resources */,
|
||||
855301CF1E20069D00FF108E /* dates-v3.realm in Resources */,
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user